feat(pipeline): real backend runner + control-plane protocol + hardening - #207
Conversation
Hardening (review fixes): per-pipeline serialization of advance/answer (no lost updates once phases run async); driveResumable() re-drives interrupted draft/running pipelines on boot (halted stay parked); source-qualified halt requestId (entry/exit/kind) + documented halt=human-override vs abort=final; create() validates kinds/gates/skills up front; listActive() uses an index-friendly status IN (...) via ACTIVE_STATUSES; abort() fails the active phase; PipelineStore shares the daemon DB connection (Store.database) instead of a second handle; shared errMessage util. PR4 (real runner): SessionPhaseRunner drives a prompt/slash skill on a disposable worker session via SessionManager.runPhaseTurn (honors per-phase provider/model, awaits idle/error, tears down), wired through createPipelineManagerFromConfig; fn skills still run natively. PipelineState gains workdir. PR5 (control plane): additive pipeline.create/list/get/answer/abort + pipeline.snapshot/list.result messages + SessionInfo.phase/profile (no PROTOCOL_VERSION bump); zod schemas; pipeline:create/read/answer scopes; daemon handlers with scope + tenancy checks + wire projection. Halt push-broadcast + frontend UIs deferred (poll pipeline.get to observe a halt). Still off by default (config.pipeline.enabled=false). Verified: tsc (daemon+protocol+core) clean, biome clean (287 files), 1610 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
🤖 Gemini code reviewThis PR implements a real backend runner, control-plane protocol, and concurrency hardening for SDLC pipelines. The changes look well-structured and highly robust, with comprehensive unit and integration tests. One high-priority issue was identified in the worker session auth initialization within the backend runner. Findings: 🔴 0 · 🟠 1 · 🟡 0 · 🟢 0 Tokens spent · ⬆️ Input: 20,453 · ⬇️ Output: 310 · Σ Total: 31,659 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #207 +/- ##
==========================================
+ Coverage 87.69% 87.91% +0.22%
==========================================
Files 126 128 +2
Lines 21482 21839 +357
==========================================
+ Hits 18838 19200 +362
+ Misses 2644 2639 -5
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…safety hardening Deep audit of the merged foundation + this PR (4 parallel reviews) surfaced real issues; all folded in with tests. HIGH: the runtime was inert — added a pipeline.advance verb + handler and wired PipelineManager.driveResumable() into daemon boot (server.ts startPipelines), so created/interrupted pipelines actually run. A non-idle worker turn (error / budget-exhausted waiting_approval / timeout) now FAILS the phase instead of silently passing: runPhaseTurn returns the resting status and SessionPhaseRunner throws on anything but idle. Added a per-turn timeout so a clientless worker can't hang forever + leak. Removed PhaseDef.tools (a documented hard-fence that was never enforced = false security) until the canUseTool fence lands. abort() now routes through the per-id serialization chain (was clobberable by an in-flight advance). MED: MAX_STEPS exhaustion now fails the pipeline terminally (was stuck 'running'); create() rejects kind:skill with no skill id and duplicate phase ids; the engine hands plugins a cloned view (a hostile kind/gate can't corrupt the transition); pipeline.create validates workdir + per-phase provider up front (fail fast, like session.create). Cache evicts terminal pipelines; get()/returns hand out clones. Tests (+~14): non-idle turn → phase failure (unit + real-backend error-turn integration); pipeline.advance + halt→answer→resume round-trip over the wire; cross-tenant answer/abort → not_found; workdir/provider validation; MAX_STEPS→failed; retry-via-gate exhaustion; plugin-mutation isolation; driveResumable re-driving a crashed running pipeline; get() isolation; skill-id/duplicate-id validation. Verified: tsc (daemon+protocol+core) clean, biome clean (287 files), 1624 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Combined follow-up to the merged SDLC-pipeline foundation (#204/#205/#206): the review fixes + the real backend runner (PR4) + the control-plane protocol (PR5). Still off by default (
config.pipeline.enabled=false) — no behavior change until enabled.1. Hardening (addresses the review findings)
advance/answerare serialized per pipeline id (a promise chain), so awaited work can't interleave and clobber persisted state once phases run async.driveResumable()re-drives pipelines interrupted mid-run (draft/running) on boot; halted pipelines stay parked for a human answer.requestIdis source-qualified (entry:/exit:/kind:) so a phase with both gates can't collide; documentedhalt= human-overridable decision point vsabort= final.PipelineStorereuses the daemonStore's handle (Store.database) instead of opening a second connection.status IN (…)viaACTIVE_STATUSES(with a partition-drift guard test).errMessageutil.2. PR4 — real backend runner
SessionPhaseRunnerdrives a phase'sprompt/slashskill on a disposable worker session via a newSessionManager.runPhaseTurn(mirrors the dispatcher spawn; honors per-phaseprovider/model; awaitsidle/error; always tears down). Wired throughcreatePipelineManagerFromConfigwith a lazy() => thishost thunk;fnskills still run natively.PipelineStategainsworkdir.Proven end-to-end with a
MockSessionProvider: a skill phase runs a worker turn and captures its final text as the phase summary.3. PR5 — control-plane protocol
Additive (no
PROTOCOL_VERSIONbump):pipeline.create/list/get/answer/abort(client→daemon) +pipeline.snapshot/pipeline.list.result(daemon→client) +SessionInfo.phase/profile; zod schemas;pipeline:create/read/answerscopes; daemon handlers with scope + tenancy checks and a serializable wire projection. A client drives a pipeline over the wire and observes a halt viapipeline.get.Verification
tsc --noEmitclean across daemon + protocol + corebiome checkclean (287 files)Deferred (clean follow-ups)
pipeline.blockerwould route through the tenant conductor session. Pollpipeline.getfor now.phases[];createcurrently takes explicit phases.🤖 Generated with Claude Code