Skip to content

fix(trace): a gate verdict from ANY commit counted as run-status evidence (#1413) - #1414

Merged
dc0sk merged 1 commit into
mainfrom
fix/1413-verdict-commit
Sep 19, 2026
Merged

dc0sk merged 1 commit into
mainfrom
fix/1413-verdict-commit

Conversation

@dc0sk

@dc0sk dc0sk commented Sep 19, 2026 •

Copy link
Copy Markdown
Owner

trace.py now compares the stored verdict's commit against HEAD, degrading to the same "unverified" NOTE a toolchain mismatch already produces. Two probes added to evidence-self-test.

The defect

_gate_log's own comment states the rule —

A verdict is attributable to (tree, HEAD, TOOLCHAIN).

— and the code checked two of the three: INVALID (tree/HEAD moved during the run) and a toolchain mismatch. The commit was never compared.

Found by relying on it. #1412 added an enforced requirement with a fresh // VERIFIES: binding, and scripts/trace.sh check returned PASS on a verdict taken at an earlier commit — one that predated the binding.

Sabotage-measured before the fix, with a control proving the surrounding machinery does fire:

control  : verdict commit == HEAD                        -> TRACE: PASS   rc=0
sabotage : verdict commit := deadbeef…                   -> TRACE: PASS   rc=0   <-- unchecked
control 2: verdict toolchain := "rustc 0.0.0 (not real)" -> NOTE "…run a full gate"

Scope, stated narrowly

path before this change
inside gate.sh unaffected — gate.sh:203 exports GATE_LOG, which short-circuits before the verdict path, so the live log is used and is attributable by construction
CI traceability job unaffected — no target/, so it already reports no gate log in target/ — run-status of enforced bindings unverified
standalone scripts/trace.sh check the hole — accepted a verdict from any commit

The standalone path is the one a developer runs while authoring a binding, which is exactly when the evidence claim is first made. The sharper failure it permitted: rename a test and bind the old name — the stale log still records that name passing, so the binding validates against a test that no longer exists.

Degraded, not fatal — deliberately

The toolchain case returns (None, reason) and the caller reports a NOTE. Matching it gives "not attributable" a single behaviour, and keeps an ordinary edit-then-check cycle quiet. Verified both ways after the fix:

stale commit : NOTE: the last gate verdict was taken at 0e42ea2f9d84 and HEAD is 7a3b3ea1fe64
               — a verdict does not survive a commit change; run a full gate
matching     : 0 NOTE lines, TRACE: PASS

The probes are committed, not performed once

evidence-self-test gains "PASS from another commit" and "PASS with no commit recorded", both required to be refused — alongside the existing PASS control that stops the check being satisfied by refusing everything. The unrecorded case mirrors the toolchain one: every verdict written before gate.sh recorded a commit looks like that, and must not be trusted by default.

ok: a PASS verdict is accepted as evidence            (control)
ok: a PASS from another commit verdict is refused
ok: a PASS with no commit recorded verdict is refused
EVIDENCE-SELF-TEST: PASS

Verification

GATE: PASS a7ab15cc6ef4c1cb63f97e5fe5e8064cea066a98 clean 20260919T105331Z
suites=339 tests_passed=2574 tests_failed=0

All eleven steps. The gate's own trace check step passing is the load-bearing part here: it confirms the GATE_LOG short-circuit means this change cannot affect the in-gate path, which is the scope claim above rather than an assumption.

Verification-objective: a stored gate verdict may only vouch for a cited test's run-status when it is attributable to the tree being checked, so that a binding cannot be validated by a run that predates it.

Review: none — mirroring the toolchain branch three lines above it, with the defect sabotage-measured first and the new behaviour probed in both directions.

🤖 Generated with Claude Code

https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6

…ence (#1413)

`trace.py` now compares the stored verdict's `commit` against HEAD, degrading to
the same "unverified" NOTE a toolchain mismatch already produces. Two probes
added to `evidence-self-test`.

`_gate_log`'s own comment states the rule — "A verdict is attributable to (tree,
HEAD, TOOLCHAIN)" — and the code checked TWO of the three: INVALID (tree/HEAD
moved DURING the run) and a toolchain mismatch. The commit was never compared.

FOUND BY RELYING ON IT. #1412 added an enforced requirement with a fresh
`// VERIFIES:` binding, and `scripts/trace.sh check` returned PASS on a verdict
taken at an earlier commit — one that predated the binding.

Sabotage-measured before the fix, with a control proving the machinery fires:

    control  : verdict commit == HEAD          -> TRACE: PASS  rc=0
    sabotage : verdict commit := deadbeef...   -> TRACE: PASS  rc=0   <-- unchecked
    control 2: verdict toolchain := bogus      -> NOTE "...run a full gate"

SCOPE, NARROWLY. Inside gate.sh this was never wrong: gate.sh:203 exports
GATE_LOG, so the live log is used and is attributable by construction. In CI the
traceability job has no target/ and already reports run-status unverified. The
hole was the STANDALONE `trace.sh check` path — the one used while AUTHORING a
binding, which is when the evidence claim is first made. Sharper failure it
permitted: rename a test, bind the old name, and the stale log still records that
name passing, so the binding validates against a test that no longer exists.

DEGRADED, NOT FATAL, deliberately: the toolchain case returns (None, reason) and
the caller reports a NOTE. Matching it gives "not attributable" one behaviour and
keeps an ordinary edit-then-check cycle quiet.

The probes are committed rather than performed once: evidence-self-test gains
"PASS from another commit" and "PASS with no commit recorded", both required to
be refused, alongside the existing PASS control that stops the check being
satisfied by refusing everything.

Verification-objective: a stored gate verdict may only vouch for a cited test's
run-status when it is attributable to the tree being checked, so that a binding
cannot be validated by a run that predates it.

Review: none — mirroring the toolchain branch three lines above it, with the
defect sabotage-measured first and the new behaviour probed in both directions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0188ATCj6DZ9aRVQ2vSirua6
@dc0sk
dc0sk merged commit 2e7b794 into main Sep 19, 2026
13 checks passed
@dc0sk
dc0sk deleted the fix/1413-verdict-commit branch September 19, 2026 11:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant