From 7067fb03b0fb44d010a08f7ab268ea042209223c Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sun, 19 Jul 2026 17:13:24 +0000 Subject: [PATCH] docs: box help matches what grant/revoke now mutate (#101 review) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 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 --- bin/box | 20 ++++++++++++++------ test/cli.sh | 16 ++++++++++++++++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/bin/box b/bin/box index 3150444..0a24edd 100755 --- a/bin/box +++ b/bin/box @@ -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 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 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 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 diff --git a/test/cli.sh b/test/cli.sh index d4e2a73..e4f38c5 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -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.