verification: bind statements and specification bodies (P1-a); un-stale the fidelity pin

STATEMENT BINDING (Phase 3d). The coverage gate pins every constant's name,
kind and axiom cone, both directions, and none of selftest_audit.sh's nine
attacks defeat it. It is nevertheless blind to what a declaration SAYS — and
that is demonstrated here rather than argued:

  Wrapping one branch of `LTLAcc.pinAccept`'s body in `id (…)` is
  definitionally equal. Every downstream proof still compiles. The name, the
  kind, the type and the axiom cone are unchanged. The inventory gate reports
  "222 constants, environment == allowlist" — GREEN.

That edit is harmless by construction; the point is that nothing stood between
it and a genuinely vacuous redefinition of a specification. Proofs/Inventory.lean
now also emits, for every inventoried constant, its fully-elaborated TYPE, and
for every definition its fully-elaborated BODY — 266 lines over 222 constants.
Proof terms are deliberately absent: by proof irrelevance a theorem's content
is its statement. check.sh Phase 3d binds the SHA-256 and the block is
committed as AUDIT-MANIFEST.txt so a mismatch is DIFFED, not merely reported.

The existing gate is untouched, per the standing rule that the port flows FROM
this repo, not to it: INV lines are byte-identical, inventory_gate.sh is
unchanged, and all nine of its attacks still fail as before.

selftest_statements.sh replays the defeq edit as case 1, asserting BOTH that
the coverage gate passes it and that Phase 3d catches it — so if the coverage
gate ever grows to see this, the test says so instead of quietly re-labelling.
Cases 2-4 cover a hand-edited committed block, a truncated block, and a
constant inventoried without a statement.

FIDELITY PIN (unrelated, found while running the button). Phase 4 had been
failing since 2026-07-23: LIED_PIN_DIV expected 3,867 divergences between the
Lean model and the deployed consistency verifier, and observed 0. Cause is
pacta ddbb5a4, which restored the RFC 9162 2.1.4.2 Step-7 terminal `sn == 0`
condition; that one conjunct removes every divergence in the pinned
73,573-case family. KNOWN-GAPS gap 14 already recorded the closure on the day
it landed — only this constant was stale, so the button had been red for five
days with nobody running it. The pin now reads 0 with the history in a comment.
Nothing about the paper, public log entry 13, or the attested commit 172a1d0
changes; the historical divergence stays reproducible at the tagged pre-fix
commit.

KNOWN-GAPS gap 16 records what the binding does not buy: identity, not
meaning; an author who edits and re-pins in one commit is caught by review and
not by the script; and proof terms are unbound by design.

Button green end to end: ATTESTATION GREEN (Lean + fidelity).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
mrwulf 2026-07-29 09:07:13 +02:00
parent 9f406557fb
commit b575e857e3
6 changed files with 562 additions and 6 deletions

View file

@ -159,3 +159,24 @@ list is COMPLETE, not merely that the items are acceptable.
ConsRec-equivalent acceptance; or (B) mechanize the signed-head +
pin-store state machine and prove the refinement; or (C) keep the
boundary and this scoped claim permanently.
16. **The statement binding covers identity, not meaning** (added
2026-07-28 with `check.sh` Phase 3d). The coverage gate pins every
constant's name, kind and axiom cone, both directions; Phase 3d
additionally pins every constant's fully-elaborated TYPE and every
definition's fully-elaborated BODY, digest committed as
`AUDIT-MANIFEST.txt`. What that buys is that the corpus is the one
that was reviewed. What it does NOT buy: (a) whether those
statements say anything worth believing is a question a human
reading `STATEMENT-MAP.md` answers, not the button; (b) an author
who edits a definition and refreshes the digest in the same commit
passes every phase — the defence is that both changes are visible in
the diff at the pinned commit; (c) proof TERMS are deliberately not
bound, by proof irrelevance, so a proof rewritten to a different term
of the same statement and cone moves nothing.
Worth recording plainly, because it was measured rather than
reasoned: wrapping one branch of `pinAccept`'s body in `id (…)` is
definitionally equal, compiles, leaves name/kind/type/cone untouched,
and the coverage gate reports GREEN. Only the digest sees it. That is
the exact size of the gap Phase 3d closes, and
`selftest_statements.sh` case 1 replays it.

