Issue-Matrix Verdict Reference

Look-up material for the issue-matrix.json verdict vocabulary and the reference-classification model that decides which discovered #NNNN citations require a verdict row at all (#3469). This page documents the two levers as a single source of truth so an operator or agent never has to reverse-engineer gating behavior from gate error text.

The two levers

The issue-matrix approval gate has two independent decisions, and confusing them is the root of most "why is this blocking approval" surprises:

  • Classification decides whether a row is required (gating). It runs once, over every #NNNN reference discovered in mission artifacts (spec.md, plan.md, research.md, analysis docs, WP prompts, contracts), and is consumed identically by every site that computes "referenced-but-missing": the WP approved-transition blocker, the merge-time merge_gates check, and status/doctor. One shared function, one answer — a reference is never gating at one site and non-gating at another.
  • Verdict decides whether an existing row is resolved. An operator may record any verdict on any row regardless of its classification; a not-applicable verdict makes a row non-gating regardless of classification, and an implementation-target classification requires a row to exist even if the scaffolder didn't produce one for it.

The two levers can disagree by design (an operator can always assert not-applicable on a row the scaffolder classified gating), and that disagreement always resolves in favor of the operator's explicit verdict.

Verdict values

Verdict Meaning Gates approved? Gates done/merge?
fixed The mission fixed the referenced issue. No (resolves the row) No
verified-already-fixed The issue was confirmed already fixed before this mission touched it. No No
deferred-with-followup Work is deliberately deferred; a follow-up is tracked. Requires an evidence-token (see below). No No
in-mission A later work package in this mission will close the issue. No — accepted at per-WP approved Yes — rejected at done; must resolve to a terminal verdict before the mission merges
not-applicable The reference is cited for context only, or is a PR/commit reference — the mission owes it no work. Terminal. No No — passes unchanged, unlike in-mission

not-applicable is additive: it was added alongside the four pre-existing values with no change to their meaning, so every issue-matrix.json file written before this change continues to validate with zero migration.

What done enforces

At done (spec-kitty consolidate, through the issue_matrix_verdict_terminality merge gate, and spec-kitty agent tasks move-task --to done) the verdict decides resolution for every existing row, whatever its classification:

  • Every in-mission row blocks in block mode and warns in warn mode, whether or not its issue is cited. An uncited row, a context_only citation such as see #12, a one-digit reference the detector skips, or a row recorded with agent issue-verdict alone all count.
  • When a matrix exists, it is judged whole, even for a Mission with zero gating references: a malformed row or an unknown verdict is unresolved too.
  • A Mission with zero gating references and no matrix has nothing to enforce and passes. After consolidation that means the coordination ref is valid and holds neither issue-matrix.json nor issue-matrix.md (ISSUE_MATRIX_NOT_AUTHORED). A deleted ref, a failed git probe or an empty matrix still fails.
  • A Mission with gating references still needs a matrix and a row for each of them (the completeness gate is unchanged); a missing matrix fails.

A PASS says what it judged:

  • No unresolved (in-mission/unknown) rows among N issue-matrix row(s)
  • No issue-matrix authored and no gating issue references — nothing to enforce

approved is unchanged: an in-mission row is accepted there, and a Mission with zero gating references never needs a matrix to be approved.

To clear a blocking row, record a terminal verdict (fixed, verified-already-fixed, deferred-with-followup or not-applicable):

spec-kitty agent issue-verdict --mission <handle> --issue '#5678' --verdict fixed --actor <actor> --evidence-ref <evidence>

Design record: ADR 2026-10-26-1.

Reference classification: which citations become gating rows

Every discovered #NNNN token is classified into exactly one of three buckets, aggregated over all of its occurrences in the mission's artifacts (not just the first one found):

  • Implementation-target (gating) — the default. A row is required and must carry a verdict before approved.
  • Context-only (non-gating) — the citation is scaffolded as a row for audit purposes, but does not block approval. A row still records the citation truthfully (typically with not-applicable); it just isn't required to unblock the transition.
  • PR/commit reference (non-gating) — the same as context-only: recorded, never blocking.

Default-gating fail-safe

Classification only ever demotes a reference from gating to non-gating, and only on a positive, explicit signal. An unmarked bare #NNNN in ordinary prose always gates. The demoting signals are:

  • A PR or pull token immediately associated with the reference (either hash form, PR #300, or a full /pull/<n> URL) — classified as a PR/commit reference.
  • A line-scoped context marker anywhere on the line: Follow-up:, baseline-red — the whole statement, and every reference in it, is context.
  • A positional citation label: see #, parent, epic. These bind to the reference they lead into, not to the line: in **Input**: Issue #6926 (part of epic #6100), the epic label demotes #6100 and leaves #6926 gating (#7011).
  • A cross-repo reference (owner/repo#NN, or a full /issues/<n> URL for a different repository) — already excluded from discovery entirely; it never becomes a row of any kind.

Ambiguity never fails open: if none of the above signals is present, the reference gates.

The Mission's own source issue is never non-gating

The issue a Mission exists to fix always gates, whatever its prose looks like (#7011). The source issue is read from the Mission's own record — every one of these sources that is present contributes:

  • meta.json source_description, when it opens with the issue form (Issue #N: …, GitHub issue N …, Ticket #N …, #N …); a passing mention of another number deeper in the blurb is not a source issue.
  • the spec's **Input**: header line — its first same-repo reference;
  • any Closes/Fixes/Resolves #NNNN line in the spec.

A source issue that some scanned artifact cites is promoted to implementation_target before classification reaches any consumer, so the scaffold writes it an unknown row and the approval gate forces an explicit verdict. Promotion never invents a row for a number no artifact cites, and it never demotes: it is a guarantee layered on top of the heuristic, not a replacement for it. The not-applicable verdict remains legitimate for references the Mission genuinely owes no work — it just cannot be reached by automatic classification for the Mission's own issue (ADR 2026-09-20-1 unchanged).

Multi-occurrence aggregation: implementation-target wins

A #NNNN cited once as context (e.g. parent: #200) and once as an implementation target (e.g. "fixes the defect from #200") is classified gating — the least-demoted occurrence wins across the whole aggregate. A reference is never silently downgraded because one of its several citations happened to carry a demoting marker.

The evidence-token rule (deferred-with-followup)

A deferred-with-followup verdict's evidence_ref must contain either:

  • an issue reference in #NNN form (the follow-up issue), or
  • a literal Follow-up: token.

A bare deferred-with-followup with no follow-up handle in evidence_ref is rejected. issue-verdict --help documents this rule directly so it is discoverable before the gate rejects a submission, not only after.

Early discoverability

The specify/plan/tasks/analyze prompt templates carry a non-gating heads-up that approvals require an issue-matrix verdict for every referenced implementation-target issue — so the gate is not a surprise encountered only at the first blocked approval.

See also