Bugfix spec: every phase demands the same human approval twice
Phase 1 of 3 for a bug (bugfix → design → tasks). Reviewed with the PR that carries the spec chain (tier 3,
human-approves-pr).
Summary
Issue #281: walking pdlc-work-item-loop end-to-end with only the default gates selected (requirements approval + design approval), the human approved 5 times instead of the expected 2. Each artifact phase consumes one approval to lock the artifact (status: approved in front matter, demanded by validate-artifacts with locked: true on the producing node's exit) and a second to pass the graph's human approval node — and tasks-breakdown consumes an approval even though the graph deliberately gives it no approval node at all.
Steps to reproduce
- Start a work item on
pdlc-work-item-loopwith the default phase selection (brainstorming skipped,design-critic-reviewnot opted in). - Author
requirements.md; the skill instructs the session to request human review and not proceed until approved, so the session blocks on a human "approved" — outside any gate — then setsstatus: approvedand runsthe-loop graph complete. - The pointer advances to
requirements-approval, whoserequest-reviewasks the same human the same question again;classify-feedbackonly reads comments arriving while the gate is open, so the pre-gate approval is discarded. - Repeat for
design+test-planning(one gate, but a skill-level stop each), and fortasks-breakdown(a skill-level stop with no gate behind it).
Expected vs actual
- Expected: one human approval at
requirements-approval, one atdesign-approval— 2 total.tasks-breakdownhas no approval node, so no approval at all. - Actual: 5 approvals — one per artifact to satisfy
locked: trueon the producing node's exit, plus one per human gate; a prompt pre-gate reply is guaranteed to be rejected ("the gate only counts feedback posted while it is open").
Root cause (confirmed)
Two approval mechanisms are stacked per phase:
flowchart LR
P[producing node<br/>e.g. requirements-definition] -->|"exit: validate-artifacts locked: true<br/>⇒ session demands approval #1 to set status: approved"| G{approval node<br/>e.g. requirements-approval}
G -->|"classify-feedback only reads comments<br/>posted while the gate is open<br/>⇒ approval #2"| N[next node]- Graph layer: every producing node's exit runs
validate-artifactswithlocked: true, requiringstatus: approvedbefore the node can complete — necessarily before the approval node is entered. The approval node can therefore never be the thing that locks the artifact. - Skill layer: the skills instruct the session to obtain human approval before setting that status ("iterate until locked";
create-tasks-planstep 4: "request human review. Do not start implementation until approved"). - The gate then re-asks, and its classification deliberately discards feedback that arrived before it opened.
The same stacked shape exists in pdlc-contribution-loop (scoped-plan demands locked: true; plan-approval follows).
Requirements
Requirement 1 — approvals are owned by approval nodes
User story: As the human approver, I want each default gate to cost me exactly one approval, so that responding promptly is never punished with a re-ask.
Acceptance criteria (EARS)
- WHEN a producing node (
brainstorming,requirements-definition,design,test-planning,tasks-breakdown, andscoped-planin the contribution loop) completes THEN the system SHALL NOT require the artifact's front matter to saystatus: approved—validate-artifactson those nodes SHALL check shape (sections, lint) only. - WHEN an approval node (
requirements-approval,design-approval,plan-approval) classifies authorized feedback asapprovedorapproved-with-commentsTHEN the system SHALL lock the gated artifact(s) itself: set front-matterstatus: approvedand record the approving authors inapprovedBy. - WHEN the classification is
changes-requested(or no decisive feedback has arrived) THEN the system SHALL NOT change the artifact's lock state. - WHEN a gated artifact is absent because its authoring phase was declared skipped THEN the lock step SHALL skip that artifact rather than block the gate.
- The fix SHALL include a regression test that fails before the fix and passes after: an end-to-end walk in which artifacts are emitted unlocked (
status: draft) and reachimplementationlocked, having consumed exactly one approval per gate.
Requirement 2 — gate-less phases need no human sign-off
User story: As the work item's author, I want phases the graph gives no approval node (tasks-breakdown, brainstorming) to proceed without a human stop, matching the graph's declared intent ("one human gate approves both").
Acceptance criteria (EARS)
- WHEN
tasks-breakdowncompletes with atasks.mdcarrying its required sections THEN the system SHALL advance toimplementationwithout any human approval. - The plugin skills and commands SHALL NOT instruct the session to request a human approval, or to block until one arrives, for any phase — approvals belong to the graph's approval nodes (
request-review+classify-feedback), which the skills defer to. - The skills SHALL instruct the session never to set
status: approveditself: the lock is the gate's act.
Security considerations
- The lock step must not widen who can approve.
lock-artifactsacts only on the outcomeclassify-feedbackproduced in the same chain run, andclassify-feedbackalready reads only authorized, non-self-authored comments. No new input surface is added; the artifact edit is a local file write within the spec directory. - Fail closed. A lock that cannot be written (unreadable file, ambiguous artifact slot) blocks the gate rather than reporting an approval that was not durably recorded.
- Removing
locked: truefrom producing nodes does not remove any human decision: every artifact that had a human gate still has it; artifacts that lose their lock requirement (brainstorm.md,tasks.md, pre-approvalcontribution.md) never had a graph-level approval node — their "lock" was a session-invented stop the graph never asked for.
Out of scope
- Widening
classify-feedback's window to count pre-gate feedback (the issue's suggested fix 2) — moving the lock to the gate removes the pre-gate ask entirely, so there is no pre-gate approval left to count. - The
pdlc-pr-loop,pdlc-adhoc-loopandpdlc-review-loopgraphs: none of them gateslocked:anywhere.
Open questions
None — the issue names the fix ("move locking to the gate") and the graph's own comments state the intent it restores.