docs(product): design contract-wide id→handle request cutover (RIG-2751) - #645
Merged
mattwilkinsonn merged 8 commits intoAug 27, 2026
Merged
Conversation
|
Compass engineering docs preview: https://compass-server-rig-2751-hand.compass-eng-docs.pages.dev Deployed from Changed pages: |
rigel-mintaka
force-pushed
the
compass-server/rig-2751-handle-cutover-design
branch
from
August 27, 2026 02:20
40da6b8 to
df32e04
Compare
rigel-mintaka
changed the base branch from
main
to
compass-server/dedup-dl264-collision
August 27, 2026 02:20
rigel-mintaka
force-pushed
the
compass-server/rig-2751-handle-cutover-design
branch
from
August 27, 2026 02:23
df32e04 to
3edf871
Compare
rigel-mintaka
force-pushed
the
compass-server/rig-2751-handle-cutover-design
branch
from
August 27, 2026 03:01
7059a7b to
ec87cb3
Compare
rigel-mintaka
added a commit
that referenced
this pull request
Aug 27, 2026
… (RIG-2751) Matt's ruling on #645: don't backfill (Compass isn't in use yet), collapse the account_handles schema into the single existing 0001_init.sql migration rather than shipping an incremental 0002. Rewrites T0: account_handles is authored directly into 0001_init.sql with accounts.handle authored without its former global-unique constraint; no incremental migration, no backfill step (no deployed rows exist to migrate). Drops the backfill-parity pgtest assertion from the test cycle and updates the Tasks checklist row. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-server/rig-2751-handle-cutover-design
branch
from
August 27, 2026 18:04
ec87cb3 to
780933f
Compare
rigel-mintaka
added a commit
that referenced
this pull request
Aug 27, 2026
…, not a 0001 seed (RIG-2751) Review finding (#645, low): the T0 rewrite said accounts.handle is populated by 'the 0001_init.sql seed', but 0001_init.sql has no INSERT/seed statements — the handle column is populated at runtime by store.CreateUser/CreateAgent (accounts.go:29,81). Re-attributes to the true runtime writers so an implementing agent doesn't hunt for a nonexistent seed. Design conclusion (column retained for display) is unchanged. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
added a commit
that referenced
this pull request
Aug 27, 2026
Round-2 review (#645, low): the prior fix cited accounts.go:29,81 for CreateUser/CreateAgent, but :81 is BootstrapAdmin's INSERT — CreateAgent's is at :254. Corrected to accounts.go:29,254 (both verified: CreateUser func at :13/INSERT :29, CreateAgent func at :234/INSERT :254). Substance unchanged; accurate line pointer. Refs 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>
…into RIG-2751 cutover Matt ratified the RIG-2751 handles/id contract (2026-08-26, "LGTM, can start"). Fold the frozen contract into the cutover record, which previously designed only the wire flip against a flat global handle lookup: - New §"The storage contract": account_handles table + two partial-unique indexes (user/system handles globally unique; agent handles unique per owner), owner-qualified resolution (matt/compass-ux, bare = caller's own owner), in-place rename + reclaim allowed both tiers (no history/reservation), cross-human safety relocated to owner peering (RIG-2796). - Re-spec the edge resolvers for owner-qualified parsing and re-key AgentByHandle / AccountsByHandles onto account_handles. - New T0 migration task (0002_account_handles.sql + backfill + relax accounts.handle global-unique); T2 resolver signature carries callerOwner + parsed QualifiedHandle; T4 despawn handle owner-qualified. - Ledger rows assigned concrete numbers (DL-269 request handle-typed / DL-270 responses keep ids / DL-271 owner-namespaced storage; next-free after the DL-268 dedup this stack sits on). - OQ section: ratification preamble (which forks closed), OQ-7 owner-qualifier grammar added, OQ-5/OQ-6 DL refs + stale signature corrected. Ref: RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
… OQ-5 scoping (RIG-2751) Review round 1 (ReviewPR645disposition): 0 high / 2 medium / 1 low. All fixed: - medium: DL-186 citation was DECISIONS.md:199 (that's DL-129); DL-186 is at :203. Fixed all 3 refs (Global Constraints, Ledger-impact, OQ-1b). - medium: \u00a7GetRoster called AgentByHandle 'global' in a post-flip frame, contradicting the record's own re-key to owner-namespaced. Reworded to 'owner-namespaced but NOT viewer-scoped', keeping the load-bearing no-viewer- scoping point that motivates the post-resolve ListAccounts visibility check. - low: OQ-5 conflated two response-handle mechanisms under DL-270; scoped them (author_handle sibling = DL-270's path; from_handle denorm = control-plane, outside DL-270's field wording). OQ-5 stays open for Matt. Co-authored-by: Matt Wilkinson <matt@rigel.build>
… (RIG-2751) Matt's ruling on #645: don't backfill (Compass isn't in use yet), collapse the account_handles schema into the single existing 0001_init.sql migration rather than shipping an incremental 0002. Rewrites T0: account_handles is authored directly into 0001_init.sql with accounts.handle authored without its former global-unique constraint; no incremental migration, no backfill step (no deployed rows exist to migrate). Drops the backfill-parity pgtest assertion from the test cycle and updates the Tasks checklist row. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…, not a 0001 seed (RIG-2751) Review finding (#645, low): the T0 rewrite said accounts.handle is populated by 'the 0001_init.sql seed', but 0001_init.sql has no INSERT/seed statements — the handle column is populated at runtime by store.CreateUser/CreateAgent (accounts.go:29,81). Re-attributes to the true runtime writers so an implementing agent doesn't hunt for a nonexistent seed. Design conclusion (column retained for display) is unchanged. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
Round-2 review (#645, low): the prior fix cited accounts.go:29,81 for CreateUser/CreateAgent, but :81 is BootstrapAdmin's INSERT — CreateAgent's is at :254. Corrected to accounts.go:29,254 (both verified: CreateUser func at :13/INSERT :29, CreateAgent func at :234/INSERT :254). Substance unchanged; accurate line pointer. Refs RIG-2751 Co-authored-by: Matt Wilkinson <matt@rigel.build>
… + close OQ section (RIG-2751) Matt ruled all remaining wire-cutover open questions (RIG-2751 comment f59001de, 2026-08-27). Fold the rulings into the design record and close the Open Questions section as Rulings, so the record is ready to freeze on merge. - OQ-4 EXTEND: admin/ops lane (SpawnAgent / ProvisionAgentWorkspace / IssueToken on CompassService) flips to handles too — inventory rows 14-16, new task T8. No id-typed request field survives anywhere. - OQ-5 ship-now: additive Message.author_handle sibling; agent fence renders handle-only and drops the id; author_account_id stays on the wire for the UI join — new task T9. - OQ-6 SCOPED: T2 resolver intersects accountVisibleFromWhere; note the Matt-directed D9-widening follow-on as its OWN compass-server change (filed separately), not folded into this freeze. - OQ-1/1b/2/3/7 confirmed at documented defaults (names, rename-in-place, atomic NOT_FOUND, vantage_handle, '/' owner-qualifier grammar). markdownlint 0 errors; design-ledger-gate OK (DL-269/270/271 collision-free).
rigel-mintaka
force-pushed
the
compass-server/rig-2751-handle-cutover-design
branch
from
August 27, 2026 19:39
8f8de1a to
27dc90f
Compare
…ing cites (RIG-2751) Sub-floor review fixes on the OQ-fold record (all doc-only, no design change): - Drop stale (OQ-5) tag from the non-goal forward-looking sentence (OQ-5 is ruled/closed; §6 ships additive author_handle). - Rewrite T2's 'per the OQ-2 working assumption' → 'per the OQ-2 ruling'. - Enumerate account_handle (IssueToken row 16) in T1's final-names list. - T8: split service.go:152 (GetAccount lookup) from :166 (hub.Provision relay) so the cite labels each construct correctly. - T9: tighten Message.author_account_id cite comms.proto:322 -> :332 (the field, not the message-block doc-comment).
mattwilkinsonn
approved these changes
Aug 27, 2026
mattwilkinsonn
deleted the
compass-server/rig-2751-handle-cutover-design
branch
August 27, 2026 21:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 existingfrom_handlefield does; no agent or client UI ever resolves an id; an unresolved handle surfaces as an in-band NOT_FOUND.This PR now carries the full ratified contract. The second commit folds in the storage/format contract Matt ratified 2026-08-26 ("LGTM, can start") after the RIG-2751 negotiation, which the original record (first commit) predated.
The ratified contract (folded, second commit)
account_handles(account_id, handle, owner_user_id NULL)table with two partial-unique indexes: user/system handles globally unique (WHERE owner_user_id IS NULL), agent handles unique per owner (WHERE owner_user_id IS NOT NULL). Handle ownership moves off the single global-uniqueaccounts.handlecolumn.owner/handle(matt/compass-ux); a bare handle is a user/system handle or the caller's own agent. Resolution parses the qualifier at the service edge, resolving in the matching namespace.UPDATEfor both tiers; reclaim allowed for both (no history, tombstone, or reservation). A user rename has zero cascade to agents (they key on the owner's stableaccount_id). Cross-human reclaim safety is the owner-peering authorization edge (RIG-2796), not handle reservation.0002_account_handles.sqlmigration + backfill + relaxaccounts.handleglobal-unique. T2's resolver carriescallerOwner+ parsedQualifiedHandle;AgentByHandle/AccountsByHandlesre-key ontoaccount_handles.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) andagent_gateway.proto(DespawnPeer), plus the three newCommsCallRequestarms held in draft on #628 (re-authored handle-first here).agents_spawn_peeralready 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)
The 2026-08-26 ratification CLOSED the storage/wire/rename forks (now the frozen contract above). The remaining OQs are the wire-cutover forks it did not decide:
author_handlesibling with this cutover (the agent's ListMessages author-id fence is the identified surface) or defer./separator, no nesting, bare-handle collision precedence (user/system wins). Matt confirms the grammar.Ledger-impact
Adds three rows (next-free at authoring, confirm at freeze): DL-269 (handle-typed request inputs, server-edge resolution, one indistinguishable in-band NOT_FOUND), DL-270 (responses/stored/events keep ids; display handles only as additive sibling fields), DL-271 (owner-namespaced
account_handlesstorage + rename/reclaim policy). No existing DL row constrains request addressing (DL-094/186/188/191/202 checked; DL-186 rules the rename-in-place field-numbering).Stacked on #664 (DL-264 dedup), which clears the duplicate-id gate failure this record's row numbering depends on.
Ref: RIG-2751
Co-authored-by: Matt Wilkinson matt@rigel.build