feat(compass): handle-address request inputs — schema, proto flip, resolver, comms edge (RIG-2880) - #698
Open
rigel-mintaka wants to merge 4 commits into
Open
Conversation
|
Compass engineering docs preview: https://compass-server-rig-2880-hand.compass-eng-docs.pages.dev Deployed from Changed pages: |
rigel-mintaka
added a commit
that referenced
this pull request
Aug 28, 2026
…path (RIG-2880) Review-fix on #698 (low, non-gating). The re-review noted that comms.ReparentAgent's new edge pre-check makes the store's validateNewParent same-owner ErrPermissionDenied unreachable via the RPC path — a future reader at the store clause might mistake it for dead code. Adds a one-line note that it is edge-shadowed on the RPC path and remains as store-layer defense-in-depth (the store is independently callable and tested). Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
added a commit
that referenced
this pull request
Aug 28, 2026
…path (RIG-2880) Review-fix on #698 (low, non-gating). The re-review noted that comms.ReparentAgent's new edge pre-check makes the store's validateNewParent same-owner ErrPermissionDenied unreachable via the RPC path — a future reader at the store clause might mistake it for dead code. Adds a one-line note that it is edge-shadowed on the RPC path and remains as store-layer defense-in-depth (the store is independently callable and tested). Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-server/rig-2880-handle-cutover-foundation
branch
from
August 28, 2026 03:24
241ec9b to
8dc76ab
Compare
Contributor
Author
|
Review loop complete (self-driven Round 1 (
Round 2 ( Adds DL-269/270/271 to the ledger (rebase-merged with #695's DL-281). |
rigel-mintaka
added a commit
that referenced
this pull request
Aug 28, 2026
…path (RIG-2880) Review-fix on #698 (low, non-gating). The re-review noted that comms.ReparentAgent's new edge pre-check makes the store's validateNewParent same-owner ErrPermissionDenied unreachable via the RPC path — a future reader at the store clause might mistake it for dead code. Adds a one-line note that it is edge-shadowed on the RPC path and remains as store-layer defense-in-depth (the store is independently callable and tested). Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-server/rig-2880-handle-cutover-foundation
branch
from
August 28, 2026 03:45
8dc76ab to
139ecdb
Compare
…solver, comms edge (RIG-2880)
The handle-addressing cutover: every request-input account field carries a `@handle`, resolved to an account id at the service edge, per `docs/designs/product/compass-handle-addressing-cutover/design.md` (Plan T0-T3, plus the compass-agent T6 tool rewire folded in to keep the tree green).
**T0 — resolution index** (`0001_init.sql`). Adds `account_handles(account_id, handle, owner_user_id NULL)` with two partial-unique indexes: user/system handles globally unique (`owner_user_id IS NULL`), agent handles unique per owner. `accounts.handle` stays as a display column, no longer the resolution key. No backfill (pre-dogfood); store write paths insert the handle row in the same tx as the account.
**T1 — proto flip** (`comms.proto`, `agent_gateway.proto`, `compass.proto`). Every request-input account field renamed to its `@handle` form (`member_handles`, `add/remove/subscribe/unsubscribe_handles`, `agent_handle`, `new_parent_handle`, `parent_handle`, `owner_handle`, `vantage_handle`, `account_handle`), field numbers kept in place (rename-in-place, DL-186). Response, stored, and event account fields stay id-typed (the stable join keys clients hold). All four gen lanes regenerated; gen-fence clean.
**T2 — resolver** (`accounts.go`). `AccountsByHandles` is an owner-qualified batch resolve over `account_handles`: atomic (any miss fails the whole call, naming every unresolved handle in submitted spelling — OQ-2), visibility-scoped (an invisible handle misses exactly like an unknown one — OQ-6), system-excluded. `AgentByHandle` re-keyed to `(owner, handle)`; `UserByHandle` global-tier helper; `QualifiedHandle`/`ParseQualifiedHandle` split on the first `/`.
**T3 — comms edge** (`resolve.go` new, `mapping.go`, `roster.go`). Handles resolve before the store calls, with oracle-safe NOT_FOUND remaps on the authority/visibility legs (DL-269): CreateAgent foreign parent, ReparentAgent `ErrPermissionDenied` merge, OpenAgentWorkspace re-key to the submitted handle, roster vantage visibility-close. A delivery mention `@handle` resolves in the posting author's owner namespace. `memberUpdatesFromWire` merges post-resolution so two spellings of one handle cannot yield two conflicting `MemberUpdate`s.
**T6 (folded) — compass-agent despawn tool** (`lifecycle.ts`). `agents_despawn_peer` takes `agent_handle` (non-blank) and builds `DespawnPeerRequest{agentHandle}`; the description drops "by its agent account id". The roster tool is unchanged (it never set the vantage). Folded into this PR so the whole tree — Go, proto, and the bun lane the proto regen touches — stays green in one atomic change rather than landing a red intermediate.
Admin/ops (T8) and lifecycle despawn (T4) server handlers are mechanically renamed to the handle-typed fields but still consume the value as an id (documented id-passthrough skew); their real resolution lands in the follow-up T4/T8 commits on this stack.
**Verification.** `go vet ./...` and `-tags "pgtest podman unix microvm"` clean, `go build ./...` clean, all test binaries compile, `compass-proto:drift`/`gen-fence` pass, store/comms/delivery pgtests pass against live Postgres, `go test -race` clean; `compass-agent` typecheck + 610 bun tests green; `compass-ui`/`compass-client` typecheck green.
Adds DL-269 (request-input account fields are handle-typed), DL-270 (response/stored/event fields stay id-typed), DL-271 (owner-namespaced `account_handles` resolution) on merge.
Unblocks the remaining cutover lanes: T5 rebase of #628/#630, T8 admin/ops resolution, T9 additive `Message.author_handle`, and the compass-ui T7 sweep.
Spec-impact: none. Refs RIG-2880
Co-authored-by: Matt Wilkinson <matt@rigel.build>
…ing, add DL rows (RIG-2880) Review-fix commit on the handle-addressing foundation. Addresses the review of the parent commit (three code findings plus the mandated ledger rows); the low-2 finding (globally-visible user directory) is a pre-existing decision surfaced to Matt, not changed here. HIGH — ReparentAgent oracle leak (DL-269 violation). A foreign new_parent_handle (one that exists under another owner) fell through the edge to the store, which re-keyed the error to name the AGENT handle, while an unknown parent named the submitted parent handle. The divergence let a caller distinguish "this parent exists under another owner" from "no such handle" — an enumeration probe against another owner's tree. Fixed by mirroring CreateAgent's same-owner edge pre-check in ReparentAgent (go/internal/comms/comms.go): after resolving the new parent, resolve the caller's owner and the parent's owner, and if they differ return the indistinguishable NOT_FOUND naming the submitted new_parent_handle. The store's clause-0 permission-denied catch stays correct for agent-authority; the store clause-1 foreign-parent path is now unreachable via the edge. MEDIUM — reparent cross-owner tests asserted code only, not message. TestReparentAgentCrossOwnerParentNotFound now captures both the foreign-parent and unknown-parent errors and asserts each names its own submitted parent handle, that the foreign error does NOT name the agent handle, and that the two are byte-identical modulo the caller's own submitted spelling — the oracle invariant, not just the code. LOW-1 — memberUpdatesFromWire OQ-2 completeness. The four member lists (add/subscribe/unsubscribe/remove) resolved sequentially, so a miss in a later list was masked by an earlier list's miss — the error named only the first failing list. Rewritten to one combined resolveHandles call over all four lists, slicing results back per-list by offset; every unresolved handle across every list is now named in one pass. New TestUpdateChannelMembersNamesMissesAcrossAllLists defends this. Ledger — adds the three rows the frozen record's §Ledger impact mandates (DL-269 request-input handle-typing + oracle invariant, DL-270 response/stored/event stay id-typed, DL-271 owner-namespaced account_handles resolution), placed in the Storage section. The design PR merged without adding them; per house convention the impl PR fills its allocated numbers. Verification: go build + go vet (all tags) clean; gofmt + compass-go:lint (0 issues) clean; comms pgtest suite green against live Postgres (reparent oracle fix, one-pass member resolution, and both new tests); design-ledger-gate green (244 rows, anchor resolves). Co-authored-by: Matt Wilkinson <matt@rigel.build>
…path (RIG-2880) Review-fix on #698 (low, non-gating). The re-review noted that comms.ReparentAgent's new edge pre-check makes the store's validateNewParent same-owner ErrPermissionDenied unreachable via the RPC path — a future reader at the store clause might mistake it for dead code. Adds a one-line note that it is edge-shadowed on the RPC path and remains as store-layer defense-in-depth (the store is independently callable and tested). Co-authored-by: Matt Wilkinson <matt@rigel.build>
…st shadowing a human (RIG-2880) The per-owner `account_handles` namespace (DL-271) changes the spawn-collision domain from global to per-owner, which the two `go/server` lifecycle spawn tests did not yet reflect — they asserted the pre-cutover global-unique behavior and reddened the `pgtest` CI suite. Matt ruled the intended `SpawnPeer` contract (2026-08-27): allow cross-owner same-name agents, but keep a defense-in-depth guard so an agent can never shadow a human or the system sender. - **Shadow guard** (`lifecycle.go`): `SpawnAsAccount` resolves the bare handle in the global user/system index (`UserByHandle`) before `CreateAgent`; a hit is the same in-band `already_exists`/`errHandleTaken` a duplicate agent handle gets, never revealing the account kind. Storage permits an agent handle to overlap a user/system handle (the two partial-unique indexes never contend), but resolution resolves the global tier first (OQ-7), so this closes the one way a peer could be created onto a human's handle. - **`resumeOrReject` simplification** (`lifecycle.go`): with the guard excluding user/system handles and the per-owner namespace isolating foreign owners, a `CreateAgent` `ErrConflict` can only mean a same-owner agent already holds the handle. The two former reject branches (non-agent handle, foreign owner) are now unreachable and removed; an `AgentByHandle(callerOwner, handle)` miss there is an invariant violation (`CodeInternal`), not a routine collapse. - **Tests** (`lifecycle_pgtest_test.go`): `...DifferentOwnerIsAlreadyExists` is rewritten as `...CreatesDistinctPeer` — owner B spawning owner A's handle now creates a distinct owner-B peer, A untouched, each owner's handle resolving to its own agent. `...CollidesWithUserAccountIsAlreadyExists` keeps its `already_exists` assertion, now backed by the guard, with an added system-handle sibling pinning the indistinguishable code shape. - **Fake runner** (`service_placement_pgtest_test.go`): an opt-in FIFO container-name override (mirroring the existing `startIDs`), so the cross-owner test's two genuinely-distinct spawns get distinct container names instead of colliding on the fixture's constant `fakeContainer` (production derives the name from the globally-unique account id, so real spawns never collide). Verification: `go build ./...`, `go vet -tags "pgtest podman unix microvm"`, and the `go/server`, `go/internal/store`, `go/internal/comms` pgtest suites all green against live Postgres. Self-driven `review` agent: 0 high / 0 medium / 2 low (both no-change informational). Refs RIG-2880 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-server/rig-2880-handle-cutover-foundation
branch
from
August 28, 2026 05:36
139ecdb to
61f85c6
Compare
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.
The handle-addressing cutover: every request-input account field carries a
@handle, resolved to an account id at the service edge, perdocs/designs/product/compass-handle-addressing-cutover/design.md(Plan T0-T3, plus the compass-agent T6 tool rewire folded in to keep the tree green).T0 — resolution index (
0001_init.sql). Addsaccount_handles(account_id, handle, owner_user_id NULL)with two partial-unique indexes: user/system handles globally unique (owner_user_id IS NULL), agent handles unique per owner.accounts.handlestays as a display column, no longer the resolution key. No backfill (pre-dogfood); store write paths insert the handle row in the same tx as the account.T1 — proto flip (
comms.proto,agent_gateway.proto,compass.proto). Every request-input account field renamed to its@handleform (member_handles,add/remove/subscribe/unsubscribe_handles,agent_handle,new_parent_handle,parent_handle,owner_handle,vantage_handle,account_handle), field numbers kept in place (rename-in-place, DL-186). Response, stored, and event account fields stay id-typed (the stable join keys clients hold). All four gen lanes regenerated; gen-fence clean.T2 — resolver (
accounts.go).AccountsByHandlesis an owner-qualified batch resolve overaccount_handles: atomic (any miss fails the whole call, naming every unresolved handle in submitted spelling — OQ-2), visibility-scoped (an invisible handle misses exactly like an unknown one — OQ-6), system-excluded.AgentByHandlere-keyed to(owner, handle);UserByHandleglobal-tier helper;QualifiedHandle/ParseQualifiedHandlesplit on the first/.T3 — comms edge (
resolve.gonew,mapping.go,roster.go). Handles resolve before the store calls, with oracle-safe NOT_FOUND remaps on the authority/visibility legs (DL-269): CreateAgent foreign parent, ReparentAgentErrPermissionDeniedmerge, OpenAgentWorkspace re-key to the submitted handle, roster vantage visibility-close. A delivery mention@handleresolves in the posting author's owner namespace.memberUpdatesFromWiremerges post-resolution so two spellings of one handle cannot yield two conflictingMemberUpdates.T6 (folded) — compass-agent despawn tool (
lifecycle.ts).agents_despawn_peertakesagent_handle(non-blank) and buildsDespawnPeerRequest{agentHandle}; the description drops "by its agent account id". The roster tool is unchanged (it never set the vantage). Folded into this PR so the whole tree — Go, proto, and the bun lane the proto regen touches — stays green in one atomic change rather than landing a red intermediate.Admin/ops (T8) and lifecycle despawn (T4) server handlers are mechanically renamed to the handle-typed fields but still consume the value as an id (documented id-passthrough skew); their real resolution lands in the follow-up T4/T8 commits on this stack.
Verification.
go vet ./...and-tags "pgtest podman unix microvm"clean,go build ./...clean, all test binaries compile,compass-proto:drift/gen-fencepass, store/comms/delivery pgtests pass against live Postgres,go test -raceclean;compass-agenttypecheck + 610 bun tests green;compass-ui/compass-clienttypecheck green.Adds DL-269 (request-input account fields are handle-typed), DL-270 (response/stored/event fields stay id-typed), DL-271 (owner-namespaced
account_handlesresolution) on merge.Unblocks the remaining cutover lanes: T5 rebase of #628/#630, T8 admin/ops resolution, T9 additive
Message.author_handle, and the compass-ui T7 sweep.Spec-impact: none. Refs RIG-2880
Co-authored-by: Matt Wilkinson matt@rigel.build