feat: rig platform — what is this machine, computed not stored #74

Merged
dan-claude-bot merged 5 commits from feat/platform into main 2026-07-20 12:46:00 +00:00
5 changed files with 440 additions and 0 deletions

View file

@ -6,6 +6,45 @@ on the way to cutting its first release, and this file starts there.
## Unreleased ## Unreleased
### Added
- **`rig platform` — what is this machine, calculated at run time, stored
nowhere** (#64) — rig read no hardware at all; the single exception was
`uname -m` in `runner-install.sh`, used to pick a tarball and then
discarded. So "is this the 32GB one, or the M900?" was answered by logging
in and running `free -h`, `nproc`, `df -h` and `uname -r` by hand, four
commands deep, on a machine you were already unsure about. `rig platform`
prints hostname, OS, kernel, CPU, memory, disk and virtualization, then a
provenance block (which rig, when, and the role marker's traits). It
**computes rather than stores**: specs change without rig doing anything —
RAM added, root disk resized, the unattended-upgrades bootstrap itself
enables patching the kernel — so a stored spec is stale the moment the
machine changes, and refreshing one per run would collide with bootstrap's
"a second run changes nothing" contract. Nothing is written, so nothing can
go stale. The corollary is deliberate: reading only `/proc`, `uname`,
`/etc/os-release`, `df` and `systemd-detect-virt` means it needs no root,
makes no network call, and **runs on a pristine Debian box rig has never
bootstrapped** — useful for deciding what to converge a machine into, not
only for auditing it afterwards. That also makes it the rare rig command
the harness can RUN for real instead of grepping: the tests assert the
actual answer describes the actual test machine. Provenance is read, never
written, and degrades per-file — `/etc/rig/manifest` is #61 and does not
exist yet, so those lines read `not bootstrapped` on every machine today and
nothing else depends on it. The reader is keyed to #61's documented schema
(`schema`, `bootstrapped_by`/`_at`, `converged_by`/`_at`) and fixtures pin
that exact spelling, so the integration cannot land silently broken; birth
and latest stay separate rather than one being inferred from the other. A
fresh machine writes both pairs equal, so two identical lines read as
"never re-converged"; a manifest missing the pair is partial rather than
fresh, and says so instead of backfilling from birth. Named `platform` and not `status` on purpose:
`users status` and `runner status` cross-check recorded against live state
and print `DRIFT`, and a command that records nothing cannot drift — which
also leaves `rig status` free for the machine-wide roll-up. Known
limitation, stated rather than guessed at: `CPU`/`MEMORY` are read from
`/proc` with no cgroup awareness, and whether an `lxc` guest sees its own
limits or the host's totals depends on whether `lxcfs` is in play — it is
unverified, so those two lines are unreliable there.
### Changed ### Changed
- **BREAKING: `--class human|server` is now `--root-door closed|open`** (#77) — - **BREAKING: `--class human|server` is now `--root-door closed|open`** (#77) —

View file

@ -719,6 +719,102 @@ default, dumps it, restores into a second container whose superuser differs, and
asserts the rows and an ordered checksum survived — the same proof, done against asserts the rows and an ordered checksum survived — the same proof, done against
throwaway containers on every push. throwaway containers on every push.
### `rig platform`
```sh
rig platform
```
What is this machine — computed at run time, **stored nowhere**:
```
PLATFORM
HOSTNAME hetzner-cp-1
OS Debian GNU/Linux 13 (trixie)
KERNEL 6.12.95+deb13-amd64 (x86_64)
CPU AMD Ryzen 7 3700X 8-Core Processor (16 cores)
MEMORY 31Gi total, 24Gi available
DISK 456Gi total, 201Gi free on /
VIRT kvm
PROVENANCE
CONVERGED 0.6.0, 2026-08-02T09:11:03Z
BOOTSTRAP 0.4.0, 2026-07-19T14:24:51Z
ROLE dev-server (root-door=closed host=yes join=authkey)
```
"Is this the 32GB one, or the M900?" was previously a question you answered by
logging in and running `free -h`, `nproc`, `df -h` and `uname -r` by hand —
four commands deep, on a machine you were already unsure about.
**Why this computes instead of storing.** It would be easy to write the specs
into a file at bootstrap. That is the wrong shape: specs change without rig
doing anything — someone adds RAM, resizes the root disk, or the
unattended-upgrades that bootstrap itself enables patches the kernel. A stored
spec is stale the moment the machine changes, and refreshing it on every run
would collide with bootstrap's contract that a second run changes nothing.
Computing at run time removes the problem instead of managing it: the answer
is correct by construction because there is nothing to go stale.
The corollary is deliberate: **`rig platform` works on a machine rig has never
converged.** It reads only `/proc`, `uname`, `/etc/os-release`, `df` and
`systemd-detect-virt`, so it runs on bare Debian before bootstrap — useful for
deciding *what to converge this into*, not just for auditing afterwards. It
needs no root, makes no network call, and writes nothing, ever.
The `PROVENANCE` block is the complementary half — which rig, and when, which
is *decided* rather than observed, so it is stored. It is **read, never
written**: `CONVERGED`/`BOOTSTRAP` come from `/etc/rig/manifest` and `ROLE`
from `/etc/rig/role`. Neither file is required — a machine missing one reads
`not bootstrapped` for that line, which is itself the useful answer. The
manifest is #61 and is not implemented yet, so today those lines read `not
bootstrapped` on every machine; nothing else in the command depends on it.
**The two dates are deliberately separate**, matching #61's schema: `BOOTSTRAP`
is birth (`bootstrapped_by`/`bootstrapped_at`, first-write-wins, pinned
forever) and `CONVERGED` is latest (`converged_by`/`converged_at`, updated only
when the converging version actually differs). That distinction is what answers
"is this machine still converged by a rig that predates the fix?".
A **fresh machine writes both pairs with equal values**, so two identical lines
mean "bootstrapped and never re-converged since" — not a missing record. A
later re-converge by a different rig moves `CONVERGED` and leaves `BOOTSTRAP`
untouched, which is the whole point of keeping them apart.
`CONVERGED not recorded` therefore does **not** describe a freshly
bootstrapped box; no writer produces a manifest without the pair. It means the
file is partial or hand-edited, and the value is deliberately not backfilled
from `BOOTSTRAP` — inferring a convergence that never happened would be worse
than saying so. Likewise a manifest whose `schema=` this rig does not know is
named as such instead of being half-read in silence.
**Known limitation — `CPU` and `MEMORY` inside a container-style guest are
unverified.** `CPU` and `MEMORY` are read straight from `/proc/cpuinfo` and
`/proc/meminfo`, with no cgroup awareness. Inside a box-minted guest (`VIRT`
says `lxc`) it is **not currently established** whether those files report the
instance's configured limits or the host's totals: neither file is namespaced
by the kernel, but `lxcfs` — when the guest has it — overmounts both with
limit-aware versions, so the answer depends on the guest's setup rather than
on anything rig controls. Until someone confirms it against a real guest,
treat those two lines as unreliable on `lxc` machines and check the instance
config if the number matters. Everything else (OS, kernel, disk, virt,
provenance) is the guest's own either way.
Deliberately not guessed at: cgroup-aware limit detection would be the fix if
the numbers do turn out to be the host's, but writing it against a *reasoned*
answer rather than an *observed* one risks correcting a bug that isn't there
and papering over one that is.
Deliberately **not** here: NIC names, MAC addresses, PCI inventory, mount
tables, sensors — this is a cheatsheet, not `inxi`, and the bar is "what would
I want to know before I SSH in". Nor any health judgement ("disk nearly
full"): that needs thresholds this command has no business owning. It is
called `platform` and not `status` on purpose — `rig users status` and `rig
runner status` cross-check recorded state against live state and print
`DRIFT`, and this command records nothing, so it cannot drift and must not
borrow a promise it structurally cannot make. That leaves `rig status` free
for the machine-wide roll-up it will eventually want to be.
### `rig runner install --repo <owner/repo>` ### `rig runner install --repo <owner/repo>`
Runner box only, run after `rig bootstrap runner-server` (the same two-step rhythm Runner box only, run after `rig bootstrap runner-server` (the same two-step rhythm

13
bin/rig
View file

@ -54,6 +54,15 @@ commands:
writes a gzipped SQL artifact (--no-owner --no-acl, so it restores writes a gzipped SQL artifact (--no-owner --no-acl, so it restores
onto a different instance); `restore` loads one back, connecting as onto a different instance); `restore` loads one back, connecting as
the container's own superuser, behind a confirm gate. Run as root. the container's own superuser, behind a confirm gate. Run as root.
platform
What is this machine: hostname, OS, kernel, CPU, memory, disk and
virtualization, then rig's own provenance (which rig, when, and the
role marker bootstrap wrote). Computed at run time from /proc, uname,
/etc/os-release, df and systemd-detect-virt and stored NOWHERE — a
spec goes stale the moment someone adds RAM, so there is nothing to
go stale here. Writes nothing, needs no root, makes no network call,
and therefore also runs on a pristine Debian box rig has never
bootstrapped, where the provenance block reads 'not bootstrapped'.
runner install --repo <owner/repo> [options] runner install --repo <owner/repo> [options]
GitHub Actions runner as a systemd service under an unprivileged GitHub Actions runner as a systemd service under an unprivileged
user — outbound-only, no Docker. Prompts for the short-lived user — outbound-only, no Docker. Prompts for the short-lived
@ -383,6 +392,10 @@ case "$cmd" in
;; ;;
esac esac
;; ;;
platform)
shift
exec "$ROOT/commands/platform.sh" "$@"
;;
runner) runner)
shift shift
sub="${1:-}" sub="${1:-}"

205
commands/platform.sh Executable file
View file

@ -0,0 +1,205 @@
#!/usr/bin/env bash
# rig platform — what is this machine? Calculated at run time, stored nowhere.
#
# Read-only in the strongest sense rig has: it reads /proc, uname,
# /etc/os-release, df and systemd-detect-virt, and writes NOTHING, ever. That
# is the design, not an implementation detail — specs change without rig doing
# anything (RAM added, root disk resized, unattended-upgrades patching the
# kernel), so a stored spec is stale the moment the machine changes, and
# refreshing one on every run would collide with bootstrap's convergence
# contract ("safe to re-run; a second run changes nothing").
#
# The corollary is worth having deliberately: this runs on a machine rig has
# never converged, and needs no root. It answers "what should I converge this
# into?", not only "what did I converge this into?".
set -euo pipefail
HERE="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)"
# shellcheck source=SCRIPTDIR/lib/users-config.sh
. "$HERE/lib/users-config.sh" # read_role_marker — one reader of /etc/rig/role
die() { printf 'rig-platform: ERROR: %s\n' "$1" >&2; exit "${2:-1}"; }
usage() {
cat <<'EOF'
usage: rig platform
Describes the machine you are on: hostname, OS, kernel, CPU, memory, disk
and virtualization, then rig's own provenance (which rig, when, and the role
marker bootstrap wrote).
Computed at run time from /proc, uname, /etc/os-release, df and
systemd-detect-virt. Writes nothing, needs no root, makes no network call —
so it also works on a pristine Debian box rig has never bootstrapped, where
the provenance block reads 'not bootstrapped'.
EOF
}
# --- args ------------------------------------------------------------------
while [ $# -gt 0 ]; do
case "$1" in
-h|--help) usage; exit 0 ;;
*) die "unknown flag: $1" 2 ;;
esac
done
# One aligned column for every line, so the output diffs cleanly across a
# fleet and reads as one table rather than a log.
field() { printf '%-10s %s\n' "$1" "$2"; }
# --- hostname ---------------------------------------------------------------
# uname -n is the coreutils fallback: `hostname` lives in its own package and a
# minimal image may not carry it, and this command's whole point is running
# before anything has been installed.
HOSTNAME_V="$(hostname 2>/dev/null || uname -n)"
# --- OS ---------------------------------------------------------------------
# THE os-release TRAP: /etc/os-release defines VERSION, NAME and ID, so
# sourcing it in the MAIN shell silently clobbers same-named script variables.
# Every site in this tree sources it in a SUBSHELL instead (bootstrap.sh:305,
# bootstrap-tenant.sh:126, runner-install.sh:88, db.sh:52,
# coolify-backup-install.sh:88), and test/cli.sh greps commands/ to keep it
# that way. Follow the form verbatim.
if [ -r /etc/os-release ]; then
OS="$(. /etc/os-release && printf '%s %s' "${NAME:-}" "${VERSION:-${VERSION_ID:-}}")"
else
OS=""
fi
# --- kernel -----------------------------------------------------------------
KERNEL="$(uname -r) ($(uname -m))"
# --- CPU --------------------------------------------------------------------
# 'model name' is x86's spelling; arm64 /proc/cpuinfo has no such field, so an
# unnamed CPU still reports its core count rather than nothing at all.
CPU_MODEL="$(awk -F': ' '/^model name/ {print $2; exit}' /proc/cpuinfo 2>/dev/null || true)"
CORES="$(nproc 2>/dev/null || true)"
# --- memory -----------------------------------------------------------------
# /proc/meminfo is in kB. MemAvailable is the kernel's own estimate of what a
# new workload could claim (MemFree undercounts badly, reclaimable cache being
# most of a busy box's RAM); it predates every kernel rig targets, but degrade
# rather than print a wrong number if it is missing.
mem_kb() { awk -v k="$1" '$1 == k":" {print $2; exit}' /proc/meminfo 2>/dev/null || true; }
MEM_TOTAL_KB="$(mem_kb MemTotal)"
MEM_AVAIL_KB="$(mem_kb MemAvailable)"
human_kb() { # kB -> IEC, matching df's units below
[ -n "${1:-}" ] || { printf 'unknown'; return 0; }
numfmt --to=iec-i "$(( $1 * 1024 ))" 2>/dev/null || printf '%s kB' "$1"
}
# --- disk -------------------------------------------------------------------
# -P is the one-line-per-filesystem guarantee (a long device name otherwise
# wraps and breaks field positions); -B1 gives bytes, so numfmt renders the
# same IEC units as memory above instead of df's own bare 'G'.
DISK_TOTAL="" DISK_FREE=""
if DF="$(df -PB1 / 2>/dev/null)"; then
DISK_TOTAL="$(printf '%s\n' "$DF" | awk 'NR==2 {print $2}')"
DISK_FREE="$(printf '%s\n' "$DF" | awk 'NR==2 {print $4}')"
fi
human_b() { # bytes -> IEC; falls back like human_kb rather than to 'unknown'
[ -n "${1:-}" ] || { printf 'unknown'; return 0; }
numfmt --to=iec-i "$1" 2>/dev/null || printf '%s B' "$1"
}
# --- virtualization ---------------------------------------------------------
# THE set -e TRAP: systemd-detect-virt exits NON-ZERO on bare metal while
# printing 'none'. That is a normal, correct answer — without the `|| true` a
# bare-metal machine would turn this whole command into a failed run. The
# substitution also swallows the binary being absent entirely (a non-systemd
# box), which lands as 'unknown'.
VIRT="$(systemd-detect-virt 2>/dev/null || true)"
printf '%s\n' "PLATFORM"
field HOSTNAME "$HOSTNAME_V"
field OS "${OS:-unknown}"
field KERNEL "$KERNEL"
field CPU "${CPU_MODEL:-unknown}${CORES:+ ($CORES cores)}"
field MEMORY "$(human_kb "$MEM_TOTAL_KB") total, $(human_kb "$MEM_AVAIL_KB") available"
field DISK "$(human_b "$DISK_TOTAL") total, $(human_b "$DISK_FREE") free on /"
field VIRT "${VIRT:-unknown}"
echo
# --- provenance: READ, never written ----------------------------------------
# The complementary half of the answer — which rig, and when — is decided
# rather than observed, so unlike everything above it IS stored. rig writes it
# during bootstrap; this command only ever reads it.
#
# /etc/rig/manifest is #61 and is NOT implemented yet, so on every machine in
# existence today this block reads 'not bootstrapped'. That degradation is the
# point: the two features are independent and neither blocks the other. The
# parse is the flat key=value shape the manifest is specified to use — the
# same jq-free shape /etc/rig/users and /etc/rig/role already use, parseable
# with `read` on a box that has no YAML parser.
#
# RIG_MANIFEST / RIG_ROLE_MARKER override the paths so the harness can drive
# both the present and the absent case against fixtures, non-root, without a
# real marker on the machine running the tests (repo precedent: the
# RIG_ROLE_MARKER gate in bin/rig, install.sh and users-close-root.sh).
MANIFEST="${RIG_MANIFEST:-/etc/rig/manifest}"
MARKER="${RIG_ROLE_MARKER:-/etc/rig/role}"
manifest_field() { # $1 = key — empty when absent, unreadable or unset
local k v
[ -r "$MANIFEST" ] || return 0
# `|| [ -n "$k" ]` so a manifest whose last line lacks a trailing newline
# still yields that line: read returns 1 at EOF even having filled k/v.
# Same guard parse_users_file uses (lib/users-config.sh:47) — #61's writer
# should not have to know whether this reader tolerates a missing \n.
while IFS='=' read -r k v || [ -n "$k" ]; do
[ "$k" = "$1" ] || continue
printf '%s\n' "$v"
return 0
done < "$MANIFEST"
return 0
}
printf '%s\n' "PROVENANCE"
# Keys are #61's documented schema verbatim — schema/bootstrapped_by/
# bootstrapped_at/converged_by/converged_at — NOT invented ones. #61 keeps
# birth and latest deliberately separate ("is this machine converged by a rig
# that predates the fix?"), so both are reported and neither is inferred from
# the other: CONVERGED answers currency, BOOTSTRAPPED answers provenance.
# Every field degrades independently, so a partial manifest from a future
# schema still renders what it does carry.
if [ -r "$MANIFEST" ]; then
M_SCHEMA="$(manifest_field schema)"
B_BY="$(manifest_field bootstrapped_by)"; B_AT="$(manifest_field bootstrapped_at)"
C_BY="$(manifest_field converged_by)"; C_AT="$(manifest_field converged_at)"
# A manifest with no schema= line is pre-#61; say so rather than render blanks.
if [ -z "$M_SCHEMA$B_BY$B_AT$C_BY$C_AT" ]; then
field RIG "manifest present but carries no recognised fields ($MANIFEST)"
else
# 'not recorded' rather than 'unknown', and it does NOT describe a fresh
# box: #61's writer records both pairs equally at bootstrap, so no writer
# produces a manifest missing converged_* — its absence means the file is
# partial or hand-edited. Deliberately not backfilled from bootstrapped_*,
# because inferring a convergence that never happened is worse than saying
# the record is not there. README and the fixtures pin exactly this.
field CONVERGED "${C_BY:-not recorded}${C_AT:+, $C_AT}"
field BOOTSTRAP "${B_BY:-not recorded}${B_AT:+, $B_AT}"
[ -n "$M_SCHEMA" ] && [ "$M_SCHEMA" != "1" ] && \
field NOTE "manifest schema=$M_SCHEMA is newer than this rig reads (expects 1)"
fi
else
field RIG "not bootstrapped (no $MANIFEST)"
fi
# The role marker is bootstrap's own line — 'role=dev-server
# root-door=closed host=yes join=authkey' — printed as the role plus its
# traits. The example tracks the CURRENT vocabulary (#76's -server/-box role
# suffixes, #77's root-door trait); this command renders whatever fields the
# marker carries, so a pre-rename box still prints its own class= line as-is.
MARKER_LINE="$(read_role_marker "$MARKER")"
if [ -n "$MARKER_LINE" ]; then
ROLE_NAME="" ROLE_TRAITS=""
for kv in $MARKER_LINE; do
case "$kv" in
role=*) ROLE_NAME="${kv#role=}" ;;
*) ROLE_TRAITS="${ROLE_TRAITS:+$ROLE_TRAITS }$kv" ;;
esac
done
field ROLE "${ROLE_NAME:-unknown}${ROLE_TRAITS:+ ($ROLE_TRAITS)}"
else
field ROLE "not bootstrapped (no $MARKER)"
fi

View file

@ -977,6 +977,93 @@ else
echo "skip: runner status/remove/repoint non-root refusals (running as root)" echo "skip: runner status/remove/repoint non-root refusals (running as root)"
fi fi
# ---------------------------------------------------------------------------
# rig platform (#64). Unusually testable for this repo: it needs no root, no
# network and no fixtures, and it WRITES NOTHING — so unlike every other
# command here the harness can RUN it for real on the machine running the
# tests and assert on the actual answer, instead of proving arg-parse
# refusals and grepping the rest.
# ---------------------------------------------------------------------------
check "platform: --help exits 0" 0 "usage:" "$ROOT/commands/platform.sh" --help
check "platform: unknown flag exits 2" 2 "unknown flag" "$ROOT/commands/platform.sh" --nope
check "platform: dispatches through bin/rig" 0 "PLATFORM" "$ROOT/bin/rig" platform
# The real run: exit 0 and every field present, as the running user.
check "platform: runs as this user, exit 0" 0 "PLATFORM" "$ROOT/bin/rig" platform
for f in HOSTNAME OS KERNEL CPU MEMORY DISK VIRT; do
check "platform: reports $f" 0 "$f" "$ROOT/bin/rig" platform
done
# Not just the labels — the VALUES have to describe THIS machine. uname -r and
# the hostname are the two the harness can independently compute and compare,
# which is what separates "it printed a table" from "it read the machine".
check "platform: KERNEL is this kernel" 0 "$(uname -r)" "$ROOT/bin/rig" platform
check "platform: HOSTNAME is this host" 0 "$(uname -n)" "$ROOT/bin/rig" platform
# MemAvailable/df rendered, not left as the 'unknown' fallback: a numfmt or
# /proc parse that silently broke would still print the labels above.
check "platform: MEMORY carries real numbers" 0 "total," "$ROOT/bin/rig" platform
# Provenance degrades on a machine rig never converged — #61's manifest does
# not exist yet, so 'not bootstrapped' is the state of the world today and the
# command must ship complete without it. Both paths driven against fixtures.
PLATWORK="$(mktemp -d)"
# THE INTEGRATION CONTRACT (#61, found in #74 review): these fixtures carry
# #61's documented schema VERBATIM — schema/bootstrapped_by/bootstrapped_at/
# converged_by/converged_at. An earlier draft of this reader invented `version`
# and `bootstrapped`, which no writer would ever have produced: the command
# would have rendered 'unknown' forever the day #61 landed, and nothing here
# would have said so. Keep these keys in step with #61; that is the point.
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2026-07-19T14:24:51Z\nconverged_by=0.6.0\nconverged_at=2026-08-02T09:11:03Z\n' > "$PLATWORK/manifest"
check "platform: no manifest reads 'not bootstrapped'" 0 "RIG not bootstrapped" \
env RIG_MANIFEST="$PLATWORK/absent" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
check "platform: no role marker reads 'not bootstrapped'" 0 "ROLE not bootstrapped" \
env RIG_MANIFEST="$PLATWORK/absent" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
# A manifest that DOES exist is read, never written — the forward-compatible
# half, so #61 landing needs no change here.
check "platform: reads #61's converged_by/at" 0 "CONVERGED 0.6.0, 2026-08-02T09:11:03Z" \
env RIG_MANIFEST="$PLATWORK/manifest" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
check "platform: reads #61's bootstrapped_by/at" 0 "BOOTSTRAP 0.4.0, 2026-07-19T14:24:51Z" \
env RIG_MANIFEST="$PLATWORK/manifest" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
# A FRESH bootstrap carries both pairs with EQUAL values — #61 is explicit
# ("On a fresh machine both pairs are written with equal values"); rule 2 only
# suppresses converged_* churn on a later same-version re-run. So equal dates
# are the never-re-converged case and must render as themselves, not be
# special-cased into looking unset.
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2026-07-19T14:24:51Z\nconverged_by=0.4.0\nconverged_at=2026-07-19T14:24:51Z\n' > "$PLATWORK/manifest-fresh"
check "platform: a fresh bootstrap shows both pairs equal (#61)" 0 "CONVERGED 0.4.0, 2026-07-19T14:24:51Z" \
env RIG_MANIFEST="$PLATWORK/manifest-fresh" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
# A manifest missing converged_* is therefore NOT a fresh box — no writer
# produces that — so it is partial or hand-edited. Degrade loudly rather than
# backfilling from birth, which would invent a convergence that never happened.
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2026-07-19T14:24:51Z\n' > "$PLATWORK/manifest-partial"
check "platform: a partial manifest says so, never infers from birth" 0 "CONVERGED not recorded" \
env RIG_MANIFEST="$PLATWORK/manifest-partial" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
# A newer schema renders what it recognises and says the rest is unreadable,
# rather than pretending a partial read is the whole truth.
printf 'schema=2\nbootstrapped_by=9.9.9\nbootstrapped_at=2027-01-01T00:00:00Z\n' > "$PLATWORK/manifest-v2"
check "platform: a newer schema is named, not silently half-read" 0 "schema=2 is newer" \
env RIG_MANIFEST="$PLATWORK/manifest-v2" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
# A manifest carrying none of #61's keys is reported as such — the pre-#61 or
# corrupt case, distinct from both 'absent' and 'read fine'.
printf 'somethingelse=1\n' > "$PLATWORK/manifest-alien"
check "platform: an unrecognised manifest is not read as empty" 0 "no recognised fields" \
env RIG_MANIFEST="$PLATWORK/manifest-alien" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
# ...including one whose last line has no trailing newline: `read` returns 1 at
# EOF even having filled the variables, so an unguarded loop drops that line
# silently — the timestamp would vanish while the version still rendered. #61's
# writer must not have to know this reader's tolerances (found in #74 review).
printf 'schema=1\nbootstrapped_by=0.4.0\nbootstrapped_at=2026-07-19T14:24:51Z' > "$PLATWORK/manifest-nonl"
check "platform: reads a manifest with no trailing newline" 0 "BOOTSTRAP 0.4.0, 2026-07-19T14:24:51Z" \
env RIG_MANIFEST="$PLATWORK/manifest-nonl" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform
printf 'role=dev class=human host=yes join=authkey\n' > "$PLATWORK/role"
check "platform: renders the role marker's traits" 0 "dev (class=human host=yes join=authkey)" \
env RIG_MANIFEST="$PLATWORK/absent" RIG_ROLE_MARKER="$PLATWORK/role" "$ROOT/bin/rig" platform
# The defining property: it writes NOTHING. Not the manifest it just reported
# missing, not the marker, not anything else in the fixture directory — the
# whole design rests on this, so assert it rather than trust it.
env RIG_MANIFEST="$PLATWORK/absent" RIG_ROLE_MARKER="$PLATWORK/absent" "$ROOT/bin/rig" platform >/dev/null 2>&1
check "platform: writes nothing (no manifest created)" 1 "" test -e "$PLATWORK/absent"
rm -rf "$PLATWORK"
check "bare users shows usage, exit 2" 2 "usage:" "$ROOT/bin/rig" users check "bare users shows usage, exit 2" 2 "usage:" "$ROOT/bin/rig" users
check "users: bad subcommand exits 2" 2 "usage:" "$ROOT/bin/rig" users frobnicate check "users: bad subcommand exits 2" 2 "usage:" "$ROOT/bin/rig" users frobnicate