Frontmatter
| title | fix(ai): detect config leaf default drift (#12767) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 8, 2026, 4:25 PM |
| updatedAt | Jun 8, 2026, 4:55 PM |
| closedAt | Jun 8, 2026, 4:55 PM |
| mergedAt | Jun 8, 2026, 4:55 PM |
| branches | dev ← codex/12767-config-default-drift |
| url | https://github.com/neomjs/neo/pull/12769 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Delivers #12767 — a static, detect-only guard for AiConfig SSOT-derivation drift (template leaf-default advanced, gitignored config stale), the exact failure class that bit #12760 (and the
gemma4-vs-gemma4:31bmismatch I flagged in #12764). It warns + points at the migrate command; it never overwrites the user's config (verified). No blockers; one non-blocking watch-item (the source-text parser doesn't skip comments).
Opening: Clean friction-to-gold — this generalizes a one-off config-default mismatch into a systemic, ADR-0019-aligned drift detector. The detect-only/warn-don't-overwrite discipline is exactly right for a gitignored user config, and the no-overwrite test proves it. Notes below; no blockers.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: ticket #12767 (
bug/regression/architecture— the #12760 template-vs-stale-config drift),initServerConfigs.mjson dev (the existing import/export/envVar drift detector), ADR 0019 (template = SSOT, local config derived), the #12764 config-default-mismatch context. - Expected Solution Shape: a generic, static, read-only detector comparing the local config's
leaf()defaults vs the template's, flagging same-key default-flips, and warning (not auto-overwriting) the gitignored config. Unit-testable. - Patch Verdict: Matches.
projectLeafDefaultsprojectskey: leaf(default, 'ENV', 'type')from source text;detectDriftcompares bykey:env:typeidentity and flags default differences;initConfigs/initTier1Configwarn + emit the migrate command, leaving the config byte-identical (proven by the no-overwrite test). One-way (template-advanced) by design. Clean refactor splittingprojectSourceShape(src)(pure) fromprojectShape(filePath)(reads).
🕸️ Context & Graph Linking
- Target Issue ID: Resolves #12767
- Related Graph Nodes: #12740 (cost-safety), #12760 (the template-default flip that triggered this), #12764 (the config-default mismatch finding this generalizes)
🔬 Depth Floor
Challenge (non-blocking): projectLeafDefaults is a hand source-text parser (findClosingParen / splitTopLevelArgs / regex key:\s*leaf\s*\(), not an AST/runtime read — so it does not skip comments. A commented-out // modelProvider: leaf('gemini', 'NEO_MODEL_PROVIDER', 'string') in either file would match the leaf( regex and project as a real leaf-default, risking a false-positive drift warning. Low-likelihood (config/template rarely carry commented leaves) and benign (the detector warns, never auto-fixes — verified), so a watch-item, not a gate. The parser is otherwise robust — it correctly handles nested parens / quotes / escapes (the host: leaf(path.resolve(neoRootDir, …), …) test confirms the depth-tracking). If commented-leaf false-positives ever surface, a comment-strip pre-pass closes it.
Rhetorical-Drift Audit: Pass. "Config leaf default drift" and the one-way "template advanced, config stale" docstring accurately describe the mechanic (compare key:env:type default literals, report template-side changes). The warning + migrate-command framing matches the detect-only behavior. No overshoot.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: A clean friction-to-gold cycle — thegemma4/gemma4:31bconfig-default mismatch surfaced in a #12764 PR review became a systemic, static drift detector that catches the whole class (any same-key:env:typedefault flip between the SSOT template and the derived gitignored config). The right shape: warn + point at the migrate command, never silently overwrite a user's config.
🎯 Close-Target Audit
- Close-target:
#12767(newline-isolatedResolves); confirmedbug/ai/regression/architecture/build— notepic. ✓
Findings: Pass. Related: #12740 is non-closing.
🪜 Evidence Audit
- PR declares
Evidence: L1(static config-shape audit + focused source-projection unit coverage) → L1 required (no runtime-verify ACs). - Accurate — the ACs are a static source-text projection + drift comparison, fully unit-coverable; no sandbox-unreachable runtime effect. (Correctly framed as L1, unlike a pure-function N/A — this is a static-artifact audit.)
Findings: Pass.
🧪 Test-Execution & Location Audit
- Branch checked out (
gh pr checkout 12769, headd5314ed1c). - Canonical Location:
test/playwright/unit/ai/scripts/setup/initServerConfigs.spec.mjs— correct;serial. - Ran the full spec → 23 passed: the existing
#10815drift suite (exercising the refactoredprojectShape) + the 3 new#12767cases (leaf-default projection, same-env default-change drift, and the end-to-end warn-without-overwriting that assertsconfig.mjsis byte-identical after the run).
Findings: Pass — and the no-overwrite assertion is the key safety guarantee, explicitly covered.
N/A Audits — 📑 📡 🔗 🛂 🔌
N/A: no public/consumed-surface contract change — internal setup-script detector, warn-only (📑); no openapi.yaml (📡); auto-fires on the existing prepare/init path, no new convention another skill must invoke + the migrate command is in the warning (🔗); standard source-text projection, not an external port (🛂); no wire-format/schema change (🔌).
📋 Required Actions
No required actions — eligible for human merge. (The comment-skip parser note is a non-blocking watch-item.)
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 — 10 deducted: the source-text leaf parser doesn't skip comments (a small false-positive surface); the ADR-0019 SSOT-derivation guard, the detect-only/warn discipline, and theprojectSourceShape/projectShapesplit are otherwise exemplary.[CONTENT_COMPLETENESS]: 95 — 5 deducted: thorough JSDoc on every helper + the drift descriptors + the one-way docstring; a one-line note on the comment-skip limitation atprojectLeafDefaultswould close it.[EXECUTION_QUALITY]: 90 — 10 deducted: correct drift detection, detect-only verified, 23 specs green, robust nested-paren/quote handling; the comment-skip + the inherent source-text-parser brittleness are the deductions.[PRODUCTIVITY]: 100 — confirmed the #12767 ACs (project env-bound leaf defaults; report same-env default changes; warn without overwrite) are met + tested; nothing left unaddressed.[IMPACT]: 75 — a systemic guard against a recurring config-SSOT-derivation drift class (the #12760 incident + the #12764 mismatch); meaningful for cost-safety correctness (a stale local provider default is exactly the kind of silent drift this catches).[COMPLEXITY]: 60 — Moderate-high: a hand source-text parser (paren-matching, top-level-arg splitting, quote/escape handling) + the drift comparison + the materialize-fast-path interaction; real reasoning load in the parser correctness.[EFFORT_PROFILE]: Heavy Lift — a 225-line source-projection + drift-detection layer with careful parser handling and end-to-end no-overwrite coverage.
[KB_GAP] / [TOOLING_GAP]: none.
Closing: A genuinely valuable systemic guard — it turns a one-off config-default mismatch into automatic detection of the whole drift class, with the right "warn, never overwrite a gitignored config" discipline (and a test that proves it). The only thing to keep an eye on is the source-text parser's comment-blindness, which is benign given it only warns. Eligible for human merge.
Authored by GPT-5 (Codex Desktop). Session e8f07ef9-ef7e-4815-8ff4-7abe13720621.
Resolves #12767 Related: #12740
Extends
initServerConfigs.mjsdrift detection so env-bound AiConfig leaf defaults participate in source-shape comparison. Existing gitignored overlays that still defaultNEO_MODEL_PROVIDERto Gemini now surface as drift instead of reportinghasDrift: false.Evidence: L1 (static config-shape audit plus focused source-projection unit coverage) -> L1 required (no runtime-verify ACs). No residuals.
Deltas from ticket
key: leaf(default, ENV, type)calls using a small balanced scanner, not the live AiConfig singleton.changedLeafDefaultstodetectDrift()and warning output.Test Evidence
npm run test-unit -- test/playwright/unit/ai/scripts/setup/initServerConfigs.spec.mjs— 23 passed.changedLeafDefaultsfor stalechatProviderandmodelProviderdefaults, withhasDrift: true.git diff --checkpassed.Post-Merge Validation
npm run prepare -- --migrate-configwhen ready to refresh gitignored overlays; the detector will now warn before that refresh.Commit
d5314ed1c—fix(ai): detect config leaf default drift (#12767)