#!/usr/bin/env bash
# canon-drift — assert every vendored copy sits at its declared distance
# from its canon.
#
# House rule 6 promises the vendored layer is byte-identical across the rooms
# and CLJS lines "modulo the namespace rename". That promise was checked by
# hand, which means it was checked by memory. This makes it a number.
#
# canon-drift check every row of canon.tsv; non-zero exit on drift
# canon-drift --list print the table, change nothing
# canon-drift --update RE-BASELINE the deltas (a deliberate, reviewed act)
#
# The assertion is EQUALITY, not "at most". A copy that drifted further has an
# unpropagated fix; a copy that drifted less has usually had a sanctioned
# divergence silently reverted. Both are findings.
set -euo pipefail
DEV_DIR="$(cd "$(dirname "$0")/.." && pwd)"
WORKSPACE="$(cd "$DEV_DIR/.." && pwd)"
MANIFEST="$DEV_DIR/canon.tsv"
TAB="$(printf '\t')"
mode="check"
case "${1:-}" in
--list) mode="list" ;;
--update) mode="update" ;;
"") ;;
*) printf 'usage: canon-drift [--list|--update]\n' >&2; exit 2 ;;
esac
# The rename is a literal prefix substitution, exactly as ardz's _ns_rename
# does it: s/<from>/<to>/g with the dot escaped. Nothing else is rewritten —
# the protocol ids (/sueta/2/msg/1.0), the OrbitDB provider type ("sueta") and
# the domain tags (sueta-id|v2) have no dot after the name and so survive,
# which is precisely what wire compatibility depends on.
drift() { # canon copy from to -> changed-line count, or MISSING
local canon="$WORKSPACE/$1" copy="$WORKSPACE/$2" from="$3" to="$4"
# "-" in BOTH rename columns means the copy is not namespaced at all and is
# expected byte-identical — the clj-kondo hooks, which are plain Clojure with
# the same ns name in every repo. Substituting "-" for "-" would be a no-op
# anyway; spelling it out keeps the manifest readable and stops the next
# person inventing a sentinel string.
if [ "$from" = "-" ] && [ "$to" = "-" ]; then
[ -f "$canon" ] && [ -f "$copy" ] || { echo MISSING; return; }
diff "$canon" "$copy" | grep -c '^[<>]' || true
return
fi
[ -f "$canon" ] && [ -f "$copy" ] || { printf 'MISSING'; return 0; }
diff <(sed "s/${from//./\\.}/$to/g" "$canon") "$copy" | grep -c '^[<>]' || true
}
fail=0
rows=0
out="$(mktemp)"; trap 'rm -f "$out"' EXIT
while IFS="$TAB" read -r canon copy from to want reason; do
case "${canon:-}" in ''|'#'*) continue;; esac
rows=$((rows + 1))
got="$(drift "$canon" "$copy" "$from" "$to")"
if [ "$mode" = "update" ]; then
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$canon" "$copy" "$from" "$to" "$got" "$reason" >>"$out"
[ "$got" = "$want" ] || printf ' ~ %s -> %s: %s => %s\n' "$canon" "$copy" "$want" "$got"
continue
fi
if [ "$got" = "$want" ]; then
[ "$mode" = "list" ] && printf ' ok %-46s %-46s %s\n' "$canon" "$copy" "$got"
else
fail=1
printf 'DRIFT %s\n canon: %s\n copy: %s\n declared: %s changed lines\n actual: %s\n' \
"$([ "$got" -gt "$want" ] 2>/dev/null && echo 'grew — an unpropagated canon change?' || echo 'shrank — a sanctioned divergence reverted?')" \
"$canon" "$copy" "$want" "$got" >&2
[ "$reason" != "-" ] && printf ' sanctioned: %s\n' "$reason" >&2
fi
done < "$MANIFEST"
if [ "$mode" = "update" ]; then
# keep the comment header, replace only the data rows
{ grep -E '^($|#)' "$MANIFEST"; cat "$out"; } > "$MANIFEST.new"
mv "$MANIFEST.new" "$MANIFEST"
printf 'canon-drift: re-baselined %s rows — REVIEW THE DIFF before committing.\n' "$rows"
exit 0
fi
if [ "$fail" = 0 ]; then
printf 'canon-drift: OK (%s vendored copies each at their declared distance)\n' "$rows"
else
printf '\ncanon-drift: FAILED — propagate the change, or re-baseline deliberately\n with `canon-drift --update` and explain the new delta in canon.tsv.\n' >&2
fi
exit "$fail"