Flows YAML — defaults, JSON Schema pipeline, REST CRUD + SSE #1076

Closed
opened 2026-05-10 15:42:51 +00:00 by claude-desktop · 1 comment
Collaborator

User story

As an operator, I want the 9 default flows shipped on disk, a JSON Schema generated from Zod that drives both server validation and editor autocomplete, and a complete REST surface (with SSE change broadcast) so that the dashboard can manage flows without ssh-ing into the host.

Acceptance criteria

Default YAML files (flows/defaults/)

  • pr-opened.yml, review-requested.yml, issue-opened.yml, issue-labeled.yml, issue-assigned.yml, issue-unassigned.yml, issue-closed.yml, pr-merged.yml, breakdown-comment.yml.
  • Each file passes flows.schema.json validation; references only registered ops.
  • Side-effect parity with the legacy JSON node engine on recorded fixtures (asserted by shadow-mode diff in Migration ticket).
  • Per-default fixture-replay test under flows/defaults/__tests__/.

Zod → JSON Schema pipeline

  • Zod schemas in apps/server/src/domain/flows-yaml/schema.ts cover FlowFile, TriggerKind, every op's argsSchema (discriminated on uses), expression types.
  • Each Zod field has .describe() (becomes JSON Schema description and Monaco hover text).
  • Build script apps/server/scripts/generate-flow-schema.ts runs zod-to-json-schema; output committed to apps/web/public/schemas/flows.schema.json.
  • Expression-string fields tagged format: "flow-expression".
  • just qa regenerates and fails if committed file stale; web build runs generator before bundling.
  • POST/PUT re-parse with Zod (authoritative); JSON Schema mismatch → 422.
  • Snapshot test of generated schema; round-trip test that each default *.yml validates.

REST CRUD endpoints (apps/server/src/http/flows-yaml-routes.ts)

  • GET /flows — list {name, source, trigger_summary, enabled, mtime}.
  • GET /flows/:name — raw YAML + parsed metadata + last-run summary.
  • POST /flows — create under flows/custom/; 409 if name exists.
  • PUT /flows/:name — body includes expected_mtime; mismatch → 409 with current contents.
  • DELETE /flows/:name — 409 if source: "default"; otherwise remove file.
  • POST /flows/:name/disable — toggle enabled: false overlay (flows/custom/.disabled.json).
  • POST /flows/:name/dry-run — synthetic event input, per-step trace, no side effects, no flow_runs row.
  • POST /flows/reload — full re-scan.
  • GET /flows/runs (paginated) and GET /flows/runs/:id.
  • GET /schemas/flows.schema.json served from apps/web/public/schemas/.

Default-fork behavior

  • PUT on a source: "default" flow writes a copy to flows/custom/<name>.yml; custom version then served.
  • DELETE on a custom-shadowed default removes the custom file (resets to factory).

SSE

  • flow.changed { name, source, mtime } broadcast on every fs-watcher event and successful CRUD write.
  • All open editors receive it; client decides whether to refresh.

Auth + audit

  • All endpoints gated by existing dashboard auth.
  • Each write logs operator id + diff summary to existing audit log.

Tests

  • CRUD round-trip; mtime conflict; default-fork; default-delete-resets.
  • Dry-run produces deterministic trace and writes nothing to flow_runs.

Out of scope

  • Engine internals (covered by Engine+Ops ticket).
  • Editor UI (covered by Editor UI ticket).
  • Git commit-and-push button (deferred; spec §10.3.7).

References

  • Spec: docs/specs/flows-yaml.md §3, §10.3.2, §10.3.5, §10.3.7, §11 Phase 1 step 3.
  • Replaces: apps/server/src/http/flows-routes.ts:43–76 seed list.
