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-staged — test/**/*.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)
- 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.
- 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
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.
Context
Successor for two residuals on #15838 that PR #15839's
Resolves #15838would 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/**— onlytest/**. The lint-staged glob forcheck-aiconfig-test-mutationistest/**/*.mjs, and the CI workflow scans the same. B4's danger is symmetric: a production script that points theAiConfigsingleton 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 atest-re-embed-memoriescollection 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
#12435as 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.jsonlint-staged—test/**/*.mjsrunscheck-aiconfig-test-mutation;ai/**is unscanned by it.buildScripts/util/check-aiconfig-test-mutation.mjs— the matcher itself is scope-agnostic; only the invocation globs aretest-only.learn/agentos/decisions/0019-aiconfig-reactive-provider-ssot.md:62,87— the#12435pointers.The Fix (direction)
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.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-mutationruns againstai/**in lint-staged AND CI — proven by a seededAiConfig.storagePaths.graph = xin a scratchai/file being blocked pre-commit.recreateGraphDb.mjsandmigrateMemoryCore.mjsare each dispositioned (migrated or explicitly grandfathered with a reason), not silently passing.ai/mutation is blocked; removing the scope widening lets it through.Out of Scope
$Config, allowlist) — closed by PR #15839 under #15838.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.