feat(flows): SQLite persistence + default graph + read endpoints (NF-4) #352
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!352
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/325-nf4-persistence-default-graph"
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
flows,flow_runs,flow_node_runs) added as an appended migration block inapps/server/src/infrastructure/database/db.ts, with CRUD helpers:listFlows/getFlow/upsertFlow/deleteFlow/seedDefaultFlow,insertFlowRun/finishFlowRun/listFlowRuns/getFlowRun/deleteFlowRun/pruneFlowRunsOlderThan,insertFlowNodeRun/listFlowNodeRuns. No hard FK constraints (cascade is imperative) per the story's Bun-WAL caveat.apps/server/src/domain/flows/default-graph.json+ loader indefault-graph.tsthat compiles eagerly againstdefaultRegistry()on module load — a malformed shipped graph fails boot loudly instead of silently seeding a broken row.DEFAULT_GRAPH_VERSION = 1.GET /flows,GET /flows/:id,GET /flows/runs,GET /flows/runs/:id) inapps/server/src/http/flows-routes.ts, wired into the Hono app inmain.ts. Intentionally not behindguardMutating— NF-7 owns mutations, and the read-only surface lives on the same LAN boundary as/issues/pipeline//board. One-liner comment on each flags it.seedDefaultFlowAtBoot()wired intomain.tsright afterloadWebhookConfig. Upsert rule: insert when missing, rewrite whenversion !== DEFAULT_GRAPH_VERSION, no-op when equal, skip when an operator row hijacksid="default"— operator data never clobbered.pruneFlowRuns(ttlDays, now)added to the existing sweeper as a new Phase 4; default TTL 30 days, overridable viaSweeperConfig.flowRunTtlDays. Counts surface onSweepResult.{flow_runs_pruned, flow_node_runs_pruned}and in the pass log.Design notes
flowsrow exists withid="default"", andGraph.on.triggeris a single kind — so the shipped default graph is a single flow covering the most common dispatch path (issue.assigned → agent.dispatch(<routed-type>)). Gaps are flagged inline indefault-graph.tsand below.router.switchon the trigger'sroutedTypefield so the three dispatch branches (designer/design-reviewer/assigneefallback) are mutually exclusive.FILTER_DROPon the losing branches skips the dispatch cleanly — same pattern NF-5's soak can diff against once the trigger-shape contract is final.defaultGraphRegistry()kept as an indirection. NF-3 already stuffsforge.*+agent.*intodefaultRegistry(), so the function is currently a thin rename — but the indirection lives here so a future split (e.g. opting out ofagent.*for a read-only soak) only touches one file.CREATE TABLEDDL) — noted inline.Default-graph coverage vs. gap inventory (for NF-5 / NF-6)
Covered (this story):
issue.assigned→ label-overridearea:design→agent.dispatch(designer, implement)issue.assigned→ label-overridearea:design-review→agent.dispatch(design-reviewer, review)issue.assigned→ default →agent.dispatch(<routedType>, implement)NOT covered — tracked as NF-6 gaps:
pull_request_review.review_requested→agent.dispatch(reviewer)with the designer /area:dashboard→design-revieweroverrides (today inwebhook-handlers.ts).check_suite.completed→ reviewer re-request vs. fix-ci branching (today the state machine inwebhook-ci.ts)./raise-cap,/hold,/ready,/unassignslash-command dispatches.issue.closed(today indomain/workflow/deps.ts).Each of those is its own flow when NF-6 lands the cutover — no further DB work is needed; the
flowstable accepts arbitrary ids, andseedDefaultFlowAtBoot()only ownsid="default".Test plan
apps/server/src/domain/flows/default-graph.test.ts— 14 tests: shipped JSON parses, compiles against the NF-3 registry, uses a canonicalTRIGGER_KINDSkind, has asourcenode + at least oneagent.dispatch,defaultGraphBody()is stable.apps/server/src/infrastructure/database/flows-db.test.ts— 29 tests: CRUD on all three tables,listFlowsorders default→operator then by id,seedDefaultFlowreturns the four-state tag (inserted/unchanged/updated/skipped) and never touches operator rows,pruneFlowRunsOlderThancascades, pagination via keyset.apps/server/src/http/flows-routes.test.ts— 17 tests: every endpoint end-to-end, 404 paths,?limitclamping to[1, 500],?before=keyset,?flow_id=filter,seedDefaultFlowAtBoot()transitions including the operator-short-circuit.apps/server/src/background/flows-retention.test.ts— 7 tests: directpruneFlowRuns+runSweepintegration covering TTL override + cascade.bun x turbo run typecheckclean;bun x biome checkclean.apps/server/src/main.test.ts(46 tests) orapps/server/src/infrastructure/database/db.test.ts(24 tests). The 3 pre-existingsession JSONL pruningfailures + 1-2 foreman failures are unrelated to this story (confirmed by re-running againstmainatbdd183b).Total new tests: 67 (target was 30–40 — richer coverage because the three tables multiplied the CRUD surface).
Closes #325
🤖 Generated with Claude Code
54e38c21fba8ea470438