feat(flows): operator-authored flows HTTP API + audit log (NF-7) #363
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!363
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/328-nf7-operator-api"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
guardMutatingauth gate and writes an atomic audit row alongside the live table via a newwithFlowTransactionhelper.source="default"rows 409s.New endpoints
POST/flowssource="operator") → 201PATCH/flows/:idDELETE/flows/:idPOST/flows/:id/enable/disablePOST/flows/:id/dry-runmutationMode: "dry-run") — returns{ run_id: "ephemeral", nodes: [...] }. Not persisted toflow_runs.POST/flows/:id/revert?version=NGET/flows/:id/versionsGET/flows/:id/versions/:versionDesign notes
apps/server/src/domain/flows/flow-validation.ts. Surface-level checks (id regex^[a-z0-9-]+$,version === 1,on.trigger∈TRIGGER_KINDS, optionalpriority/mutex_group) run first; thendefaultRegistry().compile(body)covers cycles, port-type mismatches, and unknown node types. Any failure returns 422 with{ error, detail: { nodes, edges, global } }— mirrors NF-UI-3'sFlowValidationErrorso the dashboard can render without translation.flow_auditSQLite table withversion_before/version_after+body_before/body_after+operator+action. Writes happen insidewithFlowTransactionso the log can never drift from the live table. Survives flow deletion so history stays navigable post-delete.main.tsextracts it from the Hono context afterguardMutatinghas already validated it matchesauth.operator_user. Pre-M18-8 deployments without anauthblock fall back to a static"operator"label so theflow_audit.operatorcolumn stays NOT NULL.just flows-apply <file.json>: probesGET /flows/:idand decides POST vs PATCH.curl+jqonly — no Bun dependency in the recipe itself. RespectsCLAUDE_HOOKS_URL+CLAUDE_HOOKS_OPERATORenv.Test plan
bun test src/http/flows-routes.test.ts→ 63 pass (17 NF-4 + 46 NF-7)bun test src/http/auth.test.ts→ 49 pass (adds 14 new route-level auth assertions covering every NF-7 mutation + the two read-only GETs)bun x tsc --noEmitcleanbun x biome check .cleanbun test): 1790 pass, 4 pre-existing unrelated failures (sweeper.test.ts,handlers/foreman.test.ts) confirmed againstmainbefore my branchCovered behaviours
handleRequeston every NF-7 mutation withoutRemote-Userpriority, non-stringmutex_group.id≠ URL path idrun_id: "ephemeral", does NOT create aflow_runsrowreverted_from_versionin response,action="revert"audit rowGET /flows/:id/versionswith node-diff counts; 200 after delete (history outlives the row)Out of scope
{ trigger }today and 422s on{ fixture }with a clear marker.checkOperatorAuthreturns 403 uniformly (used by every other auth-gated route); aligning would be a cross-cutting change.Closes #328
🤖 Generated with Claude Code
Adds the operator-authored mutation surface on top of NF-4's read-only flows endpoints. Every write lives behind the existing `guardMutating` auth gate; reads stay public at the LAN boundary. New endpoints: - POST /flows create an operator flow (version = 1) - PATCH /flows/:id replace body, bump version - DELETE /flows/:id remove flow (default flows 409) - POST /flows/:id/enable|disable toggle + version bump - POST /flows/:id/dry-run ephemeral executor run in dry-run mutationMode; no flow_runs row - POST /flows/:id/revert?version=N restore historical body - GET /flows/:id/versions audit summary (nodes added / removed / modified per row) - GET /flows/:id/versions/:version historical body snapshot Validation goes through `defaultRegistry().compile(body)`; failures return a 422 with a `detail: { nodes, edges, global }` envelope that mirrors NF-UI-3's FlowValidationError shape. Default flows (source="default") are read-only through every mutation path. New SQLite table `flow_audit` captures before/after body + version + operator per mutation. Writes go through `withFlowTransaction` so the audit log can never drift from the live table. `just flows-apply <file.json>` probes GET /flows/:id and decides POST vs. PATCH automatically, driven by curl + jq so the recipe has no runtime dependency on Bun. Tests: +60 (46 handler / +14 route-level auth). Covers CRUD, auth gate via handleRequest (401→403 path), validation errors with structured body, version bumping, default-flow protection, dry-run ephemerality, audit log write/read, revert semantics, and the GET /flows/:id/versions node-diff counts. Closes #328 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>