Context
PR #15211 (#14500) shipped the ADR-0019 mechanical backstop for B3 (defensive optional-chaining on AiConfig reads) and A5 (hasEnvValue reintroduction). A1 — module-level env re-derivation (const DB_PATH = process.env.X || path.join(...)) in ai/ files that already import AiConfig — was explicitly scoped out as the named fast-follow: it needs a two-signal detection (import-awareness) that the B3/A5 single-regex shape doesn't, and bundling it would have slowed the ratchet's landing. This leaf completes the mechanically-detectable subset of the catalog.
The Problem
A1 is tagged [live-on-dev] in the ADR-0019 §3 catalog (daemon entrypoints re-derive paths with AiConfig in scope). The sanctioned form is reading the resolved leaf at the use site — the leaf already owns env-override-with-default. Nothing mechanical prevents new A1 sites; review diligence is the empirically-falsified defense (the same 4/4-miss class that motivated the parent epic).
The Architectural Reality
buildScripts/util/check-aiconfig-antipatterns.mjs (shipped by PR #15211) carries an extensible RULES array, the shared codeMask string/comment masking (imported from the B4 sibling), an ESCAPE_MARKER relief valve, and a census-seeded ALLOWLIST ratchet. A1 slots in as a third rule — but with a file-level pre-condition, not just a line regex.
- The A1-vs-C1 distinction is load-bearing (ADR-0019 §3 classification correction + §5.5): A1 = re-derivation in a file that DOES import
AiConfig (fix: read the leaf); genuine C1 = a non-entrypoint pure-defaults module with NO Neo import (its process.env literals are the SANCTIONED form). The lint MUST fire only when the file imports AiConfig (or Memory_Config/Neo.ai.Config shapes) — otherwise it would flag the pure-defaults pattern the ADR prescribes.
- Detection heuristic: a module-level
const|let|var declaration whose initializer reads process.env. in an AiConfig-importing ai/ file. Column-0 declaration is the pragmatic module-level signal in Neo's indent style; the escape marker covers judgment-call residue (e.g. a deliberate bootstrap-boundary value object per §5.5).
The Fix
- Add an
A1 rule to check-aiconfig-antipatterns.mjs: on the current dev code-only projection, it fires per offending line ONLY when the file content also matches an AiConfig import (two-signal: file gate + same-line, column-zero declaration regex). This leaf does not widen the shared codeMask; executable template-interpolation and parser-grade mask fidelity are owned by successor #15276 and are pinned here as an explicit boundary.
- Census the current
ai/ tree at implementation time; seed the ALLOWLIST with live A1 files (the daemon entrypoints named by the ADR are expected members) so the ratchet bites only NEW offenders.
- Extend the spec: positive (re-derivation with import), negative (pure-defaults module WITHOUT AiConfig import must NOT flag — the C1-sanctioned form), function-local
process.env reads must NOT flag (module-level only), escape marker, allowlist.
- The existing workflow (
aiconfig-antipattern-lint.yml) already covers the checker path — no workflow change expected.
Acceptance Criteria
Out of Scope
- B5 / C1 / A6 / A7 checks (AST-level; separate fast-follows under the parent epic).
- Parser/tokenizer-grade shared
codeMask fidelity, including executable template interpolation and the broader slash/continuation grammar (#15276).
- Cleanup of the allowlisted A1 sites (Diamond-2 subs own that).
Decision Record impact
aligned-with ADR 0019 (implements its §7.2 sub-2 catalog subset; no amendment).
Related
Parent epic: #12456 · predecessor: #14500 / PR #15211 (B3+A5 ratchet, checker + workflow + spec shapes to mirror) · terminal predecessor: PR #15226 (closed Drop+Supersede) · parser-grade mask successor: #15276.
Live latest-open sweep: checked latest 20 open issues at 2026-07-16 ~07:22Z; no equivalent found. A2A in-flight claim sweep: herd-window claims (#14674, #15207, #15185, #15087, D#15204/G5) all disjoint — clean. Structure-map gate: N/A — extends an existing buildScripts/util checker in place; sibling precedent is the file itself.
Origin Session ID: 75ed6708-c66b-4989-862d-2286e87abbf1
Retrieval Hint: "AiConfig antipattern lint A1 module-level env re-derivation two-signal import-aware"
Context
PR #15211 (
#14500) shipped the ADR-0019 mechanical backstop for B3 (defensive optional-chaining on AiConfig reads) and A5 (hasEnvValuereintroduction). A1 — module-level env re-derivation (const DB_PATH = process.env.X || path.join(...)) inai/files that already importAiConfig— was explicitly scoped out as the named fast-follow: it needs a two-signal detection (import-awareness) that the B3/A5 single-regex shape doesn't, and bundling it would have slowed the ratchet's landing. This leaf completes the mechanically-detectable subset of the catalog.The Problem
A1 is tagged
[live-on-dev]in the ADR-0019 §3 catalog (daemon entrypoints re-derive paths withAiConfigin scope). The sanctioned form is reading the resolved leaf at the use site — the leaf already owns env-override-with-default. Nothing mechanical prevents new A1 sites; review diligence is the empirically-falsified defense (the same 4/4-miss class that motivated the parent epic).The Architectural Reality
buildScripts/util/check-aiconfig-antipatterns.mjs(shipped by PR #15211) carries an extensibleRULESarray, the sharedcodeMaskstring/comment masking (imported from the B4 sibling), anESCAPE_MARKERrelief valve, and a census-seededALLOWLISTratchet. A1 slots in as a third rule — but with a file-level pre-condition, not just a line regex.AiConfig(fix: read the leaf); genuine C1 = a non-entrypoint pure-defaults module with NO Neo import (itsprocess.envliterals are the SANCTIONED form). The lint MUST fire only when the file importsAiConfig(orMemory_Config/Neo.ai.Configshapes) — otherwise it would flag the pure-defaults pattern the ADR prescribes.const|let|vardeclaration whose initializer readsprocess.env.in an AiConfig-importingai/file. Column-0 declaration is the pragmatic module-level signal in Neo's indent style; the escape marker covers judgment-call residue (e.g. a deliberate bootstrap-boundary value object per §5.5).The Fix
A1rule tocheck-aiconfig-antipatterns.mjs: on the current dev code-only projection, it fires per offending line ONLY when the file content also matches an AiConfig import (two-signal: file gate + same-line, column-zero declaration regex). This leaf does not widen the sharedcodeMask; executable template-interpolation and parser-grade mask fidelity are owned by successor #15276 and are pinned here as an explicit boundary.ai/tree at implementation time; seed the ALLOWLIST with live A1 files (the daemon entrypoints named by the ADR are expected members) so the ratchet bites only NEW offenders.process.envreads must NOT flag (module-level only), escape marker, allowlist.aiconfig-antipattern-lint.yml) already covers the checker path — no workflow change expected.Acceptance Criteria
codeMask, A1 flags same-line, column-zero module-level declarations whose executableprocess.env.token remains visible, ONLY inai/files importing AiConfig (two-signal detection). Masked template-interpolation is explicitly outside this leaf and owned by #15276.Out of Scope
codeMaskfidelity, including executable template interpolation and the broader slash/continuation grammar (#15276).Decision Record impact
aligned-with ADR 0019 (implements its §7.2 sub-2 catalog subset; no amendment).
Related
Parent epic: #12456 · predecessor: #14500 / PR #15211 (B3+A5 ratchet, checker + workflow + spec shapes to mirror) · terminal predecessor: PR #15226 (closed Drop+Supersede) · parser-grade mask successor: #15276.
Live latest-open sweep: checked latest 20 open issues at 2026-07-16 ~07:22Z; no equivalent found. A2A in-flight claim sweep: herd-window claims (#14674, #15207, #15185, #15087, D#15204/G5) all disjoint — clean. Structure-map gate: N/A — extends an existing
buildScripts/utilchecker in place; sibling precedent is the file itself.Origin Session ID: 75ed6708-c66b-4989-862d-2286e87abbf1
Retrieval Hint: "AiConfig antipattern lint A1 module-level env re-derivation two-signal import-aware"