## User story As an operator, I want the 9 default flows shipped on disk, a JSON Schema generated from Zod that drives both server validation and editor autocomplete, and a complete REST surface (with SSE change broadcast) so that the dashboard can manage flows without ssh-ing into the host. ## Acceptance criteria ### Default YAML files (`flows/defaults/`) - [ ] `pr-opened.yml`, `review-requested.yml`, `issue-opened.yml`, `issue-labeled.yml`, `issue-assigned.yml`, `issue-unassigned.yml`, `issue-closed.yml`, `pr-merged.yml`, `breakdown-comment.yml`. - [ ] Each file passes `flows.schema.json` validation; references only registered ops. - [ ] Side-effect parity with the legacy JSON node engine on recorded fixtures (asserted by shadow-mode diff in Migration ticket). - [ ] Per-default fixture-replay test under `flows/defaults/__tests__/`. ### Zod → JSON Schema pipeline - [ ] Zod schemas in `apps/server/src/domain/flows-yaml/schema.ts` cover `FlowFile`, `TriggerKind`, every op's `argsSchema` (discriminated on `uses`), expression types. - [ ] Each Zod field has `.describe()` (becomes JSON Schema `description` and Monaco hover text). - [ ] Build script `apps/server/scripts/generate-flow-schema.ts` runs `zod-to-json-schema`; output committed to `apps/web/public/schemas/flows.schema.json`. - [ ] Expression-string fields tagged `format: "flow-expression"`. - [ ] `just qa` regenerates and fails if committed file stale; web build runs generator before bundling. - [ ] `POST`/`PUT` re-parse with Zod (authoritative); JSON Schema mismatch → 422. - [ ] Snapshot test of generated schema; round-trip test that each default `*.yml` validates. ### REST CRUD endpoints (`apps/server/src/http/flows-yaml-routes.ts`) - [ ] `GET /flows` — list `{name, source, trigger_summary, enabled, mtime}`. - [ ] `GET /flows/:name` — raw YAML + parsed metadata + last-run summary. - [ ] `POST /flows` — create under `flows/custom/`; 409 if name exists. - [ ] `PUT /flows/:name` — body includes `expected_mtime`; mismatch → 409 with current contents. - [ ] `DELETE /flows/:name` — 409 if `source: "default"`; otherwise remove file. - [ ] `POST /flows/:name/disable` — toggle `enabled: false` overlay (`flows/custom/.disabled.json`). - [ ] `POST /flows/:name/dry-run` — synthetic event input, per-step trace, no side effects, no `flow_runs` row. - [ ] `POST /flows/reload` — full re-scan. - [ ] `GET /flows/runs` (paginated) and `GET /flows/runs/:id`. - [ ] `GET /schemas/flows.schema.json` served from `apps/web/public/schemas/`. ### Default-fork behavior - [ ] `PUT` on a `source: "default"` flow writes a copy to `flows/custom/<name>.yml`; custom version then served. - [ ] `DELETE` on a custom-shadowed default removes the custom file (resets to factory). ### SSE - [ ] `flow.changed { name, source, mtime }` broadcast on every fs-watcher event and successful CRUD write. - [ ] All open editors receive it; client decides whether to refresh. ### Auth + audit - [ ] All endpoints gated by existing dashboard auth. - [ ] Each write logs operator id + diff summary to existing audit log. ### Tests - [ ] CRUD round-trip; mtime conflict; default-fork; default-delete-resets. - [ ] Dry-run produces deterministic trace and writes nothing to `flow_runs`. ## Out of scope - Engine internals (covered by Engine+Ops ticket). - Editor UI (covered by Editor UI ticket). - Git commit-and-push button (deferred; spec §10.3.7). ## References - Spec: `docs/specs/flows-yaml.md` §3, §10.3.2, §10.3.5, §10.3.7, §11 Phase 1 step 3. - Replaces: `apps/server/src/http/flows-routes.ts:43–76` seed list.
Author
Collaborator

Inherited acceptance criteria from #1075

The engine PR (#1079) ships every #1075 acceptance criterion except the disk-loader's reload endpoint. That work belongs here:

  • POST /flows/reload — returns { loaded: number, errors: { file, error }[] }. Wraps FlowRegistry.reload() (already implemented in apps/server/src/domain/flows-yaml/loader.ts). Gated by existing dashboard auth. Ties #1075's loader/file-watcher AC closed once shipped here.

This is consistent with the original §10.3.5 surface listed in this ticket — flagged so the cross-issue link is explicit and #1075 can close as a single coherent shipment.

## Inherited acceptance criteria from #1075 The engine PR (#1079) ships every #1075 acceptance criterion **except** the disk-loader's reload endpoint. That work belongs here: - [ ] `POST /flows/reload` — returns `{ loaded: number, errors: { file, error }[] }`. Wraps `FlowRegistry.reload()` (already implemented in `apps/server/src/domain/flows-yaml/loader.ts`). Gated by existing dashboard auth. Ties #1075's loader/file-watcher AC closed once shipped here. This is consistent with the original §10.3.5 surface listed in this ticket — flagged so the cross-issue link is explicit and #1075 can close as a single coherent shipment.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#1076
No description provided.