Skip to content

Let tests say "I did not verify this" - #113

Merged
b-macker merged 2 commits into
masterfrom
claude/naab-inadmissible-action-prevention-4cmn1m
Aug 1, 2026
Merged

Let tests say "I did not verify this"#113
b-macker merged 2 commits into
masterfrom
claude/naab-inadmissible-action-prevention-4cmn1m

Conversation

@b-macker

@b-macker b-macker commented Aug 1, 2026

Copy link
Copy Markdown
Owner

Summary

Five times recently an assertion passed while the property it named was untrue — grep '"key_fingerprint":"' matching an empty value, an attestation-count equality against a counter incremented on non-attesting paths, a taint total asserted on the wrong scope, a baseline inflated 2x by a telemetry defect, and a test-phase split that ran 19 fewer tests while printing clean summaries. None were caught by review; all were caught by running the degraded case.

Four accidental discoveries is a poor sampling method, so this audits the set: 360 assertion sites in the example's run.sh and 2294 across 235 shell suites.

Most of it is already disciplined

Worth saying first, because it shaped the scope. Absence checks generally pair with a positive existence check, a fail-safe default (${X:-1}, where unset means fail), or a named control — TR-01 is labelled "the anti-regression", PU-01 fails outright if its scenario produced fewer than six samples, C-02 is guarded by C-01 asserting presence. 43 of governance_v4's 69 candidates already pair with a positive check. Those were left alone.

The systematic finding

Seven security suites define only ok() and fail() — no skip(). Unable to say "inconclusive", they were forced to record every "executor not available" branch as a PASS:

test_gov_comment_styles_gov002.sh
  before:  Results: 4/4 passed
  after:   Results: 0/4 passed, 4 skipped (unverified)

That suite verified nothing at all — every check unverifiable in this environment, the Python polyglot block evaluating to null so the probe never satisfies — while reporting four passes. 19 sites across 7 files, plus a SKIP count in each summary.

Five individual findings

before
H01 health verdict inferred from stderr containing the string "governance", which every governed run prints. The living script emits its verdict as its last act, so the line is absent exactly when a run died early — a gk_fail-severity check passing hardest in the case that matters most. IMPAIRED still fails; absence is now inconclusive.
L24-03 trivially true at zero attestations; non-vacuous only because a neighbouring assertion stayed strict. Now self-guarding.
DB-01, DB-03 initialiser 0 indistinguishable from "the compliant scenario never ran" → sentinel -1
DB-04 fallback "1.00 0 0" made a missing result file read as a confirmed known-negative
E04 asserted a line was absent from a dashboard that a failed run never produced

Deliberately unchanged

T9 greps stable source; A05 genuinely tests config acceptance; B02 requires the value present; FM-01/FM-02 treat a missing result file as uncaught → fail. tests/gorilla (62 candidates) is out of scope — older scenario tests, weakest claims.

Test Plan

  • Ran bash run-all-tests.sh with no new failures — 441 tests, 0 unexpected failures
  • Added/updated tests for new functionality — n/a; this removes false passes rather than adding checks
  • bash tests/security/test_error_msg_leaks.sh — 874 checks, 0 failures
  • Tested manually in the REPL — n/a, test harness

Every fix verified by degraded case. H01 three ways:

scenario old new
run died early, stderr mentions "governance" PASS "healthy" SKIP
genuinely healthy run PASS PASS
IMPAIRED in stderr FAIL FAIL

DB-01/DB-04 likewise skip on absent measurements while the real run still passes 4/4.

Note test_r24_fixes.sh fails 2/10 locally (exit 127naab-scanner/naab-gov not built in this container). Pre-existing and identical before this change, and run-all-tests.sh does not invoke it.

Related Issues

Follow-up to #109#112. docs/governance-campaign-findings.md records the audit and the rule it produced: an assertion about an absence needs the thing it is absent from to exist. A missing line in a document nobody wrote, a zero from a counter nobody incremented, and a clean result are the same observation until something distinguishes them.


