#!/usr/bin/env bash # check-book.sh — THE button for verifying-crypto-with-lean. # # This script is the only source of the words "ALL GREEN" for this # repository. It rebuilds the book from the committed sources and then # verifies that every countable claim printed in the book matches reality # measured at run time: chapter counts, the week plan, exercise/solution # pairing, the recomputed SLH-DSA arithmetic, the transparency log's leaf # and certificate counts, the first dual-signed head, the extracted # parameter card, and the 71-digit Q — digit for digit. # # Phases # 0 source hygiene (check-book.py) # 1 build (tectonic via build.sh; fails on TeX errors) # 1b built-PDF claims (pages, unresolved refs, page-count claims) # 2 internal congruence (counts and arithmetic inside the book) # 3 cross-repo congruence (log + fips205 + P25519 siblings) # # Environment # BOOK_LOCAL_ONLY=1 skip phase 3; verdict is downgraded, never ALL GREEN # ESTATE_ROOT parent dir of the sibling repos (default: ../) # LTL_DIR / FIPS205_DIR / P25519_FILE override individual siblings # # Modes # ./check-book.sh full run # ./check-book.sh --selftest adversarial self-test: mutates copies of the # sources and asserts the button turns RED set -euo pipefail cd "$(dirname "$0")" HERE="$(pwd)" selftest() { echo "=== SELFTEST: the button must go red for the right reasons ===" command -v python3 >/dev/null || { echo "python3 required"; exit 1; } # the mutated copy keeps the REAL sibling repos: only the book is mutated, # so a red verdict proves the mutation was caught, not that a repo was lost local ESTATE; ESTATE="${ESTATE_ROOT:-$(dirname "$HERE")}" local tmp out pass=0 fail=0 run_copy() { tmp="$(mktemp -d)"; mkdir -p "$tmp/chapters" cp main.tex README.md "$tmp/"; cp chapters/*.tex "$tmp/chapters/" } run_mutated() { # $1 description, $2 mutation cmd, $3 expected FAIL substring run_copy ( cd "$tmp" && eval "$2" ) out="$(SKIP_BUILD=1 ESTATE_ROOT="$ESTATE" python3 "$HERE/check-book.py" "$tmp" 2>&1)" \ && { echo " FAIL mutation NOT caught: $1"; fail=$((fail+1)); rm -rf "$tmp"; return; } if echo "$out" | grep -q "FAIL.*$3"; then echo " ok caught for the right reason: $1"; pass=$((pass+1)) else echo " FAIL red, but not on the expected check ('$3'): $1"; fail=$((fail+1)) echo "$out" | grep " FAIL" | head -3 fi rm -rf "$tmp" } # control: the unmutated copy must pass (proves the harness can go green) run_copy if SKIP_BUILD=1 ESTATE_ROOT="$ESTATE" python3 "$HERE/check-book.py" "$tmp" >/dev/null 2>&1; then echo " ok control: unmutated copy passes"; pass=$((pass+1)) else echo " FAIL control: unmutated copy should pass but is red"; fail=$((fail+1)) SKIP_BUILD=1 ESTATE_ROOT="$ESTATE" python3 "$HERE/check-book.py" "$tmp" | grep FAIL || true fi rm -rf "$tmp" run_mutated "chapter-count claim drifts (fourteen -> thirteen)" \ "sed -i 's/fourteen chapters/thirteen chapters/' README.md" \ "README chapter count" run_mutated "a solution deleted (ch13 solhead 13.6 dropped)" \ "sed -i 's/\\\\solhead{13.6}/% gone/' chapters/ch13-second-summit.tex" \ "ch13: exercises == solutions" run_mutated "one digit of the 71-digit Q changed in ch07" \ "sed -i 's/740582127325613583022312264370627886761/740582127325613583022312264370627886762/' chapters/ch07-primality-certificates.tex" \ "printed Q == repository" run_mutated "leaf-count claim drifts (nineteen leaves -> twenty)" \ "sed -i \"s/log's nineteen leaves/log's twenty leaves/\" chapters/ch14-attestation-protocol.tex" \ "ch14 'nineteen leaves'" run_mutated "worst-case arithmetic drifts (3,824 -> 3,689)" \ "sed -i 's/3{,}824/3{,}689/' chapters/ch13-second-summit.tex" \ "ch13 prints worst total" run_mutated "a chapter stops ending on its checkpoint" \ "printf '\n\\\\begin{aha}\nstray box after the checkpoint\n\\\\end{aha}\n' >> chapters/ch05-numbers-and-automation.tex" \ "ch05.*last environment is checkpoint" run_mutated "week plan and heading diverge (heading says fifteen)" \ "sed -i 's/A fourteen-week plan/A fifteen-week plan/' main.tex" \ "week-plan heading" echo if [ "$fail" -gt 0 ]; then echo "SELFTEST RED: $fail defect(s) in the button itself"; exit 1 fi echo "SELFTEST GREEN: $pass/$pass (control + 7 mutations, each caught on its own check)" exit 0 } [ "${1:-}" = "--selftest" ] && selftest echo "=== Phase 1: build ===" BUILDLOG="$(mktemp)" ./build.sh 2>&1 | tee "$BUILDLOG" command -v pdfinfo >/dev/null && command -v pdftotext >/dev/null || { echo "FAIL: poppler-utils (pdfinfo/pdftotext) required"; exit 1; } # Print-quality gate: an overfull box past 10pt is text visibly leaving the # margin or a border slicing through content. The 2026-08-08 visual audit # found the two worst rendering defects in the book had been announced in # every build log as 80pt/73pt overfull warnings — and ignored. Never again. if grep -oE "Overfull \\\\hbox \([0-9]+\.[0-9]+pt" "$BUILDLOG" \ | grep -oE "[0-9]+\.[0-9]+" | awk '$1 > 10 {bad=1} END {exit bad}'; then echo " ok no overfull box exceeds 10pt (print-quality gate)" else echo " FAIL overfull boxes past 10pt — text is leaving the page:" grep -E "Overfull \\\\hbox \([0-9]{2,}" "$BUILDLOG" | sort -u | head -5 rm -f "$BUILDLOG"; exit 1 fi rm -f "$BUILDLOG" python3 "$HERE/check-book.py" "$HERE" echo if [ "${BOOK_LOCAL_ONLY:-0}" = "1" ]; then echo "VERDICT: build green + local claims green; cross-repo NOT verified." else echo "ALL GREEN — the book builds and every countable claim matches" echo "measured reality (sources, PDF, transparency log, extracted code)." fi