> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rkat.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Realm Config Inheritance Plan

> Implementation plan for deterministic realm configuration inheritance and credential provenance.

# Realm Config Inheritance — Implementation Plan (amended)

> Decisions: **(A)** owning-realm credential provenance, **(B)** general parent-chain inheritance.
> Status: red-team verdict = **amend\_then\_ship**. Architecture sound; 14 must-fixes folded in below.
> Source artifacts: `realm_synthesis.json` (design), `realm_redteam_verdict.json` (red-team). This doc is the merged, authoritative plan.

## Canonical design

CANONICAL DESIGN — "One chain, two consumers": a single typed RealmChain (parent edge on RealmConfigSection, terminal `global` root) drives BOTH (a) an eager whole-Config composition (EffectiveConfig) for top-level fields the agent reads flat, AND (b) a lazy per-resolution walk for connection/credential facts that feeds materialize\_connection\_target the OWNING realm's own RealmConnectionSet.

WHY THIS SHAPE (resolving the three judges' fatal flaws):

1. DECISION A provenance is STRUCTURALLY free and keeps the load-bearing typed invariant intact. The only realm-stamping site that matters (materialize\_connection\_target, connection.rs:977) sets AuthBindingRef.realm = realm.realm\_id. The lazy walk hands materialize the OWNING realm's *own* RealmConnectionSet (its realm\_id IS the owner). Therefore registry.rs:208 `if auth_binding.realm != realm.realm_id` (a STRICT typed equality, verified) STAYS UNCHANGED and KEEPS HOLDING. The eager-flatten designs (Approach 1 sidecar maps, Approach 2 single-set-multi-owner) BOTH forced a downgrade of that equality to an unwrap\_or/membership check — a silent fallback alongside typed validation = dogma violation. We reject eager flattening of the connection set precisely to preserve this. No new field on AuthBindingRef, BindingOrigin, or RealmConnectionSet → ZERO wire/schema churn for the credential path.
2. DUAL-TRUTH (top-level model/mcp/skills/hooks/limits unreachable from the connection resolver) is resolved by introducing ONE chain authority (RealmChain) consumed by both layers. Top-level inheritance is done by an EAGER EffectiveConfig composition produced once at the ConfigStore seam (the agent then reads the already-composed flat Config exactly as today — zero changes to the \~30 flat read sites at factory.rs:1022/3039/etc). Connection facts use the SAME RealmChain lazily. There are not two chain definitions; there is one RealmChain::resolve, called by the EffectiveConfig composer and by the connection resolvers.
3. AUTH/BACKEND cross-realm credential-redirect hazard is resolved by NO-INHERIT for auth & backend profiles: a binding's referenced backend\_profile/auth\_profile are resolved ONLY within the owning realm's own section (the realm that defines the binding). This makes binding-owner == backend-owner == auth-owner an INVARIANT, so lookup\_auth\_binding (which co-resolves binding+backend+auth in ONE RealmConnectionSet, connection.rs:1200) never sees a split. A child that wants a parent's backend/auth must inherit the whole binding (which carries them) or redeclare all three. This is the only coherent rule given the single-set lookup coupling.

ABSTRACTION-LEVEL MODEL (filesystem-free):

* A realm is a node in a config graph. `RealmConfigSection.parent: Option<RealmId>` is the single outgoing edge (linear chain, no diamonds). A realm with parent=None that is not itself `global` implicitly parents to the reserved `global` realm. `global.parent` MUST be None (root). env\_default is NOT a chain node; it remains the single typed post-chain fallback owner.
* RealmChain::resolve(config, head) walks edges head→…→global, dedup + depth-capped + cycle-checked, producing an ordered `Vec<RealmId>` (index 0 = most-derived consuming realm; last = global if present). This is the ONE deterministic ordering that replaces the flat cross-realm scan (connection.rs:912) AND the \[preferred,"default"] list (connection.rs:678-683).
* EffectiveConfig: a composer folds the full Config along the chain (parent-first, child-wins per the merge table) to produce the flat Config the agent reads. It is produced by a ChainComposingConfigStore decorator that wraps a per-realm config-section locator. The locator is INJECTED (dyn RealmConfigSource), so the filesystem projection (`realm_paths_in` → `<state_root>/<realm>/config.toml`) stays in meerkat-cli/meerkat-store, never in core. WASM injects a degenerate single-realm source (its synthesized config.realm\['default'], lib.rs:441) so its chain is \[default] with no global → behaviorally identical to today.
* Connection/credential resolution: the resolvers walk RealmChain and, per resolved binding, materialize using the OWNING realm's own RealmConnectionSet::from\_config(owner\_id, owner\_section). AuthBindingRef.realm = owner. Downstream (TokenKey/LeaseKey/FileTokenStore) is unchanged and already correct.

STATE NEVER INHERITS: composition reads only config sections of ancestor realms. Sessions/leases/event-log/ops-snapshots/.rkat projection are keyed by the CONSUMING realm via realm\_paths\_in(head) only; the composer never touches ancestor RealmPaths except to READ config. The one state-adjacent fact that follows the OWNING realm is the credential TokenKey.realm (decision A) — that is provenance of where a credential is DEFINED, not inheritance of credential state.

GLOBAL vs env\_default stay DISTINCT and typed: `global` is a durable Configured realm at the chain root (may hold persisted OAuth tokens at `<creds_root>/global/<binding>.json`, may publish a durable lease). env\_default is SyntheticEnvDefault, ephemeral, never a chain node, appended last only when allow\_env\_default and nothing on the chain matched. Both recognized by typed predicates (RealmId::is\_global mirroring is\_env\_default), never by raw string compares elsewhere. A `parent` edge targeting the env\_default slug is rejected at chain construction.

CLI LOGIN migrates from hardcoded "dev" to `global`: a single login lands at `<creds_root>/global/<binding>.json` and every realm inherits it via the default chain. Pre-1.0 clean break: existing dev/\* tokens orphaned, one-time re-login.

## Provenance design (decision A)

CARRIER (unchanged): AuthBindingRef.realm: RealmId (connection.rs:196) remains THE single fact that determines credential/token scope. TokenKey::from\_auth\_binding (auth/token\_store.rs:59) copies it verbatim; FileTokenStore::path\_for projects `<root>/<realm>/<binding>[@profile].json` (file.rs:33); LeaseKey::from\_auth\_binding (handles.rs:1320) mirrors it. NO new field is added to AuthBindingRef or BindingOrigin.

MECHANISM (the one change): make AuthBindingRef.realm the DEFINING realm, not the consuming realm, by feeding materialize\_connection\_target the OWNING realm's OWN RealmConnectionSet. materialize stamps realm.realm\_id (connection.rs:977-982, UNCHANGED). Because the lazy walk builds RealmConnectionSet::from\_config(owner\_id, owner\_section) for the chain member that defines the binding, realm\_id IS the owner. The existing weak provenance (stamp whatever realm the flat scan happened to find it in) becomes STRONG provenance (the defining chain member). In the degenerate single-realm case behavior is byte-identical.

WHY NO new field / NO eager-flattened multi-owner set: a single RealmConnectionSet can carry only ONE realm\_id. Approach 1 bolted on sidecar binding\_owner maps and Approach 2 used a single set with a separate provenance map — both then had to relax registry.rs:208 `auth_binding.realm != realm.realm_id` (a STRICT typed equality, verified at the live consumer) into an unwrap\_or/membership check, i.e. a silent fallback alongside typed validation (dogma violation). The lazy owning-set design keeps that equality a genuine invariant: the set fed to registry.resolve IS the owner's set, so realm\_id == auth\_binding.realm by construction. This is the decisive reason the credential layer is lazy, not eager.

RESOLVER (meerkat-auth-core/src/resolver.rs): ZERO logic change. It does no realm selection — it consumes binding.auth\_binding\_ref().realm verbatim (resolver.rs:437 → TokenKey). Once the connection layer stamps the owning realm, ManagedStore reads `<root>/<owner>/<binding>.json`, the AuthMachine LeaseKey is keyed by owner, all automatically. One-canonical-path: provenance is decided ONCE upstream, never re-derived at the credential layer (the flat cross-realm scan that smelled like weak provenance is DELETED, not extended).

BOTH stamping sites + the model-swap filter are converged in ONE pass (judge requirement): (1) core materialize via the chain walk; (2) factory resolve\_selected\_binding\_for\_provider (factory.rs:1838, image/web\_search) re-rooted to walk selected\_realm's chain and stamp the owning member; (3) factory model-swap filter (factory.rs:3114) relaxed to accept inherited owners on the chain. Any one left consuming-stamped misses inherited creds.

SOURCE-KIND CAVEAT (must be pinned by a test, stated in design): decision A only BITES for ManagedStore (and the OAuth-lease / Command-freshness LeaseKey) because those are realm-namespaced via TokenKey/LeaseKey. CredentialSourceSpec::Env (resolver.rs:45, reads process env globally with RKAT\_ override), InlineSecret, and Command MATERIAL are realm-AGNOSTIC — an inherited Env-source binding yields identical creds regardless of owning realm. The design states this explicitly so no operator expects env-key provenance to be realm-scoped. An RCT pins it.

## Cycle & determinism

CYCLE / DEPTH / MISSING-PARENT (RealmChain::resolve, private ctor, fail-closed typed Result, NO unwrap/panic):

* Seed: insert head into `seen: BTreeSet<RealmId>` first (so a realm naming itself as parent is caught as a 1-cycle), push head into ordered `Vec`.
* Loop: read section.parent for the current node. (a) parent not in config.realm AND parent != global → RealmChainError::MissingParent. (b) parent == env\_default slug → RealmChainError::ParentIsEnvDefault (env\_default may never be a chain node). (c) seen.insert(parent) == false → `RealmChainError::Cycle` with the captured path. (d) push parent, continue.
* Depth guard: MAX\_REALM\_CHAIN\_DEPTH = 16; exceeding → RealmChainError::DepthExceeded (belt-and-suspenders even though `seen` bounds finite configs; bounds work + stack).
* Termination: when current node's parent is None. If that terminal node is `global`, done. Else append `global` IFF config.realm.contains\_key(global) AND global ∉ seen (a realm may legitimately reach global via explicit edges; dedup prevents double-visit). If global absent from config, chain terminates at the explicit root and env\_default fallback applies post-chain.
* global invariant: if the head or any visited node is `global` and global.parent.is\_some() → RealmChainError::GlobalHasParent.
* ITERATIVE (no recursion) → wasm stack-safe and depth-guard-safe.

DETERMINISM:

* The chain is a LINEAR sequence (parent is `Option<RealmId>`, a single edge, NOT a list) → order is fully determined by the edges, ZERO dependence on BTreeMap-key iteration order. This is strictly MORE deterministic than the deleted flat scan (which depended on config.realm BTreeMap alpha order at connection.rs:912).
* Folding for EffectiveConfig is parent-first (root→head), making child-wins a deterministic last-write-wins over BTreeMaps; within each section BTreeMap iteration preserves existing determinism. No HashMap/HashSet anywhere (seen is BTreeSet).
* Connection candidate ordering = the chain sequence head→…→global, then env\_default — one deterministic order replacing the old (preferred, "default", alpha-scan, env\_default).
* selected\_binding\_id\_for\_provider iterates realm.bindings (BTreeMap) → deterministic per-member pick. First-marked provider\_default wins by id order (unchanged).

## Contract / schema / wasm / machine

CONTRACT/SCHEMA:

* RealmConfigSection.parent is SCHEMA-INVISIBLE. Config.realm is wire-projected through the opaque ConfigContractSchema.realm: `BTreeMap<String, Value>` (contracts/wire/config.rs:62); RealmConfigSection is absent from wire-types.json/params.json/rest-openapi.json (verified). → NO regen-schemas trip from the parent field, verify-schema-freshness does NOT fire, verify-version-parity (version-string only) does NOT fire.
* AuthBindingRef / BindingOrigin: UNCHANGED (provenance rides the existing realm field, no new variant/field). So the REST bare-AuthBindingRef path (rest-openapi.json via RestCreateSessionRequest, emit.rs:904) and the lossy WireAuthBindingRef projection (drops origin, wire/connection.rs:41) are BOTH untouched → no SDK codegen ripple, no verify-rest/rpc-surface-alignment churn.
* RealmConnectionSet: gains NO field (lazy approach) → its hand-written WireRealmConnectionSet projection (wire/connection.rs:247) and the auth\_binding\_wire.rs struct-literal test are untouched. This is the deliberate economy vs Approach 2 (which broke the struct literal + forced threading provenance into meerkat-llm-core + relaxing registry.rs:208).
* New ConnectionTargetError::RealmChain(RealmChainError) variant: internal Result, NOT wire-exposed → no schema impact.
* DEFENSIVE GATE STEP after implementation: run `make regen-schemas && make verify-version-parity` and ASSERT a NO-OP git diff (do not assume). If any schema file changes, a wire type leaked unexpectedly — STOP and re-audit.
* SDK config-set round-trip: a client setting \[realm.X] config cannot see/validate `parent` (opaque Value map by design) — acceptable; an RCT pins that the unknown field round-trips through the opaque map.

WASM:

* meerkat-core stays wasm32-clean. RealmChain, RealmChainError, compose\_effective\_config, the per-area merge, and ChainComposingConfigStore are pure in-memory data/logic over Config (no std::fs, no tokio). RealmConfigSource and any IO are injected behind the existing cfg(not(wasm32)) gates (FileConfigStore already cfg-gated, config.rs:121). The async\_trait pattern uses the existing `#[cfg_attr(target_arch="wasm32", async_trait(?Send))]` shape (config\_store.rs:35).
* WASM web-runtime synthesizes only config.realm\['default'] (lib.rs:441) and injects a degenerate single-realm RealmConfigSource with no global → its chain is \[default], EffectiveConfig == the single doc, behaviorally identical to today. Provenance still flows via AuthBindingRef.realm into the external resolver. ManagedStore stays cfg(not(wasm32)) (resolver.rs:237). GATE: BuildBuddy wasm-check-submit (buildbuddy.yml:439).

MACHINE-AUTHORITY:

* Realm/parent resolution + Config composition touch NO canonical machine (verified: connection.rs/config.rs have no MachineSchema/generated reducer; AuthMachine, catalog/dsl/auth\_machine.rs, governs only per-binding LEASE lifecycle keyed `<realm>:<binding>`, consuming an already-resolved identity, doing no realm selection). Freeze governance RESPECTED: chain invariants (cycle/depth/single-parent/global-root/env-default-not-parent) enforced by PLAIN RUST — typed RealmChain newtype with private field + fallible ctor + typed RealmChainError. NO new DSL/machine domain.
* RMAT: ForbiddenShellAuthorityReads (xtask rmat\_policy.rs:446) targets machine-owned field reads (.phase(), apply\_mode, etc.); parent-chain reads config DATA, not machine-owned semantic state → does NOT trip the read-seam gate, PROVIDED resolution is not (wrongly) routed through a machine field read. GATE: `make rmat-audit`.
* The provenance change DOES re-key the AuthMachine LeaseKey to the OWNING realm for inherited bindings — correct (the lease for an inherited binding lives at its owning realm), pre-1.0 clean break (any in-flight lease previously keyed under consuming realm is re-keyed, no migration).

REGEN/GATE SEQUENCE: (1) `make regen-schemas` → assert no-op diff. (2) `make verify-version-parity` → green. (3) `make verify-schema-freshness` → green. (4) `./scripts/repo-cargo clippy --workspace -- -D warnings`. (5) `MEERKAT_BUILDBUDDY=1 make test` (unit+int). (6) `make rmat-audit`. (7) BuildBuddy wasm-check.

## Merge-semantics table

| area                                                                                                                               | rule                                                                                                  | rationale                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `realm.<X>.backend` (`[realm.X.backend.*]` map)                                                                                    | no-inherit (resolved within owning realm's own section)                                               | A binding's backend\_profile is resolved ONLY in the realm that DEFINES the binding (the owner). Makes binding-owner==backend-owner an invariant so lookup\_auth\_binding's single-set co-resolution (connection.rs:1200) never splits credential realm from connection-config realm. Resolves judge fatal flaw #1 for Approaches 1/2. A child wanting a parent backend inherits the whole binding (which carries it).                                                                                                                                                               |
| `realm.<X>.auth` (`[realm.X.auth.*]` map)                                                                                          | no-inherit (resolved within owning realm's own section)                                               | Same as backend: auth\_profile (incl. its CredentialSourceSpec) is resolved only in the owning realm's section. Prevents a child redefining auth profile key K from silently re-sourcing a parent-owned binding's credentials (Approach 2's credential-redirect hazard). binding-owner==auth-owner invariant.                                                                                                                                                                                                                                                                        |
| `realm.<X>.binding` (`[realm.X.binding.*]` map)                                                                                    | map-key-union child-wins; OWNING realm = first chain member (child-first) that defines the binding id | The inheritance unit. The lazy walk visits head→…→global; the first member defining the requested/selected binding id is the owner; materialize is fed THAT member's own RealmConnectionSet so AuthBindingRef.realm=owner (decision A). For the EffectiveConfig.realm map, child-wins union makes inherited sections visible. Per-section from\_config still validates provider agreement in isolation before any cross-realm interaction (preserves ProviderMismatch semantics).                                                                                                    |
| `realm.<X>.default_binding` (`Option<String>`)                                                                                     | child-wins-scalar (nearest-child non-None wins; resolves against owner's binding set)                 | Single pointer. Converge BOTH selection policies (resolve\_realm\_binding\_target\_for\_provider's default\_binding-only inline pick :703 and selected\_binding\_id\_for\_provider's default\_binding>provider\_default>sole :825) onto selected\_binding\_id\_for\_provider applied per chain member, so there is ONE canonical selection policy.                                                                                                                                                                                                                                   |
| `realm.<X>.parent` (`Option<RealmId>`)                                                                                             | no-inherit (structural metadata of THIS node only)                                                    | The parent edge defines the graph; inheriting it would corrupt the chain. Consumed by RealmChain::resolve only, never folded into any effective section or doc.                                                                                                                                                                                                                                                                                                                                                                                                                      |
| Config.models (ModelDefaults per-provider default model strings)                                                                   | map/field-key-union child-wins (per provider)                                                         | The user's headline model-config-inheritance goal. Each provider key (anthropic/openai/gemini) folds independently so a child overrides its anthropic default while inheriting the parent's openai default. Reworks today's whole-replace (config.rs:315) to field-level. Composed eagerly into EffectiveConfig; the agent's flat reads (factory.rs:3039) see the merged result with no code change.                                                                                                                                                                                 |
| Config.agent.model + agent.{system_prompt,tool_instructions,extraction_prompt,max_tokens_per_turn} + Config.max\_tokens            | child-wins-scalar (nearest-child non-default wins)                                                    | Scalar/optional model-shaping fields. Mirrors existing last-non-default-wins (config.rs:280-294) applied parent→child. Presence-sensitive non-Option fields (model, max\_tokens\_per\_turn, max\_tokens) need toml::Value presence parsing to distinguish omitted from default (open question).                                                                                                                                                                                                                                                                                      |
| Config.hooks (HooksConfig: entries Vec + scalar timeouts)                                                                          | list-append (entries parent-first then child); scalars child-wins                                     | PRESERVES the one pre-existing additive layering (append\_entries\_from :1927, load\_layered\_hooks :183). Parent hooks run before child hooks (child most-specific/last). Folds the orphaned load\_layered\_hooks into the one canonical chain path. Child cannot delete a parent hook. Dedup by hook identity to avoid double-run (open question on identity key).                                                                                                                                                                                                                 |
| Config.tools.mcp\_servers (id→server map) + tools.\*\_enabled toggles                                                              | map-key-union child-wins (servers); child-wins-scalar (toggles)                                       | MCP servers keyed by name: child adds servers + overrides a same-named parent server while inheriting the rest. Reworks merge\_tools whole-replace (:312) to union. Enabled toggles: a child explicitly disabling a tool overrides parent. Deterministic via BTreeMap iteration.                                                                                                                                                                                                                                                                                                     |
| Config.skills (SkillsConfig sources/registries)                                                                                    | list-append parent-first, child-wins on source-identity collision                                     | Skill sources additive like hooks: child inherits parent sources + appends its own; on a name/path/url collision child shadows the one source without dropping the rest. Dedup identity = source path/url/name (open question).                                                                                                                                                                                                                                                                                                                                                      |
| Config.limits (LimitsConfig, incl. max\_sessions)                                                                                  | child-wins-scalar (per-field)                                                                         | Per-field override so a child tightens max\_sessions while inheriting other caps. Reworks today's whole-section whole-replace (:333) which makes partial child override impossible along a chain.                                                                                                                                                                                                                                                                                                                                                                                    |
| Config.{storage,store,budget,retry,compaction,rest,self_hosted,provider_tools,presentation}                                        | child-wins-scalar (per-field); model\_fallback keeps catalog-reset semantics                          | Runtime/infra sections: per-field child-wins (Option::is\_some or !=Default). These ARE config (not state) so they inherit, though most are realm-uniform in practice. model\_fallback preserves use\_catalog\_default\_chain reset (:352) applied along the chain.                                                                                                                                                                                                                                                                                                                  |
| Config.model\_fallback (ModelFallbackConfig)                                                                                       | child-wins with catalog-default-chain reset precedence                                                | If a child sets use\_catalog\_default\_chain, it resets to default (matches :352); else child-wins-whole when non-default. Special-cased to preserve the existing reset contract.                                                                                                                                                                                                                                                                                                                                                                                                    |
| STATE: sessions, leases, event log, ops snapshots, .rkat/sessions projection, RealmPaths session/manifest dirs, TokenStore RECORDS | no-inherit (ABSOLUTE)                                                                                 | Composition reads only ancestor config sections/docs. All state keyed by the CONSUMING (head) realm via realm\_paths\_in(head); the composer never touches ancestor RealmPaths except to READ config. The TokenStore NAMESPACE KEY (TokenKey.realm) is the OWNING realm for credential RECORDS (decision A) — that is provenance of where a credential is defined, NOT inheritance of credential/session state. A child gains the parent's binding DEFINITION and resolves that binding's token at the parent's credential namespace; it does NOT gain the parent's sessions/leases. |

## Type / function changes

* **RealmConfigSection.parent** `[meerkat-core/src/connection.rs:1272 (struct body, add after default_binding at :1280)]` — Add `#[serde(default, skip_serializing_if = "Option::is_none")] pub parent: Option<RealmId>`. Typed newtype, NOT String. Schema-INVISIBLE: Config.realm is wire-projected through the opaque ConfigContractSchema.realm: `BTreeMap<String, Value>` (contracts/wire/config.rs:62), and RealmConfigSection is absent from every emitted schema (verified). NO regen needed for this field alone. RealmConfigSection already derives schemars under feature=schema (:1271) so the field compiles under that feature with no extra annotation.
* **GLOBAL\_REALM\_SLUG + RealmId::is\_global + RealmId::global** `[meerkat-core/src/connection.rs:148 (next to ENV_DEFAULT_REALM_SLUG) and :150-159 (impl RealmId block)]` — Add `pub const GLOBAL_REALM_SLUG: &str = "global";`. Add `RealmId::is_global(&self) -> bool { self.as_str() == GLOBAL_REALM_SLUG }` mirroring is\_env\_default. Add `RealmId::global() -> RealmId { RealmId::from_known_valid(GLOBAL_REALM_SLUG) }` (infallible mint via the existing private-ctor path used for env\_default at :1168).
* **RealmChain (new type)** `[meerkat-core/src/connection.rs (new, near RealmConfigSection ~:1262)]` — `pub struct RealmChain { realms: Vec<RealmId> }` — PRIVATE field, single fallible ctor `pub fn resolve(config: &Config, head: &RealmId) -> Result<RealmChain, RealmChainError>`. Walks parent edges (BTreeSet seen-dedup + Vec order), appends `global` iff config.realm contains it and not already seen, fail-closed on cycle/depth/missing-parent/global-has-parent/parent-is-env-default. Accessor `pub fn realms(&self) -> &[RealmId]`. Iterates head→global (child-first). NO unwrap/panic.
* **RealmChainError (new typed error)** `[meerkat-core/src/connection.rs (new, IdentityError-adjacent family)]` — `#[derive(Debug,Clone,Error,PartialEq,Eq)] pub enum RealmChainError { Cycle{chain:Vec<String>}, DepthExceeded{head:String,max:usize}, MissingParent{realm:String,parent:String}, GlobalHasParent{realm:String}, ParentIsEnvDefault{realm:String} }`. NOT wire-exposed (internal Result). Add `MAX_REALM_CHAIN_DEPTH: usize = 16` const next to GLOBAL\_REALM\_SLUG. Add `From<RealmChainError>` for ConnectionTargetError (new variant `ConnectionTargetError::RealmChain(RealmChainError)` at connection.rs:617).
* **compose\_effective\_config (new fn)** `[meerkat-core/src/config.rs (new, near merge :278)]` — `pub fn compose_effective_config(sections: &BTreeMap<RealmId, Config>, head: &RealmId) -> Result<Config, RealmChainError>` — takes the per-realm Config docs along the chain (root-first), folds them via per-area merge rules (see merge table) into the effective flat Config the agent reads. Reuses RealmChain::resolve on the head's chain (the realm graph is read from the head doc's .realm map plus injected ancestor docs). The `realm` MAP itself is also merged (map-key-union child-wins) so an inherited \[realm.X] section is visible to the connection resolvers.
* **Config::merge rework (field-level child-wins + realm-map fold)** `[meerkat-core/src/config.rs:278-357]` — Rework so it (1) ACTUALLY folds self.realm (today it is silently dropped — verified, merge never references self.realm) via map-key-union child-wins; (2) uses FIELD-LEVEL child-wins for limits/store/comms/compaction/rest (today whole-section whole-replace at :324-337 prevents partial child override); (3) keeps hooks.entries.extend append (:350) and adds skills/mcp\_servers append/union; (4) keeps model\_fallback catalog-reset (:352). Presence-aware where serde(default) loses omitted-vs-default: parse ancestor docs from toml::Value for the presence-sensitive scalar fields (see open questions). This is the SINGLE merge engine; compose\_effective\_config calls it parent-first.
* **RealmConfigSource (new trait) + ChainComposingConfigStore (new decorator)** `[meerkat-core/src/config_store.rs (new, near ConfigStore trait :37)]` — `pub trait RealmConfigSource: Send+Sync { async fn config_for_realm(&self, realm: &RealmId) -> Result<Option<Config>, ConfigError>; }` (?Send on wasm32 via the existing async\_trait cfg pattern). `pub struct ChainComposingConfigStore { head: RealmId, source: Arc<dyn RealmConfigSource>, catalog: ModelCatalog }` implementing ConfigStore: get() resolves the head's chain, fetches each member Config via source, composes via compose\_effective\_config, returns the effective Config. set()/patch() operate on the HEAD realm's own doc only (writes never compose). NO fs in core — source is injected.
* **resolve\_realm\_binding\_target\_for\_provider — chain-aware + owning-set** `[meerkat-core/src/connection.rs:665-750]` — Replace candidate list `[preferred,"default"]` (:674-684) and the inline default\_binding-ONLY pick (:700-719) with: build RealmChain from head (preferred or "default" or global); iterate chain members; per member build RealmConnectionSet::from\_config(member, section) and call selected\_binding\_id\_for\_provider (CONVERGING the two divergent selection policies onto one); FIRST member yielding a provider binding is the owner; materialize with THAT member's own set. Explicit\_realm path: walk its chain so an explicit realm inherits a binding from its parent, stamping the OWNING member.
* **resolve\_auth\_binding\_candidates\_for\_provider — chain-aware + owning-set** `[meerkat-core/src/connection.rs:886-966]` — DELETE the flat scan `for realm_id in config.realm.keys()` (:912-914), the literal "default" push (:911), and push\_candidate\_realm\_ids (:862, now unused). Build RealmChain from head; for each chain member build its OWN RealmConnectionSet and run selected\_binding\_id\_for\_provider; push one materialized candidate per member that has a provider binding (each carrying owner=member via materialize). env\_default append (:942) UNCHANGED as the single typed terminal fallback.
* **resolve\_auth\_binding\_or\_default\_for\_provider — explicit ref chain-aware** `[meerkat-core/src/connection.rs:755-795]` — Explicit-AuthBindingRef branch (:762-783): if the named realm's own section does not define the binding, walk its chain to find the OWNING member, build that member's set, materialize with owner=member. Keep is\_env\_default rejection (:764). One canonical path: provenance decided here, once, upstream.
* **materialize\_connection\_target — UNCHANGED logic, owner fed in** `[meerkat-core/src/connection.rs:968-1010]` — NO code change: it already stamps realm.realm\_id (:978). Correctness comes from callers feeding it the OWNING realm's own RealmConnectionSet (realm\_id==owner). registry.rs:208 equality continues to hold. Add a debug\_assert documenting the single-writer invariant.
* **factory resolve\_selected\_binding\_for\_provider — chain-aware (SECOND stamping site)** `[meerkat/src/factory.rs:1806-1843]` — Replace single-section from\_config on selected\_realm (:1814-1821) + `realm: selected_realm` stamp (:1838) with a chain walk rooted at selected\_realm: find the owning member that defines a usable binding (reuse selected\_binding\_id\_for\_provider :1688), build that member's OWN set, stamp realm=OWNER. Without this, image/web\_search inherited bindings stamp the consuming realm and miss parent creds (judge fatal flaw). image (:1903) and web\_search (:1936) callers flow through this fix automatically.
* **factory model-swap candidate filter — accept inherited owners** `[meerkat/src/factory.rs:3099-3126]` — The catalog\_default\_chain filter `target.auth_binding.realm == *realm` (:3114) rejects inherited candidates (owner != preferred). Change to: accept any candidate on the preferred realm's chain (i.e. drop the equality, keep `!is_env_default()`), since the chain walk already scopes candidates to ancestors. Otherwise hot model-swap silently drops every inherited binding (judge fatal flaw).
* **CLI login realm: dev → global (collapse 3 literals)** `[meerkat-cli/src/main.rs:4755, :4003, :5391, :5742]` — CLI\_INTERACTIVE\_OAUTH\_REALM\_ID (:4755) → RealmId::global() (typed). auth\_config\_realm\_or\_default (:4003) unwrap\_or("dev") → unwrap\_or(GLOBAL\_REALM\_SLUG). ensure\_cli\_interactive\_oauth\_config (:4886) + resolve\_configured\_cli\_interactive\_oauth\_target (:4977) take a target RealmId and synthesize/read \[realm.global] into the GLOBAL realm's ConfigStore (new seam: resolve a store for global, not always scope). noninteractive\_login (:5322) gains a realm param (currently ignores override); TokenKey::parse("dev",..) at :5391/:5742 → resolved owner. All `dev:<binding>` user strings (:5417/:5489/:5687/:5699) → resolved owner. Update pinned test (not bypass) at :16447.
* **WireRealmConnectionSet / WireAuthBindingRef** `[meerkat-contracts/src/wire/connection.rs:237 and :22]` — NO CHANGE. RealmConnectionSet gains NO field (lazy approach), so the hand-written WireRealmConnectionSet projection (:247) is untouched and realm/get returns the head realm's OWN composed-but-uncomposed section as today. WireAuthBindingRef (drops origin) untouched. This is the deliberate economy that yields zero SDK codegen / surface-alignment churn.

## MUST-FIX amendments (red-team)

* **MF-01** (amends canonical\_design + P2; add gate to P2)
  * finding: Verified at connection.rs:681-682 and :911: TWO distinct legacy fallbacks exist. The flat scan `for realm_id in config.realm.keys()` (the plan deletes it) AND a hard-coded literal `"default"` realm candidate pushed unconditionally in BOTH resolvers (`candidates.push("default")` and `push_candidate_realm_ids(..., Some("default"))`). The plan's type\_changes for resolve\_realm\_binding\_target\_for\_provider does mention replacing `[preferred,"default"]`, but RCT-09 only pins the flat-scan removal, and no RCT pins that a `[realm.default]` shared-creds config is NO LONGER consulted for a head realm that does not parent to it. An operator with `[realm.default]` holding shared OpenAI creds, running in realm `prod` (no openai binding, no parent edge), resolves openai today via the implicit `default` candidate; after the cut the chain is \[prod, global?] and resolution falls to env\_default or hard-fails silently. No existing test covers `default` as a non-head fallback.
  * fix: State explicitly in canonical\_design that the literal `"default"` realm candidate is REMOVED in BOTH resolvers alongside the flat scan (it is a distinct mechanism, not the same one). Decide and document the head-default: with WorkspaceDerived realms the head is `ws-{hash}`, not `default`/`dev`, so the implicit-global-tail is the only bridge. Add RCT-25 (below) proving a `[realm.default]` section is no longer consulted and the same creds work once moved to `[realm.global]`. The migration (MF-08) must rewrite the config SECTION too, not only copy the credential directory, or legacy single-realm users lose their only binding.
* **MF-02** (amends cycle\_and\_determinism + P2)
  * finding: Verified at connection.rs:764: the explicit-AuthBindingRef branch does `config.realm.get(realm_id).ok_or_else(|| UnknownRealm)` — an absent explicit realm hard-errors UnknownRealm, while the non-explicit path (resolve\_realm\_binding\_target\_for\_provider) uses `continue` on an absent realm. The pinned test `auth_binding_candidates_scan_configured_realms_before_env_default` (connection.rs:2026) sets preferred\_realm=`missing` (absent from config) and asserts candidates\[0].realm==`dev` via the flat scan. The plan lists this test for rewrite in P2 but never specifies the TARGET behavior when the HEAD realm is absent from config: does RealmChain::resolve error, yield a single-node chain \[head] contributing nothing, or implicitly append global? RealmChain::resolve reads `section.parent` but `config.realm.get(head)` is None so there is no section to read.
  * fix: Specify: an absent head yields a single-node chain \[head] that contributes no sections (head's own section None → skipped), then implicit-global-tail (if global present) + env\_default apply exactly as today's per-member `continue` path. Do NOT make an absent head a hard MissingParent/UnknownRealm error in the non-explicit path. Preserve the per-member asymmetry: explicit-named absent realm still errors UnknownRealm; non-explicit/preferred absent realm falls through. Pin with RCT-26.
* **MF-03** (amends type\_changes (ConnectionTargetError) + P5/P6 gate)
  * finding: Verified at meerkat-rest/src/auth\_endpoints.rs:106: `target_error_status` is an EXHAUSTIVE match over ConnectionTargetError with no wildcard, and ConnectionTargetError (connection.rs:618) is NOT #\[non\_exhaustive]. Adding `ConnectionTargetError::RealmChain(RealmChainError)` (plan type\_changes) is a hard COMPILE ERROR in meerkat-rest. (The RPC mapping at handlers/auth.rs:200 uses `_ =>` so RPC compiles — the critiques overstated an RPC compile break.) meerkat-rest is in no phase before P8, so this surfaces only at the broad lane.
  * fix: Add meerkat-rest/src/auth\_endpoints.rs:106 to the edit-site list; add the RealmChain arm to target\_error\_status (map to BAD\_REQUEST or NOT\_FOUND per the inner error). Add `-p meerkat-rest -p meerkat-rpc` to the P5/P6 gates so the compile break and behavior change are caught before P8.
* **MF-04** (amends new phase P5b (REST/RPC auth re-root))
  * finding: Verified: REST resolve\_binding\_identity (auth\_endpoints.rs:77-104) and RPC resolve\_binding\_identity (handlers/auth.rs:151-163) both build `AuthBindingRef { realm: realm_id.clone(), ... }` from the REQUEST realm and call `realm.lookup_auth_binding` on a SINGLE realm section (resolve\_realm, single-section from\_config). These are the OAuth/credential PERSISTENCE paths (require\_managed\_store\_source → PreparedTokenCommitSnapshot → TokenKey). After decision A makes provenance = owning realm, a client POSTing creds for child realm `team` whose binding lives only in `global` either 404s (single-section lookup misses) or persists the TokenKey under `team` (request realm) while the chain-aware factory build path reads from `global` (owner) — credential written to one dir, read from another = silent auth failure on two network surfaces. REST resolve\_oauth\_target (auth\_endpoints.rs:127) also calls resolve\_realm\_binding\_target\_for\_provider with an explicit realm, so the explicit-realm chain-widening behavior change hits REST/RPC too. Both files are in NO phase.
  * fix: Add a phase (e.g. P5b) re-rooting resolve\_binding\_identity in BOTH meerkat-rest and meerkat-rpc onto the chain: resolve the owning chain member for (realm, binding) and stamp AuthBindingRef.realm = owner before lookup/persist, mirroring the factory. Decide explicitly whether REST/RPC credential-persist should inherit (walk chain) or stay strict-owner (write only to the named realm); document it. Add RCT-27 (REST) and RCT-28 (RPC).
* **MF-05** (amends new phase P5b/P6 (per-surface composer wiring))
  * finding: Verified: ChainComposingConfigStore is wired only at the CLI (P6, meerkat-cli/src/main.rs:3737). REST builds its own store at lib.rs:415 (FileConfigStore → TaggedConfigStore → ConfigRuntime), RPC at main.rs:226 (FileConfigStore → ConfigRuntime, set\_config\_runtime at :348), MCP-server at lib.rs:450 — NONE wrap the composer. ConfigRuntime.get() (config\_runtime.rs:128) just returns store.get(). So top-level inheritance (model/mcp/hooks/skills/limits) AND the implicit-global-tail are silently dead on REST/RPC/MCP: config.realm on those surfaces contains only the head realm's own doc, so the connection-layer implicit-global-tail won't fire either (config.realm has no `global` key). Divergent resolution across interchangeable surfaces is exactly the regression class the dogma forbids.
  * fix: Wrap ChainComposingConfigStore at EVERY surface's store-construction site (rest lib.rs:415, rpc main.rs:226, mcp-server lib.rs:450), OR compose inside ConfigRuntime once. Add surface-level RCTs (RCT-29) asserting an inherited \[realm.global] model/mcp/cred is visible through each surface's config\_runtime.get(). Note the set(get()) hazard in MF-06 — the composer must NOT be the symmetric store the write path round-trips.
* **MF-06** (amends type\_changes (ChainComposingConfigStore / new EffectiveConfigReader) + P3)
  * finding: Verified: ConfigStore (config\_store.rs:37) is a SYMMETRIC trait — get()→Config, set(Config), patch(delta)→Config. The RPC config/set handler (handlers/config.rs:285) does `prior = config_runtime.get()` then `config_runtime.set(config, ...)`. If ChainComposingConfigStore.get() composes the chain (inherited mcp/hooks/skills flattened in) and set() writes head-only (plan: 'writes never compose'), then any read-modify-write round-trips the COMPOSED blob into the head doc, durably materializing every parent-inherited entry into the child. This permanently flattens inheritance and violates user decision 3 (children CANNOT remove inherited mcp/hook entries — after one set(get()) they own frozen copies they can never shed, and future parent edits stop propagating). Same shape exists wherever config is read-then-written.
  * fix: Do NOT have ChainComposingConfigStore implement the symmetric ConfigStore trait. Introduce a read-only EffectiveConfigReader (get\_effective) consumed ONLY by the agent build path (factory) and surface config/get-for-resolution, and keep ConfigStore.get() returning the RAW head doc everywhere a write may follow. The RPC/REST prior-capture and patch path must read the raw head, never the composed view. ChainComposingConfigStore.set/patch must persist only the delta against the head doc's PRE-composition state. Add RCT-30 pinning that a config write after a composed read does NOT flatten inherited entries into the head doc.
* **MF-07** (amends type\_changes (RealmConfigSource) + P3/P6)
  * finding: Verified at config\_store.rs:191: FileConfigStore::get() returns `Ok(Config::default())` when the file is absent, NOT None. The plan's RealmConfigSource trait returns `Result<Option<Config>>` and RCT-16 asserts 'absent ancestor doc = empty pass-through (NOT Config::default clobber)'. If P6's filesystem RealmConfigSource delegates to FileConfigStore::get for ancestor docs, an absent ancestor yields a full Config::default() folded into the chain. Because compose folds parent-first and model\_fallback has a catalog-reset branch (config.rs:352, if other.model\_fallback.use\_catalog\_default\_chain → reset), a default ancestor can RESET model\_fallback mid-chain and clobber any field where 'default' is meaningful. RCT-16 passes against a hand-rolled mock source but the real CLI source built on FileConfigStore violates it.
  * fix: The filesystem RealmConfigSource MUST check tokio::fs::try\_exists itself and return None on absence (not call FileConfigStore::get). Document that config\_for\_realm returns None iff the file is absent, Some(parsed) otherwise. Strengthen RCT-16 to assert a None ancestor is byte-identical to that ancestor being omitted from the chain AND that model\_fallback catalog-reset is not triggered by an absent ancestor. Add an RCT (RCT-31) exercising the REAL FileConfigStore-backed source, not only the mock.
* **MF-08** (amends new phase P6b (migration) + canonical\_design/provenance\_design prose)
  * finding: User-confirmed decision 1 is MIGRATE dev creds to global (one-time copy, not orphan). The plan CONTRADICTS this: canonical\_design says 'existing dev/\* tokens orphaned, one-time re-login', provenance\_design repeats 'pre-1.0 clean break, one-time re-login', and open\_question #6 re-litigates it. Verified: no phase contains migration code (grep for 'migrat' across P0-P8 = zero), and no RCT covers dev→global copy. Credential root is `<base>/meerkat/credentials/<realm>/<binding>.json` (mod.rs:78, file.rs path\_for), so migration is a `credentials/dev/* → credentials/global/*` directory copy. Without it, every existing logged-in install fails auth on first run after upgrade because the resolver now stamps owner=global and reads credentials/global/ which is empty.
  * fix: Add an explicit migration phase (P6b) implementing the one-time idempotent copy: copy `credentials/dev/<binding>` → `credentials/global/<binding>` IFF the target is absent (no clobber); leave dev/ intact or remove per decision. Reconcile canonical\_design/provenance\_design to say COPY, not orphan; remove open\_question #6 (it is a settled decision). Add RCT-32: creates a dev token on disk, runs migration, asserts the credential resolves at owning realm global; re-running migration is a no-op; an existing global cred is not clobbered.
* **MF-09** (amends P5 (add the gate edit to the writes) + RCT-20 fixture)
  * finding: Verified at factory.rs:1894 (image) and :1924 (web\_search): the chain-capable branch (resolve\_realm\_binding\_for\_provider with Some(selected\_realm)) is taken ONLY when `!config.realm.contains_key(selected_realm)`. When the selected realm EXISTS but its own section defines no image/web\_search binding (the inheritance case), it falls to resolve\_selected\_image/web\_search\_binding\_for\_provider → resolve\_selected\_binding\_for\_provider (factory.rs:1806) which does single-section from\_config(selected\_realm) and stamps realm=selected\_realm. The plan edits resolve\_selected\_binding\_for\_provider (P5) to walk the chain but does NOT edit the :1894/:1924 contains\_key gate that decides which path is taken. So a child realm that EXISTS but inherits its image/web\_search binding silently gets no binding. RCT-20 will pass if its fixture defines the binding directly in selected\_realm, masking the gate bug.
  * fix: Edit the contains\_key gates at factory.rs:1894 and :1924 so the chain walk is taken whenever the selected realm's OWN section lacks the binding, not only when the realm is absent (or fold the chain walk into resolve\_selected\_binding\_for\_provider and route through it unconditionally for present realms). RCT-20's fixture MUST define the image binding ONLY in the parent while selecting the child, to exercise the inherited path.
* **MF-10** (amends P5 (factory self\_hosted) + P6 (CLI doctor); add to edit-site list)
  * finding: Verified two unlisted explicit-realm callers that P2's chain-widening behaviorally changes: meerkat/src/factory.rs:3349 configured\_self\_hosted\_connection has a `config.realm.contains_key(realm_id)` guard then calls resolve\_realm\_binding\_target\_for\_provider(Some(realm\_id), ...); its fallthrough swallows errors to Ok(None) at :3377-3383 (silent). And CLI doctor self\_hosted at meerkat-cli/src/main.rs:5981/5999 passes Some(preferred\_realm). After P2, a self\_hosted binding defined only in a PARENT of the explicit realm now resolves where it previously errored (owner=parent stamping) — a behavior change for `rkat doctor` and self-hosted client construction, with the Ok(None) swallow potentially masking a chain-resolution failure. No RCT covers self\_hosted inheritance. These are concrete execution seams the plan's edit-site enumeration misses.
  * fix: Enumerate ALL explicit-realm callers in the plan (factory.rs:3349, cli main.rs:5981/5999, rest auth\_endpoints.rs:127, rpc handlers/auth.rs:188) and decide per-caller whether chain-widening is desired for self\_hosted (or explicit no-inherit if that is the product choice). Edit the contains\_key guard at factory.rs:3349 for the inherited case, and verify the Ok(None)-on-error swallow at :3377 does not mask a chain-resolution failure. Add RCT-33 for self\_hosted inheritance via the explicit-realm seam.
* **MF-11** (amends merge\_semantics\_table + type\_changes (Config::merge) + P3)
  * finding: Verified the merge ambiguity is load-bearing: merge\_semantics\_table marks realm.X.backend and realm.X.auth as 'no-inherit (resolved within owning realm's own section)' (RCT-08), but compose\_effective\_config type\_changes + RCT-15 say Config::merge 'folds self.realm via map-key-union child-wins' so 'an inherited \[realm.X] section is visible'. RealmConnectionSet::from\_config (connection.rs:1057-1064, the actual line) resolves each binding's backend\_profile/auth\_profile via backends.get()/auth\_profiles.get() WITHIN the same section and errors UnknownBackend/UnknownAuth if absent. If 'fold the realm map child-wins' is read as unioning the backend/auth/binding SUB-maps across realms, no-inherit is destroyed and the credential-redirect hazard re-opens. If it means only outer realm-key union, the phrase is a no-op when the head doc already has the keys. The plan never states the level unambiguously, and RCT-08 + RCT-15 can both pass under a wrong implementation. Additionally, a child that declares a binding referencing a parent-only backend\_profile will fail from\_config(child) with a confusing UnknownBackend, with unspecified failure semantics.
  * fix: State explicitly: compose\_effective\_config unions ONLY the outer realm→section keys (adds ancestor realm entries absent from the head); it MUST NOT union backend/auth/binding sub-maps across realms. Each ancestor RealmConfigSection stays an intact map entry; the chain walk visits them as separate owning sets. Make the no-inherit-for-auth/backend rule fail-closed and validated: a child binding whose backend\_profile/auth\_profile is not present in the child's OWN section either errors with a typed message naming the missing profile and realm, or requires redeclaring backend+auth. Add RCT-34: a child binding referencing a parent-only backend\_profile fails closed with a typed error (distinct from RCT-08's inherited-WHOLE-binding case).
* **MF-12** (amends cycle\_and\_determinism + P2 gate)
  * finding: Nearest-child-wins precedence is unpinned across BOTH chain consumers. resolve\_auth\_binding\_candidates\_for\_provider returns an ORDERED Vec the factory iterates as fallbacks (build path consumes candidates\[0]; model-swap at factory.rs:3114 finds first matching). Today's order is preferred → literal 'default' → alpha-scan → env\_default; the plan replaces it with chain order \[head, parents..., global, env]. RCT-09 only asserts unrelated siblings are excluded; RCT-06/07 assert owner stamping. NONE assert the relative ORDER when MULTIPLE chain members each define a usable provider binding (head defines anthropic binding A, global defines anthropic binding B — which is selected first?). cycle\_and\_determinism discusses sequence determinism but never pins the PRODUCT choice of nearest-wins. A regression here silently switches which real credential/account the agent authenticates with. Also default\_binding has divergent semantics: the connection path uses selected\_binding\_id\_for\_provider PER member (checks default\_binding against THAT member's own bindings), so a child default\_binding pointing at a parent-owned binding id silently no-ops in the connection path while appearing 'set' in EffectiveConfig.
  * fix: Add RCT-35 pinning nearest-child-wins: when head and an ancestor both define a usable provider binding, candidate\[0] is the head, ancestor next, global last, env terminal. Make nearest-child-wins an explicit invariant shared by compose\_effective\_config.default\_binding selection AND the candidate resolver, with shared fixtures. Decide and document child.default\_binding pointing at a parent-owned binding id: either resolve against the COMPOSED chain or document it as owner-section-scoped only; pin with an RCT.
