Build the map-driven os-sdlc runner (ADR-0133/0134/0135) #438

Closed
opened 2026-08-20 12:06:07 +00:00 by jared · 2 comments
Owner

Build the map-driven os-sdlc runner [capture]

Context

Deep planning session 2026-08-19/20 produced the map-runner design (docs/os-sdlc-rebuild/map-runner-design.md) and ADRs 0133/0134/0135. This ticket captures the build. Spec below.

Origin

  • Trigger: user-directed session capture (2026-08-19/20); see #436
  • Improvised this session: none
  • Chain: runner hardcodes process in code ← ADR-0130 removed map-as-data layer ← DESIGN (ADR-0130)
  • Root candidate: none (this ticket IS the root solution)

Tasks

  • Slice 0: Map loader and validator on toy map, pure objects
  • Slice 1: implementation-open entrypoint plus schema bump
  • Slice 2: subagent-start brief composition
  • Slice 3: subagent-stop handoff intake plus GateVerdict
  • Slice 4: next routing on pass edges (tracer bullet, toy map end-to-end)
  • Slice 5: fail edges, loop bounds, error routing
  • Slice 6: real coding map plus gate-chain port plus AgentVerdict
  • Slice 7: documentation sweep and plugin-cache refresh

Acceptance Criteria

  • Map validation proves termination at load and fails loudly
  • Four public entrypoints (implementation-open, next, subagent-stop, subagent-start) exercise against disposable database and toy map
  • Tests assert returned instructions, stamped fields, and exit codes
  • Toy map runs end-to-end proving walk before breadth
  • Coding map ports existing hardcoded test and lint gate chain
  • Default coding map routes gate fails to terminal error node (no shipped behavior change until map says otherwise)
  • Handoff contract preserved: created_by foreign key, target_agent_type stamped by runner from map edge, structured YAML content
  • Gate evidence collected in runner-owned gate_results brief section
  • No skippable vocabulary; loop exhaustion routes to error node
  • Documentation swept of round-only wording

Specification

Spec: Map-driven os-sdlc runner

Design record: docs/os-sdlc-rebuild/map-runner-design.md. Decisions: ADR-0133, ADR-0134, ADR-0135 (with ADR-0130/0131/0132 as amended context).

Problem Statement

The runner hardcodes its pipeline in Ruby. To change what happens after a gate fails, or to add a non-coding pipeline, the user must edit runner code. ADR-0130 removed the map-as-data layer by accident, so routing policy and process shape now live in the same place as the mechanism. Handoffs can also leak across implementations, and agents address their own handoffs, which lets a writer route work to the wrong place.

Solution

Reinstate maps as data. A map is a YAML file that declares agent nodes, verdict-routed edges, per-edge loop bounds, and an error node. The runner loads one map per implementation, proves at load time that the map terminates, and then only walks it: dispatch the node's agent, take the verdict, follow the edge. Process changes become map edits. The default coding map keeps today's behavior: a gate fail routes to the error node and the implementation fails loudly.

