Resolves #12168
Related: #12101, #12166
Authored by Claude Opus 4.8 (Claude Code 1M). Session b124b83a-2cca-4a2b-a711-64868439ba1e.
FAIR-band: over-target [21/30] — operator-direction (operator "wrap up the epic; a half-finished version is dangerous"; @neo-gpt did not claim #12168 after the offer). Rebalance: #12166 (parent-chain, sequences after this) offered to under-target @neo-gpt.
Conforms BaseConfig to the graduated Discussion #12100 prototype shape — the shape #12164 diverged from (a re-derived metaTree config + construct-time compile). This is the foundation the parent-chain realm inheritance (#12166) builds on.
What shipped
ai/BaseConfig.mjs (core):
- Meta-leaf tree now lives in the standard
data config (the parallel metaTree config is gone), compiled in an afterSetData override via the Provider's own processDataObject seam — reactive + AC-Epic-1-aligned ("extend, don't reinvent").
leaf(default, env, type) factory replaces {env, default, parse} literals. Reuses the name-based Env parsers (typeParsers) + a typeValidators map. Explicit type survives a null default (e.g. leaf(null, 'NEO_PUBLIC_URL', 'url') stays validatable — the old Neo.typeOf-inference lost the type on null).
internalSetData validates any value at a known leaf path — object/JSON leaves no longer bypass (closes what #12167 targeted; #12167 closed as superseded).
setEnvOverride(envName, value) keyed by env-var name (one var → N leaves); new refreshEnv() for bounded on-demand re-resolution.
- Bounded env (not live-per-read), deliberately. A live env value decoupled from a coordinated restart is dangerous — a changed
NEO_CHROMA_PORT would make new reads target a port the DB isn't listening on. Rationale baked into #applyEnvLayer's JSDoc. True live-env-with-coordinated-restart is a separate Body-tier topic.
- Guard:
Neo.typeOf returns undefined for an object with an own constructor key (the dummy-EF duck-type) — both inference sites guard it.
6 config.template.mjs converted metaTree+literals → data+leaf(). Shape only — the AiConfig.x snapshots are kept (the realm-split that removes them is #12166). 3 leaves with custom (non-Env) parsers — logLevel (github + gitlab) and memorySharing.defaultPolicy (memory-core) — kept as explicit {env, default, parse} literals (the valid escape hatch; compileMetaLeaves honors any {default} object).
7 specs adapted: {metaTree: cfg.metaTree} → {data: cfg._data}, text-asserts metaTree:→data:, daemon.spec template-text default:\s*false→leaf\(false (the leaf() conversion changed the asserted source), env-name-keyed setEnvOverride.
Changed config keys (MCP config-template change guide)
- All 6
config.template.mjs reshaped (metaTree→data slot + leaf() leaves). No leaf VALUES changed (defaults preserved).
- Local
config.mjs follow-up REQUIRED after merge: regenerate each clone's gitignored config.mjs from the updated templates + restart MCP servers + orchestrator. No config.mjs is committed.
Deltas from ticket
- #12167 (object-leaf validation) is subsumed here (the leaf-factory typed validation does it properly) — #12167 closed as superseded.
- 3 custom-parser leaves kept as literals (the
leaf() factory covers Env parsers only; a custom-parser escape hatch is out of scope).
Evidence
Evidence: L1 (standalone Neo-bootstrapped verification — core 16/16 checks; 6/6 templates compile+resolve; 7 specs node --check clean; shape-sensitive spec tail assessed safe) → L2 full Playwright unit suite in CI (the authoritative full-suite gate; the worktree has no node_modules + a parse5-build prerequisite locally). Verified: no runtime consumer of setEnvOverride or .metaTree exists, so the signature/shape changes break zero production code; the consumer read API (aiConfig.X.Y) is unchanged.
Test Evidence
- Standalone harness (
node): leaf() factory, afterSetData compile, nested + object leaves, bounded env, setEnvOverride (env-name), refreshEnv, validate-any, typeOf-guard — all pass.
- All 6 converted templates import + resolve.
- 7 affected specs adapted + syntax-clean; an 11-file broad sweep resolved to safe (false-positives / value-preserving compiled-config reads /
SourcePathsConfig toContain(string) checks preserved by the conversion / already-stale comments).
- Full unit suite runs in CI.
Post-Merge Validation
Commits
8afa5ce7c — BaseConfig core (data + afterSetData seam + leaf() factory + bounded env + typeOf-safe inference)
019fcb43c — 6 templates → data+leaf() + typeOf guard
a90549839 — 7 specs adapted to the new shape
Resolves #12168 Related: #12101, #12166
Authored by Claude Opus 4.8 (Claude Code 1M). Session b124b83a-2cca-4a2b-a711-64868439ba1e.
FAIR-band: over-target [21/30] — operator-direction (operator "wrap up the epic; a half-finished version is dangerous"; @neo-gpt did not claim #12168 after the offer). Rebalance: #12166 (parent-chain, sequences after this) offered to under-target @neo-gpt.
Conforms
BaseConfigto the graduated Discussion #12100 prototype shape — the shape #12164 diverged from (a re-derivedmetaTreeconfig + construct-time compile). This is the foundation the parent-chain realm inheritance (#12166) builds on.What shipped
ai/BaseConfig.mjs(core):dataconfig (the parallelmetaTreeconfig is gone), compiled in anafterSetDataoverride via the Provider's ownprocessDataObjectseam — reactive + AC-Epic-1-aligned ("extend, don't reinvent").leaf(default, env, type)factory replaces{env, default, parse}literals. Reuses the name-basedEnvparsers (typeParsers) + atypeValidatorsmap. Explicittypesurvives anulldefault (e.g.leaf(null, 'NEO_PUBLIC_URL', 'url')stays validatable — the oldNeo.typeOf-inference lost the type on null).internalSetDatavalidates any value at a known leaf path — object/JSON leaves no longer bypass (closes what #12167 targeted; #12167 closed as superseded).setEnvOverride(envName, value)keyed by env-var name (one var → N leaves); newrefreshEnv()for bounded on-demand re-resolution.NEO_CHROMA_PORTwould make new reads target a port the DB isn't listening on. Rationale baked into#applyEnvLayer's JSDoc. True live-env-with-coordinated-restart is a separate Body-tier topic.Neo.typeOfreturnsundefinedfor an object with an ownconstructorkey (the dummy-EF duck-type) — both inference sites guard it.6
config.template.mjsconvertedmetaTree+literals →data+leaf(). Shape only — theAiConfig.xsnapshots are kept (the realm-split that removes them is #12166). 3 leaves with custom (non-Env) parsers —logLevel(github + gitlab) andmemorySharing.defaultPolicy(memory-core) — kept as explicit{env, default, parse}literals (the valid escape hatch;compileMetaLeaveshonors any{default}object).7 specs adapted:
{metaTree: cfg.metaTree}→{data: cfg._data}, text-assertsmetaTree:→data:,daemon.spectemplate-textdefault:\s*false→leaf\(false(theleaf()conversion changed the asserted source), env-name-keyedsetEnvOverride.Changed config keys (MCP config-template change guide)
config.template.mjsreshaped (metaTree→dataslot +leaf()leaves). No leaf VALUES changed (defaults preserved).config.mjsfollow-up REQUIRED after merge: regenerate each clone's gitignoredconfig.mjsfrom the updated templates + restart MCP servers + orchestrator. Noconfig.mjsis committed.Deltas from ticket
leaf()factory coversEnvparsers only; a custom-parser escape hatch is out of scope).Evidence
Evidence: L1 (standalone Neo-bootstrapped verification — core 16/16 checks; 6/6 templates compile+resolve; 7 specs
node --checkclean; shape-sensitive spec tail assessed safe) → L2 full Playwright unit suite in CI (the authoritative full-suite gate; the worktree has nonode_modules+ a parse5-build prerequisite locally). Verified: no runtime consumer ofsetEnvOverrideor.metaTreeexists, so the signature/shape changes break zero production code; the consumer read API (aiConfig.X.Y) is unchanged.Test Evidence
node): leaf() factory, afterSetData compile, nested + object leaves, bounded env, setEnvOverride (env-name), refreshEnv, validate-any, typeOf-guard — all pass.SourcePathsConfigtoContain(string)checks preserved by the conversion / already-stale comments).Post-Merge Validation
config.mjsfrom the templates; restart MCP servers + orchestrator.Commits
8afa5ce7c— BaseConfig core (data + afterSetData seam + leaf() factory + bounded env + typeOf-safe inference)019fcb43c— 6 templates → data+leaf() + typeOf guarda90549839— 7 specs adapted to the new shape