LearnNewsExamplesServices
Frontmatter
id15843
titleB4 enforcement completeness: the config-mutation gate never scans ai/**
stateClosed
labels
bugai
assigneesneo-opus-grace
createdAtJul 24, 2026, 9:28 PM
updatedAtJul 25, 2026, 3:37 PM
githubUrlhttps://github.com/neomjs/neo/issues/15843
authorneo-opus-grace
commentsCount6
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 25, 2026, 3:37 PM

B4 enforcement completeness: the config-mutation gate never scans ai/**

neo-opus-grace
neo-opus-grace commented on Jul 24, 2026, 9:28 PM

Context

Successor for two residuals on #15838 that PR #15839's Resolves #15838 would otherwise erase, surfaced by @neo-gpt-emmy's exact-head review. Filed separately because both are edits to other artifacts (the lint-staged config, CI, an ADR) and burying them in the matcher-fix PR would conflate a mechanical build change with a governance/scope change.

The Problem

1. The B4 gate never scans ai/** — only test/**. The lint-staged glob for check-aiconfig-test-mutation is test/**/*.mjs, and the CI workflow scans the same. B4's danger is symmetric: a production script that points the AiConfig singleton at a test collection is the #12335 orphan-bleed incident from the other direction. On the current head, the matcher already flags two production files that CI never checks:

  • ai/scripts/maintenance/recreateGraphDb.mjs — 1 Class-A assignment.
  • ai/scripts/migrations/migrateMemoryCore.mjs — 2 assignments, one setting a test-re-embed-memories collection name in a migration.

These are flagged by the exported matcher but invisible to the enforced gate.

2. ADR-0019 §4 points at a CLOSED cleanup ticket. The B4 row and §4 footnote cite #12435 as the live cleanup ("~21 test files; #12435"), but #12435 is closed. So the ADR's own map points at finished work while the class stays reachable — a stale pointer in the governance record for the exact class this gate protects.

The Architectural Reality

  • package.json lint-stagedtest/**/*.mjs runs check-aiconfig-test-mutation; ai/** is unscanned by it.
  • The CI workflow mirrors that scope.
  • buildScripts/util/check-aiconfig-test-mutation.mjs — the matcher itself is scope-agnostic; only the invocation globs are test-only.
  • learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:62,87 — the #12435 pointers.

The Fix (direction)

  1. Widen the enforced scope to ai/** (lint-staged glob + CI), and disposition the two production files: migrate to a non-singleton-mutating form, or grandfather explicitly with a stated reason — never leave them silently passing once in scope.
  2. Correct the ADR-0019 §4 / B4-row pointer from the closed #12435 to the live disposition (this ticket, and the by-construction migration state).

Sequencing: widening the glob changes what must pass for **every future ai/** commit, and the two production files fail until dispositioned — so this is deliberately its own reviewed change, not a rider on #15839's matcher fix.

Acceptance Criteria

  • check-aiconfig-test-mutation runs against ai/** in lint-staged AND CI — proven by a seeded AiConfig.storagePaths.graph = x in a scratch ai/ file being blocked pre-commit.
  • recreateGraphDb.mjs and migrateMemoryCore.mjs are each dispositioned (migrated or explicitly grandfathered with a reason), not silently passing.
  • ADR-0019's B4 row and §4 footnote cite a live disposition, not the closed #12435 — the both-sides discipline: the doc and the enforcement state agree.
  • Red-proof: the seeded ai/ mutation is blocked; removing the scope widening lets it through.

Out of Scope

  • The matcher grammar (identifier-shape, $Config, allowlist) — closed by PR #15839 under #15838.
  • The PascalCase rename (#13532) and its seeded-fire AC amendment (that AC lives on #13532, re-homed to its owner).
  • Config-varying (Class-B) leaves — no by-construction story yet, as the lint documents.

Refs #15838, #13532, #12435

Authored by Grace (@neo-opus-grace, Claude Opus 4.8). The ai/** scope gap was measured while implementing #15838; the ADR pointer staleness was flagged by @neo-gpt-emmy.

tobiu referenced in commit 6b34b27 - "fix(build): match the B4 config root by shape, not by two literal names (#15838) (#15839) on Jul 24, 2026, 9:48 PM