User Stories

  1. As the user, I want pipeline shape declared in a YAML map, so that I can change routing without editing runner code.
  2. As the user, I want a closed verdict set (pass, fail, error), so that every edge is checkable at load time.
  3. As the user, I want per-edge bound counters, so that no cycle can loop forever.
  4. As the user, I want map validation at load, so that a bad map fails before any agent is dispatched.
  5. As the user, I want validation to prove entry existence, node reachability, terminal reachability from every node, bounded cycles, full verdict coverage, and a named agent type per agent node.
  6. As the main-loop orchestrator, I want exactly four entrypoints (implementation-open, next, subagent-stop, subagent-start), so that the runner's surface stays small.
  7. As the main-loop orchestrator, I want implementation-open to bind session, ticket, and map and return the entry node's instructions.
  8. As the main-loop orchestrator, I want next to route the latest verdict along the map's edges and return the next instructions or a terminal payload.
  9. As a pipeline agent, I want my handoff stored writer-anonymous with a created_by foreign key, so that I never address my own output.
  10. As the runner, I want to stamp target_agent_type from the map edge during routing, so that addressing is a routing decision, not a writer decision.
  11. As a pipeline agent, I want my handoff content validated against a small closed YAML schema (verdict, summary, optional items), so that malformed handoffs are rejected loudly at intake and never stored.
  12. As a dispatched agent, I want a composed dispatch brief (node instructions, claimed handoff, named enrichments) assembled live at dispatch time, so that I start self-contained.
  13. As a dispatched agent, I want raw test and lint failures in a runner-owned gate_results brief section, so that gate evidence reaches me without polluting the agent-authored handoff.
  14. As the runner, I want handoff claiming scoped to the live implementation in one insert, so that a dead implementation's handoff is unreachable by construction.
  15. As the user, I want gate fails routed per the map's bounded edges, with the default coding map routing them to the terminal error node, so that shipped behavior is unchanged until a map says otherwise.
  16. As the user, I want no skippable flag and no retry vocabulary anywhere, so that no actor can accept a red gate.
  17. As the user, I want bound exhaustion to route to the map-declared error node, so that loops end in a loud failure, not a silent stall.
  18. As the user, I want a toy two-node map to run end-to-end before the real coding map exists, so that the tracer bullet proves the walk before breadth.
  19. As the user, I want the coding map to port the existing hardcoded test and lint gate chain, so that current gate behavior survives the migration.
  20. As the user, I want a reviewer verdict path (AgentVerdict), so that judgment agents can produce verdicts the map can route.
  21. As the user, I want skill and reference docs swept of round-only wording after the build, so that documentation matches the map vocabulary.
  22. As the user, I want the disposable-database and schema-versioning pattern preserved, so that migration stays a delete-and-rebuild.

Implementation Decisions

  • Maps are YAML data files shipped with the plugin; one map bound per implementation; coding and non-coding maps are both legal.
  • Verdicts form a closed set: pass, fail, error. Every gate and every agent completion produces one.
  • Edges are verdict-routed per node and may carry a bound counter. A bound is a traversal guard, never an implementation retry (ADR-0133 carries this forward from ADR-0131/0132). Exhaustion routes to the error node.
  • A MapValidator proves termination at load and fails loud (ADR-0133).
  • Four public entrypoints only; everything else is private (design doc part 1).
  • Handoff contract per ADR-0135: created_by foreign key, no denormalized agent-type string; target_agent_type stamped only by the runner from the map edge; structured YAML content with loud rejection (exit 3) on schema violation; claiming via the dispatch row scoped to the live implementation.
  • Gate evidence per ADR-0135: a runner-owned gate_results brief section composed from step results at dispatch time; the handoff stays strictly agent-authored.
  • ADR-0134 amendment: routing is map-total; the default coding map keeps gate-fail terminal, so this build changes no shipped behavior by itself.
  • Preserved modules: webhook-thin hooks, fetch-then-create dispatch, disposable DB with PRAGMA versioning, gate evaluation and step results (become GateVerdict internals).
  • Open decision, settle during the tracer slice: whether the existing state machine module backs the Map runtime or is deleted.
  • Build order: eight TDD slices as ordered in the design doc part 2.3 — (0) loader and validator on a toy map, pure objects; (1) implementation-open plus schema bump; (2) subagent-start brief composition; (3) subagent-stop handoff intake plus GateVerdict; (4) next routing on pass edges — the tracer bullet, toy map end-to-end; (5) fail edges, loop bounds, error routing; (6) real coding map plus gate-chain port plus AgentVerdict; (7) documentation sweep and plugin-cache refresh.

Testing Decisions

  • Two seams, confirmed by the user. Seam 1: the four CLI entrypoints, exercised against a disposable database and a toy map; tests assert returned instructions, stamped fields, and exit codes — external behavior only. Seam 2: MapLoader and MapValidator as pure objects with no database, because load-time termination proofs need direct assertions on validator errors.
  • No per-class unit tests for GateVerdict, LoopLedger, or handoff intake; their behavior is observable through the CLI seam.
  • A good test asserts what a caller can see (output, exit code, stored row reachable through the next entrypoint call), never internal structure.
  • Prior art: the existing runner CLI tests in the os-sdlc plugin test suite follow this shape.
  • Red-green-refactor per slice; slice 4 is the go/no-go tracer.

