Skip to content

feat(server): scope user-secret writes per caller (RIG-3655) - #1187

Open
rigel-mintaka wants to merge 3 commits into
compass-server/rig-3655-d9-scope-selectorfrom
compass-server/rig-3655-t5-scope-writes
Open

feat(server): scope user-secret writes per caller (RIG-3655)#1187
rigel-mintaka wants to merge 3 commits into
compass-server/rig-3655-d9-scope-selectorfrom
compass-server/rig-3655-t5-scope-writes

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 4 PRs:

  1. main
  2. docs(design): rule the user-secret scope selector and correct D8 (RIG-3655) #1169
  3. "feat(server): scope user-secret writes per caller (RIG-3655)" (this PR)
  4. feat(server)!: delete the server-secret write path #1220
  5. docs(secrets): correct the value-location prose the DB store obsoleted #1223

SetSecret and DeleteSecret now carry a SecretScope selector. An unspecified
scope resolves to the caller's own user coordinate, so a client built before
the field existed writes a private value rather than a tenant-wide one. Tenant
scope is explicit and requires an admin, checked at the RPC edge where
requireUser already holds the caller's role.

The wire numbers deliberately differ from the store's, so the handler maps
between them; a cast would turn an omitted field into a tenant write, which is
the case the default exists to prevent.

DeclareSecret and its InsertSecret query were scope-blind and hardcoded the
tenant coordinate, so every declare landed at (0, '') no matter what the
handler resolved. Both now take the coordinate, validated by the same
validateScopeShape the upsert door uses.

A re-set at user scope does NOT retire an existing tenant row: it writes a new
primary key, and the shared value keeps resolving for every other user until an
admin deletes it at tenant scope. A regression test pins that, since the
intuition runs the other way.

Refs RIG-3655

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

@linear-code

linear-code Bot commented Sep 12, 2026

Copy link
Copy Markdown

RIG-3655

@rigel-mintaka
rigel-mintaka added this pull request to stack #1188 September 12, 2026 23:02
@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-3655-t5-s.compass-eng-docs.pages.dev

Deployed from compass-server/rig-3655-t5-scope-writes at 6ceb365.

rigel-mintaka and others added 2 commits September 12, 2026 23:18
SetSecret and DeleteSecret now carry a SecretScope selector. An unspecified
scope resolves to the caller's own user coordinate, so a client built before
the field existed writes a private value rather than a tenant-wide one. Tenant
scope is explicit and requires an admin, checked at the RPC edge where
requireUser already holds the caller's role.

The wire numbers deliberately differ from the store's, so the handler maps
between them; a cast would turn an omitted field into a tenant write, which is
the case the default exists to prevent.

DeclareSecret and its InsertSecret query were scope-blind and hardcoded the
tenant coordinate, so every declare landed at (0, '') no matter what the
handler resolved. Both now take the coordinate, validated by the same
validateScopeShape the upsert door uses.

A re-set at user scope does NOT retire an existing tenant row: it writes a new
primary key, and the shared value keeps resolving for every other user until an
admin deletes it at tenant scope. A regression test pins that, since the
intuition runs the other way.

Refs RIG-3655

Co-authored-by: Matt Wilkinson <matt@rigel.build>
Completes the T5 cutover. The scope selector previously reached only the
value-free declaration registry, so the value path stayed name-keyed: a user
setting a name another user already held overwrote their value, and two rows
for one name made buildManifest emit a duplicate TOML key, failing FetchSecrets
for every live session.

SetSecret and DeleteSecret now go through the DB-backed StoreResolver at the
resolved coordinate. The upsert is atomic, so the declare-then-set trio and its
rollback are gone, and DeclareSecret with its InsertSecret query goes with them.
Boot resolves the master key and builds the resolver from it, still failing
closed when the key is absent. FetchSecrets resolves per agent, collapsing
scope precedence in SQL.

With the upsert the sole writer, the value columns are NOT NULL.

ServeConfig.SecretProvider carries the provider URI both resolvers read,
reachable in production via --secret-provider or COMPASS_SECRET_PROVIDER; empty
keeps the SDK default chain. Tests set the same field rather than a test-only
knob.

The isolation tests now assert through the production delivery path instead of
a store query no production code calls, which is what let the clobber hide.

Refs RIG-3655

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-3655-t5-scope-writes branch from aa26c43 to 3694f3e Compare September 13, 2026 03:27
T5 resolves the master key unconditionally at boot, so the e2e stack's
compass-server now fails closed before serving: the job staged neither the
cdylib the secrets read path dlopens nor a resolvable key, and every leg
failed in shared fixture stand-up.

Stages the cdylib with the pgtest job's recipe and seeds an obviously-fake
key into a dotenv the podman user can read, reaching the spawned server
through the provider env it already inherits.

Refs RIG-3655

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@trunk-io

trunk-io Bot commented Sep 13, 2026

Copy link
Copy Markdown

❌ This stack could not start testing because there was a merge conflict. See more details here.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@trunk-io

trunk-io Bot commented Sep 14, 2026

Copy link
Copy Markdown

Stacked PR 1223 failed testing in the merge queue. Please investigate the failure and re-submit the stack.

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.

2 participants