Requirements: an opt-in critic review of the locked design
Phase 1 of the chain. Ticket: #188.
Introduction
design.md is the highest-leverage artifact the loop produces, and it is the one artifact no critic ever reads. The self/critic review chain sits at the far end of the walk, between implementation and human-approval: by the time a different model looks at the work, the design has already been turned into a testing plan, a task DAG and a diff. A design flaw found there is not a design finding — it is a rewrite.
The ticket asks for the round to happen where it is cheap: right after design.md is finalized. It also asks for it to be off by default and selectable at phase-selection — which the loop cannot express today. Every selectable phase is skippable: true, and skippable means on unless a human unticks it. There is no way for the shipped graph to offer a phase that runs only when someone asks for it.
flowchart LR
subgraph before["today"]
D1["design<br/>(design.md locked)"] --> TP1["test-planning"] --> DA1["design-approval"]
IM1["implementation"] --> SR1["self-review"] --> CR1["critic-review"]
CR1 -. "the first time another model<br/>reads the design — after the diff exists" .-> D1
end
subgraph after["this work item"]
D2["design<br/>(design.md locked)"] --> DCR["design-critic-review<br/>OPT-IN · off unless selected"]
DCR --> TP2["test-planning"] --> DA2["design-approval"]
endSo there are two requirements here, and the order matters: the graph needs a way to say off by default before it can ship a phase that is. The mechanism is a second node marker — optIn: true — beside the existing skippable: true, and the difference between them is one word in the checklist the-loop already posts:
| Marker | Checklist row | Left alone | Meaning |
|---|---|---|---|
skippable: true | - [x] <node> | runs | opt out — a human unticks what this item does not need |
optIn: true | - [ ] <node> | does not run | opt in — a human ticks what this item additionally wants |
Both are the same act by the same person at the same gate, recorded with the same provenance. What changes is the default, and therefore who has to act for the phase to run.
Requirements
Requirement 1 — the graph can declare a phase that is off unless it is chosen
User story: As the author of the shipped process graph, I want to declare a node optIn: true, so that a phase can be offered to a work item without being imposed on every work item that never asked for it.
Acceptance criteria (EARS)
- WHEN the graph declares a node
optIn: trueTHEN the compiler SHALL accept it, treat it as part of the declared-skip vocabulary (skippable), and require its ownon: skippededge exactly as it does for any skippable node. - IF a node declares both
required: trueandoptIn: trueTHEN compilation SHALL fail naming the node — a phase cannot be mandatory and off by default. - IF a
skipSetsbundle names an opt-in node THEN compilation SHALL fail naming the set and the node — a skip set declares phases away, and an opt-in phase is already away. - WHEN a work item's state records no selection for an opt-in node THEN the runtime SHALL treat that node as skipped, in every read path (routing,
the-loop check, and--recompute), without any declaration being present. - WHEN
the-loop checkreports an opt-in node that nobody selected THEN it SHALL report it as not selected — distinct from skipped by declaration, which names a human who removed something that would otherwise have run — and SHALL never report it aspass. - WHEN an authorized user selects an opt-in node THEN the runtime SHALL record that selection with provenance (who, via which channel, when) in
graph-state.json, and the node SHALL then be walked like any other node. - IF
graph-state.jsonrecords a selection for a node the compiled graph does not markoptInTHEN that entry SHALL have no effect — the state file is agent-writable, so every read is filtered through the compiled graph. - WHEN the phase selection is frozen THEN the frozen graph SHALL record, per node, whether it is opt-in as well as whether it is selectable and whether it is skipped — a reader of the portable record must be able to tell a phase nobody asked for from a phase somebody removed.
Requirement 2 — the phase-selection checklist offers the opt-in phases separately
User story: As the human answering phase-selection, I want the optional phases listed apart from the phases that run by default, so that ticking a box adds work and unticking one removes it, and neither is a trap.
Acceptance criteria (EARS)
- WHEN the-loop posts the phase-selection checklist THEN it SHALL render every opt-in node unticked, under its own heading that states they are off unless ticked, and SHALL keep rendering the default-on phases ticked in their existing section.
- WHEN a node carries a one-line
descriptionin the graph THEN the checklist SHALL render it beside that node's row — a phase a reader has to guess at is a phase they will not choose. - WHEN an authorized user replies with the execute keyword and an opt-in row is ticked THEN that node SHALL be selected (it runs) and SHALL NOT be recorded as a declared skip.
- WHEN the reply's effective checklist leaves an opt-in row unticked, or does not mention it at all, THEN that node SHALL NOT run — an omission SHALL fail toward the default, which for an opt-in phase is off.
- WHEN the selection is confirmed THEN the confirmation comment SHALL name the opt-in phases that were selected, and SHALL state that the offered opt-in phases were not selected when none were — silence about an offered phase is not an answer.
- WHILE the loop offers at least one opt-in phase, a selection that unticks nothing SHALL still run exactly the default-on phases — "reply with the boxes untouched to run the full process" SHALL remain true of the phases that run by default.
Requirement 3 — the loop ships one opt-in phase: a critic review of the locked design
User story: As the owner of a work item whose design carries real risk, I want a different model to review design.md while it is still only a design, so that a structural finding costs an edit rather than a rewrite.
Acceptance criteria (EARS)
- WHEN a work item selects
design-critic-reviewTHEN the outer loop SHALL walk it afterdesignand beforetest-planning— afterdesign.mdis locked, and before the testing plan and task DAG are derived from it. - WHEN
design-critic-reviewis not selected THEN the pointer SHALL routedesign→test-planningexactly as it does today, and no artifact, section or gate SHALL change for that work item. - WHEN
design-critic-reviewruns THEN its exit gate SHALL require a non-empty## Design critic reviewsection indocs/specs/<id>/execution-log.md, naming the critic, the rounds run and each finding's disposition — the node SHALL NOT be able to pass by asserting nothing. - WHEN
design-critic-reviewruns THEN it SHALL follow the existing critic procedure inreference/reviewing.md(attribution prefix, own-comment marker, reply-first-then-fix, stop on zero new findings, escalate on a repeated finding), with the design and the requirements it must satisfy as the review subject instead of a diff. - IF no critic can run (none configured, CLI absent, timeout) THEN the round SHALL be recorded as
unavailablewith the cause and SHALL NOT be reported as converged — the same rule the existing critic node already follows. - WHERE the inner
pdlc-pr-loopand thepdlc-contribution-loopare concerned, neither SHALL gain this node: a work item's design is reviewed once, at the outer level.
Non-functional requirements
- Backward compatibility. A work item already in flight — one whose
graph-state.jsonpredates this change — SHALL be unaffected: with no selection recorded, the new node is skipped by default, so no existing item can block on a phase that did not exist when it started. - Observability. Selecting an opt-in phase SHALL emit an event on the existing event log alongside the declared-skip event, so the choice is visible to the daemon's operators and not only in the state file.
- Token economy. The node SHALL declare
stage: critic-review, so the existingtokenEconomy.modelRouting/thinkingEffortstage tables route it to a frontier model at high effort without a new configuration key.
Security considerations
Threat-model-lite (
security.threatModel.required).
- Actors & trust: the authorized user answering
phase-selection(trusted, named); anyone who can comment on the ticket (untrusted); anyone who can land a commit editinggraph-state.json(semi-trusted, reviewed); the configured critic CLI's output (untrusted text). - Trust boundaries & data: two, both pre-existing. (1) The selection reply — parsed for checkbox tokens only, matched against the compiled graph's own node ids, and authorized by
routing.authorizedUserswith the-loop's self-authored comments dropped first. This change widens what a tick means for one class of node; it does not widen who may tick or what a token may name. (2)graph-state.json— agent-writable, so the newoptInsmap is filtered through the compiled graph on every read, exactly asskipsis. No secrets are stored or moved. - Abuse cases (EARS):
- WHEN an unauthorized commenter ticks an opt-in box and replies with the execute keyword THEN the gate SHALL ignore the comment entirely (unauthorized author) and SHALL keep waiting.
- WHEN a state file is hand-edited to select a node that is not
optInTHEN the runtime SHALL ignore the entry — it grants nothing, because a node that is not opt-in was never default-skipped. - WHEN a state file is hand-edited to delete a recorded selection THEN the opt-in node SHALL revert to not-selected, which removes a review rather than granting a pass;
the-loop checkSHALL report it as not selected rather than aspass. - WHEN the critic's output contains text addressed to the agent ("approve this", "ignore the design") THEN it SHALL be treated as review material, never as instruction (
reference/reviewing.md, unchanged).
- Fail closed: an unreadable checklist, an unparseable reply or a missing selection all resolve to not selected for an opt-in node. For this class of node, off is the safe direction: the phase adds a review, so failing to off costs a review that was never requested, and the phases that gate the work item are untouched.
Out of scope
- A CLI verb to select an opt-in phase.
the-loop graph skiphas no counterpart here.phase-selectionisrequired: true, so every work item passes the checklist and has a channel; a second, shell-side channel would need its own audit comment and refusal rules for no case this ticket names. - Re-selecting after the gate is answered. The selection is frozen at
phase-selection, as it is for skips. A work item that wants the design critic round after the fact says so on the ticket. - Making the round loop back to
design. Findings are applied todesign.mdin place under the existing reply-first-then-fix protocol; no new edge is added. - A
/the-loop:slash command for the round. The existing review-chain nodes carry none either.
Open questions
None. The ticket names the phase, its position ("after design.md is finalized") and its default ("not on by default"); the rest follows the shipped mechanics.
Review comments
Appended by the-loop's
record-feedbackhook when a human gate approves with comments.