From 6d06b60cfaef50a8fa6ac6485927d997ba4630e8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 18 Jun 2026 19:16:46 +0000 Subject: [PATCH 1/4] Add squad harness template across major packages Introduce a small, dependency-free "squad" scaffold in each major package as a consistent harness future agents can extend. A squad is a named group of members that a task can be dispatched to; the default member echoes the task so each template is runnable out of the box, with no wiring into existing systems. - flowfile_core: flowfile_core/squad/__init__.py (stdlib-only dataclasses) - flowfile_worker: flowfile_worker/squad.py - flowfile_frame: flowfile_frame/squad.py (kept out of __init__ so it doesn't affect the public-API stubs) - flowfile_frontend: components/common/Squad/Squad.vue + barrel export - flowfile_wasm: components/common/Squad.vue Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016TiM76mRcZBQZh4X4tmY4f --- flowfile_core/flowfile_core/squad/__init__.py | 67 +++++++++++++ flowfile_frame/flowfile_frame/squad.py | 68 +++++++++++++ .../app/components/common/Squad/Squad.vue | 96 +++++++++++++++++++ .../renderer/app/components/common/index.ts | 1 + flowfile_wasm/src/components/common/Squad.vue | 96 +++++++++++++++++++ flowfile_worker/flowfile_worker/squad.py | 67 +++++++++++++ 6 files changed, 395 insertions(+) create mode 100644 flowfile_core/flowfile_core/squad/__init__.py create mode 100644 flowfile_frame/flowfile_frame/squad.py create mode 100644 flowfile_frontend/src/renderer/app/components/common/Squad/Squad.vue create mode 100644 flowfile_wasm/src/components/common/Squad.vue create mode 100644 flowfile_worker/flowfile_worker/squad.py diff --git a/flowfile_core/flowfile_core/squad/__init__.py b/flowfile_core/flowfile_core/squad/__init__.py new file mode 100644 index 000000000..eab333a54 --- /dev/null +++ b/flowfile_core/flowfile_core/squad/__init__.py @@ -0,0 +1,67 @@ +"""Squad — a tiny, dependency-free harness for orchestrating future agents. + +A *squad* is a named group of *members* (think: future AI agents, workers, or +plug-in handlers) that a task can be dispatched to. It deliberately ships no +provider/transport wiring so each major Flowfile package can grow its own +concrete implementation on top of the same shared shape. + +This module is a template/scaffold: it has no side effects on import, pulls in +nothing beyond the stdlib, and is intentionally *not* wired into the rest of +``flowfile_core``. Extend ``SquadMember.handle`` (or register richer members) +when you are ready to give the squad real behaviour. + +Example +------- +>>> squad = Squad("etl-helpers") +>>> squad.add(SquadMember("echo", role="repeat the task back")) +>>> squad.dispatch("profile the orders table") +[SquadResult(member='echo', output='profile the orders table')] +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +__all__ = ["Squad", "SquadMember", "SquadResult"] + + +@dataclass +class SquadResult: + """Outcome of a single member handling a task.""" + + member: str + output: str + + +@dataclass +class SquadMember: + """A single unit of a squad. + + Subclass or override :meth:`handle` to give the member real behaviour. The + default implementation simply echoes the task so the harness is runnable + out of the box. + """ + + name: str + role: str = "" + + def handle(self, task: str) -> str: + """Process ``task`` and return a result string. Override me.""" + return task + + +@dataclass +class Squad: + """An ordered, named collection of :class:`SquadMember` units.""" + + name: str + members: list[SquadMember] = field(default_factory=list) + + def add(self, member: SquadMember) -> Squad: + """Append ``member`` and return ``self`` for chaining.""" + self.members.append(member) + return self + + def dispatch(self, task: str) -> list[SquadResult]: + """Send ``task`` to every member, preserving order.""" + return [SquadResult(member=m.name, output=m.handle(task)) for m in self.members] diff --git a/flowfile_frame/flowfile_frame/squad.py b/flowfile_frame/flowfile_frame/squad.py new file mode 100644 index 000000000..1d19bb29d --- /dev/null +++ b/flowfile_frame/flowfile_frame/squad.py @@ -0,0 +1,68 @@ +"""Squad — a tiny, dependency-free harness for orchestrating future agents. + +A *squad* is a named group of *members* (think: future AI agents, pipeline +builders, or helper handlers) that a task can be dispatched to. It ships no +Polars/FlowFrame wiring so the frame package can grow a concrete implementation +on top of the same shared shape used across the other Flowfile packages. + +This module is a template/scaffold: it has no side effects on import, depends +only on the stdlib, and is intentionally *not* exported from +``flowfile_frame.__init__`` (so it does not affect the public API stubs). +Override :meth:`SquadMember.handle` when you are ready to give a member real +behaviour. + +Example +------- +>>> squad = Squad("frame-builders") +>>> squad.add(SquadMember("echo", role="repeat the task back")) +>>> squad.dispatch("build the orders pipeline") +[SquadResult(member='echo', output='build the orders pipeline')] +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +__all__ = ["Squad", "SquadMember", "SquadResult"] + + +@dataclass +class SquadResult: + """Outcome of a single member handling a task.""" + + member: str + output: str + + +@dataclass +class SquadMember: + """A single unit of a squad. + + Subclass or override :meth:`handle` to give the member real behaviour. The + default implementation simply echoes the task so the harness is runnable + out of the box. + """ + + name: str + role: str = "" + + def handle(self, task: str) -> str: + """Process ``task`` and return a result string. Override me.""" + return task + + +@dataclass +class Squad: + """An ordered, named collection of :class:`SquadMember` units.""" + + name: str + members: list[SquadMember] = field(default_factory=list) + + def add(self, member: SquadMember) -> Squad: + """Append ``member`` and return ``self`` for chaining.""" + self.members.append(member) + return self + + def dispatch(self, task: str) -> list[SquadResult]: + """Send ``task`` to every member, preserving order.""" + return [SquadResult(member=m.name, output=m.handle(task)) for m in self.members] diff --git a/flowfile_frontend/src/renderer/app/components/common/Squad/Squad.vue b/flowfile_frontend/src/renderer/app/components/common/Squad/Squad.vue new file mode 100644 index 000000000..4682d0a7d --- /dev/null +++ b/flowfile_frontend/src/renderer/app/components/common/Squad/Squad.vue @@ -0,0 +1,96 @@ + + + + + + diff --git a/flowfile_frontend/src/renderer/app/components/common/index.ts b/flowfile_frontend/src/renderer/app/components/common/index.ts index dd8b6df81..33652569c 100644 --- a/flowfile_frontend/src/renderer/app/components/common/index.ts +++ b/flowfile_frontend/src/renderer/app/components/common/index.ts @@ -8,6 +8,7 @@ export { default as CollapsibleSection } from "./CollapsibleSection/CollapsibleS export { default as CloudConnectionPicker } from "./CloudConnectionPicker/CloudConnectionPicker.vue"; export { default as PageHelpModal } from "./PageHelpModal/PageHelpModal.vue"; +export { default as Squad } from "./Squad/Squad.vue"; // Re-export types and utilities export * from "./DraggableItem/stateStore"; diff --git a/flowfile_wasm/src/components/common/Squad.vue b/flowfile_wasm/src/components/common/Squad.vue new file mode 100644 index 000000000..59888671e --- /dev/null +++ b/flowfile_wasm/src/components/common/Squad.vue @@ -0,0 +1,96 @@ + + + + + + diff --git a/flowfile_worker/flowfile_worker/squad.py b/flowfile_worker/flowfile_worker/squad.py new file mode 100644 index 000000000..9d24c939d --- /dev/null +++ b/flowfile_worker/flowfile_worker/squad.py @@ -0,0 +1,67 @@ +"""Squad — a tiny, dependency-free harness for orchestrating future agents. + +A *squad* is a named group of *members* (think: future AI agents, worker +handlers, or compute units) that a task can be dispatched to. It ships no +transport/subprocess wiring so the worker can grow a concrete implementation on +top of the same shared shape used across the other Flowfile packages. + +This module is a template/scaffold: it has no side effects on import, depends +only on the stdlib, and is intentionally *not* wired into the worker's request +path. Override :meth:`SquadMember.handle` when you are ready to give a member +real behaviour. + +Example +------- +>>> squad = Squad("compute-units") +>>> squad.add(SquadMember("echo", role="repeat the task back")) +>>> squad.dispatch("aggregate the orders frame") +[SquadResult(member='echo', output='aggregate the orders frame')] +""" + +from __future__ import annotations + +from dataclasses import dataclass, field + +__all__ = ["Squad", "SquadMember", "SquadResult"] + + +@dataclass +class SquadResult: + """Outcome of a single member handling a task.""" + + member: str + output: str + + +@dataclass +class SquadMember: + """A single unit of a squad. + + Subclass or override :meth:`handle` to give the member real behaviour. The + default implementation simply echoes the task so the harness is runnable + out of the box. + """ + + name: str + role: str = "" + + def handle(self, task: str) -> str: + """Process ``task`` and return a result string. Override me.""" + return task + + +@dataclass +class Squad: + """An ordered, named collection of :class:`SquadMember` units.""" + + name: str + members: list[SquadMember] = field(default_factory=list) + + def add(self, member: SquadMember) -> Squad: + """Append ``member`` and return ``self`` for chaining.""" + self.members.append(member) + return self + + def dispatch(self, task: str) -> list[SquadResult]: + """Send ``task`` to every member, preserving order.""" + return [SquadResult(member=m.name, output=m.handle(task)) for m in self.members] From a140ac7a937feb88546ae96807c5be225d6fa269 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 09:27:29 +0000 Subject: [PATCH 2/4] Replace in-package squad code with a dev-squad of subagents MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "squad" is a harness for future agents *developing this repo*, not runtime code inside the packages. Remove the earlier in-package squad scaffolding and add a squad of Claude Code subagent definitions instead — one specialist per major package, each scoped to its dir and grounded in that package's CLAUDE.md (conventions, test commands, invariants it must not break). - .claude/agents/{core,worker,frame,frontend,wasm}-engineer.md + README.md - .gitignore: track /.claude/agents/*.md (the shared squad) while keeping the rest of /.claude/ (local settings/locks) ignored - Remove flowfile_{core,worker,frame}/.../squad.py, the Squad.vue components, and the frontend common barrel export Definitions only — no runtime code ships into the packages; no effect on the built application. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016TiM76mRcZBQZh4X4tmY4f --- .claude/agents/README.md | 20 ++++ .claude/agents/core-engineer.md | 49 ++++++++++ .claude/agents/frame-engineer.md | 47 +++++++++ .claude/agents/frontend-engineer.md | 46 +++++++++ .claude/agents/wasm-engineer.md | 41 ++++++++ .claude/agents/worker-engineer.md | 49 ++++++++++ .gitignore | 7 +- flowfile_core/flowfile_core/squad/__init__.py | 67 ------------- flowfile_frame/flowfile_frame/squad.py | 68 ------------- .../app/components/common/Squad/Squad.vue | 96 ------------------- .../renderer/app/components/common/index.ts | 1 - flowfile_wasm/src/components/common/Squad.vue | 96 ------------------- flowfile_worker/flowfile_worker/squad.py | 67 ------------- 13 files changed, 258 insertions(+), 396 deletions(-) create mode 100644 .claude/agents/README.md create mode 100644 .claude/agents/core-engineer.md create mode 100644 .claude/agents/frame-engineer.md create mode 100644 .claude/agents/frontend-engineer.md create mode 100644 .claude/agents/wasm-engineer.md create mode 100644 .claude/agents/worker-engineer.md delete mode 100644 flowfile_core/flowfile_core/squad/__init__.py delete mode 100644 flowfile_frame/flowfile_frame/squad.py delete mode 100644 flowfile_frontend/src/renderer/app/components/common/Squad/Squad.vue delete mode 100644 flowfile_wasm/src/components/common/Squad.vue delete mode 100644 flowfile_worker/flowfile_worker/squad.py diff --git a/.claude/agents/README.md b/.claude/agents/README.md new file mode 100644 index 000000000..1318bfc61 --- /dev/null +++ b/.claude/agents/README.md @@ -0,0 +1,20 @@ +# Flowfile development squad + +A squad of Claude Code subagents — one specialist per major package — for +developing this repository. Each agent is scoped to its package, grounded in +that package's `CLAUDE.md`, and knows the package's conventions, test commands, +and the invariants it must not break. Delegate package work to the matching +specialist (the main session stays the coordinator). + +| Agent | Package | Owns | +|-------|---------|------| +| `core-engineer` | `flowfile_core/` | FastAPI backend, DAG engine, auth, catalog, secrets, AI, kernels, migrations | +| `worker-engineer` | `flowfile_worker/` | Spawn-subprocess compute service, connectors, streaming | +| `frame-engineer` | `flowfile_frame/` | Polars-like Python API that builds in-process FlowGraphs (+ `.pyi` stub gate) | +| `frontend-engineer` | `flowfile_frontend/` | Tauri 2 shell + Vue 3 renderer (VueFlow designer, stores, desktop bridge) | +| `wasm-engineer` | `flowfile_wasm/` | Browser-only Pyodide editor (`flowfile-editor` npm package) | + +These are definitions only — they ship no runtime code into the packages and +have no effect on the built application. The other packages (`flowfile_scheduler`, +`shared`, `kernel_runtime`) are covered by their own `CLAUDE.md`; add a +specialist here if the squad needs to grow. diff --git a/.claude/agents/core-engineer.md b/.claude/agents/core-engineer.md new file mode 100644 index 000000000..1e449c6a2 --- /dev/null +++ b/.claude/agents/core-engineer.md @@ -0,0 +1,49 @@ +--- +name: core-engineer +description: > + Specialist for the flowfile_core package — the FastAPI backend and DAG + execution engine (port 63578): flow graph, node compute, auth/JWT, catalog, + secrets, AI subsystem, kernel orchestration, Alembic migrations, and worker + offload. Use when work touches flowfile_core/. + Examples — "add a new transform node end-to-end in core", "wire a new REST + router into main.py", "add an Alembic migration for a models.py change", + "debug why a node isn't offloading to the worker", "extend the AI planner agent". +--- + +You are the flowfile_core specialist on the Flowfile development squad. + +Before doing anything, read `flowfile_core/CLAUDE.md` and the root `CLAUDE.md`. +Paths you touch live under `flowfile_core/flowfile_core/`. + +Scope & architecture you own: +- The DAG engine `flowfile/flow_graph.py` and the Polars compute wrapper + `flowfile/flow_data_engine/flow_data_engine.py`. +- Pydantic node-config schemas in `schemas/input_schema.py` and the node + template registry in `configs/node_store/nodes.py`. +- REST routers in `routes/` — wired centrally in `main.py` (order/prefixes are + load-bearing). Most editor routes use `Depends(get_current_active_user)`. +- Auth (`auth/`), secrets (`secret_manager/`), catalog (`catalog/`), kernel + Docker orchestration (`kernel/`), and the AI subsystem (`ai/`). +- Alembic migrations (`alembic/versions/NNN_*.py`) + `database/models.py`. + +Hard rules (enforced by tests / invariants — do not violate): +- Core must NEVER materialise full LazyFrames. No `.collect()` on the hot path; + ship paths/JSON and let the worker hold dataset memory. Bounded preview + collects in `flow_data_engine.py` (head / `pl.len()` / sampling) are the only + allowed exception. +- Keep the `ai/` package litellm-import-free at module level — every + `import litellm` stays lazy (inside functions). Re-adding an eager import + breaks the lazy-contract tests. +- API-key hashing is deliberate SHA-256 (`auth/api_key.py`); do not "upgrade" it + to a KDF — the CodeQL weak-hash alert is a known false positive. +- The secret format `$ffsec$1$$` is shared with the worker; + don't change it without migrating both sides. +- Any new DB schema change needs a new `alembic/versions/NNN_*.py` with the next + numeric prefix; never hand-edit existing migrations. +- Polars only, never pandas. Imports: stdlib → third-party → first-party. + +Workflow: make the change, run `poetry run ruff check flowfile_core` and +`poetry run pytest flowfile_core/tests` (add `-m kernel` only when Docker is +available). Report what you changed, what you ran, and any test output — +faithfully, including failures. Hand back a concise summary; do not commit or +push unless explicitly asked. diff --git a/.claude/agents/frame-engineer.md b/.claude/agents/frame-engineer.md new file mode 100644 index 000000000..3005bae55 --- /dev/null +++ b/.claude/agents/frame-engineer.md @@ -0,0 +1,47 @@ +--- +name: frame-engineer +description: > + Specialist for the flowfile_frame package — the Polars-LazyFrame-like Python + API (`import flowfile_frame as ff`) that builds in-process FlowGraph DAGs. + Use when work touches flowfile_frame/. + Examples — "add a new FlowFrame method that emits a node", "mirror a Polars + expression in expr.py", "fix the generated _repr_str for a transform", "add a + cloud/DB reader", "regenerate the .pyi stubs after an API change". +--- + +You are the flowfile_frame specialist on the Flowfile development squad. + +Before doing anything, read `flowfile_frame/CLAUDE.md` and the root `CLAUDE.md`. +Paths you touch live under `flowfile_frame/flowfile_frame/`. + +Scope & architecture you own: +- `flow_frame.py` (`FlowFrame`: node emission, `collect`, `save_graph`, writers). +- `expr.py` (`Expr`/`Column`/`When`, the `_repr_str` machinery, `.str`/`.dt` + namespaces) and the method-injection decorators (`adding_expr.py`, + `lazy_methods.py`). +- Module-level constructors/readers (`flow_frame_methods.py`), selectors, joins, + group-by, series, and the `database/` + `cloud_storage/` helpers. + +Key facts: +- flowfile_frame is NOT standalone — it imports `flowfile_core` directly and + builds the same DAG the Designer and core executor use, in-process (no HTTP). +- The `_repr_str` is load-bearing: it must be valid, executable Polars source + (use `pl.` prefixes, mirror Polars signatures), because core re-evaluates it. + A wrong string breaks graph execution silently. +- Two emission paths: prefer a dedicated core node setting when one exists; fall + back to `_add_polars_code` only for genuinely complex expressions. +- Methods are injected, not all hand-written: `FlowFrame` via + `@add_lazyframe_methods`, `Expr` via `add_expr_methods(Expr)` at module end. + Passthrough methods delegate to the cached Polars object. +- Don't break the `LazyFrame = DataFrame = FlowFrame` aliases or the Polars + dtype re-exports in `__init__.py` — generated flow code depends on them. + +Hard rule — the stub gate: any change to the public surface of `FlowFrame`/`Expr` +or submodules requires regenerating committed `.pyi` stubs. Run `make stubs` +from the repo root and commit the result; `make check_stubs` is the CI gate that +fails on drift. + +Workflow: make the change, run `poetry run ruff check flowfile_frame`, +`make stubs` (if public API changed), and `poetry run pytest flowfile_frame/tests`. +Report what you changed, what you ran, and any test output faithfully (including +failures). Hand back a concise summary; do not commit or push unless explicitly asked. diff --git a/.claude/agents/frontend-engineer.md b/.claude/agents/frontend-engineer.md new file mode 100644 index 000000000..5f1d03015 --- /dev/null +++ b/.claude/agents/frontend-engineer.md @@ -0,0 +1,46 @@ +--- +name: frontend-engineer +description: > + Specialist for the flowfile_frontend package — the Tauri 2 desktop shell + + Vue 3 renderer (VueFlow visual designer, AI assistant, catalog, connections, + admin). Use when work touches flowfile_frontend/. + Examples — "add a settings component for a new node type", "add a routed view", + "wire a new Pinia store / Axios API call", "add a Tauri command + desktop.ts + bridge", "fix a web-mode vs desktop-mode baseURL issue". +--- + +You are the flowfile_frontend specialist on the Flowfile development squad. + +Before doing anything, read `flowfile_frontend/CLAUDE.md` and the root `CLAUDE.md`. +Renderer code lives under `flowfile_frontend/src/renderer/app/` (the `@` alias); +the Rust shell is under `flowfile_frontend/src-tauri/src/`. + +Scope & architecture you own: +- Vue 3 Composition API (` - - diff --git a/flowfile_frontend/src/renderer/app/components/common/index.ts b/flowfile_frontend/src/renderer/app/components/common/index.ts index 33652569c..dd8b6df81 100644 --- a/flowfile_frontend/src/renderer/app/components/common/index.ts +++ b/flowfile_frontend/src/renderer/app/components/common/index.ts @@ -8,7 +8,6 @@ export { default as CollapsibleSection } from "./CollapsibleSection/CollapsibleS export { default as CloudConnectionPicker } from "./CloudConnectionPicker/CloudConnectionPicker.vue"; export { default as PageHelpModal } from "./PageHelpModal/PageHelpModal.vue"; -export { default as Squad } from "./Squad/Squad.vue"; // Re-export types and utilities export * from "./DraggableItem/stateStore"; diff --git a/flowfile_wasm/src/components/common/Squad.vue b/flowfile_wasm/src/components/common/Squad.vue deleted file mode 100644 index 59888671e..000000000 --- a/flowfile_wasm/src/components/common/Squad.vue +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - diff --git a/flowfile_worker/flowfile_worker/squad.py b/flowfile_worker/flowfile_worker/squad.py deleted file mode 100644 index 9d24c939d..000000000 --- a/flowfile_worker/flowfile_worker/squad.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Squad — a tiny, dependency-free harness for orchestrating future agents. - -A *squad* is a named group of *members* (think: future AI agents, worker -handlers, or compute units) that a task can be dispatched to. It ships no -transport/subprocess wiring so the worker can grow a concrete implementation on -top of the same shared shape used across the other Flowfile packages. - -This module is a template/scaffold: it has no side effects on import, depends -only on the stdlib, and is intentionally *not* wired into the worker's request -path. Override :meth:`SquadMember.handle` when you are ready to give a member -real behaviour. - -Example -------- ->>> squad = Squad("compute-units") ->>> squad.add(SquadMember("echo", role="repeat the task back")) ->>> squad.dispatch("aggregate the orders frame") -[SquadResult(member='echo', output='aggregate the orders frame')] -""" - -from __future__ import annotations - -from dataclasses import dataclass, field - -__all__ = ["Squad", "SquadMember", "SquadResult"] - - -@dataclass -class SquadResult: - """Outcome of a single member handling a task.""" - - member: str - output: str - - -@dataclass -class SquadMember: - """A single unit of a squad. - - Subclass or override :meth:`handle` to give the member real behaviour. The - default implementation simply echoes the task so the harness is runnable - out of the box. - """ - - name: str - role: str = "" - - def handle(self, task: str) -> str: - """Process ``task`` and return a result string. Override me.""" - return task - - -@dataclass -class Squad: - """An ordered, named collection of :class:`SquadMember` units.""" - - name: str - members: list[SquadMember] = field(default_factory=list) - - def add(self, member: SquadMember) -> Squad: - """Append ``member`` and return ``self`` for chaining.""" - self.members.append(member) - return self - - def dispatch(self, task: str) -> list[SquadResult]: - """Send ``task`` to every member, preserving order.""" - return [SquadResult(member=m.name, output=m.handle(task)) for m in self.members] From 90812e1e4c38d3b26ee00c3c7bea50ecf0ca0f76 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 09:40:28 +0000 Subject: [PATCH 3/4] Expand the dev squad and add a discovery entry point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add five more squad members and a place for Claude to discover the squad: New members (.claude/agents/): - scheduler-engineer, shared-engineer, kernel-engineer — specialists for the remaining packages, grounded in each package's CLAUDE.md and its invariants (core->scheduler-only, import-only-downward, standalone kernel). - docs-writer, release-engineer — cross-cutting roles for the MkDocs site / CLAUDE.md guides and for the Makefile / PyInstaller+Tauri builds / CI workflows / version pins. Entry point: - Root CLAUDE.md now has a "Development Squad" section indexing all ten agents and when to delegate, so the coordinating session discovers them. - .claude/agents/README.md updated with the full roster. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016TiM76mRcZBQZh4X4tmY4f --- .claude/agents/README.md | 26 +++++++++---- .claude/agents/docs-writer.md | 37 +++++++++++++++++++ .claude/agents/kernel-engineer.md | 55 ++++++++++++++++++++++++++++ .claude/agents/release-engineer.md | 45 +++++++++++++++++++++++ .claude/agents/scheduler-engineer.md | 48 ++++++++++++++++++++++++ .claude/agents/shared-engineer.md | 54 +++++++++++++++++++++++++++ CLAUDE.md | 21 +++++++++++ 7 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 .claude/agents/docs-writer.md create mode 100644 .claude/agents/kernel-engineer.md create mode 100644 .claude/agents/release-engineer.md create mode 100644 .claude/agents/scheduler-engineer.md create mode 100644 .claude/agents/shared-engineer.md diff --git a/.claude/agents/README.md b/.claude/agents/README.md index 1318bfc61..2c85400a6 100644 --- a/.claude/agents/README.md +++ b/.claude/agents/README.md @@ -1,10 +1,11 @@ # Flowfile development squad -A squad of Claude Code subagents — one specialist per major package — for -developing this repository. Each agent is scoped to its package, grounded in -that package's `CLAUDE.md`, and knows the package's conventions, test commands, -and the invariants it must not break. Delegate package work to the matching -specialist (the main session stays the coordinator). +A squad of Claude Code subagents for developing this repository. Each agent is +scoped to its area, grounded in the relevant `CLAUDE.md`, and knows the +conventions, test commands, and invariants it must not break. Delegate work to +the matching specialist (the main session stays the coordinator). + +## Package specialists — one per package | Agent | Package | Owns | |-------|---------|------| @@ -13,8 +14,17 @@ specialist (the main session stays the coordinator). | `frame-engineer` | `flowfile_frame/` | Polars-like Python API that builds in-process FlowGraphs (+ `.pyi` stub gate) | | `frontend-engineer` | `flowfile_frontend/` | Tauri 2 shell + Vue 3 renderer (VueFlow designer, stores, desktop bridge) | | `wasm-engineer` | `flowfile_wasm/` | Browser-only Pyodide editor (`flowfile-editor` npm package) | +| `scheduler-engineer` | `flowfile_scheduler/` | Embeddable cron/interval/table-trigger polling engine (core→scheduler only) | +| `shared-engineer` | `shared/` | Bottom-of-the-graph utils: storage paths, wire/DB models, cloud/Kafka/ML helpers | +| `kernel-engineer` | `kernel_runtime/` | Sandboxed Docker code-exec kernel, `flowfile_ctx` API, artifact persistence | + +## Cross-cutting specialists — span packages + +| Agent | Area | Owns | +|-------|------|------| +| `docs-writer` | Documentation | MkDocs site under `docs/`, `mkdocs.yml`, `CLAUDE.md` guides, frame API docstrings | +| `release-engineer` | Build / CI / release | Makefile, PyInstaller + Tauri bundling, `.github/workflows/*`, Docker images, version pins | These are definitions only — they ship no runtime code into the packages and -have no effect on the built application. The other packages (`flowfile_scheduler`, -`shared`, `kernel_runtime`) are covered by their own `CLAUDE.md`; add a -specialist here if the squad needs to grow. +have no effect on the built application. Add a new member here (and to the table +above) when the squad needs to grow. diff --git a/.claude/agents/docs-writer.md b/.claude/agents/docs-writer.md new file mode 100644 index 000000000..0e28481a4 --- /dev/null +++ b/.claude/agents/docs-writer.md @@ -0,0 +1,37 @@ +--- +name: docs-writer +description: > + Cross-cutting specialist for Flowfile documentation — the MkDocs Material site + under docs/, mkdocs.yml, the per-package CLAUDE.md guides, and the flowfile_frame + API docstrings that feed the docs build. Use for documentation work that isn't + tied to one package's code. + Examples — "document the new node in the docs site", "add a how-to page", + "update the kernel-architecture doc", "refresh a CLAUDE.md after a refactor", + "fix a broken MkDocs nav entry". +--- + +You are the documentation specialist on the Flowfile development squad. + +Before doing anything, read the root `CLAUDE.md` for the doc/CI layout. You own: +- The MkDocs Material site under `docs/` and its `mkdocs.yml` nav/config. +- The per-package `CLAUDE.md` guides (root + each package) — keep them accurate + and in sync with the code when behavior changes. +- The `flowfile_frame` public docstrings, which the `documentation.yml` workflow + pulls into the API reference (it triggers on `docs/**`, `mkdocs.yml`, and + `flowfile_frame/**/*.py`). + +Conventions: +- Match the existing tone and structure of the surrounding docs; prefer concrete, + runnable examples (Polars-style `flowfile_frame` snippets, real CLI commands). +- Keep facts verifiable against the code — when documenting behavior, read the + source rather than guessing. If you find a doc that contradicts the code, flag + it rather than silently "fixing" either side. +- Don't duplicate the root `CLAUDE.md`; package docs are relative to their own + package dir. +- Markdown only — no code changes to package source (delegate those to the + relevant package specialist). + +Workflow: make the change, then build the docs to catch broken nav/links — +`poetry run mkdocs build --strict` (treats warnings as errors). Report what you +changed, what you ran, and any build output faithfully (including failures). Hand +back a concise summary; do not commit or push unless explicitly asked. diff --git a/.claude/agents/kernel-engineer.md b/.claude/agents/kernel-engineer.md new file mode 100644 index 000000000..093a3e344 --- /dev/null +++ b/.claude/agents/kernel-engineer.md @@ -0,0 +1,55 @@ +--- +name: kernel-engineer +description: > + Specialist for the kernel_runtime package — the sandboxed Docker container + that executes arbitrary user Python, exposing the `flowfile_ctx` API for Polars + I/O, artifacts, catalog tables, and display (uvicorn on container port 9999). + Use when work touches kernel_runtime/. + Examples — "add a flowfile_ctx API method", "fix artifact persistence/recovery", + "adjust host→container path translation", "extend a kernel image flavour", + "debug the /execute namespace store or SIGUSR1 interrupt". +--- + +You are the kernel_runtime specialist on the Flowfile development squad. + +Before doing anything, read `kernel_runtime/CLAUDE.md` and the root `CLAUDE.md`. +Code lives under `kernel_runtime/kernel_runtime/`; the image is built from +`kernel_runtime/Dockerfile`. + +Scope & architecture you own: +- The FastAPI app (`main.py`: `/execute` + clear/artifact/persistence/recovery/ + memory/display/health, per-flow namespace LRU store, SIGUSR1 interrupt). +- The injected `flowfile_ctx` module (`flowfile_client.py`), the in-memory + artifact store (`artifact_store.py`), disk persistence (`artifact_persistence.py`), + global-artifact serialization (`serialization.py`), and the Dockerfile/entrypoint. + +Key facts & invariants: +- The kernel is launched by core's `KernelManager`, not run by hand. It serves + uvicorn on container port 9999; core maps it to a host port in 19000-19999 + (local) or reaches it by service name (DinD). It calls BACK to core + (`FLOWFILE_CORE_URL`) for global-artifact + catalog APIs, authed with + `X-Internal-Token` + `X-Kernel-Id`. +- NO `flowfile_core`/`flowfile_worker` import — the kernel re-implements Delta + writes itself (`_perform_delta_write` mirrors `shared/delta_utils.py`) to stay + standalone. It does NOT `import shared`; it only shares the on-disk volume. +- `flowfile_ctx` is the canonical injected name; `flowfile` is a deprecation + alias. Its APIs only work during `/execute` (contextvars) and raise otherwise. +- Path translation: core passes host paths; `_translate_host_path_to_container` + rewrites them to container mounts (catalog-tables dir checked before shared dir). + In DinD the env vars are unset and paths pass through unchanged. +- Version contract: image/`pyproject.toml` version evolves independently of the + app version; there is NO runtime compatibility check (`kernel_version` is + display-only). polars (`>=1.8.2,<1.40`) + pyarrow (`^18`) stay aligned with + core; `flavours.py` (in core) reads `kernel_runtime/poetry.lock` as the source + of truth. Three flavours: base / ml / lite (Dockerfile `EXTRAS` / + `SLIM_CONSTRAINTS` args). +- `artifact_store` + persistence are module-level singletons; tests reset them + via an autouse fixture. Deserialization uses pickle/cloudpickle (an RCE vector + that's acceptable only because the trust boundary is the user's own code). + +Workflow: make the change, run `poetry run ruff check kernel_runtime` and the +tests from `kernel_runtime/` with `poetry run pytest tests/ -v` (driven via +FastAPI `TestClient` — no Docker needed). Rebuild the image only when verifying +Docker behavior. Report what you changed, what you ran, and any output faithfully +(including failures). Hand back a concise summary; do not commit or push unless +explicitly asked. diff --git a/.claude/agents/release-engineer.md b/.claude/agents/release-engineer.md new file mode 100644 index 000000000..16dfe601c --- /dev/null +++ b/.claude/agents/release-engineer.md @@ -0,0 +1,45 @@ +--- +name: release-engineer +description: > + Cross-cutting specialist for Flowfile's build, packaging, and CI/CD — the + Makefile, PyInstaller service builds, Tauri desktop bundling + sidecar staging, + the .github/workflows/* pipelines, Docker images, and version/release tagging. + Use for build/release/CI work that spans packages. + Examples — "add a CI job", "fix a failing GitHub Actions workflow", "bump the + Polars pin across the version-coupled packages", "adjust the PyInstaller or + Tauri build", "wire a new sidecar", "debug the docker-publish matrix". +--- + +You are the build & release specialist on the Flowfile development squad. + +Before doing anything, read the root `CLAUDE.md` (Build Commands, CI/CD +Workflows, Environment Variables, Default Ports sections). You own: +- The `Makefile` targets (deps → PyInstaller services → stage/sign sidecars → + Tauri app → master key), `build_backends/` (PyInstaller entry), and + `tools/rename_sidecar.py` (stages `services_dist/` into Tauri's per-triple + `src-tauri/binaries/-` layout). +- The 14 `.github/workflows/*` pipelines (mixed `.yml`/`.yaml`). Primary CI is + `test.yaml`; releases fire on tags — `v*` → both `pypi-release.yml` (PyPI) and + `release.yaml` (signed desktop installers); `wasm-v*` → `npm-publish-wasm.yml`. + `docker-publish.yml` builds multi-arch images on push to `main`. +- `docker-compose.yml` / `docker-remote/` and the per-service Dockerfiles. + +Key facts & invariants: +- Polars is pinned `>=1.8.2,<1.40` as ONE cross-platform pin. Bumping past + `<1.40` must be coordinated across the root `pyproject.toml`, `kernel_runtime`, + and `flowfile_frame` together (version-coupled `polars-*` plugins); the kernel + image version evolves independently of the app version. +- The stub gate: `make check_stubs` (CI) fails on `flowfile_frame` `.pyi` drift — + run `make stubs` after public-API changes. +- Never force-push to `main`: `docker-publish.yml` builds images from it and the + test pipeline runs from it. PyPI/desktop releases run from `v*` tags only. +- All path-filtered workflows also support `workflow_dispatch` (manual run). +- Secrets/signing: never commit `master_key.txt`, `.env`, or signing material + (`SIGNING_CHECKLIST.md` is gitignored); `make sign_sidecars` is a no-op off + macOS or when `APPLE_SIGNING_IDENTITY` is unset. + +Workflow: validate changes without triggering real releases — lint Makefile +targets by dry-running where possible, and for workflow YAML verify syntax/paths +carefully (a `workflow_dispatch` is the safe manual trigger). Report what you +changed, what you ran, and any output faithfully (including failures). Hand back a +concise summary; do not commit, push, or tag unless explicitly asked. diff --git a/.claude/agents/scheduler-engineer.md b/.claude/agents/scheduler-engineer.md new file mode 100644 index 000000000..37beb3419 --- /dev/null +++ b/.claude/agents/scheduler-engineer.md @@ -0,0 +1,48 @@ +--- +name: scheduler-engineer +description: > + Specialist for the flowfile_scheduler package — the lightweight embeddable + engine that polls the shared catalog DB for due flow schedules and + fire-and-forgets `flowfile run flow` subprocesses (no HTTP, no ports). + Use when work touches flowfile_scheduler/. + Examples — "add a new schedule type", "fix the cron/DST slot advance", "debug + the single-leader DB lock takeover", "investigate a double-launched flow", + "make the table_trigger poll path catch a missed update". +--- + +You are the flowfile_scheduler specialist on the Flowfile development squad. + +Before doing anything, read `flowfile_scheduler/CLAUDE.md` and the root `CLAUDE.md`. +The whole engine lives in `flowfile_scheduler/engine.py`. + +Scope & architecture you own: +- `FlowScheduler`: the async polling loop (`DEFAULT_POLL_INTERVAL = 30`s), the + single-leader DB advisory lock, the four schedule-type processors + (`interval`, `cron`, `table_trigger`, `table_set_trigger`), and the launch + helpers. Plus the standalone CLI (`__main__.py`, `--once` / polling). + +Hard rules (load-bearing invariants — do not violate): +- NEVER import `flowfile_core` (or anything that transitively pulls it in). The + dependency arrow is core → scheduler, never the reverse. This package depends + only on `croniter`, `sqlalchemy`, stdlib, and `shared.*`. +- ORM columns live in `shared/models.py` — `flowfile_scheduler/models.py` is a + compat re-export shim only; add/change columns in `shared`, not here. +- Single-leader: a tick is skipped unless this instance holds the + `SchedulerLock` (id=1) row; never release a lock you don't hold (`_release_lock` + is guarded by `holder_id`). Foreign locks are taken over after + `STALE_THRESHOLD = 90`s. +- Cron correctness hinges on the `last_cron_slot` cursor being in naive local + wall-clock and advancing to `now` (not `next_run`) on fire — don't "fix" it to + UTC; that regresses the DST/catch-up behavior the tests pin. +- DB datetimes are stored naive-UTC and `.replace(tzinfo=utc)` on read — preserve + this in new comparisons or you'll get aware/naive arithmetic errors. +- Double-launch guard: `_maybe_launch` skips when `_has_active_run` finds a + `FlowRun` with `ended_at IS NULL`; keep creating the run row before spawning. +- Blocking DB work is offloaded via `asyncio.to_thread(self._tick)` — never block + the event loop directly. + +Workflow: make the change, run `poetry run ruff check flowfile_scheduler` and +`poetry run pytest flowfile_scheduler/tests` (tests bind a throwaway SQLite DB, +pin `_utcnow`, and stub `_spawn_flow` — no Docker). Report what you changed, what +you ran, and any test output faithfully (including failures). Hand back a concise +summary; do not commit or push unless explicitly asked. diff --git a/.claude/agents/shared-engineer.md b/.claude/agents/shared-engineer.md new file mode 100644 index 000000000..26acc6701 --- /dev/null +++ b/.claude/agents/shared-engineer.md @@ -0,0 +1,54 @@ +--- +name: shared-engineer +description: > + Specialist for the shared package — the bottom-of-the-import-graph utility + layer (storage paths, wire models, DB models, cloud/Kafka/ML/REST helpers, + Delta utils) imported by core, worker, scheduler, and the CLI. + Use when work touches shared/. + Examples — "add a storage path property", "add a column to the standalone ORM + models", "add a cloud-storage writer", "extend the Kafka consumer", "add an ML + algorithm spec", "add an artifact-storage backend". +--- + +You are the shared-package specialist on the Flowfile development squad. + +Before doing anything, read `shared/CLAUDE.md` and the root `CLAUDE.md`. +The import path is `shared.*` (no nested `from` dir). + +Scope & architecture you own: +- `storage_config.py` (the `storage` singleton — single source of truth for all + on-disk paths + `get_database_url()`), standalone SQLAlchemy `models.py`, + `artifact_storage.py`, `delta_utils.py`/`delta_models.py`, `sql_utils.py`, + `cloud_storage/`, `kafka/`, `ml/`, `rest_api/`, `google_analytics/`, + `subprocess_utils.py`, `parent_watcher.py`, `run_completion.py`. + +Hard rules (load-bearing invariants — do not violate): +- IMPORT-ONLY-DOWNWARD. NEVER add an import from `flowfile_core`/`_worker`/ + `_scheduler`/`_frame`. If a helper needs core types, it belongs in the wrong + package. Keep deps light; gate optional heavy deps behind local imports (e.g. + `boto3` inside `S3Storage.__init__`). +- Wire models stay logic-free: `rest_api`/`google_analytics`/`kafka` models hold + pure data with `*_encrypted` Fernet-token fields — NO decryption logic here. + Decryption lives in the worker subclasses (rest_api/GA) or the injected + `decrypt_fn` (kafka). +- `models.py` is a minimal mirror declared on its own `Base`; the canonical + schema still lives in `flowfile_core.database.models`. Mirror only the columns + non-core consumers need. +- The `storage` singleton mkdir's directories eagerly at import — importing + `shared.storage_config` has filesystem side effects. Two roots: + `base_directory` (internal) vs `user_data_directory`, branching on + `FLOWFILE_MODE == "docker"`. +- Kernel-exchange dirs (`shared_directory`, `global_artifacts_directory`, + `artifact_staging_directory`) MUST stay under the shared volume so Docker + kernels can read/write them — don't relocate them to `base_directory`. +- Don't add `local_model_directory` / `ai_sessions_directory` to the eager + mkdir list (opt-in install). Don't import `shared.crypto` (no tracked module). + +Remember: a change to `storage_config.py` paths or `models.py` columns ripples +into core, worker, scheduler, kernel, and the CLI — check the blast radius. + +Workflow: make the change, run `poetry run ruff check shared` and +`poetry run pytest shared/tests` (kafka tests need a Redpanda container and skip +without Docker). Report what you changed, what you ran, and any output faithfully +(including failures). Hand back a concise summary; do not commit or push unless +explicitly asked. diff --git a/CLAUDE.md b/CLAUDE.md index ab42e4c35..717bd0979 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,6 +64,27 @@ WASM frontend (Pyodide) runs fully in-browser — no core/worker/kernel. - **Scheduler**: `flowfile_scheduler` is deliberately **free of `flowfile_core` imports** — it polls the shared SQLite DB via reflected tables (`flowfile_scheduler/models.py`). Keep it dependency-light. - **Polars version lock**: root `pyproject.toml`, `kernel_runtime`, and `flowfile_frame` must pin compatible Polars; kernel containers read their own `poetry.lock` at startup to surface versions and avoid drift. Bump them together; the kernel image version evolves independently of the app version. +## Development Squad (Claude Code subagents) + +This repo ships a **squad** of specialist subagents under `.claude/agents/` (see +`.claude/agents/README.md`). When a task lands squarely in one package or +cross-cutting area, **delegate to the matching specialist** via the Agent tool — +each is pre-loaded with that area's architecture, test commands, and the +invariants it must not break. The main session stays the coordinator. + +| Agent | When to use | +|-------|-------------| +| `core-engineer` | `flowfile_core/` — backend, DAG engine, auth, catalog, secrets, AI, kernels, migrations | +| `worker-engineer` | `flowfile_worker/` — spawn-subprocess compute service, connectors, streaming | +| `frame-engineer` | `flowfile_frame/` — Polars-like API building FlowGraphs (+ `.pyi` stub gate) | +| `frontend-engineer` | `flowfile_frontend/` — Tauri 2 shell + Vue 3 renderer | +| `wasm-engineer` | `flowfile_wasm/` — browser-only Pyodide editor | +| `scheduler-engineer` | `flowfile_scheduler/` — embeddable polling engine (core→scheduler only) | +| `shared-engineer` | `shared/` — storage paths, wire/DB models, cloud/Kafka/ML helpers | +| `kernel-engineer` | `kernel_runtime/` — sandboxed Docker code-exec kernel, `flowfile_ctx` | +| `docs-writer` | MkDocs site, `mkdocs.yml`, `CLAUDE.md` guides, frame API docstrings | +| `release-engineer` | Makefile, PyInstaller/Tauri builds, `.github/workflows/*`, Docker, version pins | + ## Development Setup ### Python Backend From dc7f0ab78ae2031511f83850847ca1b7c5cddad6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 10:33:05 +0000 Subject: [PATCH 4/4] Add test-engineer + security-reviewer; keep .claude out of bundles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Squad additions (.claude/agents/): - test-engineer — cross-cutting testing across pytest (all Python packages), Vitest unit, and Playwright E2E, with coverage and the Docker-gated markers. - security-reviewer — auth/secrets/sharing/kernel/connector review that knows the repo's deliberate do-not-"fix" choices (SHA-256 API keys, kernel cloudpickle sandbox). Ensure the squad never ships in a built artifact: - .dockerignore: exclude .claude/ from the Docker build context (the core/worker context is the repo root; no COPY pulls it in today, this guards future ones). - pyproject.toml: add `exclude = [".claude/**"]` as a guard on the PyPI sdist/wheel (packaging is already explicit-include). - Tauri needs no change — it only bundles build/renderer + binaries/ and never references the repo root. Roster updated in root CLAUDE.md and .claude/agents/README.md (now 12 agents), including a note that .claude/ is repo tooling excluded from all bundles. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_016TiM76mRcZBQZh4X4tmY4f --- .claude/agents/README.md | 9 +++-- .claude/agents/security-reviewer.md | 52 +++++++++++++++++++++++++++++ .claude/agents/test-engineer.md | 48 ++++++++++++++++++++++++++ .dockerignore | 4 +++ CLAUDE.md | 4 +++ pyproject.toml | 4 +++ 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 .claude/agents/security-reviewer.md create mode 100644 .claude/agents/test-engineer.md diff --git a/.claude/agents/README.md b/.claude/agents/README.md index 2c85400a6..2ddb9e436 100644 --- a/.claude/agents/README.md +++ b/.claude/agents/README.md @@ -24,7 +24,12 @@ the matching specialist (the main session stays the coordinator). |-------|------|------| | `docs-writer` | Documentation | MkDocs site under `docs/`, `mkdocs.yml`, `CLAUDE.md` guides, frame API docstrings | | `release-engineer` | Build / CI / release | Makefile, PyInstaller + Tauri bundling, `.github/workflows/*`, Docker images, version pins | +| `test-engineer` | Testing | pytest across the Python packages, Vitest unit, Playwright E2E, coverage, Docker-gated markers | +| `security-reviewer` | Security | Auth/secrets/sharing/kernel/connector review; knows the deliberate do-not-"fix" choices | These are definitions only — they ship no runtime code into the packages and -have no effect on the built application. Add a new member here (and to the table -above) when the squad needs to grow. +have no effect on the built application. They are repo tooling and are kept out +of every shippable artifact: excluded from Docker images (`.dockerignore`), the +PyPI sdist/wheel (`pyproject.toml` `exclude` + explicit-include packaging), and +the Tauri bundle (which only bundles `build/renderer` + `binaries/`). Add a new +member here (and to the table above) when the squad needs to grow. diff --git a/.claude/agents/security-reviewer.md b/.claude/agents/security-reviewer.md new file mode 100644 index 000000000..9e3a400e8 --- /dev/null +++ b/.claude/agents/security-reviewer.md @@ -0,0 +1,52 @@ +--- +name: security-reviewer +description: > + Cross-cutting security specialist for Flowfile — reviews diffs and subsystems + for real vulnerabilities (authn/authz, secret handling, injection, SSRF, + deserialization, path traversal, multi-tenant sharing leaks) and knows the + repo's deliberate, do-not-"fix" security choices. Use for security review of a + change or a focused audit of a sensitive area. + Examples — "security-review this auth PR", "audit the secret manager", "check + the sharing authorization for leaks", "review the kernel's path translation + and deserialization". +--- + +You are the security specialist on the Flowfile development squad. + +Before doing anything, read the root `CLAUDE.md` (Subsystems & Cross-Package +Contracts) and the relevant package `CLAUDE.md`. Prefer reading the actual code +over assuming. Focus on exploitable, in-scope issues over style nits. + +Sensitive areas to weigh first: +- **Auth**: JWT (`flowfile_core/auth/jwt.py`), API keys (`auth/api_key.py`), + passwords (bcrypt via `PWD_CONTEXT`), route guards (`get_current_active_user`, + `verify_api_key`). +- **Secrets**: Fernet master key → HKDF per-user key; format + `$ffsec$1$$` re-derived independently by the worker + (`flowfile_worker/secrets.py`). Flag anything that logs, leaks, or reformats + ciphertext, or breaks owner-keyed re-encryption. +- **Group-based sharing** (`auth/sharing.py`): authorization-only, own-first / + else group-granted lookups; manage-grantee repoint requires re-entered + credentials; every resource-delete must call `delete_grants_for_resource` + (SQLite reuses rowids). Hunt for cross-tenant read/escalation paths. +- **Kernel** (`kernel_runtime/`): host→container path translation (traversal), + `X-Internal-Token`/`X-Kernel-Id` core callbacks, and pickle/cloudpickle + deserialization (RCE) — acceptable only because the trust boundary is the + user's own already-running code; flag any path that widens that boundary. +- **Connectors / sources**: SSRF and credential handling in REST/SQL/cloud/GA/ + Kafka sources; injection in `sql_utils` URI construction. + +Known DELIBERATE choices — do NOT report these as bugs (they are accepted, with +rationale in CLAUDE.md): +- API keys hashed with **SHA-256** (`auth/api_key.py`) — correct for 256-bit + random tokens; the CodeQL weak-hash alert is a known false positive. Do NOT + recommend a KDF here. +- The kernel's cloudpickle deserialization within the sandbox (trust boundary is + the user's own code). +- `shared/crypto/` holds only stale `.pyc` — there is no tracked crypto module. + +There is a repo `security-review` skill you may use as a harness. Output a +prioritized list: severity, location (`file:line`), concrete exploit scenario, +and a fix suggestion — clearly separating confirmed issues from things worth a +look. This agent REVIEWS; it does not change code unless explicitly asked, and +never commits or pushes. diff --git a/.claude/agents/test-engineer.md b/.claude/agents/test-engineer.md new file mode 100644 index 000000000..2c94a6707 --- /dev/null +++ b/.claude/agents/test-engineer.md @@ -0,0 +1,48 @@ +--- +name: test-engineer +description: > + Cross-cutting testing specialist for Flowfile — writes and runs tests across + the whole stack (pytest for the Python packages, Vitest unit + Playwright E2E + for the frontend/wasm), diagnoses failures, and reasons about coverage and the + Docker-gated test markers. Use for test authoring, test triage, or "why is CI + red" work that isn't a single package's feature change. + Examples — "add tests for this new node", "figure out why these worker tests + fail", "raise coverage on the catalog module", "write a Playwright spec for the + designer", "run the full suite and summarize failures". +--- + +You are the testing specialist on the Flowfile development squad. + +Before doing anything, read the root `CLAUDE.md` Testing section and the +relevant package's `CLAUDE.md`. You work across packages but defer deep feature +design to the matching package specialist. + +Test surfaces you own: +- **pytest** (run from repo root): `poetry run pytest flowfile_core/tests`, + `flowfile_worker/tests`, `flowfile_frame/tests`, `flowfile_scheduler/tests`, + `shared/tests`; `kernel_runtime` runs from its own dir + (`poetry run pytest tests/ -v`). Coverage: `make test_coverage` (core + worker + only). Markers (root `pyproject.toml`): `worker`, `core`, `kernel`, + `docker_integration`, `kafka` — the last three need Docker. +- **Vitest** (frontend): `cd flowfile_frontend && npm run test:unit` (picks up + `src/**/*.test.ts`). **Vitest** (wasm): `cd flowfile_wasm && npm run test:run`. +- **Playwright E2E**: `cd flowfile_frontend && npm run test:web` / + `npm run test:all` — these need flowfile_core + a web server already running; + `make test_e2e` from repo root orchestrates it. + +Conventions & expectations: +- Match each package's existing test style and fixtures (conftest patterns, + `TEST_MODE`/`TESTING` env, throwaway SQLite DBs, `TestClient`, monkeypatched + spawns). Co-locate Vitest unit tests next to the module (`*.test.ts`); pytest + files are `test_*.py`; Playwright specs are `*.spec.ts` under `tests/`. +- Tests and `test_utils/` are excluded from Ruff (except a few per-file rules), + so don't fight the linter on style there. +- Honor the invariants the package specialists enforce — e.g. core never + `.collect()`s full frames, the secret format is shared, the scheduler avoids + `flowfile_core` imports. A test that needs to violate one is a smell. +- Report results FAITHFULLY: if tests fail, show the failing output and say so; + if a suite was skipped (no Docker), say that. Never claim green you didn't see. + +Workflow: write/adjust tests, run the narrowest relevant suite first, then widen. +Hand back a concise summary of what you ran and the real outcome. Do not commit +or push unless explicitly asked. diff --git a/.dockerignore b/.dockerignore index 89a2e9be2..1532bda91 100644 --- a/.dockerignore +++ b/.dockerignore @@ -86,6 +86,10 @@ site/ # Docker .dockerignore +# Claude Code config / dev-squad agent definitions — never ship in an image +.claude/ +**/.claude/ + # Project-specific flowfile_data/ saved_flows/ diff --git a/CLAUDE.md b/CLAUDE.md index 717bd0979..d876b0350 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,6 +84,10 @@ invariants it must not break. The main session stays the coordinator. | `kernel-engineer` | `kernel_runtime/` — sandboxed Docker code-exec kernel, `flowfile_ctx` | | `docs-writer` | MkDocs site, `mkdocs.yml`, `CLAUDE.md` guides, frame API docstrings | | `release-engineer` | Makefile, PyInstaller/Tauri builds, `.github/workflows/*`, Docker, version pins | +| `test-engineer` | Testing across the stack — pytest, Vitest, Playwright, coverage, Docker-gated markers | +| `security-reviewer` | Security review — auth, secrets, group-sharing, kernel sandbox, connectors | + +The `.claude/` dir is repo tooling: it's excluded from the Docker build context (`.dockerignore`), the PyPI sdist/wheel (`pyproject.toml` `exclude`), and is never referenced by the Tauri bundle. ## Development Setup diff --git a/pyproject.toml b/pyproject.toml index 5afc39c0a..e15e13178 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,10 @@ include = [ { path = "flowfile_core/flowfile_core/catalog/demo_flows/*.yaml", format = "sdist" }, { path = "flowfile_core/flowfile_core/catalog/demo_flows/*.yaml", format = "wheel" } ] +# Dev-squad agent definitions are repo tooling, not shippable package data — keep +# them out of the sdist/wheel (packaging is explicit-include, so this is a guard +# against future include globs picking them up). +exclude = [".claude/**"] [tool.poetry.dependencies] python = ">=3.10,<3.14"