LearnNewsExamplesServices
Frontmatter
id15900
titleAdd ai:config-print to dump resolved config leaves at a head
stateClosed
labels
enhancementai
assigneesneo-kimi-iris
createdAtJul 25, 2026, 1:58 PM
updatedAtJul 25, 2026, 2:59 PM
githubUrlhttps://github.com/neomjs/neo/issues/15900
authorneo-kimi-iris
commentsCount0
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 25, 2026, 2:59 PM

Add ai:config-print to dump resolved config leaves at a head

Closed Backlog/active-chunk-9 enhancementai
neo-kimi-iris
neo-kimi-iris commented on Jul 25, 2026, 1:58 PM

Context

Surfaced as a [TOOLING_GAP] in my own PR #15888 review (cross-family, B4 burndown): the ticket's load-bearing evidence was a resolved-config measurement ("storagePaths.graph = :memory:, collections.* = per-process test-* under UNIT_TEST_MODE"), and a reviewer has no cheap way to reproduce it. My bare node -e probe died in Neo boot (Neo.gatekeep absent — the import chain never passed src/Neo.mjs), so verification fell back to static leaf reading. The same gap hit again today on #15889 (chroma database leaf vs databaseTest consumer-side selection).

Premise verified twice today, then the working shape spike-proven (4-line boot, run from the repo root):

$ node --input-type=module -e "globalThis.Neo ??= {}; globalThis.Neo.config={environment:'development',unitTestMode:true}; process.env.UNIT_TEST_MODE='true'; await import('./src/Neo.mjs'); const cfg=(await import('./ai/mcp/server/memory-core/config.template.mjs')).default; console.log(cfg.storagePaths.graph, cfg.collections?.memory)"
:memory: test-memory-1784980533381-6tccjs

That output reproduces #15887's load-bearing measurement to the character — the gap is real, the prescription works, and it is 4 lines of bootstrap away from being a permanent tool.

The Problem

ADR-0019's read-gate made "read resolved leaves at the use site" the rule, and PR bodies increasingly carry resolved-at-this-head measurements as load-bearing evidence (e.g. #15887's burndown rested entirely on one). But the resolution machinery (leaf → env layer → formulas → consumer-side selection) only runs inside a booted Neo context, and the boot contract is non-obvious (globalThis.Neo.config before importing src/Neo.mjs). So:

  • Authors hand-measure with one-off probes (each rediscovers the boot contract).
  • Reviewers cannot falsify the measurement without writing their own probe → V-B-A degrades to static source reading, exactly when the claim is about runtime resolution.

The Architectural Reality

  • Resolution lives in ai/configBase.mjs + per-server ai/mcp/server/*/configBase.mjs (leaf() + formulas), consumed via config.template.mjs default exports.
  • Some leaves resolve differently than they read: engines.chroma.database stays default_database while the consumer (ChromaManager) selects databaseTest when engines.chroma.useTestDatabase — so the tool must print the toggles alongside, or it invites the wrong conclusion (verified today: database prints default_database under UNIT_TEST_MODE while isolation is consumer-side).
  • Sibling precedent: ai/scripts/diagnostics/check-identity-facts.mjs, check-substrate-size.mjs — standalone diagnostics scripts with ai:* npm names. Structural pre-flight Stage 1 fast-path: same folder, same shape.

The Fix

ai/scripts/diagnostics/printAiConfig.mjs + npm script ai:config-print:

  • Minimal boot (the spiked 4 lines), then import the target server config template.
  • Positional args = dot-paths to print (e.g. npm run ai:config-print -- storagePaths.graph collections.memory); no args → a small default set covering the B4/test-isolation surface (storagePaths.graph, collections.memory, collections.session, engines.chroma.database, engines.chroma.useTestDatabase, engines.chroma.useUnitTestDatabase).
  • --server=memory-core|knowledge-base|neural-link (default memory-core) selects the config template.
  • --unit sets UNIT_TEST_MODE=true (prod resolution is the default view).
  • Output: one path = value line per leaf, plus a # server=… mode=… header — greppable, pasteable into PR bodies and reviews.
  • Read-only by construction: no assignment to any config path (asserted by the test below).

Contract Ledger Matrix

Contract Source of Authority Proposed Behavior Fallback / Evidence
npm run ai:config-print -- [paths…] this script prints resolved leaves + provenance header unknown path → prints undefined + non-zero exit with the path named
--server flag script argv parser selects the config template unknown server → usage error listing valid values
--unit flag script argv parser sets UNIT_TEST_MODE=true before import absent → prod resolution
Output format script stdout path = value lines + #-prefixed provenance header machine-stable; no colors/pagination

Acceptance Criteria

  • npm run ai:config-print --unit -- storagePaths.graph prints storagePaths.graph = :memory: (the #15887 measurement reproducible in one command).
  • Default-set run prints the toggle leaves (engines.chroma.useTestDatabase, engines.chroma.useUnitTestDatabase) alongside engines.chroma.database, so the consumer-side selection is visible.
  • A unit spec (test/playwright/unit/ai/scripts/diagnostics/printAiConfig.spec.mjs, sibling-shaped) asserts the unit-mode output for the B4-critical leaves and the read-only property (script source contains no aiConfig.<path> = assignment — the B4 guard's own shape).
  • RED-first: the spec fails before the script exists (missing module), proving it pins the behavior.
  • Unknown path → non-zero exit naming the path; unknown --server → usage error.

Out of Scope

  • An MCP-tool variant (needs a running server; the use case is a pre-merge probe at an arbitrary head).
  • CI wiring (reviewer/author tool, not a gate).
  • Schema/table pretty-printing, colors, interactive mode.
  • Printing the live config.mjs overlay (canonical template only, per ADR-0019 C3).

Avoided Traps

  • Full test/playwright/setup.mjs import — heavier than needed (DOM mocks, app facades); the 4-line boot suffices, spike-verified.
  • Dumping the whole config tree — invites diffing noise and leaks the point; named leaves only.
  • Printing only values, not toggles — misreads consumer-side selection (the database vs databaseTest case) as missing isolation.

Related

  • PR #15888 review (the [TOOLING_GAP] origin) · #15887 (the measurement this makes reproducible) · ADR-0019 §5/§6 (read-gate; C3 canonical template) · ai/scripts/diagnostics/check-identity-facts.mjs (sibling shape).

Decision Record impact: aligned-with ADR 0019 — a read-path tool that makes the read-gate cheaper to exercise; no ADR amended.

Live latest-open sweep: checked latest 20 open issues (created-desc) at 2026-07-25T11:58Z; no equivalent found. A2A in-flight sweep (last 60 min, all read-states): no overlapping claim. KB semantic sweep: no existing resolved-config dump tool.

Origin Session ID: 3b5c70eb-0622-4bf2-bdbe-bc11f8a140f8

Retrieval Hint: query_raw_memories("ai config-print resolved leaves reviewer falsifier printAiConfig")

Authored by Iris (@neo-kimi-iris, Kimi K3, Kimi Code CLI) 🌈