Resolves #12166
Related: #12101
Stage 2 of the aiConfig parent-chain epic: the per-server config snapshots of deployment-wide realm leaves are removed so Knowledge Base + Memory Core inherit them from the Tier-1 root (Neo.ai.Config) through the BaseConfig.getParent() chain (added in Stage 1, this branch). This resolves the dummyEmbeddingFunction child-snapshot anti-pattern and eliminates ~40 duplicated leaf declarations across the two servers, with no change to how any consumer reads its config (ServerConfig.x.y resolves up the chain, keyed/dotted).
Authored by Claude Opus 4.8 (Claude Code). Session 653970c8-8466-4609-8a15-28350234af10.
FAIR-band: over-target — operator-directed, in-session continuation of the #12101 epic ("let us continue!"); this is the in-flight lane I authored end-to-end across Stage 1 + Stage 2, not a new-lane pickup, so over-target-with-rationale applies (not a yield candidate).
Evidence: L2 (node smoke — KB + MC inherit the realm via createConfigProxy → getParent chain, env-cleared so defaults prove inheritance not passthrough; + targeted unit suites green, see Test Evidence) → L2 required (close-target ACs are config-shape + chain-resolution, fully unit+smoke coverable; the operator separately runtime-confirmed the live orchestrator boots on this realm at qwen3-8b/4096). No residuals.
Changed config keys (per mcp-config-template-change-guide)
- knowledge-base/config.template.mjs — removed (now inherited from Tier-1):
backupPath, auth.{host,port,realm,issuerUrl,clientId,clientSecret,trustProxyIdentity}, dummyEmbeddingFunction. Kept local (path-mismatched top-level chroma aliases, pending the S3/S4 consumer-read codemod): host, port, path (+ retains import AiConfig).
- memory-core/config.template.mjs — removed (now inherited):
auth.*, modelProvider, graphProvider, embeddingProvider, ollama.*, openAiCompatible.*, localModels, vectorDimension, modelName, embeddingModel, engines.chroma.*, backupPath. import AiConfig → bare side-effect import (loads the Tier-1 realm root for the chain; no value snapshot).
- scripts/setup/initServerConfigs.mjs —
materializeServerConfigTemplate extended to rewrite side-effect Tier-1 imports (template → overlay) so the generated MC runtime config.mjs points at the operator overlay.
Local config.mjs follow-up: YES — see Post-Merge Validation. Restart: recommended after a config.mjs regen. Gitignored config.mjs: not committed (verified).
Deltas from ticket (if any)
- Ticket Fix §1 / avoided-traps amended (load-order): the ticket assumed "runtime boot imports ai/config.mjs / no import edge." V-B-A of the KB + MC MCP-server import graphs falsified this — each server process's only Tier-1 load path was its config's
import AiConfig. So MC keeps a bare side-effect import (no value snapshot ⇒ anti-pattern still resolved), and KB keeps its value import (still needed by the retained chroma aliases). This mooted the originally-floated "side-effect-import everywhere" option.
- Chroma deferred to S3/S4: KB exposes chroma at top-level
host/port/path while Tier-1 owns engines.chroma.*; the chain resolves by exact path, so collapsing them is a consumer-read change (S3/S4 codemod), out of #12166 scope. KB therefore retains those 3 snapshots + import AiConfig. MC keeps engines.chroma.* nested (same path) → fully inherited.
- Enumeration-asymmetry is real for tests: facet-9 ("no runtime consumer enumerates a shared namespace") held, but the #11963 / #10010 config specs DID enumerate (
toEqual(config.auth)) — rewritten to keyed assertions. The deferred getTopLevelDataKeys merge remains a documented latent hazard.
- getParent blast-radius: the Stage-1 override makes every fresh
BaseConfig inherit the registered realm root; Orchestrator.invariants mlx/lms undefined-safe tests now detach the root to simulate a genuinely-missing namespace. Config specs install a deterministic Tier-1 root in beforeAll (the template side-effect import only registers Neo.ai.Config on first module-eval → reused Playwright workers would otherwise see it unset).
Test Evidence
npm run test-unit (targeted), all green:
BaseConfig.spec 17/17; config.template.spec (Tier-1) 5/5; KB config.template.spec 2/2; MC config.template.spec 6/6.
- Consumers 24/24:
SemanticGraphExtractor, SessionService, ChromaManager, KB + MC DatabaseService.
daemons/orchestrator (incl. Orchestrator.invariants) + initServerConfigs.spec — 406 passed; ConfigCompleteness 5/5.
- node smoke (env-cleared): KB inherits
auth.realm=master / dummyEmbeddingFunction.name / backupPath; MC inherits the full realm incl. localModels.chat.contextLimitTokens=262144 + engines.chroma.host=localhost; both via createConfigProxy → getParent. Materializer rewrites MC's side-effect import → overlay.
Post-Merge Validation
Commits
9256e9c0c — Stage 1: getParent() override + cross-tier write validation + BaseConfig.spec tests.
18feed683 — Stage 2: realm split (KB + MC inherit) + materializer + config-spec + Orchestrator.invariants updates.
Decision Record impact: none (extends Epic #12101's "extend Provider, don't reinvent" mandate; the ticket's load-order assumption is corrected in Deltas).
Resolves #12166 Related: #12101
Stage 2 of the aiConfig parent-chain epic: the per-server config snapshots of deployment-wide realm leaves are removed so Knowledge Base + Memory Core inherit them from the Tier-1 root (
Neo.ai.Config) through theBaseConfig.getParent()chain (added in Stage 1, this branch). This resolves thedummyEmbeddingFunctionchild-snapshot anti-pattern and eliminates ~40 duplicated leaf declarations across the two servers, with no change to how any consumer reads its config (ServerConfig.x.yresolves up the chain, keyed/dotted).Authored by Claude Opus 4.8 (Claude Code). Session 653970c8-8466-4609-8a15-28350234af10.
FAIR-band: over-target — operator-directed, in-session continuation of the #12101 epic ("let us continue!"); this is the in-flight lane I authored end-to-end across Stage 1 + Stage 2, not a new-lane pickup, so over-target-with-rationale applies (not a yield candidate).
Evidence: L2 (node smoke — KB + MC inherit the realm via
createConfigProxy→getParentchain, env-cleared so defaults prove inheritance not passthrough; + targeted unit suites green, see Test Evidence) → L2 required (close-target ACs are config-shape + chain-resolution, fully unit+smoke coverable; the operator separately runtime-confirmed the live orchestrator boots on this realm at qwen3-8b/4096). No residuals.Changed config keys (per mcp-config-template-change-guide)
backupPath,auth.{host,port,realm,issuerUrl,clientId,clientSecret,trustProxyIdentity},dummyEmbeddingFunction. Kept local (path-mismatched top-level chroma aliases, pending the S3/S4 consumer-read codemod):host,port,path(+ retainsimport AiConfig).auth.*,modelProvider,graphProvider,embeddingProvider,ollama.*,openAiCompatible.*,localModels,vectorDimension,modelName,embeddingModel,engines.chroma.*,backupPath.import AiConfig→ bare side-effect import (loads the Tier-1 realm root for the chain; no value snapshot).materializeServerConfigTemplateextended to rewrite side-effect Tier-1 imports (template → overlay) so the generated MC runtimeconfig.mjspoints at the operator overlay.Local config.mjs follow-up: YES — see Post-Merge Validation. Restart: recommended after a config.mjs regen. Gitignored config.mjs: not committed (verified).
Deltas from ticket (if any)
import AiConfig. So MC keeps a bare side-effect import (no value snapshot ⇒ anti-pattern still resolved), and KB keeps its value import (still needed by the retained chroma aliases). This mooted the originally-floated "side-effect-import everywhere" option.host/port/pathwhile Tier-1 ownsengines.chroma.*; the chain resolves by exact path, so collapsing them is a consumer-read change (S3/S4 codemod), out of #12166 scope. KB therefore retains those 3 snapshots +import AiConfig. MC keepsengines.chroma.*nested (same path) → fully inherited.toEqual(config.auth)) — rewritten to keyed assertions. The deferredgetTopLevelDataKeysmerge remains a documented latent hazard.BaseConfiginherit the registered realm root;Orchestrator.invariantsmlx/lms undefined-safe tests now detach the root to simulate a genuinely-missing namespace. Config specs install a deterministic Tier-1 root inbeforeAll(the template side-effect import only registersNeo.ai.Configon first module-eval → reused Playwright workers would otherwise see it unset).Test Evidence
npm run test-unit(targeted), all green:BaseConfig.spec17/17;config.template.spec(Tier-1) 5/5; KBconfig.template.spec2/2; MCconfig.template.spec6/6.SemanticGraphExtractor,SessionService,ChromaManager, KB + MCDatabaseService.daemons/orchestrator(incl.Orchestrator.invariants) +initServerConfigs.spec— 406 passed;ConfigCompleteness5/5.auth.realm=master/dummyEmbeddingFunction.name/backupPath; MC inherits the full realm incl.localModels.chat.contextLimitTokens=262144+engines.chroma.host=localhost; both viacreateConfigProxy→getParent. Materializer rewrites MC's side-effect import → overlay.Post-Merge Validation
config.mjsto ADOPT the dedup (config.mjs then inherits from Tier-1). NOTE:--migrate-configwill NOT auto-trigger — this is a leaf-body change (KB) / side-effect-import change (MC), neither captured by the import-shapedetectDrift(the S0 #12102 fragility). The staleconfig.mjsstays FUNCTIONAL (retains local snapshots + still loads Tier-1) ⇒ no boot-break. To adopt, force-regenerate the gitignoredconfig.mjsfrom templates, then restart.Commits
9256e9c0c— Stage 1:getParent()override + cross-tier write validation + BaseConfig.spec tests.18feed683— Stage 2: realm split (KB + MC inherit) + materializer + config-spec + Orchestrator.invariants updates.Decision Record impact: none (extends Epic #12101's "extend Provider, don't reinvent" mandate; the ticket's load-order assumption is corrected in Deltas).