feat: /etc/rig/manifest — which rig converged this machine, and when #84

Merged
dan-claude-bot merged 2 commits from feat/manifest-provenance into main 2026-07-20 13:06:01 +00:00
2 changed files with 57 additions and 3 deletions
Showing only changes of commit d2e03ce993 - Show all commits

View file

@ -49,7 +49,7 @@ manifest_path() {
manifest_value() {
[ -r "$1" ] || return 0
local k v
while IFS='=' read -r k v; do
while IFS='=' read -r k v || [ -n "$k" ]; do
[ "$k" = "$2" ] || continue
printf '%s' "$v"
return 0
@ -65,7 +65,7 @@ manifest_value() {
manifest_has() {
[ -r "$1" ] || return 1
local k
while IFS='=' read -r k _; do
while IFS='=' read -r k _ || [ -n "$k" ]; do
[ "$k" = "$2" ] && return 0
done < "$1"
return 1
@ -79,7 +79,7 @@ manifest_has() {
manifest_foreign() {
[ -r "$1" ] || return 0
local line key
while IFS= read -r line; do
while IFS= read -r line || [ -n "$line" ]; do
[ -n "$line" ] || continue
key="${line%%=*}"
case " $MANIFEST_KEYS " in

View file

@ -2054,6 +2054,60 @@ check "manifest: a converged_by with no converged_at is repaired once" 0 "conver
check "manifest: the repair settles — it does not re-fire on the next run" 0 "" \
reproduces_itself "$REPAIRED" 0.4.0
# -- a final line with NO trailing newline ----------------------------------
# A bare `while read` stops at EOF without ever handing over a populated
# partial line, so the last record of an unterminated file reads as ABSENT.
# That is not a cosmetic parse miss here: absent is exactly the input both
# rules key off, so the file's last line is the one least able to survive it.
# A hand-edit with an editor that adds no final newline, or a truncated write,
# is enough to produce one. The reader idiom is the repo's own —
# lib/users-config.sh:49 reads `|| [ -n "$line" ]` for the same reason.
NONL="$MF/nonl-owned"
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2020-01-01T00:00:00Z\nconverged_by=0.4.0\nconverged_at=2020-01-01T00:00:00Z' > "$NONL"
# The crux assertion, applied to the case that broke it: with converged_at
# unreadable, Rule 2 saw an empty at-stamp and re-fired the "one-time" repair
# on EVERY run, so the clock reached the file after all.
check "manifest: an unterminated final line does not let the clock back in" 0 "" \
clock_cannot_reach "$NONL" 0.4.0
check "manifest: an unterminated converged_at is read, not re-stamped" 0 "converged_at=2020-01-01T00:00:00Z" \
render "$NONL" 0.4.0 2026-07-19T14:24:51Z
# Rule 1 on the field that can never be reconstructed: a file truncated mid-way
# ends AT bootstrapped_at, so the unterminated line is the birth stamp itself —
# and regenerating it is the one loss no later run can undo.
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2020-01-01T00:00:00Z' > "$MF/nonl-birth"
check "manifest: an unterminated birth stamp stays pinned, not reborn today" 0 "bootstrapped_at=2020-01-01T00:00:00Z" \
render "$MF/nonl-birth" 0.4.0 2026-07-19T14:24:51Z
# And the preservation contract, whose whole subject is the file's tail: a
# later command's line is very often the last one written.
NONLF="$MF/nonl-foreign"
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2020-01-01T00:00:00Z\nconverged_by=0.4.0\nconverged_at=2020-01-01T00:00:00Z\nrunner_installed_at=2026-07-19T16:10:00Z' > "$NONLF"
check "manifest: an unterminated FOREIGN final line is not eaten by the rewrite" 0 "runner_installed_at=2026-07-19T16:10:00Z" \
render "$NONLF" 0.4.0 2026-07-19T14:24:51Z
# Reading it correctly also REPAIRS it: the rewritten copy is newline-terminated,
# so an unterminated file converges to a terminated one exactly once and then
# reproduces itself like any other. Asserted as "the source, plus the newline it
# was missing, and NOTHING else" — a plain does-it-end-in-\n check would stay
# green on an implementation that dropped the final line, since a file with the
# tail eaten is newline-terminated too.
NORMALIZED="$MF/normalized"
render "$NONLF" 0.4.0 2026-07-19T14:24:51Z > "$NORMALIZED"
adds_only_the_newline() { # adds_only_the_newline <source> <normalized>
diff <(cat "$1"; printf '\n') "$2"
}
check "manifest: the rewrite adds the missing final newline and changes nothing else" 0 "" \
adds_only_the_newline "$NONLF" "$NORMALIZED"
check "manifest: ...and the normalized file then settles" 0 "" \
reproduces_itself "$NORMALIZED" 0.4.0
# Presence, not just value: manifest_has answers the absent-vs-empty question
# `rig manifest <key>` puts in its exit code, and it read the same short file.
has_key() { # has_key <path> <key>
( set -euo pipefail
. "$ROOT/commands/lib/manifest.sh"
manifest_has "$1" "$2" )
}
check "manifest: an unterminated final key is PRESENT, not absent" 0 "" \
has_key "$NONL" converged_at
# -- the version that RAN, not the one installed now ------------------------
# The whole point: a machine outlives the rig that built it, so this is read
# from the tree at run time and never re-derived from `rig --version` later.