Skip to content

feat(store,server): model registry store + operator write RPC + fail-closed config lint (RIG-3122) - #853

Open
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-server/rig-3122-model-registry
Open

feat(store,server): model registry store + operator write RPC + fail-closed config lint (RIG-3122)#853
rigel-mintaka wants to merge 1 commit into
mainfrom
compass-server/rig-3122-model-registry

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Adds the P2 model registry: a singleton, versioned map of stable model names to ordered provider/model candidate chains, written by an admin-only operator RPC and read back by the same door. This is the routing substrate agent config bundles pin against — a profile references a model by its stable name, and the registry is what resolves that name to a concrete provider candidate at gateway time.

Store

  • New model_registry table (0001_init.sql): singleton BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (singleton) one-row pin (same shape as agent_config_bundle), monotonic version BIGINT, registry JSONB. New in this PR — no legacy rows.
  • PutModelRegistry is compare-and-swap on version: expectedVersion 0 seeds via INSERT ... ON CONFLICT DO NOTHING (a lost racing seed maps to ErrVersionConflict, never a silent success); expectedVersion N>0 updates gated on the row still holding N. Negative versions rejected up front.
  • ValidateModelRegistry is the pure, DB-free door check the write path runs before any row write (fail-closed): a stable-name key grammar (non-empty, within the per-field byte cap, and free of /, :, and whitespace — the guard that makes the reverse-lint's / escape-hatch and split-on-last-colon tier discriminators total over stored keys, design.md §P2 L530-532, L583-585), per-field and aggregate byte caps, entry/candidate count caps, and a reverse orphan lint that fails closed when PutAgentConfig publishes a bundle whose profile pins a stable name the registry does not carry (including the unconfigured nil-map case).

Server

  • PutModelRegistry / DeleteModelRegistry / GetModelRegistry on the CompassService door. Put/Delete are admin-gated; Get is authenticatedOpen (the registry names providers/models, never credentials — same posture as GetAgentConfigInfo). The admin-gate classifier fails closed to adminOnly for any unclassified procedure, and classify_exhaustive reddens CI until a new RPC is classified.
  • mapModelRegistryErr maps ErrInvalidArgumentCodeInvalidArgument, ErrVersionConflictCodeAborted (the CAS-contention convention: re-read and retry), else CodeInternal.
  • Read caps: CompassService keeps its 128 MiB connect.WithReadMaxBytes (it carries PutAgentConfig's 64 MiB decompressed bundle); the sibling CommsService and SecretsService mounts on the same network door now carry a 16 MiB cap (CommsService is authenticatedOpen, so an uncapped mount was strictly more exposed than the admin-gated capped one). Follow-up RIG-3221 tracks the still-uncapped internal RunnerService mount (Kind-gated, narrower exposure).

Tests

pgtest (store + server) and default-gate unit suites: CAS discipline (seed/replace/stale/negative), admin gating (non-admin PermissionDenied on both writes, admission on Get), wire error mapping, metadata round-trip with distinct per-field values, malformed-registry rejection (slash/colon/whitespace/newline in the key, oversized fields), stable-name grammar accept/reject, reverse-lint orphan rejection and fail-closed-on-unconfigured, and delete-clear + orphaning-clear at the wire.

Spec-impact: none. Refs RIG-3122
Co-authored-by: Matt Wilkinson matt@rigel.build

…closed config lint (RIG-3122)

Adds the P2 model registry: a singleton, versioned map of stable model names to ordered provider/model candidate chains, written by an admin-only operator RPC and read back by the same door. This is the routing substrate agent config bundles pin against — a profile references a model by its stable name, and the registry is what resolves that name to a concrete provider candidate at gateway time.

## Store

- New `model_registry` table (`0001_init.sql`): singleton `BOOLEAN PRIMARY KEY DEFAULT TRUE CHECK (singleton)` one-row pin (same shape as `agent_config_bundle`), monotonic `version BIGINT`, `registry JSONB`. New in this PR — no legacy rows.
- `PutModelRegistry` is compare-and-swap on `version`: `expectedVersion 0` seeds via `INSERT ... ON CONFLICT DO NOTHING` (a lost racing seed maps to `ErrVersionConflict`, never a silent success); `expectedVersion N>0` updates gated on the row still holding `N`. Negative versions rejected up front.
- `ValidateModelRegistry` is the pure, DB-free door check the write path runs before any row write (fail-closed): a stable-name key grammar (non-empty, within the per-field byte cap, and free of `/`, `:`, and whitespace — the guard that makes the reverse-lint's `/` escape-hatch and split-on-last-colon tier discriminators *total* over stored keys, design.md §P2 L530-532, L583-585), per-field and aggregate byte caps, entry/candidate count caps, and a reverse orphan lint that fails closed when `PutAgentConfig` publishes a bundle whose profile pins a stable name the registry does not carry (including the unconfigured nil-map case).

## Server

- `PutModelRegistry` / `DeleteModelRegistry` / `GetModelRegistry` on the CompassService door. Put/Delete are admin-gated; Get is `authenticatedOpen` (the registry names providers/models, never credentials — same posture as `GetAgentConfigInfo`). The admin-gate classifier fails closed to `adminOnly` for any unclassified procedure, and `classify_exhaustive` reddens CI until a new RPC is classified.
- `mapModelRegistryErr` maps `ErrInvalidArgument`→`CodeInvalidArgument`, `ErrVersionConflict`→`CodeAborted` (the CAS-contention convention: re-read and retry), else `CodeInternal`.
- Read caps: `CompassService` keeps its 128 MiB `connect.WithReadMaxBytes` (it carries `PutAgentConfig`'s 64 MiB decompressed bundle); the sibling `CommsService` and `SecretsService` mounts on the same network door now carry a 16 MiB cap (`CommsService` is `authenticatedOpen`, so an uncapped mount was strictly more exposed than the admin-gated capped one). Follow-up RIG-3221 tracks the still-uncapped internal RunnerService mount (Kind-gated, narrower exposure).

## Tests

pgtest (store + server) and default-gate unit suites: CAS discipline (seed/replace/stale/negative), admin gating (non-admin `PermissionDenied` on both writes, admission on Get), wire error mapping, metadata round-trip with distinct per-field values, malformed-registry rejection (slash/colon/whitespace/newline in the key, oversized fields), stable-name grammar accept/reject, reverse-lint orphan rejection and fail-closed-on-unconfigured, and delete-clear + orphaning-clear at the wire.

Spec-impact: none. Refs RIG-3122
Co-authored-by: Matt Wilkinson <matt@rigel.build>
@trunk-io

trunk-io Bot commented Sep 3, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • 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

@linear-code

linear-code Bot commented Sep 3, 2026

Copy link
Copy Markdown

RIG-3122

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-3122-mode.compass-eng-docs.pages.dev

Deployed from compass-server/rig-3122-model-registry at 2c89980.

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