Problem
Review findings are collected, counted, and then discarded. grep -n "\.issues" workflows/ticketmill.js finds exactly one consumer in the pipeline: the recordGateOutcome tally at :4669.
What the fix stages actually receive is reviewer prose:
runQualityLoop :2962 passes String(rev.comments || rev.summary || 'No comments')
reviewAndMerge :4680-4681 passes String(spec.comments || spec.summary) and String(code.comments || code.summary)
- the test loop
:3484 passes String(v.comments || v.summary || 'Fix test quality issues')
And loop control never reads findings either:
:2954 if (rev.result === 'approved')
:3477 if (v.result === 'approved')
:4667 spec.result === 'approved' && code.result === 'approved'
REVIEW_SCHEMA.issues at :638 is { type: 'array', items: {} }, untyped, with no severity field. That is why gate_findings['pr-review'].severity reports zeros across the board, documented as expected at :2021-2027.
Two consequences. Nothing downstream can address a specific finding, because findings have no identity. And a gate cannot terminate on findings being resolved, only on a reviewer changing its own verdict string.
Change
- Type
REVIEW_SCHEMA.issues with the shape CHALLENGE_SCHEMA.findings already uses at :599: objects with severity (critical/major/minor), summary, and an optional recommendation. Give each finding a stable id within its gate iteration so later stages can reference one.
- Ask for it in the prompts that produce a
REVIEW_SCHEMA result: spec review :4613, code review :4633, quality review :2934, test validation :3452.
- Feed the three fix stages from the structured array instead of the prose blob. Keep rendering
comments as context, but the findings array becomes the input of record.
- Move the three loop predicates to "approved, or zero surviving findings."
Acceptance criteria
REVIEW_SCHEMA.issues is typed and the four producing prompts ask for it.
- All three fix stages receive the structured findings;
comments still appears as context.
- All three loop predicates accept a clean pass on either condition.
gate_findings['pr-review'].severity reports real counts in a live run, and the note at :2021-2027 is updated to match.
- A reviewer that returns
changes_requested with an empty findings array still terminates the loop rather than running a fix stage with nothing to fix.
- A reviewer that omits
issues entirely degrades to today's behaviour rather than throwing. recordGateOutcome already tolerates a missing severity.
node --test stays green. Extend tests/pr-review-gate.test.js, tests/quality-loop.test.js, tests/test-loop.test.js, and tests/gate-findings.test.js.
Why this one leads
Every other structural improvement in this set needs finding identity. A fix stage cannot rebut a specific finding if it received a prose blob, and a re-raised finding cannot be detected across gates if findings are not addressable. It also removes the severity blind spot at the merge gate, which is currently the only gate whose findings carry no severity at all.
Out of scope
No suppression or filtering of findings. This issue only makes findings load-bearing. Whether volume needs reducing is a separate question that needs data this change produces.
Documentation target
Document in CHANGELOG.md and in a new page docs/architecture/gate-hygiene.md (create it), adding its row to docs/architecture/index.md's file map.
Do NOT edit docs/architecture/pipeline.md, metrics.md, or failure-semantics.md. Their moved prose is hash-frozen by tests/architecture-provenance.test.js, and since the tech-docs stage runs at :4707, after the last test-loop execution, a frozen-prose edit surfaces only as a red CI check on the batch PR. New pages are authored text and are explicitly allowed; see docs/architecture/CLAUDE.md.
Problem
Review findings are collected, counted, and then discarded.
grep -n "\.issues" workflows/ticketmill.jsfinds exactly one consumer in the pipeline: therecordGateOutcometally at:4669.What the fix stages actually receive is reviewer prose:
runQualityLoop:2962passesString(rev.comments || rev.summary || 'No comments')reviewAndMerge:4680-4681passesString(spec.comments || spec.summary)andString(code.comments || code.summary):3484passesString(v.comments || v.summary || 'Fix test quality issues')And loop control never reads findings either:
:2954if (rev.result === 'approved'):3477if (v.result === 'approved'):4667spec.result === 'approved' && code.result === 'approved'REVIEW_SCHEMA.issuesat:638is{ type: 'array', items: {} }, untyped, with noseverityfield. That is whygate_findings['pr-review'].severityreports zeros across the board, documented as expected at:2021-2027.Two consequences. Nothing downstream can address a specific finding, because findings have no identity. And a gate cannot terminate on findings being resolved, only on a reviewer changing its own verdict string.
Change
REVIEW_SCHEMA.issueswith the shapeCHALLENGE_SCHEMA.findingsalready uses at:599: objects withseverity(critical/major/minor),summary, and an optionalrecommendation. Give each finding a stable id within its gate iteration so later stages can reference one.REVIEW_SCHEMAresult: spec review:4613, code review:4633, quality review:2934, test validation:3452.commentsas context, but the findings array becomes the input of record.Acceptance criteria
REVIEW_SCHEMA.issuesis typed and the four producing prompts ask for it.commentsstill appears as context.gate_findings['pr-review'].severityreports real counts in a live run, and the note at:2021-2027is updated to match.changes_requestedwith an empty findings array still terminates the loop rather than running a fix stage with nothing to fix.issuesentirely degrades to today's behaviour rather than throwing.recordGateOutcomealready tolerates a missing severity.node --teststays green. Extendtests/pr-review-gate.test.js,tests/quality-loop.test.js,tests/test-loop.test.js, andtests/gate-findings.test.js.Why this one leads
Every other structural improvement in this set needs finding identity. A fix stage cannot rebut a specific finding if it received a prose blob, and a re-raised finding cannot be detected across gates if findings are not addressable. It also removes the
severityblind spot at the merge gate, which is currently the only gate whose findings carry no severity at all.Out of scope
No suppression or filtering of findings. This issue only makes findings load-bearing. Whether volume needs reducing is a separate question that needs data this change produces.
Documentation target
Document in
CHANGELOG.mdand in a new pagedocs/architecture/gate-hygiene.md(create it), adding its row todocs/architecture/index.md's file map.Do NOT edit
docs/architecture/pipeline.md,metrics.md, orfailure-semantics.md. Their moved prose is hash-frozen bytests/architecture-provenance.test.js, and since the tech-docs stage runs at:4707, after the last test-loop execution, a frozen-prose edit surfaces only as a red CI check on the batch PR. New pages are authored text and are explicitly allowed; seedocs/architecture/CLAUDE.md.