Commit graph

10 commits

Author SHA1 Message Date
dan-claude-bot
0ff520c850 fix: dropping the box role revokes through box, not behind its back
`users apply` converged group `incus` with a bare `gpasswd -d`, the same
move it makes for `rig-admin` and `rig`. Those two are rig's. `incus` is
box's, and `box revoke` does strictly more with it: it says out loud that
supplementary groups are read AT LOGIN, so a session the dropped operator
already holds keeps the Incus socket until that session dies, and it hands
over `loginctl terminate-user <user>` as the remedy.

rig logged "removed <user> from incus" and moved on. An operator who
dropped someone from the users file and watched apply succeed believed the
VM access was gone — and was wrong for as long as that user held a session.

Both removal paths — the per-user convergence loop and the dropped-user
sweep — now route the incus group through one `drop_incus` helper that
calls `box revoke`, keeping a single owner for the group. Never `--purge`:
that deletes the user's boxes, images and project, and destroying someone's
running machines is not a convergence step; it stays an explicit admin act.

The exit code is not trusted (the #12 lesson bootstrap already applies to
box's installer): a revoke that returns 0 with the membership still
standing has not closed the socket, so the effective state is checked and
rig falls back to removing the group itself — as it also does on a host
where box is not installed. Every fallback path carries the session warning
in rig's own voice, because the silence was the bug. The absent-group case
needs no new guard: `id -nG` cannot report a group that does not exist, so
the existing `in_group` test at both call sites is already false on a
host=no box or one where `box setup-host` never ran.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 19:04:22 +00:00
Daniel Marin
67386b4303
Merge pull request #60 from dan-claude-bot/fix/host-no-bare-socket
fix(users): the host= marker gates the box role, not the incus group
2026-07-19 20:02:12 +01:00
dan-claude-bot
7614c1c171 docs: the 0.1.0 notes carry #39/#41 — the getent-$HOME installer fix shipped without its entry
Both reviewers caught it: #41 merged after the changelog convention
landed (#40) and skipped its entry; the release PR is the last gate
before the section becomes the permanent release body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:32:56 +00:00
dan-claude-bot
a702f1f8be release: 0.1.0
rig's first release: VERSION leaves 0.1.0-dev and the Unreleased section
is stamped with today's date. The surface was drilled twice on 2026-07-19
(offline suites, tenant guests on real incus via box, db round-trip, a
real GitHub runner lifecycle, coolify install) and the ceremony itself
rehearsed end to end on a scratch fork — including the guard that made
this very PR possible (#44).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 17:32:56 +00:00
Claude
b8e8e79b87 fix(users): the host= marker gates the box role, not the incus group
users apply consulted the host= trait only when group incus was ABSENT,
so a host=no or marker-less box that nonetheless carried the group handed
box-role users a bare `usermod -aG incus` — the socket with no tier, which
incus-user answers by lazily building an unhardened project under whoever
opens it.

The marker now decides in both directions through one pure gate,
assert_marker_hosts_vms, so the verdict is identical whether or not the
group exists. The marker wins over the machine deliberately — it is the
box's declared identity and every other host= decision already treats it
as authoritative — but not silently: when the group exists and the trait
disagrees, the skip names the contradiction and rig bootstrap as the fix.

Closes #58

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 17:29:20 +00:00
dan-claude-bot
700e2185eb fix: pull-requests scope for the door's two PR-API calls; docs catch up
The cast-twin blocker (claude-bot): a declared permissions: block zeroes
unspecified scopes, so the label read and the bump fallback's gh pr
create could only 403 — every genuine ceremony would end red at the
label check. pull-requests: write added, consumers named. CONTRIBUTING
step 3 and the changelog entry now tell the shipped story: push-to-main
door, event.before interlock, self-re-arm, manual-path bump stays the
operator's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:34:29 +00:00
dan-claude-bot
4ce1ab50aa feat: merging a release-labeled PR is the release (#47)
The rig twin of heavy-duty/box#96, from the release-ceremony retro: the
tag was a separate, manual, silent-when-forgotten step, and a forgotten
tag produces no red X — the worst failure shape. The ship decision
already lives in the release PR; merging it is "ship". After that,
tagging is transcription, and transcription belongs to machines.

release.yml now also fires on pull_request closed into main, gated on
merged AND the `release` label. The job asserts in order, each fail-loud
and creating nothing: VERSION at the merge commit is non--dev; VERSION
changed in THIS PR (base vs merge — the interlock that fails a
mislabeled ordinary PR); the changelog section for that version extracts
non-empty via the existing changelog_section from release-lib.sh; and no
tag or release exists yet. Then, in the same job, it API-creates the tag
at the merge commit and publishes the release with the extracted notes.
Same-job is load-bearing: a GITHUB_TOKEN-created tag does not fire the
tag-push trigger, so the publish must live next to the tag and the
fallback job cannot double-publish; the nothing-exists assert covers a
manual race. The tag-push path survives verbatim as the documented
manual fallback and backfill, and CONTRIBUTING's Releasing section now
reads merge-is-ship with the manual tag as fallback.

test/release.sh pins the merge path in the house grep-pin style: the
merged+labeled gate, the four asserts, the same-job tag+publish (awk
from release-on-merge: to EOF), the asserts-precede-the-tag ordering,
and the surviving tag-push trigger.

Fixes #47

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 15:19:55 +00:00
dan-claude-bot
589854ae0b fix: the release suite accepts the ceremony's own tree (#44)
test/release.sh demanded a literal '## Unreleased' heading extracting
non-empty with '#32' in it — all false by construction on the very tree
the release PR produces, so the first real 'release: 0.1.0' PR turned CI
red and the ceremony blocked itself. Both fork rehearsals missed it: they
tag a branch, which runs release.yml and never ci.yml. The guard now
asserts its actual purpose — the TOP section, whatever its name, extracts
non-empty via the exact function release.yml runs — and passes on both
legitimate tree states (verified on main's shape and on a stamped copy).

Fixes #44

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 13:48:41 +00:00
dan-claude-bot
e72663ef62 fix: headless credential prompts refuse loudly, naming their variable (#42)
A bare 'read -rsp' with no tty exits non-zero and set -e ends the script
with no output at all: the release drill watched 'rig runner remove' exit 1
in complete silence, and a guest bootstrap stop mid-log the same way. Every
prompt now checks for a tty first and dies naming the variable that
unblocks an unattended run; every read is || die-guarded so EOF at a real
prompt also gets a last word. The no-bare-read test swept up runner
repoint's two prompts, which the issue had not counted.

Fixes #42

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 12:15:08 +00:00
dan-claude-bot
b6205d81c5 feat(release): CHANGELOG.md and release.yml — a tag becomes the release it claims (#32)
The PR-then-tag half of box#83's flow, rig's side. CHANGELOG.md starts
with an Unreleased section (feature PRs land their entry as part of the
PR — box's convention, now written into CONTRIBUTING alongside the
release ritual). On a tag push, release.yml asserts the bare tag equals
the tree's own VERSION — a mismatch fails loudly and creates nothing —
then creates the GitHub release with that version's changelog section as
the body, extracted by changelog_section in .github/scripts/release-lib.sh:
one function, sourced by the workflow and driven by test/release.sh
against fixtures and the shipped CHANGELOG.md itself. No assets — for a
pure-bash tree, the tag's source tarball IS the package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:57:13 +00:00