Skip to content

ci(asvs): port the --prove-absences wiring onto current main, and make docs/CI.md honest about the vault mirror (supersedes #304) - #350

Merged
wshallwshall merged 9 commits into
mainfrom
claude/asvs-304-prove-absences-port
Aug 12, 2026
Merged

ci(asvs): port the --prove-absences wiring onto current main, and make docs/CI.md honest about the vault mirror (supersedes #304)#350
wshallwshall merged 9 commits into
mainfrom
claude/asvs-304-prove-absences-port

Conversation

@wshallwshall

Copy link
Copy Markdown
Collaborator

Supersedes #304. A semantic re-read onto current main, not a merge of it -- main rewrote the prove_absences family under #303, so merging #304 would have reapplied a stale implementation of code that no longer exists in that form, and git would have raised nothing, because it conflicts on concurrent edits and never on obsolete intent.

DELIBERATELY NOT ARMED. See the flag below before arming.

Verification (the authoring session's, labelled as theirs)

Full suite 11944 passed / 855 skipped / 0 failed; ruff format/check clean; actionlint clean on the added workflow. A 28-agent adversarial pass: 20 findings, 15 refuted, verdict safe to land. Two silent failure modes were found and fixed -- a coarsened baseline cache key that would have manufactured a false proof, and a dropped #304 test that left the prover's multi-claim loop uncovered. Each hole was proved real by running the rest of the suite under the injection (159 and 158 other tests stayed green), which is what distinguishes a fixed hole from an asserted one.

Verified here before pushing

Tip 2f9aabd8, genuinely unpushed, 0 behind origin/main, merge-tree rc=0 read with no pipe, 11 files. Leak scan: 1734 added lines scanned, and the only docs/security hits are paths, not content -- confirmed no vault document content is added.

THE FLAG -- why this is not armed

This PR introduces a pattern that does not exist on main: a public-repo workflow that sparse-checks-out the private vault. Measured -- main has zero workflows referencing vault docs/security paths and zero vault sparse-checkouts. So this is a new cross-repo coupling in the public repo, not an extension of an existing one.

Mitigating, and stated so the flag is not overstated: no vault content is committed; the prove job is workflow_dispatch only; and it fails closed with exit 2 when no input is configured, so it is inert without deliberate setup. But a coupled engine/vault pair is the case that wants the owner present for both halves, so the arming decision is his.

Also in here, and worth reading on its own

bb825e9c makes docs/CI.md honest: it previously asserted that prove_report.py "is mirrored into the vault on the same footing as scorecard.py". Independently confirmed false -- the vault's asvs-verifier-drift.yml hard-wires FILE: scripts/asvs/scorecard.py at four sites and mentions prove_report nowhere. The test it cited as pinning that claim is honest and pins something else (stdlib-only imports); the doc was the defect. Widening the vault's mirror to actually carry prove_report.py is named as the open half rather than assumed.

#1203 is deliberately absent

#304 filed a ledger row for #1203; this port does not carry it, and its docs/CI.md deliberately does not cite it. That is correct: #1203 is allocated to another worktree so the ledger gate refuses it from anywhere else, and the vault cites #1203 in prose written 2026-07-24 -- sixteen days before the number was allocated -- describing unrelated content. Today that citation resolves to nothing; the day #1203 lands it would resolve to a real item saying something else, which is worse than dangling because it looks correct.

… semantic re-read)

PR #304 was 145+ commits behind main, with both sides having rewritten prove_absences and
_run_prove_absences and main having moved the shared types underneath them. This is a SEMANTIC port,
written against main's current code -- not a merge, rebase or cherry-pick, because git conflicts on
concurrent edits and is SILENT on invalidated claims, so a textual resolve would have produced working
code with false documentation beside it.

THE INVALIDATION THAT MADE IT UN-MECHANICAL. main's Findings carries a documented accounting identity
(checked_absences - proved_absences - static_screened - skipped_absences == claims that ended in a
problem-only branch) and asserted "FIVE outcomes raise a problem and increment no counter", enumerated.
#304 adds more problem-only branches. Nothing anywhere ASSERTED the identity -- zero references to
static_screened outside scorecard.py -- so the prose would have gone stale silently, ~500 lines from
the hunks that broke it.

Resolved by making a refusal PROBLEM-ONLY: it appends a PROVE-ERROR and increments no counter, so the
identity still closes. That was chosen over two alternatives and the reasoning is in the code -- folding
a refusal into static_screened would be a lie about kind, since that counter means "this claim's
evidence is a screen rather than a proof" and a refused claim carries no evidence at all; giving
refusals their own counter would be arbitrary, since every problem-only branch has an equal claim to
one and the PROBLEM is the record.

The brittle enumeration is GONE, replaced by test_prove_absences_counters_close_against_the_population,
which computes BOTH sides from a real run and DERIVES the right side from `problems` rather than
asserting a hand-chosen constant. The test it replaces asserted remainder == 1 against a fixture: true
of that fixture, silent about the identity -- which is exactly how three problem-only branches could be
added while the prose enumerating them went stale and nothing moved.

Ported additively: prove_report.py, the scheduled advisory workflow, the wiring test, and the mutation
arity screens (main has no counterpart -- _toplevel/_signature/ast.arguments all return zero on main).
The one-pristine-scratch-tree rework is kept with its VERIFIED restore, because a restore that silently
did not happen makes every later claim a fact about the previous claim's mutation.

NOT YET VERIFIED beyond ruff and a parse/run check -- the adversarial and quartet passes were stopped
early to bank the work under a usage ceiling. See the handoff for what remains.
…ero file overlap

The port touches 11 files, main's 11 new commits touch 21, and the two sets are DISJOINT, so the
merge is textually clean. That is exactly the state in which a clean merge is least informative:
the port's changes include repo-wide static scans (test_security_static, test_key_usage_scope_inventory,
test_asvs_verifier_vault_contract) and main added new modules (api/_ui_seam.py, uploads changes) plus
BACKLOG.md rows -- so the verification quartet must run on the MERGED tree, never on the pre-merge tip.
…stop a comment anchor rotting

Verification pass over the --prove-absences semantic port (PR #304, ported at af1b1d9, merged with
current main at 1121539). Twenty findings were raised across six dimensions and fifteen were
refuted; five survived, reducing to five distinct issues. This commit closes three of them. The two
it leaves are recorded at the end, with why.

BASELINE CACHE KEYING -- the false proof. A claim whose observable is already red on the pristine
tree must be refused, because a red that was already there cannot be attributed to the mutation, and
that refusal is reached ONLY through the claim's own baseline. So coarsening the cache key does not
merely slow the prover down: it manufactures a proof. Measured, not argued -- re-keying `baselines`
to a constant makes prove_absences report proved_absences=2 with problems=[], an already-failing test
booked as proof of a control that never fired, and NOTHING printed. Under that same injection 159
other tests across the two ASVS suites stayed GREEN, which is what the new test is worth. Its fixture
is one cell carrying two claims that share a mutation_path and differ only in observable, so it also
reddens a cell_id key (_live_claim hardcodes 1.1.1, so cell id separates no live claims at all) and a
mutation_path key. Only the node id separates them, which is the property being pinned.

THE INNER LOOP OVER A CELL'S CLAIMS. #304's test_the_prove_summary_reports_the_total_it_saw was the
only test driving prove_absences over a cell carrying more than one absence claim, and the port
dropped it without mentioning it. Two of its three properties were genuinely superseded, one of them
by a stronger guard main already had; this third was not. Every other `absence=` tuple in the file
has length 1, so `checked_absences` counting CLAIMS was indistinguishable from it counting CELLS.
Cell 1.1.2 in the identity test now carries two. Measured: degenerating the loop to `c.absence[:1]`
reddens it at 6 != 7 while 158 other tests stay green. Both of 1.1.2's claims land in a COUNTED
branch deliberately -- the identity test derives its right-hand side by set-dedupe on cell id, so a
cell carrying a second PROBLEM-only claim would break that derivation silently, which is the exact
class of failure this test exists to catch.

A DECAYED ANCHOR. prove_report.py cited "the loader at scorecard.py:339-340". That resolved exactly
in #304 and resolves to unrelated docstring prose here, because the port moved the loader to :544-545
-- so it was already stale at af1b1d9 and the merge did not cause it. Replaced with the function
NAME rather than corrected line numbers: nothing in this repo checks a line citation inside a Python
comment, so a corrected anchor decays again on the next move.

NOT FIXED HERE, deliberately. (1) _prove_one probes `target_in_root.is_file()` against root but reads
`target_in_scratch.read_bytes()` from the scratch copy, and _scratch_ignore is what makes those trees
differ, so a claim naming docs/security, .env*, or *.db would pass the probe and then die on an
unhandled FileNotFoundError. It is inherited from BOTH parents rather than introduced here, and the
shipped workflow roots the prover at engine/ where none of those paths exist, so it would surface
only in an ad-hoc local run -- and it fails loudly, which prove_report already classifies as an
instrument failure. It belongs in its own change against main, not folded into a port. (2) The
tree-rebuild guard has no test. Both are in the handoff.

scripts/asvs/scorecard.py is deliberately UNCHANGED by this commit. Four injections were applied to
it while measuring the above; each was reverted and confirmed byte-identical to HEAD with `git
status` before the next measurement, because an unverified restore turns every later measurement into
a fact about the previous one's mutation.
…mail TTL

Two commits, disjoint from the port's eleven files again. Merged rather than left behind because the
port edits repo-wide static scans and one of the two touches docs/BACKLOG.md, which two tests read
from disk -- so a docs-only base move is not test-neutral here and the verification has to be a
statement about the tree that will actually land.
… redaction

5f7eb55 touches three files (docs/BACKLOG.md, messagefoundry/config/wiring.py,
tests/test_connection_factory_redaction_domain.py), none of them among the port's eleven, so the
merge is textually clean. Re-verified rather than assumed on that basis: the port edits REPO-WIDE
static scans (test_security_static, test_key_usage_scope_inventory, crypto_inventory_check), and
wiring.py is exactly the kind of file those scans read -- a clean merge says nothing about whether a
scan now has something new to say.
…chanism delivers

The row this port added said prove_report.py "is mirrored into the vault on the same footing as
scorecard.py, which MIRRORED_TOOLS pins". Measured against the vault: its mirror automation is scoped
to the literal path scripts/asvs/scorecard.py, repeated in four job envs, and copies exactly that one
file; the vault holds no copy of prove_report.py and no workflow referencing it. So the sentence
would have been false on the day it landed.

Worse than false, it cited a test as its evidence. MIRRORED_TOOLS asserts the listed tool imports
only the standard library -- it says nothing about whether a vault copy exists. A reader would have
taken the citation as proof of a mirroring relationship that nothing checks and nothing performs.
That is the compensating-control-on-a-false-premise shape SDS-3.7 names, and CLAUDE.md section 11's
test for security prose is what a reader would DO with it, not whether it parses as accurate.

The replacement states the contract that IS in force (stdlib-only, so a vault copy would run on the
bare interpreter there), says plainly that no vault copy exists, and blocks the misreading in the
sentence itself rather than leaving it to inference. Widening the vault's automation is named as the
open half instead of being implied to be done.

The claim was inherited from PR #304 rather than invented here; this port made it slightly worse by
adding the MIRRORED_TOOLS citation, so it is fixed here.

The repo's own ratchet caught two drafts of this text. test_cutover_slug_rot's _PROSE pattern matches
the literal "the mirror" with no trailing word boundary, so "the mirrored-tool contract" and then
"never the mirroring" each tripped it -- and because a table row is one physical line, the row carries
no retrospective marker to exempt it. Reworded both times; the ceiling was NOT raised and that file is
untouched. This is the guard the MIRRORED_TOOLS comment in test_asvs_verifier_vault_contract.py
already warned would ratchet on this exact phrasing.

Verified: 201 tests across the nine files that read docs/CI.md or scan repo prose, plus a direct
scan of the file with the ratchet's own two patterns -- zero hits, with a positive control confirming
the detector still fires on a known-bad string.
236a1ea is docs/BACKLOG.md only, disjoint from the port's files. Not treated as test-neutral on that
basis: several tests read the live BACKLOG.md from disk, so a ledger-only base move can still move a
test result.
…w reorder

Neither commit touches the port's eleven files, but e5bd07d edits .github/workflows/ci.yml and this
port ADDS a workflow -- and tests/_workflow_contexts.py, test_required_contexts.py and
test_merge_gate_controls.py all read the workflow inventory. That is the interaction a clean merge
cannot speak to, so it is checked rather than assumed.
@wshallwshall
wshallwshall merged commit 0a0a5c9 into main Aug 12, 2026
37 of 39 checks passed
@wshallwshall
wshallwshall deleted the claude/asvs-304-prove-absences-port branch August 12, 2026 18:30
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