LearnNewsExamplesServices
Frontmatter
id12167
titleBaseConfig.internalSetData: validate object/JSON leaves (AC-Epic-5)
stateClosed
labels
bugai
assignees[]
createdAtMay 29, 2026, 12:16 PM
updatedAtMay 29, 2026, 12:42 PM
githubUrlhttps://github.com/neomjs/neo/issues/12167
authorneo-opus-ada
commentsCount1
parentIssue12101
subIssues[]
subIssuesCompleted0
subIssuesTotal0
blockedBy[]
blocking[]
closedAtMay 29, 2026, 12:42 PM

BaseConfig.internalSetData: validate object/JSON leaves (AC-Epic-5)

neo-opus-ada
neo-opus-ada commented on May 29, 2026, 12:16 PM

Context

Comparing PR #12164's BaseConfig against the graduated Discussion #12100 prototype (2026-05-29) surfaced that internalSetData validation is scalar-only: object/JSON-typed leaves bypass the parser check. AC-Epic-5 mandates the metadata-path-first rule — validate any value at a known leaf path.

The Problem

ai/BaseConfig.mjs internalSetData gates validation on Neo.typeOf(value) !== 'Object':

if (typeof key === 'string' && Neo.typeOf(value) !== 'Object' && this.#leafMetadataRegistry.has(key)) {
    value = this.#validateLeafValue(key, value);
}

So a future object/JSON leaf (e.g. {theme: 'dark'}) written to a known leaf path skips validation entirely — exactly the gap the prototype's comment warns against. AC-Epic-5 (Epic #12101): "parser-validation at internalSetData() choke point (metadata-path-first rule — validate ANY value at known leaf path; namespace recursion intermediates pass through)."

The Architectural Reality

  • The metadata-path-first rule keys on registry membership: if #leafMetadataRegistry.has(key) the write targets a leaf → validate, regardless of value type. Namespace-recursion intermediates have no registry entry → pass through naturally.
  • #validateLeafValue already type-checks against the leaf's meta.type; for an object leaf meta.type is 'Object' (inferred from the default), so the existing mechanism extends cleanly once the type gate is removed.

The Fix

Drop the Neo.typeOf(value) !== 'Object' clause so registry membership alone gates validation:

if (typeof key === 'string' && this.#leafMetadataRegistry.has(key)) {
    value = this.#validateLeafValue(key, value);
}

Contract Ledger

Target Surface Source of Authority Proposed Behavior Fallback Docs Evidence
BaseConfig.internalSetData validation gate ai/BaseConfig.mjs Validate any value at a registered leaf path; namespace intermediates pass through warn-and-keep (current leniency) method JSDoc Scalar-only gate confirmed in #12164

Acceptance Criteria

  • An object-typed leaf written to a known path is validated (no !== 'Object' bypass).
  • Namespace-recursion intermediates (keys absent from the registry) still pass through unvalidated.
  • Regression test covering an object-typed leaf at a known path.

Out of Scope

  • The validate-throws-vs-warn-keeps policy (current is lenient warn-and-keep; a separate decision).
  • Adopting the prototype's parse.validate() contract — requires the leaf(default, env, type) factory (deferred; see the parent-chain sub's Out of Scope).

Related

Origin Session ID: b124b83a-2cca-4a2b-a711-64868439ba1e Retrieval Hint: "BaseConfig internalSetData object leaf validation AC-Epic-5 metadata-path-first"