Skip to content

feat(config): profile registry — admit profiles/<name>/profile.yml at all three doors (RIG-2968) - #813

Merged
mattwilkinsonn merged 4 commits into
mainfrom
compass-server/rig-2968-profile-registry
Sep 1, 2026
Merged

feat(config): profile registry — admit profiles/<name>/profile.yml at all three doors (RIG-2968)#813
mattwilkinsonn merged 4 commits into
mainfrom
compass-server/rig-2968-profile-registry

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Adds profiles/ as a new whitelisted config-bundle top-dir at all THREE independent whitelists, mirroring the just-merged prompts/ pattern (RIG-3075 T2):

  • Store door (go/internal/store/agent_config.go): topDirProfiles const + configBundleTopDirs entry, the configMemberParts rejection enumeration, a validateProfileMember arm, and a Profiles info bucket in configBundleMemberNames.
  • Runner unpack (go/internal/runner/config_materialize.go): mirrored topDirProfiles const + configTopDirs entry, rejection enumeration, and a validateNestedMember arm (layout only — the schema checks are store-door-authoritative, mirroring the settings/models credential-denylist asymmetry).
  • CLI builder (go/cmd/compass/bundle.go): mirrored whitelist entry, member grammar, and errNotWhitelisted / no-members enumerations.

Member grammar is exactly profiles/<name>/profile.yml (three components, <name> matches configNamePattern, filename profile.yml).