Generated by Claude Code

Five times this session an assertion passed while the property it named was
untrue, and none were caught by review — only by running the degraded case.
Four accidental discoveries is a poor sampling method, so this audits the set:
360 assertion sites in the example's run.sh, 2294 across 235 shell suites.

Most of it is already disciplined. Absence checks generally pair with a positive
existence check, a fail-safe default (${X:-1}, where unset means fail), or a
named control — TR-01 is labelled "the anti-regression", PU-01 fails outright if
its scenario produced fewer than six samples, C-02 is guarded by C-01 asserting
presence. Those were left alone.

The systematic finding is seven security suites that define only ok() and fail() and
no skip(). Unable to say "inconclusive", they were forced to record every
"executor not available" branch as a PASS. test_gov_comment_styles_gov002.sh
reported "Results: 4/4 passed" while verifying nothing at all — every check
unverifiable in this environment, the Python polyglot block evaluating to null
so the probe never satisfies. It now reports 0/4 passed, 4 skipped. Nineteen
sites across seven files, plus a SKIP count in each summary.

H01 is the sharpest single case. The living script prints its health verdict as
its very last act, so the line is absent exactly when a run died early — and the
fallback then inferred a verdict from stderr containing the string "governance",
which every governed run prints. A gk_fail-severity health check passed on a
substring match, and passed hardest in the case that matters most. IMPAIRED in
stderr is still real evidence and still fails; absence is now inconclusive.

Four smaller ones of the same shape: L24-03 was trivially true at zero
attestations and non-vacuous only because a neighbouring assertion stayed strict
(now self-guarding); DB-01/DB-03 could not distinguish "no false positives" from
"the compliant scenario never ran" (initialiser 0 -> sentinel -1); DB-04's
"1.00 0 0" fallback made a missing result file read as a confirmed known
negative; E04 asserted a line was absent from a dashboard that a failed run
never produced.

Every fix verified by degraded case. H01 three ways: a died-early run now skips
where it used to pass "healthy", a genuinely healthy run still passes, IMPAIRED
still fails.

The rule is narrower than "write better assertions": an assertion about an
absence needs the thing it is absent from to exist. A missing line in a document
nobody wrote, a zero from a counter nobody incremented, and a clean result are
the same observation until something distinguishes them.

Full suite: 441 tests, 0 unexpected failures. Leak check: 874/0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELUfjXZvx8kzXo1UJjrAhC
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

NAAb Governance Report

Metric Count
Files checked 16
Passed 16
Failed 0

All governance checks passed!

Generated by NAAb Governance Engine v4.0

Reviewing the previous commit's diff before merge found six more
inconclusive-as-PASS sites, in three files that commit was already editing.
19 of 25 had been caught.

The filter searched for "not available" and "may be". The survivors read "may
not be available", "depth may not have been reached", and "(acceptable)" —
none of which match. A keyword filter over prose is itself an assertion that can
be satisfied without the property holding: it finds the phrasings you thought
of and reports clean on the ones you did not. Reading the changed files end to
end is what caught the rest, which is the same lesson the rest of this branch is
about, applied to its own detection method.

test_js_marshal_depth_rt006.sh moves from 2/3 passed to 1/3 passed, 2 skipped —
one more check that was reporting success while verifying nothing. The other two
files are unchanged in this environment; their inconclusive branches simply do
not trigger here, and are now correct if they ever do.

Count corrected 19 -> 25 in the findings doc, with the miss recorded rather than
the number quietly bumped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ELUfjXZvx8kzXo1UJjrAhC
@b-macker
b-macker marked this pull request as ready for review August 1, 2026 22:00
@b-macker
b-macker merged commit 18d2da9 into master Aug 1, 2026
23 checks passed
@b-macker
b-macker deleted the claude/naab-inadmissible-action-prevention-4cmn1m branch August 1, 2026 22:00
@b-macker b-macker mentioned this pull request Aug 2, 2026
4 tasks
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.

2 participants