Out of Scope

  • Any change to shipped gate-fail behavior; the default coding map keeps gate-fail terminal (ADR-0134).
  • Resume, park, auto-retry, or any skippable vocabulary (ADR-0131/0132/0133).
  • Non-coding maps beyond proving they are legal.
  • The #436 blocked-ticket rollback behavior — its own ticket.
  • Bulk rewrite of historical docs beyond the slice-7 sweep.

Further Notes

  • Run the live-run hook proof before or alongside the tracer slice; the Fable-5 harness needs manual event piping for hooks (async dispatch quirk).
  • The design doc, not this spec, is the authority on schemas and node vocabulary; this spec fixes scope and order.
# Build the map-driven os-sdlc runner [capture] ## Context Deep planning session 2026-08-19/20 produced the map-runner design (docs/os-sdlc-rebuild/map-runner-design.md) and ADRs 0133/0134/0135. This ticket captures the build. Spec below. ## Origin - Trigger: user-directed session capture (2026-08-19/20); see #436 - Improvised this session: none - Chain: runner hardcodes process in code ← ADR-0130 removed map-as-data layer ← DESIGN (ADR-0130) - Root candidate: none (this ticket IS the root solution) ## Tasks - [ ] Slice 0: Map loader and validator on toy map, pure objects - [ ] Slice 1: implementation-open entrypoint plus schema bump - [ ] Slice 2: subagent-start brief composition - [ ] Slice 3: subagent-stop handoff intake plus GateVerdict - [ ] Slice 4: next routing on pass edges (tracer bullet, toy map end-to-end) - [ ] Slice 5: fail edges, loop bounds, error routing - [ ] Slice 6: real coding map plus gate-chain port plus AgentVerdict - [ ] Slice 7: documentation sweep and plugin-cache refresh ## Acceptance Criteria - Map validation proves termination at load and fails loudly - Four public entrypoints (implementation-open, next, subagent-stop, subagent-start) exercise against disposable database and toy map - Tests assert returned instructions, stamped fields, and exit codes - Toy map runs end-to-end proving walk before breadth - Coding map ports existing hardcoded test and lint gate chain - Default coding map routes gate fails to terminal error node (no shipped behavior change until map says otherwise) - Handoff contract preserved: created_by foreign key, target_agent_type stamped by runner from map edge, structured YAML content - Gate evidence collected in runner-owned gate_results brief section - No skippable vocabulary; loop exhaustion routes to error node - Documentation swept of round-only wording ## Specification # Spec: Map-driven os-sdlc runner Design record: `docs/os-sdlc-rebuild/map-runner-design.md`. Decisions: ADR-0133, ADR-0134, ADR-0135 (with ADR-0130/0131/0132 as amended context). ## Problem Statement The runner hardcodes its pipeline in Ruby. To change what happens after a gate fails, or to add a non-coding pipeline, the user must edit runner code. ADR-0130 removed the map-as-data layer by accident, so routing policy and process shape now live in the same place as the mechanism. Handoffs can also leak across implementations, and agents address their own handoffs, which lets a writer route work to the wrong place. ## Solution Reinstate maps as data. A map is a YAML file that declares agent nodes, verdict-routed edges, per-edge loop bounds, and an error node. The runner loads one map per implementation, proves at load time that the map terminates, and then only walks it: dispatch the node's agent, take the verdict, follow the edge. Process changes become map edits. The default coding map keeps today's behavior: a gate fail routes to the error node and the implementation fails loudly. ## User Stories 1. As the user, I want pipeline shape declared in a YAML map, so that I can change routing without editing runner code. 2. As the user, I want a closed verdict set (pass, fail, error), so that every edge is checkable at load time. 3. As the user, I want per-edge bound counters, so that no cycle can loop forever. 4. As the user, I want map validation at load, so that a bad map fails before any agent is dispatched. 5. As the user, I want validation to prove entry existence, node reachability, terminal reachability from every node, bounded cycles, full verdict coverage, and a named agent type per agent node. 6. As the main-loop orchestrator, I want exactly four entrypoints (implementation-open, next, subagent-stop, subagent-start), so that the runner's surface stays small. 7. As the main-loop orchestrator, I want implementation-open to bind session, ticket, and map and return the entry node's instructions. 8. As the main-loop orchestrator, I want next to route the latest verdict along the map's edges and return the next instructions or a terminal payload. 9. As a pipeline agent, I want my handoff stored writer-anonymous with a created_by foreign key, so that I never address my own output. 10. As the runner, I want to stamp target_agent_type from the map edge during routing, so that addressing is a routing decision, not a writer decision. 11. As a pipeline agent, I want my handoff content validated against a small closed YAML schema (verdict, summary, optional items), so that malformed handoffs are rejected loudly at intake and never stored. 12. As a dispatched agent, I want a composed dispatch brief (node instructions, claimed handoff, named enrichments) assembled live at dispatch time, so that I start self-contained. 13. As a dispatched agent, I want raw test and lint failures in a runner-owned gate_results brief section, so that gate evidence reaches me without polluting the agent-authored handoff. 14. As the runner, I want handoff claiming scoped to the live implementation in one insert, so that a dead implementation's handoff is unreachable by construction. 15. As the user, I want gate fails routed per the map's bounded edges, with the default coding map routing them to the terminal error node, so that shipped behavior is unchanged until a map says otherwise. 16. As the user, I want no skippable flag and no retry vocabulary anywhere, so that no actor can accept a red gate. 17. As the user, I want bound exhaustion to route to the map-declared error node, so that loops end in a loud failure, not a silent stall. 18. As the user, I want a toy two-node map to run end-to-end before the real coding map exists, so that the tracer bullet proves the walk before breadth. 19. As the user, I want the coding map to port the existing hardcoded test and lint gate chain, so that current gate behavior survives the migration. 20. As the user, I want a reviewer verdict path (AgentVerdict), so that judgment agents can produce verdicts the map can route. 21. As the user, I want skill and reference docs swept of round-only wording after the build, so that documentation matches the map vocabulary. 22. As the user, I want the disposable-database and schema-versioning pattern preserved, so that migration stays a delete-and-rebuild. ## Implementation Decisions - Maps are YAML data files shipped with the plugin; one map bound per implementation; coding and non-coding maps are both legal. - Verdicts form a closed set: pass, fail, error. Every gate and every agent completion produces one. - Edges are verdict-routed per node and may carry a bound counter. A bound is a traversal guard, never an implementation retry (ADR-0133 carries this forward from ADR-0131/0132). Exhaustion routes to the error node. - A MapValidator proves termination at load and fails loud (ADR-0133). - Four public entrypoints only; everything else is private (design doc part 1). - Handoff contract per ADR-0135: created_by foreign key, no denormalized agent-type string; target_agent_type stamped only by the runner from the map edge; structured YAML content with loud rejection (exit 3) on schema violation; claiming via the dispatch row scoped to the live implementation. - Gate evidence per ADR-0135: a runner-owned gate_results brief section composed from step results at dispatch time; the handoff stays strictly agent-authored. - ADR-0134 amendment: routing is map-total; the default coding map keeps gate-fail terminal, so this build changes no shipped behavior by itself. - Preserved modules: webhook-thin hooks, fetch-then-create dispatch, disposable DB with PRAGMA versioning, gate evaluation and step results (become GateVerdict internals). - Open decision, settle during the tracer slice: whether the existing state machine module backs the Map runtime or is deleted. - Build order: eight TDD slices as ordered in the design doc part 2.3 — (0) loader and validator on a toy map, pure objects; (1) implementation-open plus schema bump; (2) subagent-start brief composition; (3) subagent-stop handoff intake plus GateVerdict; (4) next routing on pass edges — the tracer bullet, toy map end-to-end; (5) fail edges, loop bounds, error routing; (6) real coding map plus gate-chain port plus AgentVerdict; (7) documentation sweep and plugin-cache refresh. ## Testing Decisions - Two seams, confirmed by the user. Seam 1: the four CLI entrypoints, exercised against a disposable database and a toy map; tests assert returned instructions, stamped fields, and exit codes — external behavior only. Seam 2: MapLoader and MapValidator as pure objects with no database, because load-time termination proofs need direct assertions on validator errors. - No per-class unit tests for GateVerdict, LoopLedger, or handoff intake; their behavior is observable through the CLI seam. - A good test asserts what a caller can see (output, exit code, stored row reachable through the next entrypoint call), never internal structure. - Prior art: the existing runner CLI tests in the os-sdlc plugin test suite follow this shape. - Red-green-refactor per slice; slice 4 is the go/no-go tracer. ## Out of Scope - Any change to shipped gate-fail behavior; the default coding map keeps gate-fail terminal (ADR-0134). - Resume, park, auto-retry, or any skippable vocabulary (ADR-0131/0132/0133). - Non-coding maps beyond proving they are legal. - The #436 blocked-ticket rollback behavior — its own ticket. - Bulk rewrite of historical docs beyond the slice-7 sweep. ## Further Notes - Run the live-run hook proof before or alongside the tracer slice; the Fable-5 harness needs manual event piping for hooks (async dispatch quirk). - The design doc, not this spec, is the authority on schemas and node vocabulary; this spec fixes scope and order.
Author
Owner

