At done, every issue-matrix row is judged, cited or not
Status: Accepted (2026-10-26). Operator rulings of 2026-10-26, recorded on #6653.
Date: 2026-10-26
Deciders: Stijn Dejongh (operator).
Technical Story:
- #6653 (an uncited
in-missionrow lands with exit 0). - #4943 and PR #5222 (the
issue_matrix_verdict_terminalitymerge gate, and its zero-reference early return, #5222 F1). - Mission
issue-matrix-terminality-all-rows-01M5X1ZJ; research inkitty-specs/issue-matrix-terminality-all-rows-01M5X1ZJ/research.md. - Decisions
01M5X206M989YE99MA0KHRFTNP(judge the whole matrix) and01M5X498X3ATTAB12KQWMDPZRV(a typedISSUE_MATRIX_NOT_AUTHOREDcode on the existing reader). - It refines ADR 2026-09-20-1: classification still decides which rows are required.
Context and Problem Statement
The issue-matrix has two levers. Classification decides whether a row is required. Verdict decides whether an existing row is resolved. The done check mixed them up in two places:
_issue_matrix_in_mission_rowskept anin-missionrow only when its issue was a discovered gating reference. A row whose issue was not cited, was cited only as context (see #12), was a one-digit reference the detector skips, or was recorded withagent issue-verdictalone, was dropped before thedonecheck.- With zero gating references, both the blocker and the terminality gate returned before reading the matrix at all ("No gating issue references discovered — nothing to enforce").
So a Mission whose own matrix said it still owed a fix could be consolidated, or moved to done, with exit 0. The gate's PASS line, "No unresolved (in-mission/unknown) issue-matrix rows", described rows it never looked at.
The zero-reference early return existed for a reason (#5222 F1): a coordination Mission that never authored a matrix must not fail on a coordination probe for a file that was never written. Before this decision, the post-consolidation ref read could not tell "no matrix at a valid ref" from "the probe failed".
Decision
1. At done, the verdict decides resolution for every existing row
Every in-mission row blocks at done in block mode and warns in warn mode, whether or not its issue is cited. This applies to spec-kitty consolidate (the terminality gate) and to move-task --to done, which call the same blocker. Classification still decides which rows are required: the completeness gate and the "missing rows" list are unchanged.
2. A present matrix is judged whole, even with zero gating references
With zero gating references, a matrix that exists is still judged at done: row diagnostics (malformed rows, unknown verdicts) and in-mission rows both count as unresolved. Judging only in-mission rows would keep a PASS that claims rows nobody checked.
3. A missing matrix is still nothing to enforce
A Mission with zero gating references and no matrix passes, so #5222 F1 holds. "Missing" is a positive fact, never an inferred one:
- for a coord-less Mission, or a materialized coordination worktree, the matrix file is absent from that directory;
- after consolidation, the coordination ref is valid and neither
issue-matrix.jsonnorissue-matrix.mdis in its tree. The reader reports this with its own code,ISSUE_MATRIX_NOT_AUTHORED.
4. ISSUE_MATRIX_NOT_AUTHORED means "nothing to enforce" only without gating references
The terminality gate accepts ISSUE_MATRIX_NOT_AUTHORED as a PASS only when the Mission has zero gating references. With gating references it stays a FAIL, as before. Every other read error (ISSUE_MATRIX_REF_ABSENT, ISSUE_MATRIX_PROBE_ERROR, ISSUE_MATRIX_EMPTY_CONTENT) fails closed on every path.
This now includes a Mission with zero gating references. One case reaches it in practice: running consolidate again after a finished run tore the coordination branch down. The read ref is then the deleted coordination branch, so the gate reports ISSUE_MATRIX_REF_ABSENT and FAILs, where the old early return passed. That is an accepted FAIL. The gate runs before any mutation, so an ordinary run never reaches it, and a Mission with gating references already failed the same way.
5. A PASS says what it judged
The PASS detail names the number of rows judged ("No unresolved (in-mission/unknown) rows among N issue-matrix row(s)"), or says "No issue-matrix authored and no gating issue references — nothing to enforce".
6. approved is unchanged
An in-mission row is still accepted at approved, and a Mission with zero gating references still never needs a matrix to be approved.
Consequences
Positive
- A Mission cannot land while its own matrix says it still owes a fix.
consolidateandmove-task --to doneenforce one rule through one blocker.- The gate output no longer overclaims.
Negative
- Accepted behaviour change (C-005). A Mission that passes today can be refused in
blockmode, or warned inwarnmode, at consolidation and atmove-task --to done. The remedy is to record a terminal verdict for the row withspec-kitty agent issue-verdict. - A zero-reference coordination Mission now reads its coordination ref at the terminality gate. A deleted ref, an unreadable object, an empty matrix or any other resolution error there now FAILs where the old early return passed. That is the fail-closed rule of NFR-002.
Neutral
- The completeness gate keeps its zero-reference early return.
- The compat names
_issue_matrix_approval_blocker,_issue_matrix_evaluationand_issue_matrix_in_mission_rowskeep their names and signatures.
Alternatives Considered
- Judge only
in-missionrows with zero references, with a narrower PASS text. Rejected by the operator (Decision01M5X206M989YE99MA0KHRFTNP): the PASS would still have to describe which rows it did not check, and a malformed row would land unseen. - A separate
issue_matrix_present()probe inmission_runtime. Rejected (Decision01M5X498X3ATTAB12KQWMDPZRV): it is a second entry point that repeats the ref and lifecycle-phase resolution. The typed code on the one reader keeps a single read authority.
References
- Issue-Matrix Verdict Reference: What
doneenforces src/specify_cli/tasks/issue_matrix_approval.py(_issue_matrix_approval_blocker,issue_matrix_judged_row_count)src/specify_cli/policy/merge_gates.py(_evaluate_issue_matrix_verdict_terminality_gate)src/mission_runtime/resolution.py(read_issue_matrix_ref_content,ISSUE_MATRIX_NOT_AUTHORED)