#!/usr/bin/env bash # ───────────────────────────────────────────────────────────────────────────── # inventory_gate.sh — diff an observed environment inventory against the # pinned allowlist. This is THE production coverage gate: check.sh Phase 3b # 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 [] # # 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 # (new/renamed decl, changed kind, or changed axiom cone) # STALE — allowlist entry absent from the environment # plus an output-integrity check: the INV-COUNT trailer emitted by # Proofs/Inventory.lean must equal the number of INV lines actually seen, # so a truncated or crashed run can never pass as an empty diff. # ───────────────────────────────────────────────────────────────────────────── 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"; 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 # 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 "^$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")) if [ -n "$UNCLASS" ]; then printf '%s\n' "$UNCLASS" | sed 's/^/ UNCLASSIFIED (in environment, not allowlisted): /' FAILGATE=1 fi if [ -n "$STALE" ]; then printf '%s\n' "$STALE" | sed 's/^/ STALE (allowlisted, not in environment): /' FAILGATE=1 fi # 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 [ "$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 " $LABEL: $N_OBS constants, environment == allowlist, $AXNOTE" exit "$FAILGATE"