json_string_array took the first "Tags" array anywhere in the document,
justified by Self-before-Peer field order. An untagged Self omits the key
entirely (Go omitempty), so the match fell through into Peer and returned
a peer's tag — false-refusing every login join and false-verifying
untagged authkey joins on any tailnet with a tagged node. Extract Self by
brace counting (Location nests an object, so slicing to the next key
would truncate) and read the array inside it.
Refs #160
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The first README fence becomes two labelled, copyable commands — the
RIG_REF=main development command first (byte-identical to before, so the
#89 guard at test/cli.sh:514 still passes) and the bare latest-release
command below it, each preceded by a comment line naming its channel.
Refs #149
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The security-trade paragraph lands verbatim in the README (its twin is in
rig-templates' README, per the issue's docs task), with the 2026-07-24
ruling recorded in place.
The suite drives the whole new surface without a network: the family-suffix
dispatch and hard cut, the marker guards firing with the registry
unreachable, the unknown-role refusal listing the resolved source, every
malformed-definition refusal named by key, DIR-vs-REF identity through a
stubbed curl (tags-first precedence pinned), the renderer's box#80 guard,
the one-line pin, and template-lint's whole gate.
Override via RIG_TEMPLATES_REF when the drill was pointed somewhere; else
the pin read from the INSTALLED candidate tree — what actually landed,
never this checkout's copy.
bootstrap-tenant.sh is one mechanism parameterized by a registry definition
(template.env parsed against the allowlist, install.sh run as root with the
tenant in its environment, creds.md spliced into the shared context
skeleton). staging-box stays in-tree — mechanism-adjacent, no agent. The
dispatch in bootstrap.sh moves to the '-box' family-suffix rule so a
template added to the registry is mintable with zero code changes here; an
unknown role's refusal lists what the resolved source actually contains,
naming the source. lib/tenant-config.sh — the per-tenant case-arm table —
is deleted: the hard cut, #76's discipline.
The role-template registry moves out of rig's tree (heavy-duty/rig-templates,
ruled 2026-07-24: pinned to the rig version by default, overridable per mint).
This is the schema-and-resolution half rig keeps: RIG_TEMPLATES_DIR >
RIG_TEMPLATES_REF > the in-tree RIG_TEMPLATES_PIN (the BOX_RELEASE
discipline), template.env parsed against an allowlist and never sourced,
every refusal naming the failing key, and the lint the registry repo's CI
will run on every definition.
The pin currently names the registry's pre-seed head; it bumps to the seeded
tree in this PR's course (the seed PR is the other half of the build task).
install from this checkout via RIG_INSTALL_SOURCE, assert what landed,
a converging re-run proven by an empty diff, uninstall --all ending in
the absence assert (! -e AND ! -L). test/install-lifecycle.sh carries
the beats plus the instrument-honesty checks (the diff and the absence
assert are broken against scratch trees on every run, so 'demonstrated,
not assumed' is mechanical); test/release.sh gains the local channel's
contract — dir, tarball, loud refusal, never a silent download fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both fixture digests are computed outside the implementation, so a refactor
that changes the prefix, the hash or the slicing renames the whole fleet and
fails here. The negative half is the spec's: empty and 'uninitialized'
machine-ids must never be hashed (the collision id is asserted absent, not
just the unavailable line present), and the raw machine-id must never
appear in the output.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Option A per triage's normalization on #95: derive, never mint. The
derivation is pinned — sha256("rig-machine-id:<machine-id>"), first 32 hex
as 8-4-4-4-12 — and the prefix is the contract that keeps the id
uncorrelatable with other tools' derivations, per machine-id(5)'s own
guidance not to expose the raw value.
Missing, empty and 'uninitialized' machine-id files degrade loudly to an
'unavailable (reason)' line: hashing nothing would hand every such machine
the same identity, the worst possible failure for an identity field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The round's one blocking point, from all three reviewers: drill.sh:387
still said the exported pin beats "box's default (main)", the exact
expired claim #133 exists to remove — bootstrap's unset path has been
BOX_REF="${BOX_REF:-$BOX_RELEASE}" since rig#103 landed. Rewritten like
the other two spots: the why survives, the fallback is named as the
BOX_RELEASE pin, main is gone. The pre-flight hazard-class block
(302-306) stays untouched per the spec; the wide grep
(default.*main|main.*default over drill/, drills/, README.md) now finds
only that protected block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
drill/README.md and drill.sh's assert_installed_from comment claimed both
installers default to main 'until #103 lands'. #103 landed (box installs
the BOX_RELEASE pin), and rig's install.sh has resolved the latest release
since #32 — six days before the README was written. The behaviour was
always right; the reason given for it expired, so both places now carry
the durable reason: a sane default is what makes an unstated ref
invisible, and the clean-looking record it produces proves nothing about
the candidate.
The --box-ref example (three copies: README, file header, --help — the
header IS the --help via sed 2,33p, so the window's line count is
preserved) moves from release/0.10.0 to the 0.9.0 tag: after #103 the box
that ships is the BOX_RELEASE tag, so a release/… branch is the wrong pin
for box while staying right for rig. The hazard-class citations at the
pre-flight refusal are untouched — they cite #103 as the hazard, not as a
pending change.
Closes#133
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>