diff --git a/verification/HARNESS.sha256 b/verification/HARNESS.sha256 index 74e173e..166154d 100644 --- a/verification/HARNESS.sha256 +++ b/verification/HARNESS.sha256 @@ -1,16 +1,17 @@ e7d422f0be9a9e6f5465058292e30c711d52856428da2b01c470a57ec181540c AUDIT-MANIFEST.txt -29b20810b6d365d8aa0b1affbbd4e3a38f03f302eb839d10a314e94a60b46da7 check.sh +3fa6c199e28a6b6bf191ed5fa5f6e27d18d0cab651b0ed311fd8fadd79b30cc5 check.sh +90fcad217c7b6507abecf37a41d57149f06a8955a72e60dc03616933f981ed6b driver-allowlist.txt 070147e2667053bd5d5e1174b969fc6c91bfcf15ded1a5bff57754e15f416885 fidelity/lean_defs.py 9661bc2d33e907453ab4378da918589a709127b2e117ef1fd578d4e0472edf87 fidelity/pacta_pin.py 5d82462a002ac9fc782e95afe78b7719ba64b6410b5d2bfa620fe5317367dbf2 fidelity/run_fidelity.py 503babb3f4e6aff82ebd59e8752469ecd60fba440ed3b11b3f97c2b655fbd9bf gen/LTLAcc/HashExternal.lean f1eb5cdd158e30df14c59065fe2050448c77b5262b282208fa831d050f6b6a71 inventory-allowlist.txt -f66fb98d2a09503d9bd0d60dc964545eea6dc94b9bbb9246d1021195b79f2601 inventory_gate.sh +d628cc64efe920304f4ad8319d9b880a942abb6d850f4022fa1a26af7e8c34a1 inventory_gate.sh 736ea4be712e1b5bcda10ecb466f0dec7008a2a36eabdfd77563976299c43cce lean-guard ce4c4e3d87434b9663f46de25ce34b48a0cf0d392e0a320a0787b4674a2d7b61 lean-toolchain 6f39303b336aedc4da8381712de8180282e39c0725226c292fce15b47f9beb88 PACTA-PIN.sha256 eda93f520546a692926b2a46bcb79332e1795879e5083327a8bf2404aca5cf87 Proofs/AxiomCheck.lean -de5cdf01724fd4333b0652769bfede3ee150fedfa0a6c4ffbc8e7ab065843928 Proofs/Inventory.lean +82d41c634a2e9adfc2cc74c202d025e57411687796f820765d45608f3fb4cb20 Proofs/Inventory.lean 0b8a0fc6947af1d1e600a756eb2b07dc88d189b21df3220c4501a41be8b33f1e run_bare.sh 473e2463d9c26653c8435ad6758044742f200eb13ea0db4b8f076466c08bd87a selftest_audit.sh 3d5898161d663eccad162269a5a6c102319077e22e1f2d89a8bfcab6926d29f6 selftest-harness.sh diff --git a/verification/Proofs/Inventory.lean b/verification/Proofs/Inventory.lean index 13dc9ff..82cbfdc 100644 --- a/verification/Proofs/Inventory.lean +++ b/verification/Proofs/Inventory.lean @@ -215,7 +215,25 @@ def ppAll (e : Expr) : MetaM String := do the audit infrastructure. An instrument may declare definitions \ and whatever the elaborator generates for them — never a claim \ of its own, which no certificate covers and no allowlist pins." - drv := drv.push s!"DRV|{n}|{k}" + -- THE CONE. Round-8 review (Claude, `drv-surface-no-cones`, + -- `accounting-certifies-enumeration`): these rows carried name and kind + -- only, so when the accounting identity proved every kernel constant was + -- ENUMERATED, a claim planted in an instrument satisfied it and was then + -- examined by nothing. The reviewer's payload was exactly + -- DRV|LTLAccAudit.bait.smuggled|theorem + -- with cone [propext, Classical.choice, Quot.sound] — admitted, counted, + -- and covered by no allowlist row, no statement digest and no cone check. + -- Their verdict: the identity "converted 36 declarations nobody enumerated + -- into 36 declarations nobody examined." + -- + -- With the cone here and the rows pinned in driver-allowlist.txt by the + -- same gate the corpus uses, the identity and the audit coincide. The + -- name-prefix rule above stays as a readable first check but is no longer + -- load-bearing: the reviewer showed it breaks in one line, and membership + -- in a committed allowlist does not. + let drvCone := axiomCone env n + let drvConeStr := ",".intercalate (drvCone.toList.map (·.toString)) + drv := drv.push s!"DRV|{env.mainModule}|{n}|{k}|{drvConeStr}" let drvSorted := drv.qsort (· < ·) for l in drvSorted do IO.println l diff --git a/verification/check.sh b/verification/check.sh index 2257653..c2fd5e2 100755 --- a/verification/check.sh +++ b/verification/check.sh @@ -165,6 +165,10 @@ HARNESS_EXTRA=( lean-toolchain # which Lean the corpus claims to have been checked by fidelity/lean_defs.py # the Python transcription the differential compares fidelity/run_fidelity.py # the differential itself + driver-allowlist.txt # the INSTRUMENTS' own declaration surface, with cones. + # Not executable, so it would otherwise sit outside the + # harness set — and an allowlist an attacker may rewrite + # pins nothing, the same shape as a forgeable .audit-basis. PACTA-PIN.sha256 # WHICH pacta the differential is entitled to compare # against. Pinned here because it is not executable # and would otherwise sit outside the harness set — @@ -408,7 +412,7 @@ KERN_NAMES=$(mktemp /tmp/acc-kernnames-XXXX.txt) ACCT_NAMES=$(mktemp /tmp/acc-acctnames-XXXX.txt) LC_ALL=C grep '^KERNEL-NAME|' "$KERNLOG" | cut -d'|' -f2 | LC_ALL=C sort -u > "$KERN_NAMES" { LC_ALL=C awk -F'|' '/^INV\|/{print $2}' "$INVLOG" - LC_ALL=C grep '^DRV|' "$INVLOG" | cut -d'|' -f2 + LC_ALL=C grep '^DRV|' "$INVLOG" | cut -d'|' -f3 } | LC_ALL=C sort -u > "$ACCT_NAMES" UNACCOUNTED=$(LC_ALL=C comm -23 "$KERN_NAMES" "$ACCT_NAMES") if [ ! -s "$KERN_NAMES" ]; then @@ -467,13 +471,32 @@ elif [ "$NDRV" -eq 0 ]; then else echo " driver surface: $NDRV declarations across the audit instruments, no axiom, no claim" fi -# NOTE ON WHAT THIS DOES NOT DO. It does not pin WHICH definitions the -# instruments declare — a new inert `def` in a driver is allowed. That is -# deliberate: both drivers are byte-pinned in HARNESS.sha256 (Phase 0c), so -# their contents cannot drift unnoticed, and a second policy file listing their -# internals would add a thing to maintain without adding a thing to catch. What -# the check above adds is the property byte-pinning cannot give: that no -# instrument declares an AXIOM or a CLAIM, whatever its bytes are. +# AND THE ROWS ARE PINNED, WITH THEIR CONES, BY THE SAME GATE THE CORPUS USES. +# +# This block used to end with a note explaining why the instruments' internals +# were deliberately NOT pinned: they are byte-pinned in HARNESS.sha256, so "a +# second policy file listing their internals would add a thing to maintain +# without adding a thing to catch." +# +# Round-8 review (Claude, register keys `drv-surface-no-cones`, +# `accounting-certifies-enumeration`) showed that reasoning was wrong, by +# demonstration. Their payload +# DRV|LTLAccAudit.bait.smuggled|theorem +# is a genuine claim with cone [propext, Classical.choice, Quot.sound]. It +# passed the name-prefix rule above — `bait` is declared alongside it, so it +# looks like an elaborator artefact — it was enumerated by the accounting +# identity, and then NOTHING examined it: the rows carried no cone, no +# allowlist covered them, and the statement digest does not reach instruments. +# Byte-pinning shows that a driver CHANGED. It does not show what appeared in +# it or what that thing rests on, and "the file changed" is not a finding a +# reader can act on. +# +# So the rows now carry their cone and are diffed against a committed +# allowlist, in both directions, by inventory_gate.sh with the DRV tag — the +# same implementation, so the two surfaces cannot drift apart in their gating. +# The axiom policy differs by surface and is enforced per surface: the corpus +# admits exactly the sanctioned hash boundary, the instruments admit none. +"$HERE/inventory_gate.sh" "$INVLOG" "$HERE/driver-allowlist.txt" DRV || COVFAIL=1 # CONES ⊆ allowlist with IDENTICAL cones: the #print-axioms-pinned table # and the environment inventory are two independent computations of the diff --git a/verification/driver-allowlist.txt b/verification/driver-allowlist.txt new file mode 100644 index 0000000..dbe06f4 --- /dev/null +++ b/verification/driver-allowlist.txt @@ -0,0 +1,32 @@ +# driver-allowlist.txt — THE AUDIT INSTRUMENTS' OWN DECLARATION SURFACE, +# pinned with axiom cones, gated by inventory_gate.sh with the DRV tag: +# the same implementation that pins the corpus, in both directions. +# +# Round-8 review (Claude, register keys drv-surface-no-cones and +# accounting-certifies-enumeration). The accounting identity proved every +# kernel constant was ENUMERATED. It did not prove any of them was +# EXAMINED: a claim planted in an instrument was enumerated, carried a +# real cone, and nothing looked at it. These rows close that gap. +# +# Regenerate deliberately, never by hand: +# scratchpad/gen-driver-allowlists.sh +# and read the diff — a new row is a declaration that appeared in an +# instrument, which is exactly the event this file exists to surface. +DRV|Proofs.Inventory|LTLAccAudit.axiomCone._proof_11|theorem|Classical.choice,Quot.sound,propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone._proof_1|theorem|Classical.choice,Quot.sound,propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone._sparseCasesOn_1|def|propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone._sparseCasesOn_4|def|propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone.match_1|def|propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone.match_4|def|propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone.match_7|def|propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone.match_9|def|Classical.choice,Quot.sound,propext +DRV|Proofs.Inventory|LTLAccAudit.axiomCone|def|Classical.choice,Quot.sound,propext +DRV|Proofs.Inventory|LTLAccAudit.corpusModules|def| +DRV|Proofs.Inventory|LTLAccAudit.driverModules|def| +DRV|Proofs.Inventory|LTLAccAudit.kindOf.match_1|def| +DRV|Proofs.Inventory|LTLAccAudit.kindOf|def| +DRV|Proofs.Inventory|LTLAccAudit.normWs|def|Classical.choice,Quot.sound,propext +DRV|Proofs.Inventory|LTLAccAudit.ppAll|def|Classical.choice,Quot.sound,propext +DRV|Proofs.Inventory|LTLAccAudit.valueOf._sparseCasesOn_1|def|propext +DRV|Proofs.Inventory|LTLAccAudit.valueOf.match_1|def|propext +DRV|Proofs.Inventory|LTLAccAudit.valueOf|def|propext diff --git a/verification/inventory_gate.sh b/verification/inventory_gate.sh index 01a7e82..a8b21a2 100755 --- a/verification/inventory_gate.sh +++ b/verification/inventory_gate.sh @@ -5,7 +5,35 @@ # calls it, and selftest_audit.sh exercises this exact script against # injected evader declarations — the tested logic IS the shipping logic. # -# Usage: inventory_gate.sh +# Usage: inventory_gate.sh [] +# +# defaults to INV — the CORPUS walk. Pass DRV to gate the INSTRUMENTS' +# OWN SURFACE with this same implementation. +# +# WHY THE TAG EXISTS — round-8 review (Claude, register keys +# `drv-surface-no-cones`, `accounting-certifies-enumeration`). +# +# The accounting identity added in round 7 proved every constant the kernel +# sees is ENUMERATED by one of the two walks. The reviewer showed that +# enumeration is not audit: a claim planted in an instrument WAS enumerated — +# `DRV|LTLAccAudit.bait.smuggled|theorem` — and then nothing looked at it, +# because DRV rows carried name and kind and NO CONE, and no allowlist covered +# them. In their words, the identity "converted 36 declarations nobody +# enumerated into 36 declarations nobody examined. That is progress of one +# step, not two." +# +# The second step is here: DRV rows now carry their axiom cone and are pinned +# in a committed allowlist, by THIS gate, in both directions — exactly as the +# corpus is. One implementation, not two, because a second copy of a coverage +# gate is a second thing to drift. +# +# It also retires a heuristic. The driver-surface rule permits a theorem whose +# name extends a constant declared alongside it, since that is what the +# elaborator generates for a definition; the reviewer showed it "breaks in one +# line" — declare `def bait`, then `theorem bait.smuggled` passes. That rule is +# kept as a fast, readable first line of defence, but it is NO LONGER +# LOAD-BEARING: a planted claim now has to appear in the pinned allowlist, and +# a new row fails closed whatever it is named. # # Fail-closed in BOTH directions: # UNCLASSIFIED — constant in the environment, absent from the allowlist @@ -17,17 +45,34 @@ # ───────────────────────────────────────────────────────────────────────────── set -uo pipefail export LC_ALL=C # byte-order collation: sort/comm must agree with Lean's String order -obs_file="$1"; allow_file="$2" +obs_file="$1"; allow_file="$2"; TAG="${3:-INV}" +case "$TAG" in + INV) WHAT="the audited corpus"; TRAILER_TAG="INV-COUNT"; LABEL="inventory gate"; TRUNCLABEL="INVENTORY TRUNCATED" ;; + DRV) WHAT="the audit instruments"; TRAILER_TAG="DRV-COUNT"; LABEL="driver-surface gate"; TRUNCLABEL="DRIVER SURFACE TRUNCATED" ;; + *) echo " GATE MISUSE: unknown tag '$TAG' (expected INV or DRV)"; exit 1 ;; +esac -OBS=$(grep '^INV|' "$obs_file" | sort -u) -N_OBS=$(printf '%s' "$OBS" | grep -c '^INV|' || true) -TRAILER=$(grep '^INV-COUNT|' "$obs_file" | tail -1 | cut -d'|' -f2) -if [ -z "$TRAILER" ] || [ "$TRAILER" != "$N_OBS" ]; then +# The trailer is an OUTPUT-INTEGRITY check: it must equal the number of rows +# the driver(s) actually emitted, BEFORE de-duplication. Comparing it to the +# de-duplicated count conflates "a run was truncated" with "two rows were +# identical", and the second is a record-format defect that must be fixed at +# the source, not absorbed here. (It was: DRV rows now carry their driver.) +N_RAW=$(grep -c "^$TAG|" "$obs_file" || true) +OBS=$(grep "^$TAG|" "$obs_file" | sort -u) +N_OBS=$(printf '%s' "$OBS" | grep -c "^$TAG|" || true) +if [ "$N_RAW" -ne "$N_OBS" ]; then + echo " DUPLICATE $TAG RECORDS: $N_RAW rows collapse to $N_OBS distinct ones." + echo " Two declarations share a record, so one is covered by the other's entry:" + grep "^$TAG|" "$obs_file" | sort | uniq -d | head -5 | sed 's/^/ /' + exit 1 +fi +TRAILER=$(grep "^$TRAILER_TAG|" "$obs_file" | tail -1 | cut -d'|' -f2) +if [ -z "$TRAILER" ] || [ "$TRAILER" != "$N_RAW" ]; then echo " INVENTORY TRUNCATED: trailer=${TRAILER:-absent}, observed $N_OBS lines" exit 1 fi -ALLOW=$(grep '^INV|' "$allow_file" | sort -u) +ALLOW=$(grep "^$TAG|" "$allow_file" | sort -u) FAILGATE=0 UNCLASS=$(comm -23 <(printf '%s\n' "$OBS") <(printf '%s\n' "$ALLOW")) STALE=$(comm -13 <(printf '%s\n' "$OBS") <(printf '%s\n' "$ALLOW")) @@ -40,13 +85,29 @@ if [ -n "$STALE" ]; then FAILGATE=1 fi -# The corpus admits exactly one axiom, and it is the sanctioned boundary. +# THE AXIOM POLICY IS PER-SURFACE, and conflating the two would be a silent +# inversion. The CORPUS admits exactly one axiom — the sanctioned hash boundary. +# The INSTRUMENTS admit NONE: an audit tool that declares an axiom widens the +# trusted base from inside the thing doing the auditing. Reusing the corpus rule +# for DRV would have demanded LTLAcc.sha256 appear among instrument +# declarations, where it never does, and the gate would have failed for a reason +# that has nothing to do with the property. AXLINES=$(printf '%s\n' "$OBS" | grep '|axiom|' || true) -if [ "$AXLINES" != "INV|LTLAcc.sha256|axiom|LTLAcc.sha256" ]; then - echo " AXIOM SURFACE DRIFT: expected exactly LTLAcc.sha256, observed:" - printf '%s\n' "${AXLINES:- (none)}" | sed 's/^/ /' - FAILGATE=1 +if [ "$TAG" = DRV ]; then + if [ -n "$AXLINES" ]; then + echo " AXIOM SURFACE DRIFT: $WHAT must declare no axioms; observed:" + printf '%s\n' "$AXLINES" | sed 's/^/ /' + FAILGATE=1 + fi + AXNOTE="zero axioms declared by the instruments" +else + if [ "$AXLINES" != "INV|LTLAcc.sha256|axiom|LTLAcc.sha256" ]; then + echo " AXIOM SURFACE DRIFT: expected exactly LTLAcc.sha256, observed:" + printf '%s\n' "${AXLINES:- (none)}" | sed 's/^/ /' + FAILGATE=1 + fi + AXNOTE="single sanctioned axiom" fi -[ "$FAILGATE" = 0 ] && echo " inventory gate: $N_OBS constants, environment == allowlist, single sanctioned axiom" +[ "$FAILGATE" = 0 ] && echo " $LABEL: $N_OBS constants, environment == allowlist, $AXNOTE" exit "$FAILGATE"