File diff suppressed because one or more lines are too long

View file

@ -95,7 +95,23 @@ def axiomCone (env : Environment) (root : Name) : Array Name := Id.run do
stack := stack ++ v.getUsedConstants
return (axioms.qsort (fun a b => a.toString < b.toString))
#eval show CoreM Unit from do
/-- Whitespace-canonical: every whitespace run collapses to one space, so the
pretty-printer's line wrapping cannot perturb the digest. -/
def normWs (s : String) : String :=
(s.foldl (fun (acc : String × Bool) c =>
let c := if c.isWhitespace then ' ' else c
if c == ' ' then (if acc.2 then acc else (acc.1.push ' ', true))
else (acc.1.push c, false))
("", true)).1
/-- Fully-explicit (`pp.all`) rendering, whitespace-canonicalized. Implicit
arguments, instances and universe levels are all made visible, so two
statements that merely LOOK alike cannot share a rendering. -/
def ppAll (e : Expr) : MetaM String := do
let fmt ← withOptions (fun o => o.setBool `pp.all true) (Meta.ppExpr e)
return normWs fmt.pretty
#eval show MetaM Unit from do
let env ← getEnv
-- Resolve every corpus module to its index; a miss is a hard error.
let mut idxs : Array Nat := #[]
@ -104,6 +120,17 @@ def axiomCone (env : Environment) (root : Name) : Array Name := Id.run do
| some i => idxs := idxs.push i
| none => throwError "INVENTORY ERROR: corpus module {m} is not imported"
let mut lines : Array String := #[]
-- STATEMENT SURFACE (P1-a). The INV lines above record what each constant
-- IS and what it RESTS ON. They do not record what it SAYS: a theorem gutted
-- to a tautology keeps its name, its kind and its axiom cone, and a `def`
-- redefined to BE the thing it was meant to specify keeps all three too,
-- while the certificate stated against it silently becomes vacuous. So every
-- constant additionally contributes its fully-elaborated TYPE, and every
-- definition its fully-elaborated BODY. Proof terms are NOT emitted: by
-- proof irrelevance a theorem's content is its statement, and its term is
-- both enormous and irrelevant to what is being claimed.
let mut stmts : Array String := #[]
let mut nTypes := 0
for (n, ci) in env.constants.toList do
if let some i := env.getModuleIdxFor? n then
if idxs.contains i then
@ -115,9 +142,25 @@ def axiomCone (env : Environment) (root : Name) : Array Name := Id.run do
throwError "INVENTORY ERROR: cone divergence on {n}: walker={cone} core={coreCone}"
let coneStr := ",".intercalate (cone.toList.map (·.toString))
lines := lines.push s!"INV|{n}|{kindOf ci}|{coneStr}"
stmts := stmts.push s!"STMT|{n}|{kindOf ci}|type={← ppAll ci.type}"
nTypes := nTypes + 1
match ci with
| .defnInfo v => stmts := stmts.push s!"STMT|{n}|{kindOf ci}|value={← ppAll v.value}"
| _ => pure ()
let sorted := lines.qsort (· < ·)
for l in sorted do
IO.println l
IO.println s!"INV-COUNT|{sorted.size}"
-- FAIL CLOSED: the statement surface must cover the inventory exactly. If
-- these ever diverge, some constant is inventoried but unbound — which is
-- precisely the gap this section exists to close.
let sortedStmts := stmts.qsort (· < ·)
unless nTypes == sorted.size do
throwError "INVENTORY ERROR: {sorted.size} constants inventoried but {nTypes} carry a statement"
IO.println "STMT-BEGIN"
for l in sortedStmts do
IO.println l
IO.println "STMT-END"
IO.println s!"STMT-COUNT|{sortedStmts.size}"
end LTLAccAudit

View file

@ -6,7 +6,9 @@
# both directions.
#
# Phases: 0 resource/integrity · 1 stub+axiom-smuggling audit ·
# 2 compile manifest · 3 boundary-exact axiom audit
# 2 compile manifest · 3 boundary-exact axiom audit ·
# 3b environment-derived coverage · 3c doc-consistency ·
# 3d statement + specification binding · 4 definition fidelity
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# Toolchain bootstrap is overridable for reviewers with their own install
@ -239,7 +241,8 @@ for cert in "${!CONES[@]}"; do
"$HERE/inventory-allowlist.txt" || {
echo " PINNED BUT NOT INVENTORIED: $cert (in CONES, not in allowlist)"; COVFAIL=1; }
done
rm -f "$INVLOG"
# (INVLOG is NOT removed here: Phase 3d binds the statement block emitted by
# this same run. Removed at the end of 3d.)
# every pinned cert must actually be queried by AxiomCheck (no pin-but-never-check)
for cert in "${!CONES[@]}"; do
@ -276,6 +279,69 @@ print(' '.join(f'{int(v.replace(chr(95),\"\")):,}' for v in vals))"); do
done
[ "$DOCFAIL" = 0 ] && echo " docs agree with allowlist ($NALLOW), CONES ($NCONES), fidelity pins"
[ "$DOCFAIL" = 0 ] || { echo "DOC-CONSISTENCY FAILED"; exit 1; }
# -- Phase 3d: statement + specification binding (P1-a) ---------------------
# WHAT PHASES 3/3b DO NOT ESTABLISH. Phase 3 pins each certificate's exact
# axiom cone; Phase 3b pins the full environment surface, kind and cone, both
# directions. Neither records what a declaration SAYS. A theorem gutted to a
# tautology keeps its name, its kind and its cone. A `def` redefined to BE the
# thing it was meant to specify keeps all three, and every certificate stated
# against it silently becomes vacuous — with the allowlist unmoved.
#
# Proofs/Inventory.lean therefore also emits, for every inventoried constant,
# its fully-elaborated TYPE, and for every definition its fully-elaborated
# BODY. Proof terms are deliberately absent: by proof irrelevance a theorem's
# content is its statement. This phase binds the SHA-256 of that block, and
# the block itself is committed as AUDIT-MANIFEST.txt so a mismatch is DIFFED
# rather than merely reported.
#
# To rotate deliberately: run check.sh, take the printed OBSERVED digest, and
# update the constant below AND AUDIT-MANIFEST.txt in the same reviewable
# commit. Visibility in review is the defence; no harness audits its author.
EXPECTED_STMT_SHA256="e7d422f0be9a9e6f5465058292e30c711d52856428da2b01c470a57ec181540c"
echo "=== Phase 3d: statement + specification binding ==="
STMTFAIL=0
STMT_BLOCK=$(awk '/^STMT-BEGIN/{f=1;next} /^STMT-END/{f=0} f' "$INVLOG")
# FAIL CLOSED ON ABSENCE: no block and a matching block must not share a path.
if [ -z "$STMT_BLOCK" ]; then
echo " NO STATEMENT BLOCK emitted by Proofs/Inventory.lean (fail-closed)"; STMTFAIL=1
else
# Output integrity, same discipline as the INV-COUNT trailer: a truncated or
# crashed run must not pass as a short-but-matching block.
N_STMT=$(printf '%s\n' "$STMT_BLOCK" | grep -c '^STMT|')
STMT_TRAILER=$(grep '^STMT-COUNT|' "$INVLOG" | tail -1 | cut -d'|' -f2)
if [ -z "$STMT_TRAILER" ] || [ "$STMT_TRAILER" != "$N_STMT" ]; then
echo " STATEMENT BLOCK TRUNCATED: trailer=${STMT_TRAILER:-absent}, observed $N_STMT"; STMTFAIL=1
fi
# Every inventoried constant must carry a statement line. Inventory.lean
# asserts this internally too; asserting it here as well means a tampered
# Inventory.lean cannot simply drop its own check.
N_INV=$(grep -c '^INV|' "$INVLOG")
N_TYPES=$(printf '%s\n' "$STMT_BLOCK" | grep -c '|type=')
if [ "$N_TYPES" != "$N_INV" ]; then
echo " STATEMENT COVERAGE GAP: $N_INV constants inventoried, $N_TYPES carry a statement"; STMTFAIL=1
fi
GOT_STMT_SHA=$(printf '%s\n' "$STMT_BLOCK" | sha256sum | cut -d' ' -f1)
if [ "$GOT_STMT_SHA" != "$EXPECTED_STMT_SHA256" ]; then
printf '%s\n' "$STMT_BLOCK" > "$HERE/.stmt-manifest.observed"
echo " STATEMENT DIGEST MISMATCH."
echo " expected: $EXPECTED_STMT_SHA256"
echo " observed: $GOT_STMT_SHA"
echo " A statement or a definition body changed. First differences:"
diff -u "$HERE/AUDIT-MANIFEST.txt" "$HERE/.stmt-manifest.observed" 2>/dev/null \
| head -30 | sed 's/^/ /' || echo " (AUDIT-MANIFEST.txt absent — cannot diff)"
rm -f "$HERE/.stmt-manifest.observed"
STMTFAIL=1
elif ! printf '%s\n' "$STMT_BLOCK" | cmp -s - "$HERE/AUDIT-MANIFEST.txt"; then
# The digest's INPUT must be committed and current, or the diff above would
# compare against a stale reference and quietly mislead the next reader.
echo " COMMITTED BLOCK STALE: AUDIT-MANIFEST.txt does not match the emitted block"
echo " (the digest matched, so the committed copy needs refreshing)"; STMTFAIL=1
fi
fi
[ "$STMTFAIL" = 0 ] && echo " statements bound: $N_STMT lines over $N_INV constants, sha256 = $GOT_STMT_SHA"
[ "$STMTFAIL" = 0 ] || { echo "STATEMENT BINDING FAILED"; rm -f "$INVLOG"; exit 1; }
rm -f "$INVLOG"
# -- Phase 4: definition fidelity (Lean defs vs deployed pacta verifiers) --
echo "=== Phase 4: definition fidelity ==="
PACTA_SRC="${PACTA_SRC:-$HERE/../../proof-aware-crypto-tooling-agent/src}"

View file

@ -39,10 +39,23 @@ import lean_defs as L # noqa: E402
NMAX = int(os.environ.get("FIDELITY_NMAX", "256"))
# lied-size family pins (gap 14; valid for the default FIDELITY_LIED_NMAX=60):
# 73,573 boundary cases, 3,867 expected one-sided divergences
# (3,405 lied-old-size + 462 lied-new-size), smallest witness (n=3, m=2 claimed 1)
# 73,573 boundary cases.
#
# The divergence count was 3,867 (3,405 lied-old-size + 462 lied-new-size,
# smallest witness n=3, m=2 claimed 1) until pacta `ddbb5a4` (2026-07-23)
# restored the RFC 9162 2.1.4.2 Step-7 terminal condition `sn == 0` to
# verify_consistency. That one conjunct removes EVERY divergence in this
# family, so the pin is now 0: the deployed verifier and the mechanized
# ConsRec model agree on all 73,573 boundary cases.
#
# The pin was left at 3,867 when the fix landed, which made this assertion —
# and therefore check.sh Phase 4 — fail from 2026-07-23 until it was noticed
# on 2026-07-28. KNOWN-GAPS.md gap 14 had already recorded the closure; only
# this constant was stale. Nothing about the paper, public log entry 13, or
# the attested commit 172a1d0 changes: the historical divergence remains
# truthfully recorded and reproducible at `vulnerable/sn0-consistency-fd2f6ba`.
LIED_PIN_TOTAL = 73_573
LIED_PIN_DIV = 3_867
LIED_PIN_DIV = 0
def _h(b):

View file

@ -0,0 +1,147 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# selftest_statements.sh — adversarial self-test of check.sh Phase 3d.
#
# WHY A SECOND SELF-TEST. selftest_audit.sh attacks the coverage gate, which
# pins every constant's NAME, KIND and AXIOM CONE, both directions. That gate
# is strong and none of its nine attacks defeat it. It is also blind to what a
# declaration SAYS, and this script demonstrates that with a real, compiling
# edit rather than an argument:
#
# `LTLAcc.pinAccept` is a specification definition. Wrapping one branch of
# its body in `id (…)` is definitionally equal, so every downstream proof
# still compiles; the name, the kind, the type and the axiom cone are all
# unchanged. The inventory gate reports 222 constants, environment ==
# allowlist, GREEN. Only the statement digest sees it.
#
# That edit is deliberately harmless. The point is that the ONLY thing
# standing between it and a genuinely vacuous redefinition is the digest.
#
# Cases:
# 0 positive control: pristine tree passes Phase 3d
# 1 defeq body edit: old gate PASSES (asserted), Phase 3d FAILS (asserted)
# 2 committed AUDIT-MANIFEST.txt hand-edited → COMMITTED BLOCK STALE
# 3 statement block truncated → BLOCK TRUNCATED
# 4 a constant inventoried but carrying no statement → COVERAGE GAP
#
# Phase 3d is lifted out of check.sh at run time, so the tested logic IS the
# shipping logic. Run AFTER a green check.sh. All Lean work via lean-guard.
# ─────────────────────────────────────────────────────────────────────────────
set -uo pipefail
AENEAS_ENV="${AENEAS_ENV:-$HOME/aeneas-toolchain/env.sh}"
[ -f "$AENEAS_ENV" ] || { echo "FATAL: Aeneas environment not found: $AENEAS_ENV"; exit 1; }
source "$AENEAS_ENV"
SRC="$(cd "$(dirname "$0")" && pwd)"
AENEAS_LEAN="$AENEAS_HOME/backends/lean"
CORES="${LEAN_MAX_CORES:-0-3}"
FAILURES=0
WORK=$(mktemp -d /tmp/acc-stmt-selftest-XXXX)
trap 'rm -rf "$WORK"' EXIT
echo "=== statement-binding self-test (scratch: $WORK) ==="
cp -a "$SRC" "$WORK/verification"
T="$WORK/verification"
cp "$T/Proofs/PinStore.lean" "$T/PinStore.pristine"
cp "$T/AUDIT-MANIFEST.txt" "$T/MANIFEST.pristine"
# Phase 3d, lifted verbatim from the shipping button. HERE and INVLOG are the
# two variables it reads from its surroundings.
DRIVER="$T/phase3d.sh"
{
echo 'set -uo pipefail'
echo "HERE=\"$T\""
echo 'INVLOG="$1"'
sed -n '/^# -- Phase 3d/,/^# -- Phase 4/p' "$SRC/check.sh" | sed '$d'
} > "$DRIVER"
if [ "$(wc -l < "$DRIVER")" -lt 40 ]; then
echo "FATAL: could not lift Phase 3d out of check.sh — the phase markers moved."
echo "This self-test must attack the shipping gate; refusing to run against nothing."
exit 1
fi
# Recompile the edited leaf module + the inventory into $T/inv.out.
build_inventory() {
cd "$AENEAS_LEAN"
lake env bash -c "
set -euo pipefail
cd '$T' && export LEAN_PATH=\"\$LEAN_PATH:$T/gen:$T\"
LEAN_TIMEOUT=600 LEAN_MAX_CORES=$CORES '$T/lean-guard' Proofs/PinStore.lean >/dev/null 2>&1
LEAN_TIMEOUT=600 LEAN_MAX_CORES=$CORES '$T/lean-guard' Proofs/Inventory.lean
" > "$T/inv.out" 2>&1
local rc=$?
cd "$T"
return $rc
}
expect() { # expect <label> <invlog> <want-rc> <want-substring>
local label="$1" invlog="$2" want_rc="$3" want_txt="$4" out rc
# Phase 3d removes its INVLOG on the way out — that is correct behaviour for
# the button and fatal for a fixture, so it always gets a disposable copy.
cp "$invlog" "$T/inv.feed"
out=$(bash "$DRIVER" "$T/inv.feed" 2>&1); rc=$?
if [ "$rc" -ne "$want_rc" ]; then
echo "$label: exit $rc, expected $want_rc"; echo "$out" | sed 's/^/ /'
FAILURES=$((FAILURES+1)); return
fi
if ! grep -qF "$want_txt" <<<"$out"; then
echo "$label: exit code right, diagnostic wrong (rejected for the wrong reason)"
echo " wanted: $want_txt"; echo "$out" | sed 's/^/ /'
FAILURES=$((FAILURES+1)); return
fi
echo "$label"
}
# ── 0. positive control ────────────────────────────────────────────────────
build_inventory || { echo " ✗ case 0: pristine tree did not compile"; exit 1; }
cp "$T/inv.out" "$T/inv.pristine"
expect "case 0 control: pristine tree passes Phase 3d" "$T/inv.pristine" 0 "statements bound"
# ── 1. THE ONE THAT MATTERS: a compiling, definitionally-equal body edit ───
python3 - "$T/Proofs/PinStore.lean" <<'PY'
import sys
f = sys.argv[1]; s = open(f).read()
old = " else ConsRec n n' C true r = some (r, r')"
assert s.count(old) == 1, "pinAccept body not found — this case is vacuous"
open(f, "w").write(s.replace(old, " else id (ConsRec n n' C true r = some (r, r'))", 1))
PY
if build_inventory; then
# The premise: the coverage gate must NOT see this. If it ever does, this
# case stops testing what it claims and must be re-examined, not re-labelled.
if "$T/inventory_gate.sh" "$T/inv.out" "$T/inventory-allowlist.txt" >/dev/null 2>&1; then
echo " ✓ case 1 premise: the coverage gate passes the edit (kind and cone unmoved)"
else
echo " ✗ case 1 premise: the coverage gate caught it — this case no longer isolates Phase 3d"
FAILURES=$((FAILURES+1))
fi
expect "case 1: defeq body edit caught by the statement digest" "$T/inv.out" 1 "STATEMENT DIGEST MISMATCH"
else
echo " ✗ case 1: the edited corpus did not compile (case is vacuous)"
FAILURES=$((FAILURES+1))
fi
cp "$T/PinStore.pristine" "$T/Proofs/PinStore.lean"
# ── 2. committed block hand-edited ─────────────────────────────────────────
sed -i '1s/$/ TAMPERED/' "$T/AUDIT-MANIFEST.txt"
expect "case 2: hand-edited committed block" "$T/inv.pristine" 1 "COMMITTED BLOCK STALE"
cp "$T/MANIFEST.pristine" "$T/AUDIT-MANIFEST.txt"
# ── 3. truncated block: "nothing found" must not pass for "nothing wrong" ──
grep -v '^STMT|LTLAcc.pinAccept|def|value=' "$T/inv.pristine" > "$T/inv.truncated"
expect "case 3: truncated statement block" "$T/inv.truncated" 1 "BLOCK TRUNCATED"
# ── 4. a constant inventoried but carrying no statement ────────────────────
# Drop one type line AND fix the trailer, so only the coverage comparison
# against the INV count can still object.
grep -v '^STMT|LTLAcc.pinAccept|def|type=' "$T/inv.pristine" > "$T/inv.gap"
NEW_N=$(awk '/^STMT-BEGIN/{f=1;next}/^STMT-END/{f=0}f' "$T/inv.gap" | grep -c '^STMT|')
sed -i "s/^STMT-COUNT|.*/STMT-COUNT|$NEW_N/" "$T/inv.gap"
expect "case 4: inventoried constant with no statement" "$T/inv.gap" 1 "STATEMENT COVERAGE GAP"
echo ""
if [ "$FAILURES" -eq 0 ]; then
echo "=== SELF-TEST GREEN: 4 attack cases defeated + positive control ==="
echo " Phase 3d catches what the coverage gate provably cannot see."
exit 0
fi
echo "=== SELF-TEST FAILED: $FAILURES case(s) did not behave as claimed ==="
exit 1