Skip to content

feat: invite and accept, the CLI from docs/scenarios.md - #11

Merged
cablehead merged 6 commits into
mainfrom
feat/invite-accept-cli
Aug 18, 2026
Merged

feat: invite and accept, the CLI from docs/scenarios.md#11
cablehead merged 6 commits into
mainfrom
feat/invite-accept-cli

Conversation

@cablehead

@cablehead cablehead commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Implements the CLI worked out in docs/scenarios.md. Breaking: every command
that names a peer has changed.

The shape

An invitation and its acceptance are two halves of one handshake. One side
says hi, be friends; the other says yeah, be friends. The old names hid that:
grant-token, pin, and add-peer were three commands doing two things, and
which one you reached for depended on who happened to dial.

before                             after
------                             -----
ticket                             key
grant-token --label <l>            invite --as <l>
pin <key> --label <l>              invite <key> --as <l>
add-peer <ticket>                  accept <key>
  (no way to present a token)      accept <invite>
remove-peer <ticket>               forget <peer>
daemon -a <t> --enroll <code>      daemon --accept <invite>

An invitation is one value

invite now prints <key>.<code>: who to dial, and the code that admits you.
They were always used together and always sent together.

This is what ticket should have been. Daemon::ticket() was
endpoint.id().to_string() with a TODO: proper ticket serialization above it.
The TODO is gone and so is the word: a bare key is just a key.

accept takes either form. A bare key covers the host-attested case in ADR
0003, where nothing secret can travel to the peer.

A running daemon can now take up an invitation

--enroll only existed on pai-sho daemon, so a laptop already running a
daemon had no way to use a code it was handed. Its options were to restart, or
to give up on codes and have both sides pin each other by key. accept works
at any time.

Naming is local

Both invite and accept take --as, and both are optional. Each side names
the other for its own use. The old --label was mandatory and sat on the
inviter, which is backwards when the inviter is the one serving the port: it
had to pick a name it would never type.

An invitation can carry its grant

invite --expose 8080 attaches the grant to the invitation that justifies it.
Still default deny and still directed at one key: the key is filled in when the
invitation is claimed, instead of being typed twice.

For the build box, that is the whole setup in one command per machine:

# build box
pai-sho invite --expose 8080
# 5hc4bjqfp6...7fd25613dd...

# laptop
pai-sho accept 5hc4bjqfp6...7fd25613dd... --as buildbox
curl http://buildbox.pai-sho:8080

surfaces folded into list

list and surfaces both walked the peer table and printed overlapping JSON.
Now one row per peer carries its name, how it was admitted, what it exposes to
us, and where those ports are bound. SurfaceInfo and BindingInfo are gone.

Every peer has a name

If neither side passes --as, a peer is named after the first 8 characters of
its key. Surfaces answer by name, so an unnamed peer had nothing to answer to.
project --as renames it.

Tests

72 passing, 0.55s, 5 consecutive runs with no flakes.

The live tests drive Request::Invite and Request::Accept, so they exercise
the operator's path rather than reaching past it into PeerManager. Three are
new: an invitation carrying its own grant reaches the port with no expose
step, an unnamed peer still resolves by its short key, and a grant carried by an
invitation names only the claimer.

src/core/invite.rs is pure, with 7 tests on the parse. One parse test asserts
that ticket, grant-token, pin, add-peer, remove-peer, and surfaces
no longer exist.

Docs

docs/scenarios.md is rewritten as what shipped rather than what was proposed.
The build box section keeps the old flow under "The flow before this", because
the reason for the change is the thing worth recording.

ADR 0003 gets a note reconciling its --slug with the --as that shipped. Its
decision is unchanged. It was also the one file in the repo with non-ASCII
punctuation, which is now fixed.

Version

Breaking, so this wants 0.5.0 rather than the current 0.4.2-dev. Not bumped
here, since the release flow owns that.

@cablehead
cablehead force-pushed the feat/invite-accept-cli branch from 48101ef to 644c3c8 Compare August 18, 2026 16:04
@cablehead
cablehead marked this pull request as ready for review August 18, 2026 16:06
@cablehead
cablehead merged commit 70ada90 into main Aug 18, 2026
1 check passed
@cablehead
cablehead deleted the feat/invite-accept-cli branch August 18, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant