Parent: Epic #12101 · Stage 1 of 7 · Depends on: none (foundation) · Blocks: #12104 (Stage 2); transitively the #12105 / #12106 codemods.
What
Build a new ai/BaseConfig.mjs that extends Neo.state.Provider (Body-tier reactive substrate), replacing the current hand-rolled Neo.core.Base + standalone createConfigProxy (which never properly extended Provider). This is the architectural choke point for the meta-leaf migration: Stages 2–6 consume this contract.
The 5-sub-point Option F contract (epic AC-Epic-3)
set(path, value) — explicit mutation API; routes through Provider's setData(path, value) → internalSetData (Provider.mjs:497, the single write funnel).
setEnvOverride(path, value) — env-layer write into a private #runtimeEnvOverrides Map; takes precedence over a leaf's default at compile/read time.
observeConfig(path, fn) → cleanup — path-scoped reactive subscription returning a cleanup fn (built on the per-leaf Neo.core.Config.subscribe; full effect-system integration is explicitly out of scope per the epic).
- Direct assignment via Provider's set trap —
aiConfig.data.x.y = z routes to setData through createHierarchicalDataProxy's set trap (createHierarchicalDataProxy.mjs:81); preserves 157+ existing test mutation sites without rewrite (per @neo-gemini-pro V-B-A).
compileMetaLeaves(tree) — compiles the single meta-leaf tree ({env, default, parse} per leaf, Stage 2 shape) into (a) the data defaults object and (b) the separate #leafMetadataRegistry, applying env-overlay-merge against process.env + #runtimeEnvOverrides.
Acceptance Criteria
Contract Ledger
| Surface |
Consumer |
Contract |
Verification |
BaseConfig class |
Stage 2 template + all aiConfig readers |
extends Neo.state.Provider; AiConfig.X.Y direct read at point of use |
unit: instance instanceof Neo.state.Provider |
set(path,value) / setEnvOverride(path,value) |
Stage 3 / 4 codemod sites |
mutation API → internalSetData; env layer wins over default |
unit: set→read roundtrip + env-precedence assertion |
observeConfig(path,fn) |
reactive consumers |
path-scoped subscription returning cleanup fn |
unit: fires on change; cleanup stops firing |
direct assignment data.x = y |
157+ test mutation sites |
set-trap → setData |
unit: direct-assignment routes through validation |
compileMetaLeaves(tree) |
Stage 2 meta-leaf tree |
tree → data defaults + #leafMetadataRegistry; env-overlay-merge |
unit: compile prototype tree, assert both outputs |
Dependency chain (for downstream tickets)
- #12104 (Stage 2) consumes the
compileMetaLeaves meta-leaf tree shape from this ticket's PR (number TBD on open).
- #12105 (Stage 3) / #12106 (Stage 4) codemods consume the
set / setEnvOverride / direct-assignment surface + the AiConfig.X.Y read path from this ticket's PR.
Discussion #12100 criteria linkage
Stage-1 ACs trace to Discussion #12100 §9 Criterion 4 (OQ9 21-leaf prototype validated compileTree end-to-end) + the three STEP_BACK-convergence Stage-1 ACs (parser-validation choke point + separate metadata registry + hierarchical deep-merge), preserved per Discussion #12100's ## Discussion Criteria Mapping.
Parent: Epic #12101 · Stage 1 of 7 · Depends on: none (foundation) · Blocks: #12104 (Stage 2); transitively the #12105 / #12106 codemods.
What
Build a new
ai/BaseConfig.mjsthat extendsNeo.state.Provider(Body-tier reactive substrate), replacing the current hand-rolledNeo.core.Base+ standalonecreateConfigProxy(which never properly extended Provider). This is the architectural choke point for the meta-leaf migration: Stages 2–6 consume this contract.The 5-sub-point Option F contract (epic AC-Epic-3)
set(path, value)— explicit mutation API; routes through Provider'ssetData(path, value)→internalSetData(Provider.mjs:497, the single write funnel).setEnvOverride(path, value)— env-layer write into a private#runtimeEnvOverridesMap; takes precedence over a leaf'sdefaultat compile/read time.observeConfig(path, fn) → cleanup— path-scoped reactive subscription returning a cleanup fn (built on the per-leafNeo.core.Config.subscribe; full effect-system integration is explicitly out of scope per the epic).aiConfig.data.x.y = zroutes tosetDatathroughcreateHierarchicalDataProxy's set trap (createHierarchicalDataProxy.mjs:81); preserves 157+ existing test mutation sites without rewrite (per @neo-gemini-pro V-B-A).compileMetaLeaves(tree)— compiles the single meta-leaf tree ({env, default, parse}per leaf, Stage 2 shape) into (a) thedatadefaults object and (b) the separate#leafMetadataRegistry, applying env-overlay-merge againstprocess.env+#runtimeEnvOverrides.Acceptance Criteria
BaseConfig extends Neo.state.Provider; no parallel reactivity machinery. ReuseNeo.util.Envparsers (parseBool / parseNumber / parsePort / parseKeepAlive / parseString / parseUrl) — V-B-A confirmed no competing parser/validation layer exists insrc/state/src/core.internalSetData()choke point (AC-Epic-5): overrideinternalSetDatawith the metadata-path-first rule — validate/coerce ANY value at a known leaf path (looked up in#leafMetadataRegistry); namespace-recursion intermediates pass through unchanged.#leafMetadataRegistry(AC-Epic-5): the parser/env-var metadata registry is independent of Provider's reactive#dataConfigs(full-dotted-path →Config). Two registries, distinct responsibilities.data_: {[isDescriptor]:true, merge:'deep', value:{}}descriptor — no custom merge.data_).Contract Ledger
BaseConfigclassextends Neo.state.Provider;AiConfig.X.Ydirect read at point of useinstanceof Neo.state.Providerset(path,value)/setEnvOverride(path,value)internalSetData; env layer wins over defaultobserveConfig(path,fn)data.x = ysetDatacompileMetaLeaves(tree)datadefaults +#leafMetadataRegistry; env-overlay-mergeDependency chain (for downstream tickets)
compileMetaLeavesmeta-leaf tree shape from this ticket's PR (number TBD on open).set/setEnvOverride/ direct-assignment surface + theAiConfig.X.Yread path from this ticket's PR.Discussion #12100 criteria linkage
Stage-1 ACs trace to Discussion #12100 §9 Criterion 4 (OQ9 21-leaf prototype validated
compileTreeend-to-end) + the three STEP_BACK-convergence Stage-1 ACs (parser-validation choke point + separate metadata registry + hierarchical deep-merge), preserved per Discussion #12100's## Discussion Criteria Mapping.