Requirements: declared skips — the author decides which phases a work item walks
Phase 1 of 4 (requirements → design → testing plan → tasks). Ticket: issue #177.
Introduction
the-loop walks every work item through every phase, and for a simple documentation update that is process without payoff — PR #175 carried a full requirements.md/design.md chain for a doc fix. The obvious repair — let the harness decide what to skip — is the one the ticket explicitly forbids: an LLM that may skip phases will eventually skip requirements or design conveniently, which defeats the point of a fixed process graph.
The strategy this work item delivers is declared skips, split across three parties so no single one can cheat:
- The shipped graph declares which nodes may be skipped (
skippable: true) — a fixed vocabulary the harness cannot extend, shipped as package data a repository cannot override. The floor (verification,security-review,human-approval) is never in that vocabulary. - A human — an authorized user of the loop — declares which of those nodes are skipped for one work item, at the loop's own first phase: the-loop posts a phase checklist on the ticket and waits for their reply plus
the-loop execute. An operator may make the same declaration from a shell with an audited CLI verb. The harness never declares a skip. - The runtime records each skip as a declaration with provenance and routes around the node along a declared edge. A skip is never a forged
pass:the-loop checkreports it as skipped-by-whom, and a declaration on a non-skippable node is refused loudly.
This is the same posture the-loop graph force established (decision-041): the escape hatch moves the pointer and leaves the truth intact. Declared skips are the planned version of that hatch — declared up front, bounded by the graph, and visible everywhere.
Requirements
Requirement 1 — the graph owns the skip vocabulary
User story: As the maintainer of the process, I want the set of skippable phases fixed in the shipped graph, so that no harness, repository or work item can widen it.
Acceptance criteria
- WHEN the graph compiler reads a node carrying
skippable: trueTHEN the system SHALL expose that marker on the compiled node and ingraph show. - WHEN a node declares both
required: trueandskippable: trueTHEN compilation SHALL fail naming the node — a mandatory gate cannot also be skippable. - WHEN a node is declared skippable and no
on: skippededge leaves it THEN compilation SHALL fail naming the node — routing around a node is declared, never inferred. - WHEN the graph declares a
skipSetsbundle THEN compilation SHALL fail if any member names an undeclared or non-skippable node, naming the set and the member. - WHEN the shipped outer loop (
pdlc-work-item-loop) is compiled THEN exactlybrainstorming,requirements-definition,requirements-approval,design,design-approvalandtasks-breakdownSHALL be skippable, and a shippedspec-chainskip set SHALL name exactly those six nodes. - WHEN any set of skips is declared THEN
test-planning,implementation,verification, the review chain (self-review,critic-review,security-review,evidence,capability-docs,reviewer-briefing),human-approvalandcompleteSHALL still be walked and gated — none of them carries the skippable marker.
Requirement 2 — only a human declares a skip, and the loop asks first
User story: As an authorized user of the loop, I want to be asked up front which phases this work item needs, so that a doc fix does not produce a spec chain — and so that the agent can never make that call.
Acceptance criteria
- WHEN a work item enters the graph THEN the system SHALL enter a
phase-selectionnode first, and SHALL post to the ticket one checklist naming every skippable phase of the loop being walked (pre-ticked) and every phase that always runs. - WHEN the checklist has already been posted for a work item THEN a later entry SHALL NOT post a second one.
- WHEN no authorized reply carrying
the-loop executehas arrived THEN the node SHALLwait, and no phase of the loop SHALL run. - WHEN an unauthorized author replies with a selection and
the-loop executeTHEN the system SHALL ignore it entirely and keep waiting. - WHEN an authorized user says the execute keyword THEN the selection SHALL be taken from the current tick state of the-loop's own checklist comment — unless the execute comment itself carries a checklist, which wins. Every unticked skippable phase SHALL be recorded as a declared skip with provenance (
via: selection, the author, the phase, a timestamp), every unticked protected phase SHALL be refused and named back in a confirmation comment, and the loop SHALL proceed to the first phase that survived. - WHEN the execute comment carries no checklist and the checklist comment cannot be read THEN no skip SHALL be recorded — the full process runs (fail-closed).
- WHEN a selection omits a phase entirely THEN that phase SHALL be kept: a selection removes only what it explicitly unticks.
- WHEN posting the checklist fails THEN the node SHALL remain
waiting, no skip SHALL be recorded, and a later entry SHALL post it again. - WHEN a selection has been read but posting the confirmation fails THEN the recorded declaration SHALL still stand and the loop SHALL proceed — the confirmation is an audit convenience, exactly as the forced-transition announcement is, and the declaration itself is durable in graph state.
- WHEN an operator runs
the-loop graph skip <id> --node <token> --reason <why>THEN the system SHALL record each declaration with the actor and reason, post a self-marked audit comment, and emitgraph.skips_declared. - WHEN that verb is invoked without a non-empty
--reasonTHEN it SHALL be refused, exactly asforceis. - WHEN a skip token — from either channel — names an unknown node, a non-skippable node, or a node the pointer has already entered or passed THEN it SHALL NOT take effect: the verb refuses it by name, and a selection simply does not record it.
- WHEN the gate is answered THEN the resolved graph — every node with whether it is walked and whether it was selectable — SHALL be recorded in the work item's graph state and written to the
graphsection of its portable record, so the agreed shape travels with the work item and is readable without a checkout. A failure to write the portable copy SHALL NOT gate the selection. - WHEN the operator configures
routing.control.keywords.executeTHEN that keyword SHALL be what the gate looks for and what the checklist tells the user to say.
Requirement 3 — a skip routes and records; it never forges
User story: As a reviewer, I want every skipped phase visible with who declared it, so that reviewing a lightweight work item includes reviewing the decision to make it lightweight.
Acceptance criteria
- WHEN the pointer would enter a declared-skipped node THEN the runtime SHALL record outcome
skippedfor that node, SHALL run none of its entry or exit hooks, and SHALL follow its declaredon: skippededge to the next node. - WHEN
the-loop check(with or without--recompute) reports a declared-skipped node THEN it SHALL report statusskipcarrying the declaration's provenance — neverpass. - WHEN graph state declares a skip on a non-skippable node (a tampered or hand-edited state file) THEN the declaration SHALL have no effect anywhere: the node is evaluated on its artifacts alone and the report SHALL surface the refused declaration.
- WHEN a later gate validates an artifact whose authoring node was declared-skipped and the artifact is absent THEN that artifact SHALL be treated as skipped rather than blocking (e.g.
implementation'stasks.mdcheckmark gate aftertasks-breakdownwas skipped). An artifact that exists is gated normally regardless of declarations. - WHEN a node is skipped THEN its phase label SHALL NOT be applied — the ticket's label moves to the first non-skipped node entered.
Requirement 4 — the boundary is stated
User story: As an operator, I want to know exactly where declared skips apply, so the mechanism cannot be stretched past its design.
Acceptance criteria
- WHEN a pull request's inner
pdlc-pr-loopruns THEN declared skips SHALL have no effect on it — the inner loop's nodes are precisely the never-skippable floor, and v1 scopes the mechanism to the outer loop. - WHEN
the-loop graph skipwould be exposed over MCP THEN it SHALL NOT be — likeforce, it is a human-attributed verb, absent from the MCP surface by design.
Security considerations
Threat-model-lite (
security.threatModel.required). The new attack surface is the selection channel itself: a mechanism for legitimately not doing process steps is, by construction, a target for illegitimately not doing them.
- Untrusted actors. (a) The agent/harness working the item — it can write
graph-state.json, spec files and (because it posts with the operator's credentials) ticket comments; the entire design assumes it will try to skip conveniently. (b) Anyone who can comment on the ticket but is not inauthorizedUsers. (c) A hand-edited or corrupted state file. - Trust boundaries. (1) Who may declare:
routing.authorizedUsers— the loop's own boundary, the same onethe-loop startand every human gate use — checked by the shared_authorized_commentsreader, which also drops the-loop's own self-marked comments so the gate can never answer itself. (2) What may be declared: the skippable vocabulary ships inside the CLI package (existing R1.4 of issue-109 — a repo-supplied graph is ignored), and the runtime re-filters every declaration through it on each read, so neither a repository, a session, nor a hook can marksecurity-reviewskippable. (3) When: a declaration only ever applies to a node still ahead of the pointer. - Abuse cases.
- The agent posts
the-loop executeon the work item's behalf. Refused unless it posts as an authorized user; and its own comments carry the self-authored marker, which the reader drops before authorization is even considered. The skill states the rule explicitly so the behaviour is not merely mechanical. - An unauthorized commenter selects phases away. Ignored; the gate keeps waiting.
- Someone unticks boxes on the-loop's own checklist comment. Ticking is the intended ergonomics (owner's call), and GitHub cannot tell us who edited a comment — so the tick state alone is a proposal, never an instruction. What authorizes it is an allowlisted human saying the execute keyword over it, and the selection is frozen at that moment: later edits to the comment change nothing, because the resolved graph is already recorded in graph state and in the portable record. Anyone wanting the unambiguous form puts the checklist in the execute comment itself, where it wins.
- A reply unticks a protected phase. Refused, named back in the confirmation, and the phase runs.
- The agent writes skips into
graph-state.jsondirectly. A declaration on a non-skippable node is inert and surfaced (R3.3), so the protected gates cannot be touched. A forged declaration on a skippable node is detectable — its provenance claims a channel whose off-repo audit trail (the reply on the ticket, the marked confirmation) does not corroborate it — and bounded, because the never-skippable floor still gates the item. Stated honestly: within the repository the enforcement is audit and floor, not cryptography — the same trust model as the rest of the checked-in state. - A late reply tries to excuse a node already walked. Not recorded (R2.12).
- Integration outage. Fail-closed: the gate waits, nothing is skipped.
- The agent posts
- Fail-closed expectations. Every failure in this mechanism degrades to more process, never less: no reply → no phases run at all; an unparseable or empty selection → the full process; an invalid token → no skip; a missing
skippededge → compile failure before any traversal.