montana/Монтана-Протокол/Код/scripts/check_doc_drift.sh

290 lines
14 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# scripts/check_doc_drift.sh
#
# Защита от documentation drift между фактическим кодом и audit package.
# Закрывает class ошибок обнаруженных внешним аудитом Claude Opus 4.7
# (отчёт #1 F-1/F-4/F-18, отчёт #2 M2-12/DOC-1/DOC-2): AUDIT.md /
# audit-checklist.md / security-cards.md повторяют устаревшие числа
# (line counts, unsafe block count, domain count).
#
# Стратегия: вычислить факт из исходного кода (wc / grep) и сверить с
# заявлениями в .md файлах. При mismatch — exit 1 с понятной diagnostic.
#
# Запускается:
# - Локально перед commit (git pre-commit hook опционально)
# - В CI как gate doc_drift
#
# Использование:
# ./scripts/check_doc_drift.sh
#
# Exit code: 0 (clean) | 1 (drift detected, listing напечатан в stderr)
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
EXIT=0
errors=()
err() {
EXIT=1
errors+=("$1")
echo "FAIL: $1" >&2
}
ok() {
echo "ok: $1"
}
# ---------- 1. mt-crypto/src/lib.rs line count ----------
ACTUAL_MT_CRYPTO_LOC=$(wc -l < crates/mt-crypto/src/lib.rs | tr -d ' ')
# AUDIT.md заявляет в строке: | `mt-crypto` | ... | **NNN** | ... |
CLAIMED_MT_CRYPTO_LOC=$(grep -E '^\| `mt-crypto` \|' AUDIT.md | grep -oE '\*\*[0-9]+\*\*' | head -1 | tr -d '*')
if [ -z "$CLAIMED_MT_CRYPTO_LOC" ]; then
err "AUDIT.md не содержит mt-crypto LOC claim в expected format '**NNN**'"
elif [ "$ACTUAL_MT_CRYPTO_LOC" != "$CLAIMED_MT_CRYPTO_LOC" ]; then
err "AUDIT.md mt-crypto LOC drift: claimed $CLAIMED_MT_CRYPTO_LOC, actual $ACTUAL_MT_CRYPTO_LOC"
else
ok "AUDIT.md mt-crypto LOC = $ACTUAL_MT_CRYPTO_LOC"
fi
# audit-checklist.md заявляет: Layer 1 Rust shim** **NNN строк** —
# LOC после "shim** **" чтобы не зацепить "Layer 1" prefix
CHECKLIST_MT_CRYPTO_LOC=$(grep -oE 'Rust shim\*\* \*\*[0-9]+ строк' docs/audit-checklist.md | grep -oE '\*\*[0-9]+ строк' | grep -oE '[0-9]+')
if [ -z "$CHECKLIST_MT_CRYPTO_LOC" ]; then
err "audit-checklist.md не содержит 'Layer 1 Rust shim **NNN строк**' claim"
elif [ "$ACTUAL_MT_CRYPTO_LOC" != "$CHECKLIST_MT_CRYPTO_LOC" ]; then
err "audit-checklist.md mt-crypto LOC drift: claimed $CHECKLIST_MT_CRYPTO_LOC, actual $ACTUAL_MT_CRYPTO_LOC"
else
ok "audit-checklist.md mt-crypto LOC = $ACTUAL_MT_CRYPTO_LOC"
fi
# ---------- 2. unsafe blocks в mt-crypto ----------
# Все формы: `unsafe {`, `unsafe fn`, `let _ = unsafe {`, etc.
# Считаем все unsafe keyword tokens (excluding /// doc comments + // comments)
ACTUAL_UNSAFE=$(grep -E '\bunsafe\b' crates/mt-crypto/src/lib.rs | grep -vE '^\s*///|^\s*//[^/]' | wc -l | tr -d ' ')
# AUDIT.md фразирует "Все **N** `unsafe` blocks"
CLAIMED_UNSAFE=$(grep -oE 'Все \*\*[0-9]+\*\* `unsafe` blocks' AUDIT.md | grep -oE '[0-9]+' | head -1)
if [ -z "$CLAIMED_UNSAFE" ]; then
err "AUDIT.md не содержит 'Все **N** \`unsafe\` blocks' claim"
elif [ "$ACTUAL_UNSAFE" != "$CLAIMED_UNSAFE" ]; then
err "AUDIT.md unsafe count drift: claimed $CLAIMED_UNSAFE, actual $ACTUAL_UNSAFE"
else
ok "AUDIT.md unsafe count = $ACTUAL_UNSAFE"
fi
# ---------- 3. Domain separators в mt-codec ----------
ACTUAL_DOMAINS=$(grep -cE '^\s*pub const [A-Z_]+: &\[u8\] = b"mt-' crates/mt-codec/src/lib.rs)
# AUDIT.md заявляет "Domain separators registry (NN domains, ..."
CLAIMED_DOMAINS=$(grep -oE 'Domain separators registry \([0-9]+ domains' AUDIT.md | grep -oE '[0-9]+' | head -1)
if [ -z "$CLAIMED_DOMAINS" ]; then
err "AUDIT.md не содержит 'Domain separators registry (NN domains' claim"
elif [ "$ACTUAL_DOMAINS" != "$CLAIMED_DOMAINS" ]; then
err "AUDIT.md domain count drift: claimed $CLAIMED_DOMAINS, actual $ACTUAL_DOMAINS"
else
ok "AUDIT.md domain count = $ACTUAL_DOMAINS"
fi
# AUDIT.md row "Domain registry sync (NN domains)"
SECONDARY_DOMAINS=$(grep -oE 'Domain registry sync \([0-9]+ domains\)' AUDIT.md | grep -oE '[0-9]+' | head -1)
if [ -n "$SECONDARY_DOMAINS" ] && [ "$ACTUAL_DOMAINS" != "$SECONDARY_DOMAINS" ]; then
err "AUDIT.md secondary domain count drift: claimed $SECONDARY_DOMAINS, actual $ACTUAL_DOMAINS"
fi
# ---------- 4. mt-crypto-native C wrapper LOC ----------
ACTUAL_C_LOC=$(wc -l < crates/mt-crypto-native/csrc/mt_crypto.c | tr -d ' ')
ACTUAL_H_LOC=$(wc -l < crates/mt-crypto-native/csrc/mt_crypto.h | tr -d ' ')
# AUDIT.md `mt_crypto.c | NNN |` — Layer 2 row
CLAIMED_C_LOC=$(grep -E 'mt_crypto.c\)\s*\|\s*[0-9]+' AUDIT.md | head -1 | grep -oE '\| [0-9]+ \|' | head -1 | grep -oE '[0-9]+')
if [ -n "$CLAIMED_C_LOC" ] && [ "$ACTUAL_C_LOC" != "$CLAIMED_C_LOC" ]; then
err "AUDIT.md mt_crypto.c LOC drift: claimed $CLAIMED_C_LOC, actual $ACTUAL_C_LOC"
elif [ -n "$CLAIMED_C_LOC" ]; then
ok "AUDIT.md mt_crypto.c LOC = $ACTUAL_C_LOC"
fi
CLAIMED_H_LOC=$(grep -E 'mt_crypto.h\)\s*\|\s*[0-9]+' AUDIT.md | head -1 | grep -oE '\| [0-9]+ \|' | head -1 | grep -oE '[0-9]+')
if [ -n "$CLAIMED_H_LOC" ] && [ "$ACTUAL_H_LOC" != "$CLAIMED_H_LOC" ]; then
err "AUDIT.md mt_crypto.h LOC drift: claimed $CLAIMED_H_LOC, actual $ACTUAL_H_LOC"
elif [ -n "$CLAIMED_H_LOC" ]; then
ok "AUDIT.md mt_crypto.h LOC = $ACTUAL_H_LOC"
fi
# ---------- 5. Total audit surface ----------
RUST_BINDING_LOC=$(wc -l < crates/mt-crypto-native/src/lib.rs | tr -d ' ')
BUILD_RS_LOC=$(wc -l < crates/mt-crypto-native/build.rs | tr -d ' ')
ACTUAL_TOTAL=$((ACTUAL_MT_CRYPTO_LOC + RUST_BINDING_LOC + ACTUAL_C_LOC + ACTUAL_H_LOC + BUILD_RS_LOC))
# AUDIT.md "Total own audit surface (Layer 1 + Layer 2): NNNN lines"
CLAIMED_TOTAL=$(grep -oE 'Total own audit surface \(Layer 1 \+ Layer 2\): [0-9]+ lines' AUDIT.md | grep -oE '[0-9]+ lines' | grep -oE '[0-9]+' | head -1)
if [ -z "$CLAIMED_TOTAL" ]; then
err "AUDIT.md не содержит 'Total own audit surface (Layer 1 + Layer 2): NNNN lines' claim"
elif [ "$ACTUAL_TOTAL" != "$CLAIMED_TOTAL" ]; then
err "AUDIT.md total audit surface drift: claimed $CLAIMED_TOTAL, actual $ACTUAL_TOTAL ($ACTUAL_MT_CRYPTO_LOC + $RUST_BINDING_LOC + $ACTUAL_C_LOC + $ACTUAL_H_LOC + $BUILD_RS_LOC)"
else
ok "AUDIT.md total audit surface = $ACTUAL_TOTAL"
fi
# ---------- 6. mt-account LOC (M3 audit scope) ----------
ACTUAL_MT_ACCOUNT_LOC=$(wc -l < crates/mt-account/src/lib.rs | tr -d ' ')
# AUDIT.md row: | `mt-account` | ... | **NNNN** | ... |
CLAIMED_MT_ACCOUNT_LOC=$(grep -E '^\| `mt-account` \|' AUDIT.md | grep -oE '\*\*[0-9]+\*\*' | head -1 | tr -d '*')
if [ -z "$CLAIMED_MT_ACCOUNT_LOC" ]; then
err "AUDIT.md не содержит mt-account LOC claim в expected format '**NNNN**'"
elif [ "$ACTUAL_MT_ACCOUNT_LOC" != "$CLAIMED_MT_ACCOUNT_LOC" ]; then
err "AUDIT.md mt-account LOC drift: claimed $CLAIMED_MT_ACCOUNT_LOC, actual $ACTUAL_MT_ACCOUNT_LOC"
else
ok "AUDIT.md mt-account LOC = $ACTUAL_MT_ACCOUNT_LOC"
fi
# AUDIT.md TL;DR table: M3 row "mt-account | NNNN | ..."
TLDR_MT_ACCOUNT_LOC=$(grep -E 'M3 apply_proposal layer.*mt-account' AUDIT.md | grep -oE '\| [0-9]+ \|' | head -1 | grep -oE '[0-9]+')
if [ -n "$TLDR_MT_ACCOUNT_LOC" ] && [ "$ACTUAL_MT_ACCOUNT_LOC" != "$TLDR_MT_ACCOUNT_LOC" ]; then
err "AUDIT.md TL;DR mt-account LOC drift: claimed $TLDR_MT_ACCOUNT_LOC, actual $ACTUAL_MT_ACCOUNT_LOC"
fi
# ---------- 7. mt-account test count ----------
ACTUAL_MT_ACCOUNT_TESTS=$(grep -cE '#\[test\]' crates/mt-account/src/lib.rs)
# AUDIT.md TL;DR заявляет: "NN unit + 35 determinism invariants"
CLAIMED_MT_ACCOUNT_TESTS=$(grep -oE '[0-9]+ unit \+ [0-9]+ determinism invariants' AUDIT.md | head -1 | grep -oE '^[0-9]+')
if [ -z "$CLAIMED_MT_ACCOUNT_TESTS" ]; then
err "AUDIT.md не содержит mt-account 'NN unit + 35 determinism invariants' claim"
elif [ "$ACTUAL_MT_ACCOUNT_TESTS" != "$CLAIMED_MT_ACCOUNT_TESTS" ]; then
err "AUDIT.md mt-account unit test count drift: claimed $CLAIMED_MT_ACCOUNT_TESTS, actual $ACTUAL_MT_ACCOUNT_TESTS"
else
ok "AUDIT.md mt-account unit test count = $ACTUAL_MT_ACCOUNT_TESTS"
fi
# ---------- 8. mt-account determinism_invariants tests ----------
ACTUAL_M3_DETERMINISM=$(grep -cE '#\[test\]' crates/mt-account/tests/determinism_invariants.rs)
# Check that 29 (M3 number after v34 monetary refactor) appears
if ! grep -q "29 determinism invariants" AUDIT.md; then
err "AUDIT.md не упоминает '29 determinism invariants' для M3"
elif [ "$ACTUAL_M3_DETERMINISM" != "29" ]; then
err "M3 determinism_invariants count drift: claimed 29, actual $ACTUAL_M3_DETERMINISM"
else
ok "AUDIT.md M3 determinism invariants = 29 (matches actual $ACTUAL_M3_DETERMINISM)"
fi
# ---------- 9..12: M4+M5 crate LOC + test counts ----------
# M4 mt-lottery
ACTUAL_MT_LOTTERY_LOC=$(wc -l < crates/mt-lottery/src/lib.rs | tr -d ' ')
CLAIMED_MT_LOTTERY_LOC=$(grep -E '^\| `mt-lottery` \|' AUDIT.md | grep -oE '\*\*[0-9]+\*\*' | head -1 | tr -d '*')
if [ -z "$CLAIMED_MT_LOTTERY_LOC" ]; then
err "AUDIT.md не содержит mt-lottery LOC claim в expected format '**NNNN**'"
elif [ "$ACTUAL_MT_LOTTERY_LOC" != "$CLAIMED_MT_LOTTERY_LOC" ]; then
err "AUDIT.md mt-lottery LOC drift: claimed $CLAIMED_MT_LOTTERY_LOC, actual $ACTUAL_MT_LOTTERY_LOC"
else
ok "AUDIT.md mt-lottery LOC = $ACTUAL_MT_LOTTERY_LOC"
fi
# M4 mt-consensus
ACTUAL_MT_CONSENSUS_LOC=$(wc -l < crates/mt-consensus/src/lib.rs | tr -d ' ')
CLAIMED_MT_CONSENSUS_LOC=$(grep -E '^\| `mt-consensus` \|' AUDIT.md | grep -oE '\*\*[0-9]+\*\*' | head -1 | tr -d '*')
if [ -z "$CLAIMED_MT_CONSENSUS_LOC" ]; then
err "AUDIT.md не содержит mt-consensus LOC claim"
elif [ "$ACTUAL_MT_CONSENSUS_LOC" != "$CLAIMED_MT_CONSENSUS_LOC" ]; then
err "AUDIT.md mt-consensus LOC drift: claimed $CLAIMED_MT_CONSENSUS_LOC, actual $ACTUAL_MT_CONSENSUS_LOC"
else
ok "AUDIT.md mt-consensus LOC = $ACTUAL_MT_CONSENSUS_LOC"
fi
# M4 mt-entry
ACTUAL_MT_ENTRY_LOC=$(wc -l < crates/mt-entry/src/lib.rs | tr -d ' ')
CLAIMED_MT_ENTRY_LOC=$(grep -E '^\| `mt-entry` \|' AUDIT.md | grep -oE '\*\*[0-9]+\*\*' | head -1 | tr -d '*')
if [ -z "$CLAIMED_MT_ENTRY_LOC" ]; then
err "AUDIT.md не содержит mt-entry LOC claim"
elif [ "$ACTUAL_MT_ENTRY_LOC" != "$CLAIMED_MT_ENTRY_LOC" ]; then
err "AUDIT.md mt-entry LOC drift: claimed $CLAIMED_MT_ENTRY_LOC, actual $ACTUAL_MT_ENTRY_LOC"
else
ok "AUDIT.md mt-entry LOC = $ACTUAL_MT_ENTRY_LOC"
fi
# M5 mt-store
ACTUAL_MT_STORE_LOC=$(wc -l < crates/mt-store/src/lib.rs | tr -d ' ')
CLAIMED_MT_STORE_LOC=$(grep -E '^\| `mt-store` \|' AUDIT.md | grep -oE '\*\*[0-9]+\*\*' | head -1 | tr -d '*')
if [ -z "$CLAIMED_MT_STORE_LOC" ]; then
err "AUDIT.md не содержит mt-store LOC claim"
elif [ "$ACTUAL_MT_STORE_LOC" != "$CLAIMED_MT_STORE_LOC" ]; then
err "AUDIT.md mt-store LOC drift: claimed $CLAIMED_MT_STORE_LOC, actual $ACTUAL_MT_STORE_LOC"
else
ok "AUDIT.md mt-store LOC = $ACTUAL_MT_STORE_LOC"
fi
# M4 total surface — extract число между ":** " и " lines" (skip M4 milestone digit)
ACTUAL_M4_TOTAL=$((ACTUAL_MT_LOTTERY_LOC + ACTUAL_MT_CONSENSUS_LOC + ACTUAL_MT_ENTRY_LOC))
CLAIMED_M4_TOTAL=$(grep -oE 'Total M4 audit surface:\*\* [0-9]+ lines' AUDIT.md | awk -F'[*: ]+' '{print $(NF-1)}')
if [ -n "$CLAIMED_M4_TOTAL" ] && [ "$ACTUAL_M4_TOTAL" != "$CLAIMED_M4_TOTAL" ]; then
err "AUDIT.md M4 total surface drift: claimed $CLAIMED_M4_TOTAL, actual $ACTUAL_M4_TOTAL"
elif [ -n "$CLAIMED_M4_TOTAL" ]; then
ok "AUDIT.md M4 total surface = $ACTUAL_M4_TOTAL"
fi
# M5 total surface
ACTUAL_M5_TOTAL=$ACTUAL_MT_STORE_LOC
CLAIMED_M5_TOTAL=$(grep -oE 'Total M5 audit surface:\*\* [0-9]+ lines' AUDIT.md | awk -F'[*: ]+' '{print $(NF-1)}')
if [ -n "$CLAIMED_M5_TOTAL" ] && [ "$ACTUAL_M5_TOTAL" != "$CLAIMED_M5_TOTAL" ]; then
err "AUDIT.md M5 total surface drift: claimed $CLAIMED_M5_TOTAL, actual $ACTUAL_M5_TOTAL"
elif [ -n "$CLAIMED_M5_TOTAL" ]; then
ok "AUDIT.md M5 total surface = $ACTUAL_M5_TOTAL"
fi
# M4 + M5 determinism invariant counts
ACTUAL_M4_LOTTERY_DET=$(grep -cE '#\[test\]' crates/mt-lottery/tests/determinism_invariants.rs)
ACTUAL_M4_CONSENSUS_DET=$(grep -cE '#\[test\]' crates/mt-consensus/tests/determinism_invariants.rs)
ACTUAL_M4_ENTRY_DET=$(grep -cE '#\[test\]' crates/mt-entry/tests/determinism_invariants.rs)
ACTUAL_M5_STORE_DET=$(grep -cE '#\[test\]' crates/mt-store/tests/determinism_invariants.rs)
if [ "$ACTUAL_M4_LOTTERY_DET" != "34" ]; then
err "mt-lottery determinism_invariants count drift: expected 34, actual $ACTUAL_M4_LOTTERY_DET"
else
ok "mt-lottery determinism_invariants = 34"
fi
if [ "$ACTUAL_M4_CONSENSUS_DET" != "27" ]; then
err "mt-consensus determinism_invariants count drift: expected 27, actual $ACTUAL_M4_CONSENSUS_DET"
else
ok "mt-consensus determinism_invariants = 27"
fi
if [ "$ACTUAL_M4_ENTRY_DET" != "24" ]; then
err "mt-entry determinism_invariants count drift: expected 24, actual $ACTUAL_M4_ENTRY_DET"
else
ok "mt-entry determinism_invariants = 24"
fi
if [ "$ACTUAL_M5_STORE_DET" != "17" ]; then
err "mt-store determinism_invariants count drift: expected 17 (v34 monetary refactor удалила MonetaryState persistence), actual $ACTUAL_M5_STORE_DET"
else
ok "mt-store determinism_invariants = 17"
fi
# ---------- Summary ----------
echo ""
if [ $EXIT -eq 0 ]; then
echo "================================================================"
echo "✅ Documentation drift check PASS — все числа sync с фактическим кодом"
echo "================================================================"
else
echo ""
echo "================================================================"
echo "❌ Documentation drift check FAIL — найдено ${#errors[@]} расхождений:"
for e in "${errors[@]}"; do
echo " - $e"
done
echo ""
echo "Действие: обновить AUDIT.md / docs/audit-checklist.md / docs/security-cards.md"
echo "так чтобы числа совпадали с фактическим кодом. Если числа в коде верны —"
echo "правьте .md файлы. Если в .md правильно (concept change) — обнови код."
echo "================================================================"
fi
exit $EXIT