Pre-flight grilling complete 2026-08-20. Approved interface sketches for slices 0 and 4 live in docs/os-sdlc-rebuild/interface-sketches.md (guard manifests: any class/public method/signature not in the sketch → implementing agent stops and surfaces). Settled decisions: D4 Router stays pure, NextStep applies the Decision; D5 state_machine.rb + test deleted at slice 4 (resolves this ticket's open state-machine question); D2 slice-0 tests outside tests/runner/; D3 routing-only nodes use a single default: edge; D6 verdict persists on dispatches.verdict at E3; D7 toy map is a test fixture, no maps/ until slice 6. Process: sequential build, one child ticket per slice, seam-design sketch produced just-in-time one slice ahead. Slice 0 captured as #439.

Pre-flight grilling complete 2026-08-20. Approved interface sketches for slices 0 and 4 live in docs/os-sdlc-rebuild/interface-sketches.md (guard manifests: any class/public method/signature not in the sketch → implementing agent stops and surfaces). Settled decisions: D4 Router stays pure, NextStep applies the Decision; D5 state_machine.rb + test deleted at slice 4 (resolves this ticket's open state-machine question); D2 slice-0 tests outside tests/runner/; D3 routing-only nodes use a single default: edge; D6 verdict persists on dispatches.verdict at E3; D7 toy map is a test fixture, no maps/ until slice 6. Process: sequential build, one child ticket per slice, seam-design sketch produced just-in-time one slice ahead. Slice 0 captured as #439.
Author
Owner

Resolution

Done: Map-driven os-sdlc runner built end to end across slices 0-7: map loading/validation, DB-backed rounds, gate and agent verdicts, router with bounded retry edges, hooks integration, real coding map, agents realigned to the handoff contract, close-out remediation complete (B1 prose + intake guard, doc sweep, D57, slice-0 history row)

Evidence: Suite 851 runs, 2146 assertions, 0 failures, 0 rubocop offenses on touched code; 9 of 10 acceptance criteria passed with evidence in the #447 review, AC10 doc sweep completed 2026-08-21; ADRs 0133-0137 record the decisions

Follow-ups: Live-run hook proof is explicitly NOT claimed and is captured as #454; #455/#456/#457 capture the remaining review notes; #452 round-vocabulary seams; #453 first official map and agent configuration

## Resolution **Done:** Map-driven os-sdlc runner built end to end across slices 0-7: map loading/validation, DB-backed rounds, gate and agent verdicts, router with bounded retry edges, hooks integration, real coding map, agents realigned to the handoff contract, close-out remediation complete (B1 prose + intake guard, doc sweep, D57, slice-0 history row) **Evidence:** Suite 851 runs, 2146 assertions, 0 failures, 0 rubocop offenses on touched code; 9 of 10 acceptance criteria passed with evidence in the #447 review, AC10 doc sweep completed 2026-08-21; ADRs 0133-0137 record the decisions **Follow-ups:** Live-run hook proof is explicitly NOT claimed and is captured as #454; #455/#456/#457 capture the remaining review notes; #452 round-vocabulary seams; #453 first official map and agent configuration
jared closed this issue 2026-08-21 13:30:03 +00:00
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
jared/cc-os#438
No description provided.