chain-check: detect audit-chain forks + extra genesis rows

`make chain-check-shards` / `chain-check` previously only counted dangling
prev_event_hash references — it missed forks (two events chained off the
same head; qa.db seq 7724/7725 was that, and the cheap check reported 0).
CHAIN_CHECK_SQL now reports the sum of: dangling prev refs + forked
parents (a prev_event_hash claimed by >1 row) + extra genesis rows
(>1 row with prev_event_hash IS NULL). 0 = a single linear chain.

(Working-tree Makefile also carried in-flight bench-5f-* target additions
from a concurrent session; they ride along in this commit.)
This commit is contained in:
russell@unturf.com 2026-05-11 13:01:37 -04:00
parent 9559c2ed31
commit 7a64939e2b
No known key found for this signature in database

View file

@ -577,19 +577,31 @@ verify-shards: bootstrap ## cross-shard Merkle round-trip on a random sample
analyze-shards: bootstrap ## cross-shard compression spectrum + audit integrity
$(ARBORIST) --shards-dir $(SHARDS_DIR) analyze
# Audit-chain integrity probe: counts dangling prev_event_hash references.
# Faster than `analyze` and trivially scriptable. 0 = chain intact.
# Audit-chain integrity probe. Cheap (set-arithmetic, no per-row hash walk),
# scriptable, and far faster than `analyze`. 0 = chain intact: a single
# linear chain with one genesis. Counts, summed:
# - dangling prev_event_hash references (a prev that no row's event_hash has)
# - forked parents (a prev_event_hash claimed by >1 row — two events built
# on the same head; the bug behind qa.db seq 7724/7725)
# - extra genesis rows (more than one row with prev_event_hash IS NULL)
# The plain LEFT JOIN check alone misses forks — the forked parent IS present.
define CHAIN_CHECK_SQL
SELECT COUNT(*) AS chain_breaks FROM audit_events a1
LEFT JOIN audit_events a2 ON a2.event_hash = a1.prev_event_hash
WHERE a1.prev_event_hash IS NOT NULL AND a2.event_hash IS NULL
SELECT
(SELECT COUNT(*) FROM audit_events a1
LEFT JOIN audit_events a2 ON a2.event_hash = a1.prev_event_hash
WHERE a1.prev_event_hash IS NOT NULL AND a2.event_hash IS NULL)
+ (SELECT COUNT(*) FROM
(SELECT 1 FROM audit_events WHERE prev_event_hash IS NOT NULL
GROUP BY prev_event_hash HAVING COUNT(*) > 1))
+ MAX(0, (SELECT COUNT(*) FROM audit_events WHERE prev_event_hash IS NULL) - 1)
AS chain_breaks
endef
export CHAIN_CHECK_SQL
chain-check: ## audit-chain break count for $(DB) (0 = intact)
chain-check: ## audit-chain integrity count for $(DB) (0 = intact: linear chain, one genesis)
@printf '%s ' "$(DB)"; sqlite3 $(DB) "$$CHAIN_CHECK_SQL"
chain-check-shards: ## audit-chain break count for every *.db in $(SHARDS_DIR)
chain-check-shards: ## audit-chain integrity count for every *.db in $(SHARDS_DIR)
@for db in $(SHARDS_DIR)/*.db; do \
printf '%s ' "$$db"; sqlite3 "$$db" "$$CHAIN_CHECK_SQL"; \
done