* 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>
161 lines
8.1 KiB
Markdown
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.
|