LearnNewsExamplesServices
Frontmatter
id12168
titleBaseConfig prototype shape: data + afterSetData seam + leaf() factory
stateClosed
labels
enhancementairefactoringarchitecture
assigneesneo-opus-ada
createdAtMay 29, 2026, 12:23 PM
updatedAtMay 29, 2026, 4:48 PM
githubUrlhttps://github.com/neomjs/neo/issues/12168
authorneo-opus-ada
commentsCount1
parentIssue12101
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 29, 2026, 4:48 PM

BaseConfig prototype shape: data + afterSetData seam + leaf() factory

Closed v13.0.0/archive-v13-0-0-chunk-14 enhancementairefactoringarchitecture
neo-opus-ada
neo-opus-ada commented on May 29, 2026, 12:23 PM

Context

PR #12164 implemented BaseConfig with a metaTree config + construct-time compile + scalar-only validation — a re-derived shape, not the graduated Discussion #12100 prototype. Operator opted in (2026-05-29) to conform to the prototype if it is genuinely more elegant and makes sense. It is: the prototype uses the standard data config + an afterSetData override (the canonical Provider compile seam) + a leaf(default, env, type) factory (explicit type + .validate() parser). This restores elegance the #12164 divergence dropped, and aligns with AC-Epic-1 ("extend, don't reinvent"). Provenance: comparing #12164 to the graduated prototype surfaced the gap; operator-directed to conform.

The Problem

  1. Parallel metaTree config. A separate metaTree config slot + construct-time imperative setData is the kind of parallel machinery AC-Epic-1 disfavors, and it isn't reactive — the tree compiles only at construct, not on a data reassignment.
  2. Type inference loses null-default types. Leaves are raw {env, default, parse} literals; compileMetaLeaves infers type from the default via Neo.typeOf. A null/undefined default yields type: null → that leaf can never be validated.
  3. Scalar-only validation (#12167) — object/JSON leaves bypass the parser check.

The Architectural Reality

  • Provider#afterSetData(value)processDataObject(value) (src/state/Provider.mjs:165 / :594) is THE seam that converts a plain data object into the reactive #dataConfigs map. The prototype overrides it: compileMetaLeaves(value)super.afterSetData(plainData) → reactive build → #applyEnvLayer().
  • The prototype's leaf(default, env, type) factory produces {default, env, parse, type} where parse carries a .validate() method and type is explicit (not inferred).
  • The prototype's #resolveLeafWithEnv(path) resolves #runtimeEnvOverrides.get(meta.env) ?? process.env[meta.env] ?? default at override-resolution points (bounded — not an every-read live re-read).

The Fix

  1. Drop the metaTree config + construct override; subclasses declare the meta-tree as data: {…}; override afterSetData to compile → super.afterSetData(plainData)#applyEnvLayer.
  2. Add a leaf(default, env, type) factory (home decided via structural-pre-flight at impl — BaseConfig export or small helper module); rewrite all six templates' leaves to use it; carry explicit type + a .validate() parser.
  3. Validation via parse.validate() for any value at a known leaf path (subsumes #12167); namespace-recursion intermediates still pass through.
  4. #resolveLeafWithEnv precedence (runtime override ?? process.env ?? default), resolved at override points.

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
BaseConfig tree-source config ai/BaseConfig.mjs (metaTreedata) Tree declared as data; compiled in afterSetData n/a JSDoc metaTree+construct confirmed in #12164
leaf(default, env, type) factory new (ai/BaseConfig.mjs or helper) Produces {default, env, parse, type} w/ .validate() + explicit type n/a JSDoc Prototype #12100
internalSetData validation ai/BaseConfig.mjs parse.validate() any value at known leaf path warn-keep JSDoc Scalar-only gate (#12164)
6 config.template.mjs leaves all templates {env,default,parse} literals → leaf(...) calls n/a Current literal shape

Acceptance Criteria

  • Meta-tree declared as data; metaTree config removed; compile happens in afterSetData via super.afterSetData(plainData).
  • leaf(default, env, type) factory exists; all six templates use it; a null-default leaf carries an explicit type and is validatable.
  • Object/JSON leaves validated via parse.validate() at a known path; namespace intermediates pass through.
  • Env resolution precedence (runtime override ?? process.env ?? default) preserved; runtime-override-survives-overlay behavior retained.
  • Existing tests green; spec updated for the data/leaf() shape.

Out of Scope

  • Parent-chain realm inheritance (#12166) — sequence this ticket FIRST (correct the shape, then add hierarchy) to avoid double template churn.
  • dummyEmbeddingFunction SSOT (#12165).
  • Unbounded every-read live process.env re-resolution — explicitly NOT adopted (bounded override-point resolution only).

Avoided Traps

  • Keeping metaTree as a separate config (the #12164 re-derived divergence) — rejected per AC-Epic-1 + the graduated prototype; data + afterSetData is the canonical seam.

Decision Record impact

none — restores the graduated Discussion #12100 prototype shape; aligned with Epic #12101. Downstream amendment note: if impl surfaces an afterSetData reactivity edge, the Fix may need refresh.

Related

  • Epic #12101; Discussion #12100 (prototype source); PR #12164 (the divergent shape this corrects); #12166 (parent-chain — sequences after); #12167 (subsumed by AC-3 here); #12165 (dummyEF SSOT).

Origin Session ID: b124b83a-2cca-4a2b-a711-64868439ba1e Retrieval Hint: "BaseConfig data afterSetData seam leaf factory prototype shape conform #12100"

tobiu referenced in commit bffd8b4 - "refactor(ai): BaseConfig prototype shape — data+afterSetData + leaf() factory (#12168) (#12172) on May 29, 2026, 4:48 PM
tobiu closed this issue on May 29, 2026, 4:48 PM