Files
msd-core/gsd-core/references/verifier-evidence-gate.md
Tom Boucher 6beaa66b25 enhance(#3304): gate re-verification blockers on deterministic evidence (#4085)
* test(#3304): add failing-first suite for the convergence evidence gate

Content-assertion suite for the Step 7 re-verification evidence gate
(agents/gsd-verifier.md / gsd-core/references/verifier-evidence-gate.md).
Committed before the implementation to prove RED via gsd-test.

* enhance(#3304): gate re-verification blockers on deterministic evidence

Step 7's anti-pattern scan re-runs at full, unbounded scope on every
re-verification pass, independent of the must-haves established in Step 2.
A blocker it finds — other than the self-evidencing debt-marker check —
previously reverted a completed gap-closure round and started another
--gaps cycle on nothing more than the verifier's own new judgment call,
with no bound on how many times that could repeat.

A Step 7 blocker now blocks unconditionally in re-verification mode only
if it is a carried-forward gap (present in the prior VERIFICATION.md's
gaps: list) or the flagged file was git-modified since the prior pass
(a regression; fails closed toward blocking when history is unresolvable).
Otherwise it predates the gap-closure round unflagged and needs
deterministic evidence — a named test run red, or another concrete
reproducible artifact — to stay blocking. Unevidenced, it downgrades to
a new advisory: frontmatter list and report section instead of setting
status: gaps_found, and never reverts a completed must-have.

Maintainer approval was narrowed to this evidence condition only,
explicitly rejecting the broader "advisory whenever untraceable to a
requirement/decision/prior-gap" proposal — implemented and pinned by
tests/verifier-evidence-gate.test.cjs and documented as rejected in
gsd-core/references/verifier-evidence-gate.md so it can't silently
re-expand.

Closes #3304

* fix(#3304): correct window-truncation and indentation bugs in evidence-gate tests

gsd-test's GREEN checkpoint caught 3 real bugs in the test file itself
(not the production prose): a {0,600} match window was shorter than the
724-char paragraph it was scanning (the "exclude from Step 9 Rule 1"
phrase starts at offset 662), and two regexes assumed no indentation
after a markdown list-continuation line break. All three phrases are
confirmed unique across agents/gsd-verifier.md, so the windowed
submatches are replaced with direct whole-string assertions instead of
just widening the window.

Also acknowledges the deliberate byte growth in agents/gsd-verifier.md
that the differential-attribution check (ADR-2719) correctly flagged.

Emitted-Drift-Ack-Growth: gsd-verifier.md — adds the #3304 re-verification evidence gate (Step 7 rule, Advisory bucket, advisory: frontmatter, report section); 1488 bytes, still within the LARGE-tier 48 KiB cap (48751/49152).

* docs(#3304): backfill changeset PR number

---------

Co-authored-by: sim <sim@local>
2026-08-30 16:51:52 -04:00

161 lines
8.1 KiB
Markdown

# Convergence Evidence Gate (#3304)
Bounds Step 7's anti-pattern scan so an approved gap-closure contract can
actually close. Applies **only** when `is_re_verification = true` (Step 0) —
a first pass has no prior contract to be out-of-contract from, so this gate
is a pure no-op there.
## The problem this closes
Steps 4-7c re-verify at full, unbounded scope on every re-verification pass —
that is documented, intended design, not the bug. The bug is narrower: Step
7's `Categorize:` line lets the verifier's own free-form judgment label
*anything* it believes "prevents goal" a 🛑 Blocker, and Step 9 Rule 1
promotes any 🛑 Blocker straight into `status: gaps_found` — with no
distinction between a blocker tied to what the gap-closure round was actually
supposed to fix and a blocker that is simply a new opinion formed on this
pass. Reported real-world instance: a re-verification cycle promoted four
"architectural and security observations" to blockers, none backed by a
failing test, none traceable to a requirement/decision/prior gap, reverting a
completed, all-green gap-closure round and recommending another `--gaps`
cycle — with no bound on how many times that could repeat.
Truths, artifacts, and key links (Steps 3-6) can **never** produce this
failure mode: Step 0 re-verification mode reuses the must-haves extracted in
Step 2 verbatim ("Skip to Step 3") rather than re-establishing them, so
whatever a truth/artifact/link *is* was fixed before this re-verification
round started. Only Step 7's blanket per-file scan is unbounded by that
must-haves contract — which is exactly the mechanism the issue's diagnosis
names. This gate therefore touches Step 7 only.
## Definitions
**Self-evidencing blocker (unaffected by this gate).** The debt-marker gate
(`TBD`/`FIXME`/`XXX` with no `issue #123`/`PR #123`/`#123`/`DEF-*` reference
on the same line) is the *only* Step 7 category with zero judgment
component — a regex match plus the absence of a follow-up reference, nothing
inferred. Its own textual presence in the file is the deterministic evidence.
It keeps blocking unconditionally, exactly as before. Do **not** extend this
carve-out to any other Step 7 category (stub classification, hollow props,
empty implementations, console-log-only): every one of those already
requires judgment per Step 7's own "Stub classification" paragraph ("a grep
match is a STUB only when the value flows to rendering... and no other code
path populates it with real data") — that judgment is exactly what this gate
exists to check.
**New-scope finding.** Any Step 7 🛑 Blocker other than a self-evidencing one
(above) is a new-scope finding **unless** either of the following holds, in
which case it is in-contract and blocks unconditionally, evidence or not:
1. **Carried-forward gap** — it matches an item in the previous
VERIFICATION.md's `gaps:` list, using the same 80%-token-overlap matching
algorithm Step 3b already uses for override matching (normalize to
lowercase, strip punctuation, collapse whitespace, tokenize, intersect).
2. **Regression** — the flagged file was modified since the previous
VERIFICATION.md's `verified:` timestamp. Check file-level, not
line-level — an LLM agent re-deriving precise line provenance mid-pass is
unreliable; file-level modification is a single, robust command:
```bash
git log --since="$PREV_VERIFIED_TS" --oneline -- "$file"
```
A non-empty result means the file changed since the prior pass — the
gap-closure round could plausibly have introduced this finding, so it's
self-evidencing as a regression and blocks. **Fail closed**: if git
history is unavailable, ambiguous, or the timestamp can't be parsed,
treat the file as modified (blocks). The imprecision this trades away
(a big file with one unrelated hunk touched treats every pattern in it as
"new") only ever makes *more* things block, never fewer — consistent with
`<adversarial_stance>`.
A finding that is neither a carried-forward gap nor on a file modified since
the prior pass predates the gap-closure round entirely and was never flagged
as a gap then — this is the literal "some findings predated the gap
implementation and had previously been explicitly treated as non-blocking"
case from the issue.
**Deterministic evidence** — required for a new-scope finding to stay
blocking. One of:
- A **named test that FAILS when actually run** (red). Run exactly one test,
the same discipline Step 7b already uses for behavioral spot-checks —
never the full suite. Record the exact command and the failing output.
- **Another concrete, reproducible artifact** — a command + output that
demonstrates the defect (a crash, a probe failure, a reproducible bad
response). An assertion, opinion, or architectural preference with no test
and no reproducible command output is not evidence, however well-reasoned.
## The gate
- New-scope finding **with** deterministic evidence → 🛑 Blocker, unchanged.
This includes evidenced security findings — they are preserved and still
block.
- New-scope finding **without** deterministic evidence → downgrade out of
the blocker set. Record it in the `advisory:` frontmatter list (parallel to
the existing Step 9b `deferred:` list) with its reasoning intact. It does
**not** count toward Step 9 Rule 1's `gaps_found` trigger and does **not**
revert a completed must-have or, on its own, justify another
`/gsd:plan-phase --gaps` cycle.
This changes nothing else: a carried-forward gap or a regression still
blocks with or without a pre-existing requirement to point at, and every
non-Step-7 trigger (FAILED truth, MISSING/STUB artifact, NOT_WIRED link) is
untouched, since those can never be new-scope in the first place.
## What this deliberately does NOT implement
The issue as filed proposed a broader rule: a finding is advisory whenever
it is untraceable to a requirement/decision/prior-gap (conditions A and B),
regardless of evidence. The maintainer approved **condition C only** —
evidence, not contract-traceability, is the bar. A finding with no
pre-existing requirement to point at but with a real failing test still
blocks. Do not implement A/B: that would demote a genuine, reproducible
defect to advisory purely for being newly discovered, which is exactly the
deferral this project's no-defer rule forbids. This gate narrows *when a
blocker needs proof*, not *what counts as in scope*.
## Advisory frontmatter
```yaml
advisory: # Only if new-scope findings lack deterministic evidence (Step 7)
- finding: "Short description of the new-scope concern"
category: architectural | security | other
reason: "Why this was raised; what would resolve it"
evidence_status: "none provided" # or cite what was attempted but inconclusive
```
## Report section
```markdown
### Advisory (New Scope, Unevidenced)
New-scope findings from Step 7 with no deterministic evidence — reported,
not blocking, do not revert a completed must-have.
| # | Finding | Category | Why Advisory |
|---|---------|----------|--------------|
| 1 | {finding} | {category} | new-scope, no deterministic evidence |
```
Include this section (even if empty, stating "None") whenever
`is_re_verification = true` ran — an omitted section reads as "not
checked," not "checked and clean."
## Worked example (from the issue's reported incident)
Prior pass: `gaps_found`, 4 items — all closed by approved gap-closure plans,
re-verification begins.
- Finding: "the retry loop's backoff strategy is architecturally fragile
under sustained load." Not in the prior `gaps:` list. The flagged file was
last modified 3 weeks before this verification pass (before the
gap-closure plans even started) — not a regression. No test run, no
reproducible command demonstrating a failure. → **advisory**, does not
block, does not revert the 4 closed gaps.
- Finding: `TBD: handle the timeout case` left in a file the gap-closure plan
edited this pass. → self-evidencing debt marker, unaffected by this gate,
blocks exactly as it always has.
- Finding: a previously-closed gap's file now fails the SAME named test that
originally proved it broken. → carried-forward gap, blocks.