Context
Stage 4 of Epic #12101 — the consumer-side codemod for the four non-getter anti-pattern sub-classes from Discussion #12100's ledger (A / B / D-prod / E). Migrates ai/services/** + ai/mcp/** consumers onto the canonical Proxy-routed aiConfig.X.Y read and the operator's no-hidden-default contract. Stage 3 (#12105, the delegation-getter sub-class C) is merged.
API note: there is no aiConfig.set(path, value) method (Discussion #12100 hypothesized "Option F's .set()"); only setData + the createConfigProxy set-trap exist. So D-prod writes migrate to aiConfig.X = v (the proxy set-trap routes to setData), not a .set() call.
Contract Ledger
| Sub-class |
Before |
After |
Sites |
| A — Proxy bypass |
aiConfig.data.X (read) |
aiConfig.X (Proxy-routed) |
~12 — DreamService, ConceptDiscoveryService (×5), GapInferenceEngine (×3), DatabaseService, SessionService, GraphService |
| B — helper default-param |
config = aiConfig.data |
config = aiConfig (Proxy) |
3 — ProviderReadinessHelper.mjs:16,351,672 |
| D-prod — init mutation |
aiConfig.data.debug = true |
aiConfig.debug = true (set-trap → setData) |
~8 — the 6 mcp-server.mjs + run-bridge.mjs + mcp-cli.mjs bootstrap debug flags |
| E — hidden-default fallback |
aiConfig.X ?? <literal> |
aiConfig.X (config is SSOT) |
~20 — per-site verified (see treatment) |
- Source of authority:
AiConfig (Tier-1 realm root Neo.ai.Config) + its parent chain.
- Consumers/tests: the affected services' specs (DatabaseService, GraphService, SessionService, ConceptDiscovery, GapInference, ProviderReadiness, the KB DocumentService/QueryService/SearchService/VectorService + Source classes) +
Orchestrator-adjacent.
E treatment (per-site verified, not blanket)
Each ?? <literal> is checked against the owning config leaf:
- Redundant (literal == template default — e.g.
guideGapWeightThreshold ?? 0.8 ↔ leaf(0.8, …), minSourceLength ?? 200, prScanLimit ?? 20): remove the fallback; the config already provides the value.
- Code-only default (the literal is the real default, not in config — candidate: the 9 KB
Source ?? '<path>' defaults): add the default to the config template (the SSOT), then remove the fallback.
?? null (e.g. Orchestrator swarmHeartbeat?.targetSource ?? null): excluded — null-normalization, not a default value.
- Tenant defaults (
?? 'neo-shared' / ?? 'neo' in DocumentService/QueryService/SearchService): verified against the fail-closed tenant-isolation resolution — removed only if the resolution provably always yields the tenant (no security regression).
Acceptance Criteria
Out of Scope
- D-test — the 33 test
aiConfig.X = Y mutations (a separate test-ergonomics migration).
- Stage 5 (#12107 — Env consumer-export retraction) / Stage 6 (#12108 — delete legacy applyEnv).
Related
- Parent Epic: #12101 (Stage 4). Source: Discussion #12100 (sub-classes A/B/D-prod/E; §"Stage 3 codemod-sweep prep"). Sibling: #12105 (Stage 3, merged).
Context
Stage 4 of Epic #12101 — the consumer-side codemod for the four non-getter anti-pattern sub-classes from Discussion #12100's ledger (A / B / D-prod / E). Migrates
ai/services/**+ai/mcp/**consumers onto the canonical Proxy-routedaiConfig.X.Yread and the operator's no-hidden-default contract. Stage 3 (#12105, the delegation-getter sub-class C) is merged.API note: there is no
aiConfig.set(path, value)method (Discussion #12100 hypothesized "Option F's.set()"); onlysetData+ thecreateConfigProxyset-trap exist. So D-prod writes migrate toaiConfig.X = v(the proxy set-trap routes tosetData), not a.set()call.Contract Ledger
aiConfig.data.X(read)aiConfig.X(Proxy-routed)config = aiConfig.dataconfig = aiConfig(Proxy)ProviderReadinessHelper.mjs:16,351,672aiConfig.data.debug = trueaiConfig.debug = true(set-trap →setData)mcp-server.mjs+run-bridge.mjs+mcp-cli.mjsbootstrap debug flagsaiConfig.X ?? <literal>aiConfig.X(config is SSOT)AiConfig(Tier-1 realm rootNeo.ai.Config) + its parent chain.Orchestrator-adjacent.E treatment (per-site verified, not blanket)
Each
?? <literal>is checked against the owning config leaf:guideGapWeightThreshold ?? 0.8↔leaf(0.8, …),minSourceLength ?? 200,prScanLimit ?? 20): remove the fallback; the config already provides the value.Source?? '<path>'defaults): add the default to the config template (the SSOT), then remove the fallback.?? null(e.g. OrchestratorswarmHeartbeat?.targetSource ?? null): excluded — null-normalization, not a default value.?? 'neo-shared'/?? 'neo'in DocumentService/QueryService/SearchService): verified against the fail-closed tenant-isolation resolution — removed only if the resolution provably always yields the tenant (no security regression).Acceptance Criteria
.dataat the ~12 read sites —aiConfig.data.X→aiConfig.X.ProviderReadinessHelperdefault-params read the Proxy (aiConfig), not.data.debugmutations go through the proxy set-trap (aiConfig.debug = true).?? <literal>hidden-default removed only after verifying the config provides the default (added to the template where it lived only in code);?? nullexcluded; tenant sites guarded against isolation regression.Out of Scope
aiConfig.X = Ymutations (a separate test-ergonomics migration).Related