Skip to content

Migrate room.join / room.invite onto capability-request / capability-grant-ok #83

Description

@Mearman

Once the generic primitive exists, core/room's own verbs (spec/room.cddl:126-155) become a thin instance of capability-request/capability-grant-ok rather than a parallel hand-built copy of the same flow -- no dual implementation kept around, per the standing no-legacy-shims convention.

Design pass completed (2026-09-14) -- concrete plan below, corrected against the real current code rather than assumed from earlier issue summaries (several "already done" claims turned out to be wire-shape-only; see corrections).

The outer manage-command.verb question -- resolved, not a fork

capability-request/capability-grant (see #117) MUST be sent with the outer manage-command.verb set to the target capability (e.g. "room:member"), identical to every other domain. This is forced by capability-verb's own grammar (tokens.cddl: closed alternation of colon-delimited domain:noun regexes) -- "capability.request" (a dot) cannot legally be an outer verb. Not a stylistic choice.

room.invite -- resolved via a real capability-grant primitive (#117), not left bespoke

Tracked as its own issue (#117) since it's a genuinely separate wire primitive, not part of this migration's own diff.

Concrete migration plan for room.join

CDDL (spec/room.cddl): retire room-join/room-invite; rename room-join-ok to room-join-grant-ok, riding capability-grant-ok's own open tail rather than a free-standing rule (documentation/codegen convenience -- response bytes are unchanged).

Wire impact: request side is a breaking change (old {verb:"room:member", params:{verb:"room.join"}} bytes no longer dispatch); response side is byte-identical. No documented wire back-compat policy exists in this pre-1.0, rapidly-iterating spec -- accept the break, no shim, matching how #58/#63/#64 already broke and regenerated room's wire shape.

TypeScript: new ts/packages/core/src/domain/capability-request.ts (see #78) is the real prerequisite -- this issue is blocked on #78's domain-logic half landing, not just its CDDL. Once it exists:

  • room-client.ts's requestToJoin becomes a thin wrapper over requestCapability; its public signature and RoomJoinResult type stay unchanged, so callers above it (RoomPanel.tsx, use-room-messaging.ts, App.tsx) need no changes.
  • createRoomRouter's "room.join" dispatch branch delegates to the new generic handler with capability: ROOM_MEMBER_CAPABILITY; handleRoomJoin's existing body (bearer check, mintCapabilityToken, members list) becomes the decide callback almost verbatim.
  • Add handleRoomInvite/sendRoomInvite (net new -- wire-mesh itself has zero room.invite implementation today, confirmed by grep) built on Add capability-grant primitive (unsolicited push-grant counterpart to capability-request) #117's capability-grant.

canSatisfy -- confirmed still correctly deferred, confirmed not needed for this migration

Doesn't exist yet, gated behind #85 (generic predicate-list evaluator). Not needed here: room-join admission is either a human decision or the DM auto-approve check, neither of which is "would a held token satisfy this ask" -- and room-join grants are always root-level (no parent), so canGrant isn't load-bearing on the approval path either. Becomes relevant only if a future domain wants capability-request auto-approved based on the approver already holding a broader delegation -- a separate use case.

Two-repo follow-up (agent-comms, scope only -- not this issue's own work)

src/core/room-lifecycle.ts (joinRemoteRoom, the DM-flow join send, inviteToRoom) and src/core/room-protocol.ts (handleRoomJoin/admitRoomJoin/handleRoomInvite, registered by literal string in room-router.ts) would all need rewiring onto the new wire-mesh-core primitives once this lands. Concretely closes a real, currently-inconsistent gap: agent-comms' connect_request already has receiver-side expiry (its own PR #84) but pendingRoomJoins does not (confirmed: bare Promise, no timer) -- adopting the new generic handler unifies both admission gates onto one mechanism. Tracked as its own follow-up issue in agent-comms once #78/#83/#117 land in wire-mesh.

Verification plan

  • just spec && just conformance -- new/changed vectors: replace room-join vectors with capability-request vectors targeting room:member; add a capability-grant vector (covering retired room-invite); add an expired-capability-request vector exercising Add optional valid-until to capability-request params #82's obligation.
  • cargo test -p wire-mesh-wire -- verify (not assume) no new Rust structs are needed, per the precedent that the six room verbs already ride manage-request-frame's untyped params/response catch-all.
  • New ts/packages/core/test/capability-request.test.ts (mirrors room-token-verification.test.ts): ungated ask, valid-until expiry refusal, receiver-side timeout auto-reject, generic grant/deny round trip.
  • room-client.test.ts currently has zero invite coverage -- add real coverage as part of this migration, not just parity.
  • just build && just test && just lint.

Blocked by: #78 (domain-logic half), #81 (receiver-side timeout, reopened -- generic mechanism doesn't exist in wire-mesh yet), #82 (valid-until, closed at the wire level, needs the handler from #78 to actually check it), #117 (capability-grant, for the room.invite half).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions