Skip to content

docs(product): design contract-wide id→handle request cutover (RIG-2751) - #645

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-server/rig-2751-handle-cutover-design
Open

docs(product): design contract-wide id→handle request cutover (RIG-2751)#645
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-server/rig-2751-handle-cutover-design

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a @handle; the server resolves it to an account id exactly as the existing from_handle field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND.

Scope

Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across comms.proto (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and agent_gateway.proto (DespawnPeer), plus the three new CommsCallRequest arms held in draft on #628 (re-authored handle-first here). agents_spawn_peer already takes a handle — the precedent this mirrors.

Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5.

Design-critic pass folded

Red-teamed by CritiqueHandleCutover; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members).

Open questions (freeze-gate decisions for Matt)

  • OQ-1 — field naming (taste).
  • OQ-1b — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork.
  • OQ-2 — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles.
  • OQ-3 — GetRoster vantage field name (taste).
  • OQ-4 — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork).
  • OQ-5 — response handles: ship the DL-NEW-2 additive author_handle sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer.
  • OQ-6 (NEW, load-bearing) — member-resolution visibility scoping: (a) viewer-scoped AccountsByHandles [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling.

Ledger-impact

DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze.

Ref: RIG-2751

Co-authored-by: Matt Wilkinson matt@rigel.build

Design record for the contract-wide id→handle cutover of the compass request surface (RIG-2751), per Matt's Option-A ruling: every request-input account field takes a `@handle`; the server resolves it to an account id exactly as the existing `from_handle` field does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND.

## Scope

Field inventory verified complete against the current tree (all 7 proto files grepped): the request-input account fields across `comms.proto` (CreateChannel members, UpdateChannelMembers add/remove/subscribe/unsubscribe, ReparentAgent agent+new-parent, SetChannelPolicy owner, GetRoster vantage, OpenAgentWorkspace, CreateAgent parent) and `agent_gateway.proto` (DespawnPeer), plus the three new `CommsCallRequest` arms held in draft on #628 (re-authored handle-first here). `agents_spawn_peer` already takes a handle — the precedent this mirrors.

Explicit non-goal (a decided boundary, not a reflex flip): response/stored/event fields keep ids — handing a handle back on a response pushes resolution onto the client, the opposite of the ruling. The one exception is surfaced in OQ-5.

## Design-critic pass folded

Red-teamed by `CritiqueHandleCutover`; the architecture (edge-not-store resolution, in-band NOT_FOUND, dual-vantage-by-emptiness, T1-first sequencing) survived. Seven oracle-safety and grounding improvements folded: a per-handler NOT_FOUND remap table (two handlers today leak an unknown-vs-foreign oracle via PermissionDenied); T4's ordering preserves the lifecycle constant-query-shape bar; GetRoster's invisible-vantage posture is now defined, not inherited; the agent ListMessages author-id fence named as the concrete non-goal exception; error names all unresolved handles; a bounded skew window stated; and two load-bearing grounding corrections (the store returns ErrInvalidArgument, not NOT_FOUND, for unknown members).

## Open questions (freeze-gate decisions for Matt)

- **OQ-1** — field naming (taste).
- **OQ-1b** — field numbering: a CONFIRM of DL-186's rename-in-place, not an open fork.
- **OQ-2** — repeated-field NOT_FOUND: a CONFIRM of atomic-failure, error naming all unresolved handles.
- **OQ-3** — GetRoster vantage field name (taste).
- **OQ-4** — compass.proto admin lane: extend the ruling to the 3 admin fields or keep ids (real scope fork).
- **OQ-5** — response handles: ship the DL-NEW-2 additive `author_handle` sibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer.
- **OQ-6 (NEW, load-bearing)** — member-resolution visibility scoping: (a) viewer-scoped `AccountsByHandles` [rec — the only option consistent with the oracle posture] vs (b) unscoped; T2's resolver signature depends on this ruling.

## Ledger-impact

DL-NEW-1 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-NEW-2 (responses/stored/events keep ids; display handles only as additive sibling fields). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering). Next-free ids assigned at freeze.

Ref: RIG-2751

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@linear-code

linear-code Bot commented Aug 26, 2026

Copy link
Copy Markdown

RIG-2751

@github-actions

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-2751-hand.compass-eng-docs.pages.dev

Deployed from compass-server/rig-2751-handle-cutover-design at 40da6b8.

Changed pages:

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