mirror of
https://github.com/saymrwulf/ltl-accumulator-verified.git
synced 2026-09-03 19:53:48 +00:00
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:
parent
9f406557fb
commit
b575e857e3
6 changed files with 562 additions and 6 deletions
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
266
verification/AUDIT-MANIFEST.txt
Normal file
266
verification/AUDIT-MANIFEST.txt
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
147
verification/selftest_statements.sh
Executable file
147
verification/selftest_statements.sh
Executable 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
|
||||
Loading…
Reference in a new issue