Profile-schema validation at the store door (fail-closed, untrusted-tarball posture):

  • yml parseprofile.yml must parse as a YAML mapping; models.manager and each models.agents.* selector must be string-shaped where present.
  • Superset-key closure — top-level keys are closed to {models, corpus, extensions, settings} (deferred axes schema'd now, consumed later); any key outside the superset is rejected.
  • Cross-member models.agents frontmatter-name lint — each models.agents key must match the parsed frontmatter name: of a shipped agents/*.md def IN THE SAME BUNDLE, not its filename stem. Implemented as a second phase in validateAndHashConfigBundle over the already-collected member set, after the single streamed pass: agent-def frontmatter names and profile bodies are separated into plain maps and cross-checked before returning. It reads only collected bytes (no re-decompress) and never feeds the canonical content hash, so the version stays order-independent and metadata-zeroed.
  • Cross-family posture stays ADVISORY at PR review (OQ-1 ruling) — no door lint.

Ships config/profiles/default/profile.yml: the committed fleet config IS the default profile. models.manager is pinned to litellm/claude-opus:high (the fleet runs on litellm/claude-opus; the design superset example pins the manager at :high); models.agents empty; corpus/extensions/settings declared empty/null to document the full superset.

Red-green Go tests added at the store door (accept/reject tables + a dedicated frontmatter-name divergence test + a shipped-default-profile door test), runner unpack, and CLI builder.

Spec-impact: none. Refs RIG-2968

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

@linear-code

linear-code Bot commented Aug 31, 2026

Copy link
Copy Markdown

RIG-2968

@github-actions

github-actions Bot commented Aug 31, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-server-rig-2968-prof.compass-eng-docs.pages.dev

Deployed from compass-server/rig-2968-profile-registry at 96cb151.

@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2968-profile-registry branch from 830afaa to f1ccb10 Compare August 31, 2026 23:32
rigel-mintaka and others added 4 commits August 31, 2026 20:42
… all three doors (RIG-2968)

Adds `profiles/` as a new whitelisted config-bundle top-dir at all THREE independent whitelists, mirroring the just-merged `prompts/` pattern (RIG-3075 T2):

- **Store door** (`go/internal/store/agent_config.go`): `topDirProfiles` const + `configBundleTopDirs` entry, the `configMemberParts` rejection enumeration, a `validateProfileMember` arm, and a `Profiles` info bucket in `configBundleMemberNames`.
- **Runner unpack** (`go/internal/runner/config_materialize.go`): mirrored `topDirProfiles` const + `configTopDirs` entry, rejection enumeration, and a `validateNestedMember` arm (layout only — the schema checks are store-door-authoritative, mirroring the settings/models credential-denylist asymmetry).
- **CLI builder** (`go/cmd/compass/bundle.go`): mirrored whitelist entry, member grammar, and `errNotWhitelisted` / no-members enumerations.

Member grammar is exactly `profiles/<name>/profile.yml` (three components, `<name>` matches `configNamePattern`, filename `profile.yml`).

Profile-schema validation at the store door (fail-closed, untrusted-tarball posture):
- **yml parse** — `profile.yml` must parse as a YAML mapping; `models.manager` and each `models.agents.*` selector must be string-shaped where present.
- **Superset-key closure** — top-level keys are closed to {models, corpus, extensions, settings} (deferred axes schema'd now, consumed later); any key outside the superset is rejected.
- **Cross-member `models.agents` frontmatter-name lint** — each `models.agents` key must match the parsed frontmatter `name:` of a shipped `agents/*.md` def IN THE SAME BUNDLE, not its filename stem. Implemented as a second phase in `validateAndHashConfigBundle` over the already-collected member set, after the single streamed pass: agent-def frontmatter names and profile bodies are separated into plain maps and cross-checked before returning. It reads only collected bytes (no re-decompress) and never feeds the canonical content hash, so the version stays order-independent and metadata-zeroed.
- Cross-family posture stays ADVISORY at PR review (OQ-1 ruling) — no door lint.

Ships `config/profiles/default/profile.yml`: the committed fleet config IS the `default` profile. `models.manager` is pinned to `litellm/claude-opus:high` (the fleet runs on litellm/claude-opus; the design superset example pins the manager at :high); `models.agents` empty; corpus/extensions/settings declared empty/null to document the full superset.

Red-green Go tests added at the store door (accept/reject tables + a dedicated frontmatter-name divergence test + a shipped-default-profile door test), runner unpack, and CLI builder.

Spec-impact: none. Refs RIG-2968

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…ive def-name recovery (RIG-2968)

Review round 1 on the profile-registry store door surfaced two medium guard
gaps and one low credential-boundary gap; this lands the fixes additively.

- **Non-string YAML keys no longer bypass the models.* checks.** yaml.v3
  decodes a mapping with any non-string key as `map[any]any`, so the door's
  `.(map[string]any)` assertions on `models` / `models.agents` failed OPEN —
  a numeric or bareword-bool key silently skipped both the selector-shape
  check and the cross-member `models.agents` frontmatter-name lint. New
  `rejectNonStringKeys` helper rejects that class up front in
  `validateProfileModelSelectors`, the single per-member pass that runs and
  aborts the bundle before the cross-member lint — so it closes both bypasses
  at one point.
- **Def-name recovery is now at least as permissive as the SDK loader.**
  `agentDefFrontmatterName` did a strict whole-block YAML parse, so an
  unrelated YAML-ambiguous sibling field (e.g. `description: A thing: with a
  colon`) dropped the name and falsely rejected a profile legitimately keying
  that agent. It now falls back to a tolerant `name:` line-scan, mirroring the
  SDK's `parseFrontmatter` line-parser cascade for the name field.
- **Credential denylist extends to the profile `settings` axis.** A profile's
  `settings` sub-mapping shares the settings/config.yml credential grammar, so
  `validateProfileMember` now applies `rejectCredentialSettings` to it
  (extensions/corpus credential surfaces stay deferred with their consumption
  task).

Tests: reject cases for a numeric / bareword-bool `models.agents` key, a
non-string sibling key defeating the manager check, and a profile `settings`
credential key; an accept case proving a colon-bearing sibling field still
recovers the name (red before the line-scan fallback, green after).

Refs RIG-2968

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…list walk (RIG-2968)

The credential denylist walk (`yamlPathIsSet`, shared by profile `settings`
and the pre-existing `settings/config.yml` door) asserted `map[string]any` at
every path segment. A non-string sibling key one level above a credential leaf
makes yaml.v3 decode that node as `map[any]any`, so the per-level assertion
failed OPEN and reported the credential NOT set — the leaf then rode the
verbatim-stored bundle and materialized into every agent's config dir,
defeating the CD-3/GC-5 authoritative-door invariant.

Extract `yamlMapIndex`, which looks up a string key in both `map[string]any`
and `map[any]any` mapping nodes, and route the walk through it. A shielding
non-string sibling can no longer hide a string-keyed credential leaf. Hardens
both the new profile-settings path and the pre-existing `settings/config.yml`
+ `models.yml` paths that share the helper.

Tests:
- add a red-first reject case: a `settings.auth.broker` node with a `123:`
  non-string sibling beside the `token:` credential leaf. Red-green probed
  (neuter the `map[any]any` descent → the shielded credential is accepted).
- relabel the `on: sel` non-string-key case to an explicit-bool `true:` key.
  `on`/`off`/`yes`/`no` are `!!str` under yaml.v3's YAML-1.2 core schema, so
  the old case decoded as `map[string]any` and never exercised
  `rejectNonStringKeys` (the cross-member lint was the real rejector). An
  explicit `true:` key is genuinely `map[any]any`, so the guard is the sole
  rejection reason.

Refs RIG-2968

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…d door (RIG-2968)

Round-3 review lows: the round-2 fix widened the credential denylist walk to
descend `map[any]any` nodes, but nothing pinned the two symmetric contracts of
`yamlMapIndex`/`yamlPathIsSet`:

- Accept-symmetry: a benign profile `settings` body with a nested non-string
  key and NO credential-marked leaf must still be ACCEPTED (guards against
  `yamlMapIndex` over-matching and over-rejecting legitimate config).
- Door parity: `rejectCredentialSettings` shares its walk between profile
  settings and top-level `settings/config.yml`, so the shielded-leaf reject
  must hold on the top-level door too, not just the profile path.

Both are test-only; the production walk is unchanged from `33d3d181`. Red-green
probed: neutering the `map[any]any` descent reddens both shielded reject cases;
restored → green. Full store/runner/cmd suites green, golangci-lint 0.

Refs RIG-2968

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@rigel-mintaka
rigel-mintaka force-pushed the compass-server/rig-2968-profile-registry branch from 33d3d18 to 96cb151 Compare September 1, 2026 00:44
@mattwilkinsonn
mattwilkinsonn merged commit ec3fe59 into main Sep 1, 2026
14 checks passed
@mattwilkinsonn
mattwilkinsonn deleted the compass-server/rig-2968-profile-registry branch September 1, 2026 01:29
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