diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-08-14 01:27:57 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-08-14 01:27:57 +0200 |
| commit | 8f6e2f724fd24a077de11d4a3b3ae069d369324d (patch) | |
| tree | 25b06d11f74b7b73e2056e1bb75c1b64af6c9650 /tmp-decisions.md | |
| parent | f15efd23f66c521ca9206789482bb38e7326eeb4 (diff) | |
| download | meshbay-8f6e2f724fd24a077de11d4a3b3ae069d369324d.tar.gz | |
feat(node): operator surface — member list, invite, revoke, unpin over SSH
A node admits people from its own roster, and until now a headless operator had
no way to put anyone on it: pairing worked from the CLI, everything else needed a
browser on a machine that does not have one. Absorbs milestones 14.3/14.4.
member list who is admitted, role, status, when and how pinned
member invite <username> one-time code; the node wraps the key when they
connect, so nobody has to be online then
member revoke <username> stop serving them the key
member unpin <username> forget the pin so they can pair again after a reset
All of it goes through the daemon's loopback API with the per-run session token
(11.5.3) — _daemon_api() in daemon.py, which also replaced three hand-rolled
urllib blocks. `status` deliberately still reads the keystore, config and roster
directly, so it works while the daemon is stopped.
Two things the commands say out loud, because getting them wrong is silent:
- revoke ends by telling the operator to rotate the key. The ex-member stops
receiving it on their next connection, but they hold the current one, and
"revoked" reads like it took the key back.
- revoke/unpin refuse a username the roster does not know instead of acting on
nobody. A typo must not look like success.
Code lifetimes now differ by what the act is: 7 days for an invitation, which
crosses a human conversation and gets answered whenever someone reads their
messages, and 24 h for operator pairing, which is typed during the SSH session
that printed it. Both configurable ([node] invite_ttl_hours, pair_ttl_hours). A
day was long enough for the second and not for the first — a code that dies over
a weekend means finding a browser to issue another one.
The roster is also in the local admin UI, escaped: usernames come from the hub
and land on the page that can re-key groups and read the audit log, so H2's rule
covers them exactly as it covers filenames.
Verified by driving the real CLI against a stub daemon over a socket, which is
how the "known: <nothing>" bug in the not-found path turned up.
Tests: 89 node here (roster, endpoints, CLI routing, TTL config).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'tmp-decisions.md')
0 files changed, 0 insertions, 0 deletions