manifest_value, manifest_has and manifest_foreign each read the file with a
bare `while read`, which stops at EOF without ever handing over a populated
partial line. An unterminated final line therefore read as ABSENT — and
absent is exactly the input both convergence rules key off, so the file's
last line was the one least able to survive the miss.
Three failures, in descending order of how much they cost:
- A file truncated mid-write ends AT bootstrapped_at, so the unreadable line
is the birth stamp itself. Rule 1 saw no at-stamp and regenerated the pair
as now() — overwriting the one field that can never be reconstructed. A
fixture born 2020-01-01 came back stamped with the current clock.
- A whole file whose last line is converged_at read as empty, so Rule 2's
one-time repair re-fired on EVERY run: the render stopped being a function
of (existing file, running version) and the clock reached the file after
all. This is the crux property of the feature, broken by a missing byte.
- manifest_foreign dropped an unterminated foreign line entirely, so the
writer ate a later command's provenance — the exact preservation contract
the function exists to keep.
The idiom is the repo's own: lib/users-config.sh:49 reads
`|| [ -n "$line" ]` for the same reason.
Reading the line correctly also repairs the file, since the rewritten copy
is newline-terminated — asserted as "the source plus the newline it was
missing, and nothing else", because a plain ends-in-newline check stays
green on an implementation that drops the final line.
7 tests, each observed RED against the unfixed reader.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A rig-managed machine recorded nothing about its own provenance. The entire
durable output of a bootstrap run was one line in /etc/rig/role, and that line
says what the box IS, never what built it. VERSION was read in exactly one
place (bin/rig:9, for --version) and reports the currently INSTALLED tree, not
the one that ran; there was no timestamp anywhere in the codebase.
bootstrap now stamps a second file beside the marker: schema=1, a birth pair
(bootstrapped_by/_at, pinned forever) and a latest pair (converged_by/_at).
key=value, one per line, 0644 — the one file that must stay readable on the
most broken machine in the fleet, where there is no YAML parser and no jq.
`rig manifest [<key>]` reads it back.
Only DECIDED facts go in, which is what keeps bootstrap.sh:3's convergence
contract intact: bootstrapped_* is first-write-wins, and converged_* updates
only when the version actually differs — it is the time the converging version
last changed, not the time of the last run. The renderer is pure, so a re-run
by the same rig is byte-identical no matter where the clock is, and the
cmp-guard stays silent. OBSERVED facts (cores, RAM, disk, kernel) stay out:
they go stale on their own and belong to `rig platform` (#64).
/etc/rig/role is untouched.
Closes#61
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>