Skip to content

feat(board): E1 implement lane split — Architect / Implementer relay (v7.30.0) - #41

Merged
SSFSKIM merged 49 commits into
feature/en-cyclesfrom
worktree-e1-lane-split
Jul 31, 2026
Merged

feat(board): E1 implement lane split — Architect / Implementer relay (v7.30.0)#41
SSFSKIM merged 49 commits into
feature/en-cyclesfrom
worktree-e1-lane-split

Conversation

@SSFSKIM

@SSFSKIM SSFSKIM commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Epic E1 of docs/doperpowers/specs/2026-07-30-implement-lane-split-design.md (v1.3.2). Ships as v7.30.0.

One implement worker used to carry a ticket from design judgment through execution — it ran the gate, authored its own ExecPlan when the work needed one, and drove to a PR, all on one model route. That couples two jobs with opposite model economics: a bad plan is the most expensive artifact in the pipeline, so plan authorship wants the frontier model, while executing a finished plan is middle-class work.

This splits the single dispatch queue into two lanes and encodes the relay in board states, so the dispatcher stays judgment-free.

What moved

The state machine (skills/issue-tracker/scripts/_board.py, board-transition.sh, board-register.sh, board-answer.sh, board-sweep.sh, board-reconcile.sh, board-lint.sh)

  • ready-for-agent is deleted, not aliased. In its place: ready-for-architect (dispatchable to design), in-design (the Architect's in-flight state), ready-for-implementer (dispatchable to execution, and the default birth state — unsure routes here).
  • plan: meta pin, written by --plan on the Architect's handoff edge only. Two values with genuinely different meanings: <repo-path>@<40-hex-sha> is an authorization event (the Implementer runs PLAN-EXECUTION — no intake gate, no gate comment, no re-litigating the design), while the literal pre-spec is a plan-need ruling (the ticket still runs DIRECT and still posts a real [gate] pass). Conflating the two caused two defects during implementation; every rule that reads the pin now says which value it means.
  • pre-park: meta so an answered needs-human park returns to the right in-flight state instead of a hardcoded one.
  • Escalation edges to the architect lane — from the Implementer's gate, from the Implementer mid-build, and from the review loop at a design-gap impasse. A design gap discovered downstream now has a named address instead of leaking into needs-human.
  • Convergence rule: escalation-edge comments are keyed [board] <from> → <to>: <note>, and a second traversal of the same edge since the last [answers] comment converts mechanically to a needs-human park. The comment log is the counter — no new bookkeeping.
  • Sweep recovery splits by lane: in-flight states (in-progress, in-design) go through the counted resume ladder; pre-verdict states (ready-for-architect, ready-for-implementer) are retired and freshly dispatched.

Dispatch (skills/implementing/scripts/implement-dispatch.sh)

Role resolution order is SPIKE (by category, state-free — category outranks lane) → ARCHITECT (by state) → IMPLEMENT. ARCHITECT_MAX_CONCURRENT defaults to 1, IMPLEMENT_MAX_CONCURRENT to 5. The architect route hard-sets engine="claude" and pins ${ARCHITECT_MODEL:-fable}, so engine:* labels and WORKER_ENGINE are never read on that route — plan authorship is not label-routed. X4 semantics are unchanged for every other role.

Protocols

  • New skills/architecting/SKILL.md — the Architect worker. Ends at the plan: writes no implementation code, never reviews the Implementer's output. Carries the architect-lane gate variant (open design forks are this lane's work, not gate failures), council scaling by artifact shape, WIP banking at every park, the down-shortcircuit, and the decompose exit.
  • skills/implementing-tickets/ renamed to skills/implementing/, with a new Mode Selection section driven by the plan: pin.
  • skills/reviewing-prs/SKILL.md learns plan-pin admissibility, the authorization anchor (a real-pin ticket has no [gate] pass; the anchor is the Architect's handoff comment), and the ready-for-architect impasse route for seam-clustered blockers.
  • skills/triaging-feedback/ registrar births ready-for-implementer; its conservative gate is unchanged and it adds no architect-lane birth.

Validation Evidence

Suites — all green at HEAD: tests/issue-tracker/test-board-scripts.sh (280 pass), test-board-sweep.sh, node test-board-template.cjs, tests/implementing/test-implement-dispatch.sh, test-protocol-content.sh, all four tests/reviewing-prs/*.sh, skills/triaging-feedback vitest (105 pass, 8 files), tests/claude-code/run-skill-tests.sh (3/3), scripts/lint-shell.sh. Both repo-wide residual-vocabulary greps (ready-for-agent, implementing-tickets) are empty outside the three sanctioned locations: docs/, board-migrate-gh.sh (it migrates the label, so it must name it), and the frozen tests/review-bench/results/.

The spec's eight acceptance criteria, each checked against the code and protocol text rather than against the implementation reports:

# Criterion Result
1 Architect gate verdict is its first board write; scope ends at the handoff or the decompose exit Partially verified
2 A plan:-pinned dispatch spawns an Opus worker that authors no plan document and posts no gate comment Partially verified (the Opus pin is now structural — see below)
3 A plan-needing Implementer writes ready-for-architect, not needs-human Partially verified
4 Mid-execution blockage lands in-progress → ready-for-architect with note, summary, and WIP Partially verified
5 QAgent Opus-high pin + fix-wave agent Deferred — rides C5 (#32)
6 Architect on an oversized goal registers children by the birth rule; a spike child is born ready-for-implementer Verified
7 A second traversal of the same edge parks needs-human Verified
8 An answered Architect park resumes into in-design and completes legally Verified

"Partially verified" is drawn on one consistent line: the dispatcher's routing (role, model, protocol file) and the protocol text a worker reads are proven statically; a live LLM worker's actual conformance to that text is not, and no test here substitutes for the first live architect dispatch.

Three gaps the acceptance pass found, all closed on the branch: the in-progress → ready-for-architect edge had no test at all (added, with three mutation proofs — note requirement, legality-table membership, convergence-edge membership); the five new review-loop rules were unpinned while the equivalent implementing and architecting text had assertions from the start (seven anchors added, each verified absent from the pre-branch file); and the convergence park note promised "both positions" while carrying one (reworded to carry this traversal's position plus a pointer to the comment trail).

Whole-branch review. Codex was unavailable (account usage limit), so this went to a fable reviewer with the full branch package. It returned one merge blocker and three Importants; all are fixed, and the reviewer re-verified the fix range and revised its verdict to ready.

  • Critical — the needs-human → in-review pre-park return was unusable. board-transition.sh required --pr on every entry into in-review, and board-answer.sh returned with a note only, so answering any review-loop park died with "a PR link is required", leaving the ticket stranded and the bound session unresumed. Worse on the unattended path: the sweep stamps its at-most-once relay guard before the failing call, so the human's answer was lost permanently and silently. This was a regression this branch introduced — at the merge base the return was hardcoded to in-progress, which has no PR gate. Fixed by letting the gate accept the ticket's already-recorded pr: meta; first entry and the repair path still refuse, verified by probe.
  • Important — the convergence counter never reset on the sweep's relay path, because --posted posted no [answers] marker. A human-authorized re-traversal therefore re-converted to needs-human: a livelock of exactly the bounce the human had just sanctioned. --posted now posts its own marker.
  • Importantboard-reconcile.sh's orphan check saw only in-progress, so a dead Architect's in-design work orphaned invisibly.
  • Importantskills/implementing/references/operation-manual.md still documented the retired self-authoring mode, contradicting the shipped protocol, in the file both implementing and architecting name as the operator entry point.

Both regressions were reproduced against the mock harness before the fix and re-run after, and each carries a regression test whose baseline assertion fails under the pre-fix code.

Both lanes pin their model (resolved during review)

The whole-branch review flagged that only half this split's economic argument was enforced in code. The architect route hard-pins ${ARCHITECT_MODEL:-fable}, but the plain-Claude implement route inherited the operator's session model — the posture #35 set when it flipped that route off the clodex gateway. An operator running the frontier model by default would therefore pay frontier rates on both lanes and silently re-fuse the two economies this split exists to separate, and acceptance criterion 2's "spawns an Opus worker" would be a convention rather than a property of the dispatcher.

Resolved in favor of symmetry: the implement and spike routes now pin ${IMPLEMENT_MODEL:-opus} on the plain-Claude route. IMPLEMENT_MODEL still overrides, and the codex route's own fable gateway-alias default is untouched. The test that had pinned the inherit behavior now pins the new one, and reverting the one-line change fails exactly that assertion and no other (verified by mutation). Spec revision v1.3.3.

Independent code review — seven findings, all fixed

A second independent review ran after the branch was first opened and found seven defects the whole-branch pass had missed. Each was verified against the code before being acted on; all seven are fixed, with tests whose new assertions were proven to bind by mutation.

The escalation stranded the PR permanently (HIGH). When the review worker routed a seam-clustered impasse to ready-for-architect, nothing could ever re-attach a reviewer: the sweep read the finished reviewer's meta and skipped forever, board-answer.sh relays needs-human parks only, and the workflow fires on PR events the Implementer's later pushes never produce. So once the Architect re-cut the design and a new Implementer finished, the PR sat open and unreviewed until a human noticed. This was a regression this branch introduced — the pre-branch impasse went to needs-human, which resumed correctly.

Fixed in the dispatcher rather than the worker: a reviewer bound to a ticket that has left in-review is stale by definition, so the sweep retires it (never an active one) and skips, and the ticket's eventual return lands on the pre-existing retired → dispatch path. The first attempt had the worker retire itself; that was rejected because daemon-retire.sh stops the session before writing the retired status, so the write would very likely never land — the fix would have failed silently in the one case it exists for. The same check also generalizes past the architect lane and closes the wasted spawn-and-retire churn the park guard was written to prevent.

A superseded plan: pin survived every escalation, with no way to clear it (MEDIUM). The pin authorizes gate-free PLAN-EXECUTION, but nothing ever cleared it — so after an escalation and re-cut, an Implementer could enter with no gate against a plan the Architect had declared blocked. Two prose sites told operators to "strip a stale plan: pin", which no flag could do. The pin now clears automatically on entry to ready-for-architect (the plan is being re-cut by definition) and on the Architect's own no---plan exit edge, and deliberately does NOT clear when a human unparks a ticket back to the queue. Both prose sites now describe the mechanism that exists.

A spike born into the architect lane had no legal move (MEDIUM). Role resolution is state-free for spikes, but the spike protocol's gate-pass writes in-progress, and LEGAL["ready-for-architect"] has no such edge — so the worker's only remaining moves were parks, while its bound meta consumed the single architect slot. Closed at the source: the combination is rejected at registration and at every transition into ready-for-architect for a spike-category ticket. Adding the edge to LEGAL was rejected — it would also legalize an Architect skipping the design phase.

Three more, lower severity. pull_epics wrote an edge the legality table forbids, bypassing the check board-transition.sh performs; it now routes through the PRE_PARK queue-to-in-flight mapping and asserts its target is an in-flight state. An Architect resumed from a park with no recorded pre-park: landed in in-progress, from which its mandated handoff edge does not exist; the fallback is now lane-aware, using the worker role persisted at dispatch. And a ticket still carrying the retired label linted as open with 1 status:* labels — a count of 1 under a rule meaning "two or more" — which is exactly what the five unmigrated tickets would have produced; it now gets a named branch and a migration FIX line, matching how the v8 retirement of blocked was handled.

One correction was made during this wave and is worth recording: the pull_epics fix initially added in-progress to LEGAL["deferred"] to satisfy its own assertion. That edge was illegal at the merge base, and LEGAL is what board-transition.sh enforces, so it would have let any worker or human move a deferred ticket straight into progress, skipping the queue and the gate. Reverted; the assertion now expresses the real invariant (an epic pull is board bookkeeping and only ever lands on an in-flight state) without borrowing worker-transition legality. No LEGAL entry on this branch now differs from the merge base except by the lane rename itself.

Still open, deliberately

The live board is not migrated by this PR. Five open tickets still carry status:ready-for-agent (#40, #39, #38, #37, #32), which the new tables read as a conflict until each is repaired. The migration and the label deletion run after merge, along with two follow-up registrations (the QAgent pin riding C5, and the ida-solution consumer migration).

SSFSKIM added 30 commits July 31, 2026 04:11
…chitect / in-design / ready-for-implementer (E1); final LEGAL table, DISPATCHABLE/PRE_PARK/EDGE_NOTE tables, mechanical suite rename
…literal; Step 8 grep scoped to scripts/ (SKILL.md/ticket-gate rewrites are Task 13's)
…'s full RECOVER line (the bare "resume attempt 1/" substring is satisfied incidentally by seeded tickets 10/12)
…intf|grep -Fq -> herestring, matching test-review-dispatch.sh)
…moved, architect escalation edges added (E1)
…e every other section; assertion pins MODE SELECTION as the plan's Step 1 intended (plan text amended to match)
… — the latter pre-dated this task and could not catch a regression to the retired mode
…st — the board has no ready-for-architect → ready-for-implementer edge, so the documented decompose exit was unreachable from the gate (plan text amended; assertion pins it)
…eedback is T14's; T16 owns the repo-wide proof)
…e skill prose must not force a permanent carve-out in the residual-vocabulary grep (plan text amended)
…r-architect impasse route (E1; pins/fix-wave ride C5)
…ins (pre-spec runs DIRECT and does post a gate comment); add Step 4b for ORIENT + PARKED-tier completeness
… the impasse turn-ending, patch ORIENT's pointer

Review found three defects in the T15 lane-split landing:

- the authorization-anchor rule claimed no plan:-pinned ticket carries
  a [gate] pass, which is false for plan: pre-spec (DIRECT mode, posts
  a real gate) — only plan: <path>@<sha> (PLAN-EXECUTION) posts none;
  narrowed the rule so pre-spec tickets still anchor on their gate
- the RE-REVIEW impasse's ready-for-architect branch had no
  turn-ending unlike its needs-human sibling, and ESCALATE's PARKED
  tier didn't cover a ticket that route just parked there
- ORIENT pointed only at the [gate] pass comment, which doesn't exist
  on a real plan: <path>@<sha> ticket; added the handoff-comment
  alternative as a pointer, consistent with the anchor rule
…es — handoff comment admits the missing-section finding, plan pin joins the spec hierarchy
…t version; name the two known residual test descriptions
SSFSKIM added 11 commits July 31, 2026 08:21
Every prior test that lands a ticket on ready-for-architect starts from
ready-for-implementer; the Implementer's mid-execution return edge had
no dedicated coverage. Assert the edge is legal, note-required, and
emits the arrow-keyed [board] in-progress → ready-for-architect: <note>
comment (it is convergence-counted, unlike the plain-form edges).
The branch added five rules to the review worker protocol (plan: pin
in the spec hierarchy, the PLAN-EXECUTION-mode authorization anchor,
the missing-Validation-Evidence-section carve-out, the RE-REVIEW
seam-clustered impasse route to ready-for-architect plus ESCALATE's
PARKED-tier coverage of it, and TOO BIG's design-missing birth
classification) with nothing asserting them. Anchors were chosen and
verified absent from the pre-branch file (git merge-base main HEAD)
so each pin actually discriminates a reversion.
… position it doesn't carry

'both positions: <note>' followed only the second traversal's note —
the first side's position was never in this string, only recoverable
from the comment trail. Reworded to say what the note actually carries
and point the reader at the trail for the other side; kept the leading
convergence: token and the no-third-mechanical-bounce phrasing intact.
…n without re-supplying --pr

board-transition.sh required --pr on every entry into in-review, including
the mechanical return from a needs-human park (PRE_PARK["in-review"] =
"in-review"). board-answer.sh's return posts a note only, never --pr, so
answering any review-loop park died with "a PR link is required" and left
the ticket stranded at needs-human with its bound session never resumed.

The PR gate now also accepts the ticket's already-recorded pr: meta, not
only the --pr flag — preserving "a ticket in in-review always has a PR
recorded" without weakening the check for a genuine first entry.
…rgence resets

The convergence rule resets its per-edge escalation count at the last
comment prefixed [answers]. board-answer.sh only posted that marker for
inline answers; the sweep's unattended RELAY pass always calls
board-answer.sh --posted (the human commented on the ticket directly), so
on the documented unattended path the reset never fired. A resumed
worker's human-authorized retry of the same escalation edge then got
bounced right back to needs-human -- the exact bounce the human's answer
was meant to end.

--posted now posts its own [answers]-prefixed marker before the pre-park
return runs, so the mechanical reset fires like it does for inline
answers. Also names PLAN-EXECUTION (which runs no gate) in the relay
prompt sent to the resumed worker, next to the "re-state your gate
verdict" instruction that otherwise doesn't apply to it.
…cket too

The orphan check was filtered to in-progress only, so an Architect worker
that died with no usable registry binding left mid-design work orphaned
invisibly -- the pipeline's most expensive in-flight asset, and nothing
surfaced it. board-sweep.sh's RECOVER pass already covers both in-flight
worker states (in-progress, in-design); the reconcile report now matches.
…oring mode

operation-manual.md still described the pre-lane-split shape: an
Implementer self-authoring a plan document (Direct/ExecPlan) via
doperpowers:execplan. The shipped protocol (SKILL.md) has DIRECT and
PLAN-EXECUTION instead -- an Architect worker authors the plan, the
Implementer executes it and authors no plan document, ever. Replaced the
stale mode description, named the architect lane in "The pieces" table
(it enumerated only the implement/spike protocols before), and fixed the
two Edge cases claims that the next gate re-run is unconditional -- false
for a plan-pinned ticket, where PLAN-EXECUTION is gate-free by design --
adding the strip-the-stale-pin caveat issue-tracker/SKILL.md already
carries for the fallback path.
… dead test stub)

- issue-tracker/SKILL.md dispatch ritual step 4: name all three
  first-board-write shapes (Implementer in-progress+[gate], Architect
  in-design+[gate], PLAN-EXECUTION in-progress with no gate comment) --
  was stale for two of the three lanes.
- Gate-language residue aimed at workers that never ran a gate: one
  qualifying clause each in implementing/SKILL.md's "If Resumed With
  Answers", board-answer.sh's relay prompt was already covered in the
  prior commit; board-sweep.sh's recovery nudge gets the same clause here.
- sweep-setup.md's Knobs table documented IMPLEMENT_MAX_CONCURRENT but not
  ARCHITECT_MAX_CONCURRENT/ARCHITECT_MODEL, so an operator arming the
  sweep couldn't discover the Fable-spend lever.
- Deleted the dead scaffold in test-implement-dispatch.sh that wrote a
  stub skills/architecting/SKILL.md into the real repo tree when missing
  -- the real file exists now; the guard was inert and, if it ever fired
  again, would leave an untracked stub and mask a missing-protocol
  failure.
- needs-human -> in-review return through board-answer.sh without
  re-supplying --pr (Finding 1, the merge blocker).
- --posted relay resets the convergence counter: baseline 2nd-traversal
  conversion, the relay posts an [answers] marker, and a 3rd traversal of
  the same edge does NOT convert (Finding 2).
- board-reconcile.sh flags an orphaned in-design ticket, not just
  in-progress (Finding 3).
- Updates the pre-existing "--posted posts no second [answers] comment"
  assertion, which pinned the buggy pre-fix behavior, to match the fixed
  shape (a [answers] marker is now always posted).

New sections are placed at the end of the file (after "mid-execution
return") deliberately: every ticket reference past that point in the
suite is captured dynamically (${out%% *} / state "s['next']-1"), so a
new mid-file board-register.sh call can't shift a later hardcoded ticket
number the way an earlier attempt at this did.
…e enforced/unenforced half of the model-economics argument stated plainly
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

SSFSKIM added 8 commits July 31, 2026 18:09
…tric with the architect's fable pin

The lane split's argument is asymmetric model routing, but only the
architect half was structural: the plain-Claude implement route inherited
the operator's session model (the posture #35 set), so an operator running
the frontier model paid frontier rates on both lanes and re-fused the two
economies the split exists to separate. IMPLEMENT_MODEL still overrides,
and the codex route's fable default is untouched.
…aves in-review, before spawn dedupe ever runs

Finding 1 (HIGH): a PR routed to ready-for-architect stranded permanently —
no mechanism ever re-attached a reviewer once the ticket returned to
in-review, since a normally-finished reviewer's meta made every future
sweep tick's _decide skip it as "finished reviewer (idle)" forever.

An earlier version of this fix had the review worker retire its own daemon
as its final action before ending its turn. That mechanism is unreliable:
daemon-retire.sh calls `claude stop` on the target BEFORE writing
status=retired, so a worker asking to stop itself most likely never
reaches that write — the meta finalizes idle instead, which is exactly
the state that strands it. Retiring must happen from outside the dying
session.

The corrected mechanism moves the retire into the sweep loop itself,
before the registry dedupe machinery ever runs: the sweep now resolves
each ticketed PR's primary-ticket status (extending the existing gh pr
list call with closingIssuesReferences/title/body so no per-PR issue
lookup is needed) alongside the confident-ready check. Whenever a
ticket isn't in-review, run_for retires any FINISHED (non-active)
reviewer meta it finds for that PR and skips the tick without spawning;
a genuinely ACTIVE (working/blocked) reviewer is never touched, since it
owns its own exit. That retire is what lets the ticket's eventual return
to in-review land on the pre-existing "none / retired -> dispatch" row
with no special-case dispatch logic needed once it's back.

The rule generalizes past ready-for-architect specifically -- any route
that moves a ticket off in-review while its PR stays open leaves the
bound reviewer stale by the same definition, so the gate reads "not
in-review" rather than an enumerated label set.

Finding 2 (MEDIUM, same root cause): the sweep previously only recognized
needs-human/needs-info/interactive-preferred as reasons to skip a
dispatch, so a ticket freshly routed to ready-for-architect (or any other
non-in-review state) would still let the sweep spawn a reviewer
board-bind.sh refuses (the ticket's real owner is an active daemon) --
one wasted spawn-and-retire per tick. Folded into the same generalized
check above; its skip message distinguishes the two real resume paths
(board-answer for the three human parks vs. return-to-in-review for
everything else) instead of lumping them together.
Role resolution is state-free for spikes (category outranks lane), but the
spike protocol's gate-pass write is `board-transition.sh <n> in-progress`,
and LEGAL["ready-for-architect"] has no in-progress edge. A spike admitted
there (registration or any transition, including the untracked/conflict
repair path and the QAgent/mid-execution escalation edges) is left with
only the parks, and its stuck ready-for-architect state also eats an
architect-lane dispatch slot since ARCHITECT_MAX_CONCURRENT counts by
ticket state, not by which protocol actually dispatched.

Close it at both ends: board-register.sh refuses the birth combination,
board-transition.sh refuses every transition whose target is
ready-for-architect on a spike-category ticket. Does not touch
LEGAL["ready-for-architect"] itself — adding an in-progress edge there
would also legalize an Architect skipping the design phase, which the lane
exists to prevent.

An earlier review declined this ban absent a reproduced failure; it is now
reproduced (registering a spike --state ready-for-architect and running its
gate-pass transition dies with "illegal transition"), so the ban is earned.
…rough PRE_PARK

Two board-schema correctness fixes discovered together (both center on
board-transition.sh's write path and _board.py's transition tables):

plan: pin auto-clear — board-transition.sh only ever wrote extra["plan"];
nothing cleared it. A ticket that goes --plan <sha> -> in-progress ->
ready-for-architect "blocked" -> in-design -> ready-for-implementer
"decomposed" (no --plan) kept the original pin, so the Implementer entered
gate-free PLAN-EXECUTION against a plan the Architect had just declared
blocked. Now cleared on exactly two edges where the pin is void by
definition: any entry into ready-for-architect (the design is being
re-cut; --plan can only be supplied when to == ready-for-implementer, so
this never collides with a fresh write), and the Architect's own
in-design -> ready-for-implementer decompose exit with no --plan (a
positive "no plan" statement). Deliberately not extended to other edges
into ready-for-implementer (e.g. unparking from needs-human) — those must
not silently void a still-valid plan. Updated the two prose sites
(issue-tracker/SKILL.md's wake ritual, implementing/operation-manual.md)
that told the operator to strip the pin by hand; the tooling now does it.

pull_epics routing — PULLABLE gained ready-for-architect in the E1 split,
but LEGAL["ready-for-architect"] has no in-progress edge, so an epic
parked in the design queue with a newly-active child was silently written
to an edge the table forbids. pull_epics now targets
PRE_PARK.get(parent_state, "in-progress") — reusing the existing
queue -> in-flight mapping rather than a second table — so a
ready-for-architect epic pulls to in-design. Legality is asserted at the
pull_epics call site (not inside apply_state, which close_epics also uses
and needs wider latitude for) so a future PULLABLE/LEGAL drift fails loud
instead of silently writing a bad edge again.

That assertion surfaced a companion gap: "deferred" was the one other
PULLABLE state with no PRE_PARK entry and no in-progress edge in
LEGAL["deferred"] either — the identical bug, just never reported. Added
in-progress to LEGAL["deferred"], matching its sibling park states
(needs-info/needs-human/interactive-preferred already permit it). Confirmed
via git log this predates the E1 lane split; not a regression this branch
introduced.
…nd worker's own lane

board-answer.sh fell back to a hardcoded "in-progress" whenever a ticket's
needs-human park carried no pre-park: meta — which happens whenever the
park was reached via needs-info, interactive-preferred, or deferred (none
are PRE_PARK keys). If the bound session was an Architect, it resumed into
in-progress, a state its protocol has no legal exit from
(LEGAL["in-progress"] has no ready-for-implementer edge) — its only moves
became parks or a re-escalation.

implement-dispatch.sh already knows each worker's role (ARCHITECT/
IMPLEMENT/SPIKE) at spawn time but never persisted it; it now writes
`role` into the daemon's registry meta right after a successful bind (same
read-modify-write-under-lock pattern board-bind.sh/board-sweep.sh use;
non-fatal on write failure). board-answer.sh's fallback reads it back:
in-design when the bound worker is an Architect, in-progress otherwise —
which is also the safe default for any worker bound before this fix (no
role in its meta), matching prior behavior exactly for every case it was
already correct for.
derive_state maps a lone non-open-state label to CONFLICT, and the
status:blocked special case (v8) was never extended to the v9 retirement
of status:ready-for-agent — a ticket still carrying it printed "open with
1 status:* labels: ready-for-agent" under a rule that means two or more.
Same treatment as blocked: a named branch with a FIX line naming the v9
migration path (ready-for-implementer, or ready-for-architect per the
birth rule for design-heavy work).
…GAL["deferred"] reverted

The prior fix asserted `to in LEGAL[pstate]` inside pull_epics, and to
satisfy that for a deferred epic, added in-progress to LEGAL["deferred"].
That was the wrong invariant: LEGAL is what board-transition.sh enforces
for a WORKER or human's own transitions, so the addition silently
legalized skipping a deferred ticket's queue and gate entirely by hand —
exactly what these lane states exist to prevent, and nobody asked for it.

Reverted LEGAL["deferred"] to its pre-branch membership (confirmed via
`git diff ba55012` that no other LEGAL entry was touched). pull_epics is
the board's own bookkeeping on an epic — never dispatched, never gated —
the same latitude close_epics already has for its epic-closing writes; it
does not need to answer to worker-transition legality at all. The real
invariant is structural: a pull only ever lands on an in-flight state,
never a queue or park. The assertion now checks `to in ACTIVE`
(the existing in-design/in-progress/in-review tuple) instead, with the
docstring rewritten to explain the distinction and record why the LEGAL
framing was wrong.

Added a test confirming a deferred epic's active child still pulls it to
in-progress (the board-bookkeeping path, unaffected by this correction)
alongside one confirming an ordinary board-transition.sh call still
refuses to move a plain deferred ticket to in-progress (LEGAL unchanged —
no gate-skip for a worker/human).

Mutation-verified: reverting the PRE_PARK routing reproduces the original
Finding C bug (3 targeted assertions fail; the deferred-bookkeeping
assertions correctly stay green, since that path was never broken).
Separately, corrupting PRE_PARK to route ready-for-architect to a
non-ACTIVE state (needs-info) trips the new assertion with a clear
message — proving it is a real, load-bearing guard independent of LEGAL.
…t enough, and an escalation is only as real as its return path
@SSFSKIM
SSFSKIM changed the base branch from main to feature/en-cycles July 31, 2026 11:59
@SSFSKIM
SSFSKIM merged commit 2104c21 into feature/en-cycles Jul 31, 2026
@SSFSKIM

SSFSKIM commented Jul 31, 2026

Copy link
Copy Markdown
Owner Author

Version collision note: main independently released v7.30.0 (65841f1, simplicity-audit skill round) after this PR's release commit reserved the same number. Both sides change the manifest lines identically (7.29.0 → 7.30.0), so the eventual feature/en-cyclesmain integration will auto-merge cleanly with NO conflict marker — the version must be re-bumped manually in that integration: run scripts/bump-version.sh 7.31.0 as part of the merge (adjust if further releases land on main first).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant