feat(config): profile registry — admit profiles/<name>/profile.yml at all three doors (RIG-2968) - #813
Merged
Conversation
|
Compass engineering docs preview: https://compass-server-rig-2968-prof.compass-eng-docs.pages.dev Deployed from |
rigel-mintaka
force-pushed
the
compass-server/rig-2968-profile-registry
branch
from
August 31, 2026 23:32
830afaa to
f1ccb10
Compare
… 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
force-pushed
the
compass-server/rig-2968-profile-registry
branch
from
September 1, 2026 00:44
33d3d18 to
96cb151
Compare
mattwilkinsonn
approved these changes
Sep 1, 2026
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.
Adds
profiles/as a new whitelisted config-bundle top-dir at all THREE independent whitelists, mirroring the just-mergedprompts/pattern (RIG-3075 T2):go/internal/store/agent_config.go):topDirProfilesconst +configBundleTopDirsentry, theconfigMemberPartsrejection enumeration, avalidateProfileMemberarm, and aProfilesinfo bucket inconfigBundleMemberNames.go/internal/runner/config_materialize.go): mirroredtopDirProfilesconst +configTopDirsentry, rejection enumeration, and avalidateNestedMemberarm (layout only — the schema checks are store-door-authoritative, mirroring the settings/models credential-denylist asymmetry).go/cmd/compass/bundle.go): mirrored whitelist entry, member grammar, anderrNotWhitelisted/ no-members enumerations.Member grammar is exactly
profiles/<name>/profile.yml(three components,<name>matchesconfigNamePattern, filenameprofile.yml).Profile-schema validation at the store door (fail-closed, untrusted-tarball posture):
profile.ymlmust parse as a YAML mapping;models.managerand eachmodels.agents.*selector must be string-shaped where present.models.agentsfrontmatter-name lint — eachmodels.agentskey must match the parsed frontmattername:of a shippedagents/*.mddef IN THE SAME BUNDLE, not its filename stem. Implemented as a second phase invalidateAndHashConfigBundleover 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.Ships
config/profiles/default/profile.yml: the committed fleet config IS thedefaultprofile.models.manageris pinned tolitellm/claude-opus:high(the fleet runs on litellm/claude-opus; the design superset example pins the manager at :high);models.agentsempty; 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