* **MF-13** (amends P6 + canonical\_design (global location); remove open\_question #5)
  * finding: Verified the global-doc location conflicts with the workspace-rooted CLI store, breaking the headline cross-workspace promise of decision 2. default\_cli\_state\_root (main.rs:3733) = context\_root/.rkat/realms (context\_root defaults to current directory = WORKSPACE-local). resolve\_config\_store (main.rs:3737-3753) uses realm\_paths\_in(scope.locator.state\_root, realm) → `<workspace>/.rkat/realms/<realm>/config.toml`. P6 says the RealmConfigSource is 'realm\_paths\_in based', so realm\_paths\_in(state\_root, 'global') → `<workspace>/.rkat/realms/global/config.toml` — a PER-WORKSPACE file, NOT the single \~/.rkat doc decision 2 mandates. global\_config\_path() (config.rs:217) = \~/.rkat/config.toml exists but is never wired into resolve\_config\_store. So login-once-to-global inherited across workspaces cannot work: each workspace gets an isolated global doc, or the source reads a location login never wrote to. open\_question #5 re-litigates this settled decision.
  * fix: Make the global node's RealmConfigSource entry HOME-rooted, not state\_root-rooted: read the single dedicated doc at \~/.rkat (reuse global\_config\_path / a \~/.rkat/realms/global location independent of the workspace state\_root). The composer must compose \[workspace head doc] over \[fixed home-rooted global doc]. Login must WRITE to the SAME home-rooted location the composer reads. Remove open\_question #5 (settled by decision 2). Add RCT-36: login under workspace A is visible to a composed get() under workspace B (cross-workspace), asserting the exact physical write path equals the composer's global source path; realm\_paths\_in(state\_root,'global') is NOT the global source.
* **MF-14** (amends P3 + open\_question #2 resolution)
  * finding: User decision 3 (children CANNOT remove inherited mcp/hook entries — no tombstones) is tested only POSITIVELY. RCT-13 (hooks append parent-first) and RCT-14 (mcp map-union child-wins) verify inherited entries are PRESENT and overridable, but no RCT asserts a child with NO mcp\_servers / NO hooks STILL inherits all parent entries, or that a child setting an empty \[tools]/\[hooks] table does NOT drop inherited entries. Verified today merge\_tools whole-replaces mcp\_servers when non-empty (config.rs:380-382), so the rework to union must be tested for the no-removal property specifically. A future regression (child empty mcp map silently dropping inherited servers) would pass RCT-13/14 yet violate decision 3 — a child silently disabling an inherited security hook is an auth/safety regression.
  * fix: Add RCT-37: a child realm with NO mcp\_servers and NO hooks still inherits ALL parent mcp\_servers and hook entries; a child setting an empty \[tools]/\[hooks] table does not remove inherited entries (emptiness != removal, no tombstone honored). Resolve the hooks/skills dedup identity key (open\_question #2) as a typed function before P3 so an inherited-then-redeclared hook does not double-run; extend RCT-13 to a 3-level chain asserting the exact ordered, deduped entry list.

## RCT contracts — failing-tests-first

### Original (RCT-01..24)

* **RCT-01** \[meerkat-core] `realm_config_section_parent_roundtrips_and_defaults_none` — RealmConfigSection round-trips parent: `Option<RealmId>` through TOML/serde; absent parent == None; present parent = "global" parses to RealmId. Config.realm map serde unchanged.
* **RCT-02** \[meerkat-core] `global_realm_is_typed_and_distinct_from_env_default` — RealmId::is\_global / RealmId::global / GLOBAL\_REALM\_SLUG are typed; global is distinct from env\_default (is\_global != is\_env\_default); both recognized by predicate not raw compare.
* **RCT-03** \[meerkat-core] `realm_chain_resolves_linear_order_with_implicit_global_tail` — RealmChain::resolve walks a linear chain head→parent→global in deterministic order; parentless non-global head implicitly appends global when present; global.parent=None terminates.
* **RCT-04** \[meerkat-core] `realm_chain_detects_cycle_depth_missing_global_and_env_default` — RealmChain::resolve fails closed: self-parent → Cycle; A→B→A → Cycle{path}; missing parent → MissingParent; global with parent → GlobalHasParent; parent=env\_default → ParentIsEnvDefault; depth>16 → DepthExceeded. No panic.
* **RCT-05** \[meerkat-core] `realm_chain_omits_absent_global_and_falls_through_to_env_default` — Absent \[realm.global] → chain terminates at explicit root, no global contribution; env\_default fallback still applies post-chain when allow\_env\_default.
* **RCT-06** \[meerkat-core] `inherited_binding_stamps_owning_realm_not_consuming_realm` — OWNING != CONSUMING provenance: a binding defined ONLY in parent realm P, resolved while preferred realm is child C, yields AuthBindingRef.realm == P (the owner), NOT C. selected via the auth\_binding-omitted path.
* **RCT-07** \[meerkat-core] `resolved_target_realm_equals_owning_connection_set_realm_id` — materialize is fed the owner's own RealmConnectionSet so registry-equality invariant auth\_binding.realm == realm.realm\_id HOLDS for inherited bindings (no relaxation).
* **RCT-08** \[meerkat-core] `inherited_binding_resolves_backend_auth_in_owning_realm_only` — no-inherit for auth/backend: a binding inherited from parent P resolves its backend\_profile/auth\_profile in P's OWN section; a child C redefining auth-profile-key K does NOT re-source P's binding (binding-owner==backend-owner==auth-owner).
* **RCT-09** \[meerkat-core] `candidates_follow_parent_chain_not_flat_realm_scan` — the flat cross-realm scan is replaced by the chain: a provider binding in an UNRELATED sibling realm (not an ancestor of head) is NOT auto-discovered; only chain members + env\_default are candidates, in chain order.
* **RCT-10** \[meerkat-core] `single_target_path_uses_unified_selection_policy` — single canonical selection policy: resolve\_realm\_binding\_target\_for\_provider now honors provider\_default and single-unambiguous (not default\_binding-only) — same policy as selected\_binding\_id\_for\_provider, applied per chain member.
* **RCT-11** \[meerkat-core] `explicit_inherited_binding_resolves_at_owning_realm` — explicit AuthBindingRef naming child realm C whose binding is defined only in parent P resolves at P (owner stamped P); explicit env\_default ref still rejected.
* **RCT-12** \[meerkat-core] `effective_config_unions_model_defaults_child_wins` — compose\_effective\_config: Config.models per-provider union child-wins (child anthropic default + inherited parent openai default both present); agent.model child-wins-scalar.
* **RCT-13** \[meerkat-core] `effective_config_appends_hooks_parent_first` — compose\_effective\_config: hooks.entries append parent-first-then-child; child cannot delete parent hook; scalar hook timeouts child-wins.
* **RCT-14** \[meerkat-core] `effective_config_unions_mcp_limits_skills` — compose\_effective\_config: tools.mcp\_servers map-union child-wins per name; limits per-field child-wins (child tightens max\_sessions, inherits other caps); skills append child-wins-on-collision.
* **RCT-15** \[meerkat-core] `config_merge_folds_realm_map_child_wins` — Config::merge now folds self.realm (map-key-union child-wins) instead of silently dropping it; inherited \[realm.X] section visible after merge.
* **RCT-16** \[meerkat-core] `chain_composing_store_produces_effective_config_no_default_clobber` — ChainComposingConfigStore.get() composes the head's chain via injected RealmConfigSource into the effective flat Config; absent ancestor doc = empty pass-through (NOT Config::default clobber).
* **RCT-17** \[meerkat-auth-core] `managed_store_inherited_binding_reads_owning_realm_token` — ManagedStore credential of an inherited binding resolves the TokenStore at the OWNING realm dir (drop hardcoded 'dev'): binding owned by realm P → TokenKey.realm==P → store key under P.
* **RCT-18** \[meerkat-auth-core] `env_source_binding_is_realm_agnostic` — Env-source binding is realm-AGNOSTIC: an inherited Env-source binding resolves identical creds regardless of owning realm (decision A is a no-op for Env/InlineSecret/Command material).
* **RCT-19** \[meerkat-auth-core] `file_token_store_path_for_uses_owning_realm` — FileTokenStore::path\_for projects `<root>/<owning_realm>/<binding>[@profile].json` (currently ZERO tests on file.rs); owning realm drives the on-disk dir.
* **RCT-20** \[meerkat] `factory_image_binding_stamps_owning_realm` — factory resolve\_selected\_binding\_for\_provider (image/web\_search) stamps the OWNING realm for an inherited binding, not selected\_realm; registry.resolve equality holds at the image path.
* **RCT-21** \[meerkat] `model_swap_keeps_inherited_chain_candidates` — factory model-swap candidate filter accepts inherited candidates (owner != preferred) on the chain; hot model-swap does not drop inherited bindings.
* **RCT-22** \[meerkat] `build_agent_inherits_global_binding_resolves_at_global` — end-to-end build\_agent with preferred realm C inheriting binding from global resolves credentials at global (owning) realm and builds the client.
* **RCT-23** \[meerkat-contracts] `config_set_roundtrips_parent_through_opaque_realm_map` — SDK/wire config-set round-trips an unknown \[realm.X].parent through the opaque ConfigContractSchema.realm Value map without loss; regen-schemas no-op.
* **RCT-24** \[meerkat-cli] `cli_login_targets_global_and_realm_flag_composes_chain` — CLI login lands credentials under global (resolved owner) not 'dev'; status/test/refresh/logout read the same owning realm; --realm selects a realm AND composes its chain while NOT touching RuntimeScope (updated pin).

### Added by red-team (RCT-25..38)

* **RCT-25** \[meerkat-core] `default_realm_literal_not_consulted_for_unrelated_head_then_works_under_global` — A \[realm.default] section holding a provider binding is NO LONGER auto-consulted for a head realm that does not parent to `default` (the literal `"default"` candidate is removed in both resolve\_realm\_binding\_target\_for\_provider and resolve\_auth\_binding\_candidates\_for\_provider, distinct from the flat scan); the SAME binding moved to \[realm.global] resolves via the implicit-global-tail.
* **RCT-26** \[meerkat-core] `absent_head_realm_yields_single_node_chain_then_env_default` — When the preferred/head realm is absent from config.realm, RealmChain::resolve yields \[head] (head section None → contributes nothing) plus implicit-global-tail if global present; non-explicit resolution falls through to env\_default when allow\_env\_default; an EXPLICITLY-named absent realm still errors UnknownRealm. Rewrites the pinned auth\_binding\_candidates\_scan\_configured\_realms\_before\_env\_default (connection.rs:2026).
* **RCT-27** \[meerkat-rest] `rest_oauth_persist_for_inherited_binding_stamps_owning_realm_and_maps_realmchain_status` — POST OAuth/credential target for a child realm whose binding is defined only in a parent resolves at the OWNING realm (AuthBindingRef.realm=owner, TokenKey under owner dir) per the documented design, OR is rejected with a defined status; target\_error\_status maps the new ConnectionTargetError::RealmChain variant to a sane HTTP status (compiles).
* **RCT-28** \[meerkat-rpc] `rpc_oauth_persist_for_inherited_binding_stamps_owning_realm` — RPC auth credential-persist (resolve\_binding\_identity / resolve\_oauth\_target) for an inherited binding resolves at the owning chain member and stamps AuthBindingRef.realm=owner before persist; the RealmChain error variant maps to a defined RPC error code.
* **RCT-29** \[meerkat-rest] `rest_config_runtime_get_reflects_inherited_global_section` — An inherited \[realm.global] model/mcp/credential is visible through the REST surface's config\_runtime.get() effective config (ChainComposingConfigStore wired at the surface), matching what the factory build path resolves for the same head realm. (Companion RPC/MCP variants assert the same per-surface composer wiring.)
* **RCT-30** \[meerkat-core] `config_set_after_composed_read_does_not_flatten_inherited_entries` — A read-modify-write through the config surface (get effective → apply delta → set) does NOT durably materialize parent-inherited mcp/hook/skill entries into the head doc; after reload the head doc on disk contains ONLY its own fields plus the patched one, with inherited entries still resolved (not copied) on the next composed read. Proves get-composes/set-head-only is not round-trippable into a flatten.
* **RCT-31** \[meerkat-cli] `filesystem_realm_config_source_composes_real_temp_chain_no_default_clobber` — The PRODUCTION filesystem RealmConfigSource over a real temp state\_root with global/config.toml (binding B) and child/config.toml (parent=global, no binding) returns None for an absent ancestor (not Config::default), composes child top-level fields over global without default-clobber, and build\_agent stamps owner=global for B.
* **RCT-32** \[meerkat-cli] `dev_to_global_credential_migration_idempotent_no_clobber` — One-time migration copies `credentials/dev/<binding>` → `credentials/global/<binding>` IFF the target is absent; an existing global credential is NOT clobbered; re-running migration is a no-op; after migration a pre-existing dev token resolves at owning realm global. (Implements user decision 1.)
* **RCT-33** \[meerkat] `self_hosted_explicit_realm_inherits_binding_at_owning_realm` — configured\_self\_hosted\_connection (factory.rs:3349) for an explicit realm that EXISTS but inherits its self\_hosted binding from a parent resolves at the owning realm via the chain walk (not single-section), and the Ok(None)-on-error swallow does not mask a chain-resolution failure; the contains\_key guard takes the chain path when the realm's own section lacks the binding.
* **RCT-34** \[meerkat-core] `child_binding_referencing_parent_only_backend_profile_fails_closed` — A child binding whose backend\_profile/auth\_profile is defined only in a parent realm fails closed with a typed error naming the missing profile and realm (proving compose\_effective\_config unions ONLY outer realm-keys and does NOT merge backend/auth/binding sub-maps across realms); distinct from RCT-08's inherited-whole-binding case.
* **RCT-35** \[meerkat-core] `nearest_child_wins_when_head_and_ancestor_both_define_binding` — When head and an ancestor both define a usable provider binding, candidate\[0] is the head (nearest-child-wins), ancestor next, global last, env terminal; the same nearest-wins precedence governs compose\_effective\_config.default\_binding selection and the candidate resolver over shared fixtures.
* **RCT-36** \[meerkat-cli] `global_login_under_workspace_a_inherited_in_workspace_b` — Login writes \[realm.global] to the home-rooted global doc (assert exact path = \~/.rkat-rooted, NOT `<workspace>/.rkat/realms/global`); a fresh ChainComposingConfigStore in a DIFFERENT workspace composes that global doc into its effective config and resolves the global-owned binding (cross-workspace inheritance, decision 2); RuntimeScope realm decoupling preserved.
* **RCT-37** \[meerkat-core] `child_cannot_remove_inherited_mcp_or_hook_entries` — A child realm with no mcp\_servers and no hooks inherits ALL parent mcp\_servers and hook entries; a child setting an empty \[tools]/\[hooks] table does not drop inherited entries (emptiness != removal, no tombstone). Negative property for user decision 3.
* **RCT-38** \[meerkat-core] `explicit_env_default_ref_rejected_before_chain_walk` — An explicit AuthBindingRef with realm==env\_default (origin SyntheticEnvDefault) is rejected with UnknownRealm BEFORE any chain walk in the rewritten resolve\_auth\_binding\_or\_default\_for\_provider; no durable lease/token is created. Standalone negative test (split out of RCT-11) guarding the is\_env\_default check at connection.rs:764 against reorder during the explicit-path rewrite.

## Phases & gates

### P0 — Contracts & failing RCTs (Gate 0) (depends\_on: none)

* goal: Write ALL 24 RCTs as compiling-but-failing tests pinning every invariant BEFORE any implementation. Add the type STUBS they reference (RealmConfigSection.parent field, GLOBAL\_REALM\_SLUG/is\_global/global, RealmChain/RealmChainError signatures returning unimplemented!() — wait, no panic: return a placeholder Err so library code stays panic-free; tests assert the real behavior and FAIL).
* writes: meerkat-core: RealmConfigSection.parent field (connection.rs:1272), meerkat-core: GLOBAL\_REALM\_SLUG + RealmId::is\_global/global + MAX\_REALM\_CHAIN\_DEPTH (connection.rs:148), meerkat-core: RealmChain/RealmChainError type + ctor signature returning a typed Err placeholder (connection.rs), meerkat-core: compose\_effective\_config + Config::merge realm-fold signatures (config.rs), meerkat-core: RealmConfigSource trait + ChainComposingConfigStore skeleton (config\_store.rs), RCT-01..05, 12..16, 23 as failing tests; RCT-06..11 as failing tests; RCT-17..19 (auth-core), 20..22 (meerkat), 24 (cli) as failing tests
* GATE: ./scripts/repo-cargo nextest run -p meerkat-core -p meerkat-auth-core -p meerkat -p meerkat-contracts -p meerkat-cli compiles; the 24 RCTs RUN and FAIL with the expected assertions (not compile errors). `make regen-schemas` no-op diff confirmed for the parent field.

### P1 — RealmChain (deterministic walk + fail-closed) (depends\_on: P0)

* goal: Implement RealmChain::resolve: linear walk, BTreeSet dedup, depth cap, implicit global tail, all typed errors. Pure, wasm-clean.
* writes: meerkat-core/src/connection.rs: RealmChain::resolve full impl + RealmChainError + `From<RealmChainError>` for ConnectionTargetError
* GATE: ./scripts/repo-cargo nextest run -p meerkat-core -- realm\_chain — RCT-02,03,04,05 GREEN. clippy -p meerkat-core clean.

### P2 — Connection resolvers chain-aware + owning-set (decision A core) (depends\_on: P1)

* goal: Rewrite resolve\_auth\_binding\_candidates\_for\_provider (delete flat scan), resolve\_realm\_binding\_target\_for\_provider (unify selection), resolve\_auth\_binding\_or\_default\_for\_provider (explicit-ref chain) to walk RealmChain and feed materialize each owner's OWN RealmConnectionSet. materialize unchanged.
* writes: meerkat-core/src/connection.rs:886-966 (candidates), meerkat-core/src/connection.rs:665-750 (single-target), meerkat-core/src/connection.rs:755-795 (explicit ref), delete push\_candidate\_realm\_ids (:862)
* GATE: ./scripts/repo-cargo nextest run -p meerkat-core — RCT-06,07,08,09,10,11 GREEN; ALL pre-existing connection.rs tests either GREEN or deliberately REWRITTEN (auth\_binding\_candidates\_scan\_configured\_realms\_before\_env\_default :2027 rewritten to chain order). clippy clean.

### P3 — EffectiveConfig composition (top-level inheritance, the headline feature) (depends\_on: P1)

* goal: Implement compose\_effective\_config + Config::merge rework (fold realm map, field-level child-wins, hooks/skills append, mcp/limits union, presence-aware scalars) + RealmConfigSource trait + ChainComposingConfigStore.get/set/patch.
* writes: meerkat-core/src/config.rs: compose\_effective\_config + reworked merge + presence-aware helpers, meerkat-core/src/config\_store.rs: RealmConfigSource + ChainComposingConfigStore
* GATE: ./scripts/repo-cargo nextest run -p meerkat-core — RCT-12,13,14,15,16 GREEN; pre-existing config.rs merge tests (:2387,:2435-2746) GREEN or rewritten. clippy clean.

### P4 — Credential resolver provenance verification (bottom-up, auth-core) (depends\_on: P2)

* goal: NO resolver logic change; ADD the missing RCTs proving owning-realm flows through TokenKey/FileTokenStore and Env is realm-agnostic. Fixes any test-double that hardcoded 'dev'.
* writes: meerkat-auth-core/src/resolver.rs tests: RCT-17, RCT-18, meerkat-auth-core/src/auth\_store/file.rs tests: RCT-19 (first-ever tests on file.rs)
* GATE: ./scripts/repo-cargo nextest run -p meerkat-auth-core — RCT-17,18,19 GREEN; existing managed\_store\_\* lifecycle tests GREEN. clippy clean.

### P5 — Factory: second stamping site + model-swap filter + ConfigStore wiring (depends\_on: P3, P4)

* goal: Re-root resolve\_selected\_binding\_for\_provider to walk selected\_realm's chain and stamp owner; relax model-swap filter; wire ChainComposingConfigStore so factory consumes the effective Config.
* writes: meerkat/src/factory.rs:1806-1843 (image/web\_search owning-realm stamp), meerkat/src/factory.rs:3099-3126 (model-swap filter), meerkat/src/factory.rs build path: consume effective Config via ChainComposingConfigStore
* GATE: ./scripts/repo-cargo nextest run -p meerkat — RCT-20,21,22 GREEN; existing factory provenance tests (factory.rs:7249,:7299,:6888 rewritten to chain) GREEN. clippy clean.

### P6 — CLI login → global + ConfigStore chain wiring at surface (depends\_on: P5)

* goal: Collapse the 3 'dev' literals to RealmId::global(); login writes \[realm.global] into the global realm's store; resolve\_config\_store composes the chain; update the pinned --realm test (not bypass).
* writes: meerkat-cli/src/main.rs:4755,4003,4886,4977,5322,5391,5742 + user strings, meerkat-cli/src/main.rs:3737 resolve\_config\_store wraps FileConfigStore in ChainComposingConfigStore via a filesystem RealmConfigSource (realm\_paths\_in based), meerkat-cli/src/main.rs:16447 updated pin
* GATE: ./scripts/repo-cargo nextest run -p meerkat-cli (cargo int — rkat is bin-only) — RCT-24 GREEN; existing CLI login/oauth-config tests (:14004-14359) GREEN or rewritten. clippy clean.

### P7 — Wire/SDK round-trip + WASM degenerate chain (depends\_on: P5)

* goal: Pin opaque-Value round-trip of parent; confirm WASM single-realm chain is identical to today; inject WASM RealmConfigSource.
* writes: meerkat-contracts: RCT-23, meerkat-web-runtime/src/lib.rs: degenerate RealmConfigSource (no global) wiring
* GATE: ./scripts/repo-cargo nextest run -p meerkat-contracts — RCT-23 GREEN. `make regen-schemas && make verify-version-parity && make verify-schema-freshness` ALL no-op/green. BuildBuddy wasm-check-submit green.

### P8 — Broad final verification + governance gates (depends\_on: P6, P7)

* goal: Full-workspace verification, RMAT, e2e.
* writes: no new code; fix any cross-crate fallout surfaced by the broad lanes
* GATE: ./scripts/repo-cargo clippy --workspace -- -D warnings; MEERKAT\_BUILDBUDDY=1 make test (unit+int); make e2e-fast; make rmat-audit; make regen-schemas (no-op diff); make verify-version-parity. ALL green, ZERO failures (never dismiss as pre-existing).

### Phase/gate corrections (red-team) — incl. NEW phases P5b, P6b

* Add a new phase P5b 'Surface auth re-root + composer wiring': wrap ChainComposingConfigStore (or a read-only EffectiveConfigReader per MF-06) at REST lib.rs:415, RPC main.rs:226, MCP-server lib.rs:450; re-root resolve\_binding\_identity in meerkat-rest/src/auth\_endpoints.rs:77 and meerkat-rpc/src/handlers/auth.rs:151 onto the owning-chain stamp; add target\_error\_status RealmChain arm at auth\_endpoints.rs:106. Gate runs -p meerkat-rest -p meerkat-rpc -p meerkat-mcp-server with RCT-27/28/29/30.
* Add a new phase P6b 'dev→global credential migration': one-time idempotent copy of credentials/dev/\* → credentials/global/\* (no clobber); reconcile canonical\_design/provenance\_design prose from 'orphan' to 'copy'. Gate runs RCT-32. This phase is REQUIRED by user decision 1 and is currently entirely absent.
* P2 gate: add RCT-25 (literal default removed), RCT-26 (absent head), RCT-35 (nearest-child-wins), RCT-38 (explicit env\_default rejection) to the green set; explicitly state the rewritten target behavior for auth\_binding\_candidates\_scan\_configured\_realms\_before\_env\_default (connection.rs:2026) rather than only listing it for rewrite.
* P3 gate: forbid REWRITING the existing file-layer merge tests (config.rs:2387, :2419, :2480, :3395) — the single reworked merge engine must leave global-file→project layering precedence unchanged; only NEW realm-fold tests may be added. Add RCT-30 (no set(get()) flatten), RCT-31 (real fs source no-default-clobber), RCT-34 (sub-map no-inherit fail-closed), RCT-37 (no-removal). Resolve the hooks/skills dedup identity key (open\_question #2) before this gate.
* P5 gate: add the factory.rs:1894/:1924 image/web\_search contains\_key gate edit to the writes (MF-09); add factory.rs:3349 self\_hosted explicit-realm seam to the audit (MF-10); require RCT-20's fixture to define the image binding ONLY in the parent. Add RCT-33.
* P6 gate: resolve the global-doc location to home-rooted \~/.rkat (decision 2, MF-13) — the RealmConfigSource global node must NOT be realm\_paths\_in(state\_root,'global'); enumerate and migrate ALL CLI\_INTERACTIVE\_OAUTH\_REALM\_ID read sites (main.rs:4887, :4981, :5031) and auth\_config\_realm\_or\_default callsites together. Add RCT-36 (cross-workspace).
* P8 gate: add `make e2e-auth` (and e2e-system for the token-store path) — this is an auth/credential-provenance change and the listed P8 lanes (unit+int, e2e-fast) do not exercise the real FileTokenStore owning-realm directory layout or OAuth lease re-keying. Add a deterministic int-lane RCT round-tripping a real FileTokenStore at the owning-realm dir for an inherited binding.
* Gate 0 (P0): require each RCT to fail with the SPECIFIC assertion it pins, not a generic Err/unimplemented from the RealmChain::resolve typed-Err placeholder — otherwise a uniform-Err stub yields a false-green Gate 0. Split the type-stub compile check from the behavior-red check for RCT-03/05 (success-order/tail-omission) which a blanket-Err placeholder cannot fail-as-intended.

## Risks & mitigations

* **risk:** Eager-flatten temptation re-introduced for the connection set (sidecar maps or single-multi-owner set), forcing the registry.rs:208 equality downgrade — the exact dogma violation the judges flagged.
  * mitigation: HARD RULE in the design: RealmConnectionSet gains NO field; materialize is ALWAYS fed the owner's own from\_config set; RCT-07 pins that auth\_binding.realm == resolved set realm\_id for inherited bindings WITHOUT relaxing the equality. Any PR diff touching registry.rs:208 is a red flag.
* **risk:** Top-level config inheritance becomes a SECOND uncoordinated chain authority (dual-truth), diverging from the connection-fact chain ordering.
  * mitigation: ONE RealmChain::resolve is the sole chain authority; compose\_effective\_config and the connection resolvers BOTH call it. RCT-09 (connection order) and RCT-12/13/14 (effective config) share the same chain fixtures so divergence fails a test.
* **risk:** auth/backend cross-realm credential-redirect (a child redefining auth-profile-key K silently re-sources a parent-owned binding).
  * mitigation: no-inherit for auth/backend: binding's profiles resolved ONLY in the owning realm's section → binding-owner==backend-owner==auth-owner invariant. RCT-08 pins it with a child-redefines-K counterexample.
* **risk:** Second stamping site (factory.rs:1838) or model-swap filter (factory.rs:3114) left consuming-stamped → decision A silently broken for image/web\_search and inherited bindings dropped on hot swap.
  * mitigation: P5 converges BOTH in one pass; RCT-20 (image owning-realm) and RCT-21 (model-swap keeps inherited) gate it. Grep for `realm: selected_realm` and `auth_binding.realm == *realm` in final review.
* **risk:** serde(default) loses omitted-vs-default for non-Option scalars (model, max\_tokens, limits) → a child's default value 'overrides' an inherited non-default via naive presence check.
  * mitigation: Presence-aware merge for the affected scalar fields: parse ancestor docs from toml::Value to detect key presence before applying child-wins-scalar (the same pattern that forced merge\_\*\_from\_toml\_presence helpers, config.rs:412). RCT-12 includes an omitted-vs-explicit-default case. (Surfaced as open question for product confirmation of which fields are presence-sensitive.)
* **risk:** Login-to-global requires resolve\_config\_store to open a store OTHER than scope.locator.realm; partial wiring (login writes one realm, resolution composes from another) is the concrete trap.
  * mitigation: P6 introduces an explicit global-realm store seam; RCT-24 asserts login lands at global AND status/test/refresh read the same owning realm; the 5 token-path sites move together in one phase.
* **risk:** ChainComposingConfigStore re-reads ancestor docs on every get() — cost + cache-invalidation vs patch()/mcp-reload live config.
  * mitigation: get() composes fresh (always-correct, depth-capped, cheap); set()/patch() operate on the HEAD doc only (writes never compose), so a patch to an ancestor is naturally reflected on the next composed get(). No memoization in v1 (correctness over micro-opt); RCT-16 pins compose-on-get semantics.
* **risk:** Deleting the flat scan breaks operators who relied on sibling-realm binding pickup.
  * mitigation: Documented behavior tightening: shared bindings move to \[realm.global] (inherited by all via the implicit tail) or a named parent. RCT-09 pins the narrowing; CLI login-to-global makes the common shared-creds case work by default.
* **risk:** Existing pinned tests (connection.rs:2027, factory.rs:6888, cli:16447) encode the OLD flat-scan / dev-realm behavior.
  * mitigation: REWRITE (not bypass) each to the chain/owning-realm semantics in the phase that owns the change; never #\[ignore] them. Listed explicitly in P2/P5/P6 gates.
* **risk:** CLI tests run only under cargo int (rkat bin-only), so a core-only 'green' skips every login/realm CLI test.
  * mitigation: P6 gate runs -p meerkat-cli under the int lane; P8 runs MEERKAT\_BUILDBUDDY=1 make test (unit+int) workspace-wide.

### Residual risks (red-team)

* The lazy owning-set core (decision A) is genuinely sound and VERIFIED: feeding materialize\_connection\_target the owner's own RealmConnectionSet keeps the strict equality at meerkat-llm-core/src/provider\_runtime/registry.rs:208 (`if auth_binding.realm != realm.realm_id`) a real invariant without relaxation, adds no AuthBindingRef/RealmConnectionSet field, and yields zero wire/schema churn. This part needs no rework — guard it: any PR diff touching registry.rs:208 (qualify the crate path everywhere, it is meerkat-llm-core not core) is a red flag, and RCT-07 should ideally exercise the llm-core registry.resolve path across the core→llm-core boundary, not only core's materialize stamping.
* Several critiques framed settled user decisions as 'open questions' (plan open\_question #5 = global location, #6 = orphan vs migrate). These are NOT relitigation candidates — they are plan-vs-decision contradictions that MUST be fixed (MF-08, MF-13). Do not let the implementer treat them as live design choices.
* DISMISSED as overstated: the RPC compile break. handlers/auth.rs:200 target\_error\_response uses a wildcard `_ =>`, so the new ConnectionTargetError::RealmChain variant does NOT break RPC compilation — only REST's exhaustive target\_error\_status (auth\_endpoints.rs:106) breaks. The RPC BEHAVIOR change (credential-persist provenance) is still real and must be covered (MF-04).
* DISMISSED as minor/non-blocking: the pinned-test line is 16447 (the plan is correct; one critique's '16448' was wrong). The registry crate misattribution (registry.rs:208 is in meerkat-llm-core) is a documentation fix, not a behavioral risk, but qualify it so an implementer does not edit meerkat-tools/src/registry.rs or meerkat-core by mistake.
* validate\_explicit\_realm\_id (runtime\_bootstrap.rs:127) does not reserve `global`/`env_default` — a user can run `rkat --realm global`, creating a session realm whose state lands under `<state_root>/global`, potentially colliding with the credential-namespace assumptions of decision A, and the chain resolver only catches global.parent=Some lazily at resolve time. Lower severity than the must-fixes (fail-closed at resolve), but worth adding global/env\_default to the reserved set and validating global.parent==None at config LOAD time for a clear config error instead of a runtime auth failure.
* Presence-sensitive scalar field set (open\_question #4) remains the single reworked merge engine's sharpest edge: a child explicitly setting agent.model to the empty-string default to mean 'use catalog default' will be treated as unset by a naive `!= Default` check and the parent's model leaks through. Enumerate the exact set (agent.model, max\_tokens, agent.max\_tokens\_per\_turn, limits.\*) and require toml::Value presence parsing per field before P3; add per-field omitted-vs-explicit-default RCT cases. This is correctness, not polish.
* The 'one chain, two consumers' invariant (EffectiveConfig composer chain == connection resolver re-walk) is asserted via shared fixtures but never pinned by a single equality test. Strongly prefer threading the composer's already-computed `Vec<RealmId>` through to the connection resolvers (literally one walk, two readers) over recomputing RealmChain::resolve from the composed config; if recomputed, add a direct equivalence RCT (walk over raw per-realm sources == walk over composed config) to prevent a lossy/ordering-sensitive fold from silently diverging the two.
* The child-wins union on the .realm MAP can let a stale \[realm.parent] copy in a child's own doc shadow the authoritative parent-file section (which still passes the realm-ID equality at registry.rs:208 because it only compares IDs, not section provenance). Enforce that a member's own `[realm.<member>]` section comes ONLY from `config_for_realm(<member>)`, and reject (fail-closed) a doc declaring a \[realm.X] section for X != its owning realm. Folds into MF-11's outer-key-only union rule.

## Settled product decisions (do NOT relitigate)

1. MIGRATE existing `dev` creds to `global` (one-time idempotent copy, no clobber). Not orphan.
2. `global` realm = dedicated **home-rooted** doc at `~/.rkat` (single well-known location), not per-workspace.
3. Children can add/override but **cannot remove** inherited mcp/hook entries (no tombstones).
4. CLI login defaults to `global`. `--realm` selects+composes a chain but does NOT touch RuntimeScope.
5. **REST/RPC credential PERSIST = strict-owner write** (MF-04 resolved 2026-06-16). Credential *reads* inherit down the chain; credential *writes* land ONLY in the realm that DEFINES the binding. Persisting for a child realm whose binding is inherited is REJECTED with a typed error naming the owning realm (prevents a child-scoped request from overwriting a parent/`global` credential). RCT-27/28 assert the rejection + owning-realm stamp.

## Greenlight

The ARCHITECTURE is sound and the core mechanism is verified against the code: the lazy owning-set walk preserves the load-bearing strict equality at meerkat-llm-core/src/provider\_runtime/registry.rs:208 without relaxation, RealmConfigSection.parent is genuinely schema-invisible (wire/config.rs:62 projects realm as opaque `BTreeMap<String, Value>`), Config::merge does silently drop self.realm today, and the WASM single-realm path is degenerate-clean. Direction: GREENLIGHT after must-fixes — do NOT rework the design. But it is NOT yet implementable: 14 must-fixes block it, four of them ship a wrong/insecure auth result a real config will hit on day one — (a) the dev→global MIGRATION is entirely absent and the plan prose actively contradicts the user's copy-not-orphan decision, so every existing install loses auth on upgrade; (b) the global config doc is wired workspace-rooted (realm\_paths\_in) while decision 2 mandates a single \~/.rkat doc, so the headline cross-workspace-login promise is silently dead; (c) ChainComposingConfigStore is wired only in CLI, leaving top-level inheritance dead and surface-divergent on REST/RPC/MCP, and REST/RPC credential-persist endpoints (resolve\_binding\_identity) still single-section-stamp the request realm, writing inherited credentials to the wrong dir; (d) the symmetric ConfigStore.get()-composes/set()-head-only design lets one read-modify-write permanently flatten inherited entries into the child doc, violating the no-tombstone decision. Add phases P5b (surface re-root + composer wiring) and P6b (migration), split ConfigStore into a read-only effective reader plus a head writer, fix the global-doc location to home-rooted, edit the image/web\_search and self\_hosted contains\_key gates, fix the REST exhaustive-match compile break, resolve the realm-map fold level to outer-key-only with fail-closed sub-map validation, and add RCTs 25-38 to close the coverage holes (absent-head, literal-default removal, nearest-child-wins, no-removal, real-fs source, cross-workspace, REST/RPC provenance, migration idempotence). SINGLE BIGGEST REMAINING RISK: the cross-surface gap — the plan was scoped to CLI+factory while the same resolution must hold identically on REST/RPC/MCP credential-persist and config-get, and the symmetric ConfigStore trait makes 'compose on read' silently corrupt the child doc on the very next write; getting the read/write split and per-surface wiring wrong reintroduces dual-truth on the network surfaces where users actually persist credentials."
