docs: box help matches what grant/revoke now mutate (#101 review)

Round 2 changed the incus-admin path from a skipped group step to a real
`usermod -aG incus`, but `bin/box` help still described the superseded
design — telling operators the group step is a no-op that will not happen
(it does) and that a bare revoke has no membership to drop (it drops one).

The help is the pre-run contract: it is what an operator reads to decide
whether to run the command at all, so prose that denies a mutation the
command performs is the same class of defect the rest of this PR exists to
remove. Both sections now describe the current behavior, and `help revoke`
carries the consequence the operator would otherwise discover: once `incus`
is gone, a later `gpasswd -d <user> incus-admin` lands them in NEITHER
group, so grant's "no re-grant needed" holds only while they hold `incus`.

Pinned in both directions — the current sentence must be present and the
superseded one absent — so the prose cannot drift from the scripts again.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
dan-claude-bot 2026-07-19 17:13:24 +00:00
parent 8598cf96fe
commit 7067fb03b0
2 changed files with 30 additions and 6 deletions

20
bin/box
View file

@ -542,9 +542,12 @@ What it converges, idempotently (safe to re-run, and re-run after upgrades):
rides snapshots, 'box export' rides backups — #70)
· installs the box-net profile into their project
An incus-admin member is provisioned too, not refused (#99): the group step
is a reported no-op — they already hold more — and everything else converges,
so they finally have a project of their own. What it is not is a confinement:
An incus-admin member is provisioned too, not refused (#99): they are added
to 'incus' like anyone else — not a new privilege, since incus-admin already
opens the daemon, but the key to a FILE, because incus-user's socket is group
'incus' mode 0660 and nothing below can provision them without it. Everything
else converges, so they finally have a project of their own. What it is not
is a confinement:
incus-admin wins at the socket, so the restrictions are a default placement
they can step outside at will, and their own client keeps resolving to the
admin socket (and the default project) until incus-admin is taken away. The
@ -569,9 +572,14 @@ recreate the project, unhardened, afterwards — measured, not theoretical),
then their boxes, images, project, private bridge and trust-store
certificate are removed — irreversible, so it asks first.
On an incus-admin member there is no membership to drop, so a bare revoke is
a named no-op and --purge unmakes the provisioning without ending any access:
only 'gpasswd -d <user> incus-admin' does that, and revoke says so.
On an incus-admin member a bare revoke takes back the 'incus' membership that
grant added — reported as 'partial:', because it ends no access: incus-admin
still opens every project on this host. (One who was never granted is a named
no-op instead.) Mind what that leaves behind: with 'incus' gone, a later
'gpasswd -d <user> incus-admin' drops them into NEITHER group and their ready
project becomes unreachable — grant's "no re-grant needed" holds only while
they still hold 'incus'. --purge unmakes the provisioning the same way. Only
'gpasswd -d <user> incus-admin' ends their access, and revoke says so.
box revoke dev1 # take the tier; their boxes keep running
box revoke dev1 --purge # ...or end their sessions and delete everything

View file

@ -479,6 +479,22 @@ check "box revoke with no user exits 2 (via the CLI table)" 2 "usage: box revoke
check "help grant names the hardened network" 0 "boxnet" "$BOX" help grant
check "help revoke names --purge" 0 "purge" "$BOX" help revoke
# The help is the PRE-RUN CONTRACT: an operator reads it to decide whether to
# run the command at all, so it must not promise a mutation that will not
# happen (or deny one that will). Round 1 of #101 changed what grant/revoke
# mutate for an incus-admin member and left this prose describing the
# superseded design — these pins are why that cannot happen silently again.
# Both directions: the current sentence must be present, and the superseded
# one must be gone.
check "help grant: the admin member's group step is a real add, not a no-op" \
0 "like anyone else" "$BOX" help grant
check "help revoke: a bare revoke of a granted admin member is 'partial:'" \
0 "partial:" "$BOX" help revoke
check "help grant no longer calls the admin group step a no-op" 0 "" \
bash -c '! "'"$BOX"'" help grant | grep -q "reported no-op"'
check "help revoke no longer claims there is no membership to drop" 0 "" \
bash -c '! "'"$BOX"'" help revoke | grep -q "no membership to drop"'
# Load-bearing lines a daemon-free run cannot exercise — grepped so a deleted
# guard cannot ship green (the house test discipline).
# The expose guard must fire before ANY incus call in cmd_expose: line order.