docs(server): scope user secrets per tenant, user, and agent (RIG-3655) - #1110
Merged
Merged
Conversation
|
😎 This pull request was merged. |
rigel-mintaka
added this pull request to stack #1112
September 11, 2026 20:05
|
Compass engineering docs preview: https://compass-server-rig-3655-scop.compass-eng-docs.pages.dev Deployed from Changed pages: |
This was referenced Sep 11, 2026
Matt ruled that user secrets need three scope levels, not the flat per-tenant namespace the frozen record specified. Per-tenant isolation already existed: `secrets` carries `tenant_id` and sits in the `tenant_tables` array in `0001_init.sql`, so it is RLS-enabled and forced under `tenant_isolation`. Per-user and per-agent did not. `name` was the sole primary key, so within a tenant the namespace was flat and two users could not hold different values for one name. `FetchSecrets` says as much in its own comment and resolves with no selector. The model is three tiers (0 tenant, 1 user, 2 agent) with most-specific-wins resolution, so a tenant row is a real shared value and a user or agent row shadows it. The primary key becomes `(name, scope_kind, scope_id)`. Resolution collapses in SQL via `DISTINCT ON (name) ... ORDER BY name, scope_kind DESC`, not in Go: a shadowed row never leaves Postgres, so the resolver never decrypts a value it would discard, and the precedence lives in one ORDER BY rather than a Go merge a later writer can reorder. This amends the record before T2 writes any migration, because the AAD is bound into the ciphertext -- adding a scope field after rows exist would force a re-encrypt of every one. The tuple is fixed now, while the table is empty. Agent scoping is cheap because the identity is already at the call site and currently thrown away: `Hub.HasLiveSession` and `Hub.HasContainerBinding` look up `sessionAccounts`/`containerAccounts`, both `map[string]store.AccountID`, and return a bool. `agent_accounts.owner_user_id` is one FK hop to the owning user, so all three tiers derive from what the handler already holds. Scope is an additional filter inside a tenant, never a replacement for RLS. Four questions I could not answer from the code are in Open questions for Matt, covering write authorization per scope, `declared_by`'s meaning beside `scope_id`, the lifecycle of scoped rows on account deletion, and whether the delete verb needs scope addressing. Ledger-impact: adds DL-357 (scope model), DL-358 (resolution) and DL-359 (the AAD tuple, refining DL-351). Refs RIG-3655 Co-authored-by: Matt Wilkinson <matt@rigel.build>
rigel-mintaka
force-pushed
the
compass-server/rig-3655-scope-amendment
branch
from
September 12, 2026 02:50
f994b46 to
4895a11
Compare
mattwilkinsonn
approved these changes
Sep 12, 2026
|
This pull request was merged into |
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.
This PR is part of a stack containing 7 PRs:
mainMatt ruled that user secrets need three scope levels, not the flat per-tenant
namespace the frozen record specified.
Per-tenant isolation already existed:
secretscarriestenant_idand sits inthe
tenant_tablesarray in0001_init.sql, so it is RLS-enabled and forcedunder
tenant_isolation. Per-user and per-agent did not.namewas the soleprimary key, so within a tenant the namespace was flat and two users could not
hold different values for one name.
FetchSecretssays as much in its owncomment and resolves with no selector.
The model is three tiers (0 tenant, 1 user, 2 agent) with most-specific-wins
resolution, so a tenant row is a real shared value and a user or agent row
shadows it. The primary key becomes
(name, scope_kind, scope_id).Resolution collapses in SQL via
DISTINCT ON (name) ... ORDER BY name, scope_kind DESC, not in Go: a shadowed row never leaves Postgres, so theresolver never decrypts a value it would discard, and the precedence lives in
one ORDER BY rather than a Go merge a later writer can reorder.
This amends the record before T2 writes any migration, because the AAD is bound
into the ciphertext -- adding a scope field after rows exist would force a
re-encrypt of every one. The tuple is fixed now, while the table is empty.
Agent scoping is cheap because the identity is already at the call site and
currently thrown away:
Hub.HasLiveSessionandHub.HasContainerBindinglookup
sessionAccounts/containerAccounts, bothmap[string]store.AccountID,and return a bool.
agent_accounts.owner_user_idis one FK hop to the owninguser, so all three tiers derive from what the handler already holds.
Scope is an additional filter inside a tenant, never a replacement for RLS.
Four questions I could not answer from the code are in Open questions for Matt,
covering write authorization per scope,
declared_by's meaning besidescope_id, the lifecycle of scoped rows on account deletion, and whether thedelete verb needs scope addressing.
Ledger-impact: adds DL-357 (scope model), DL-358 (resolution) and DL-359 (the
AAD tuple, refining DL-351).
Refs RIG-3655
Co-authored-by: Matt Wilkinson matt@rigel.build