`mosaic auth enroll | assign | list | default` (W-F5). Until now a host had one
account per harness, so an author seat and a reviewer seat were the same
principal wearing two names, and a review carried out under that arrangement is
self-review. Bundles under ~/.mosaic/auth/<harness>/<bundle>/ are what a seat's
profile.json points at, so two seats on one host can hold genuinely different
accounts.
Enroll does not reimplement any harness's login. It creates the bundle
directory owner-only, points the harness's own home at it by environment, runs
the harness, and then checks what landed: credential present, permissions
tightened, and the account recorded. Claude is reached through
CLAUDE_SECURESTORAGE_CONFIG_DIR rather than a symlink because it writes by
rename(2), which replaces a symlink instead of following it. --no-login prints
the environment for an operator who would rather run the login themselves.
The check worth naming is identity: enroll reads the account back out of what
the harness wrote and refuses quietly to accept a bundle named for one account
that holds another. That mistake is otherwise silent -- an operator enrolling
the reviewer bundle logs in out of habit as the author, both seats collapse to
one principal, and nothing else in the system notices.
Assign re-parses a seat's profile before rewriting its bundle, so an already
broken profile is reported here rather than re-serialized into something that
looks repaired and still fails at launch. An unenrolled bundle is assigned but
said out loud, because the seat will refuse to launch until the account exists.
registerAuthCommand now returns its Command so these local verbs can hang off
it. They never talk to the gateway and work on a host where it is down.
41 tests. Each of the load-bearing checks was mutation-tested: nine mutations,
each killing exactly the one test that covers it.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01WYgWocp36goy8hj2ui6ps1