Context
The guard half of #17239, split per its reviewer's fork answer (option 1: guard first, burndown after). Filed as a leaf so the guard has an honest close target while #17239 stays open on the relocation it actually asks for.
Two independent reasons the guard leads rather than trails, both from the parent's own thread:
- The count was wrong three times. #17239 named three crossings. Running its own AC-1 grep found six. Rewriting the detector to read the AST found ten — but one of those ten was my own detector defect, not a real crossing. The true census is nine crossings across seven files, all under
buildScripts/. Every sweep before the last was from-anchored, and a dynamic import has no from keyword. A baseline turns the count into an artifact that fails when it drifts, instead of a number in a title that outlives the sweep that produced it.
publish.mjs is the atomic dev → main release path. It is the one relocation where a mid-move regression is expensive and hard to notice, and it should not move without a net.
The Problem
neomjs/neo is the engine; ai/ is the Brain. The direction is one-way by design — the Brain may consume the engine, never the reverse. Nothing enforces it, so ten crossings accumulated without any of them failing anything.
src/** is clean, and stays clean. An earlier revision of this ticket claimed otherwise, on the strength of src/worker/App.mjs:779 doing import('../ai/Client.mjs'). That resolves to src/ai/Client.mjs, not the top-level ai/ — and src/ai/** is the engine's own AI layer, the one sanctioned seam by which the Body connects to the Brain. Engine code, by design.
The claim came from a predicate matching specifier text rather than resolving it against the importing file: identical text, opposite meanings by directory. Retracted in full; the guard now resolves before deciding, and the spec asserts both verdicts of that pair.
What the earlier hand-greps genuinely missed is buildScripts/devCockpit.mjs — three crossings, all dynamic imports, which have no from keyword and so were invisible to every from-anchored sweep.
The Architectural Reality
Ten crossings, three classes. A and B are the parent ticket's split; C is one neither census had.
| Class |
What it is |
Fix shape |
Sites |
| A |
engine tooling reaching a Brain service |
moves a concern |
labels.mjs, rebuildContentIndexesAndSeo.mjs, publish.mjs |
| B |
agent-serving script misfiled in an engine directory |
moves a file |
agentCoAuthorEmails.mjs, deriveFleetRoster.mjs, agent-preflight.mjs, devCockpit.mjs (×3) |
The A/B split is #17239's and it is complete. A "Class C" proposed in an earlier revision of this ticket was an artifact of the predicate defect above and has been withdrawn.
The Fix
buildScripts/util/check-engine-brain-boundary.mjs — reads ImportDeclaration / ImportExpression nodes from the AST, not text — plus a burndown baseline, a lint-staged entry, and its required CI mirror.
Three properties that are the whole point:
- Asserts the property, not the list. The baseline exempts known debt; it is not the assertion. A crossing in a file the baseline does not cover fails, so a new one cannot pass by leaving the listed nine intact.
- The ratchet fails both ways. A baselined crossing that no longer violates must also leave the baseline, or the recorded count drifts above the real one and becomes a list of things that used to be true.
- Rows carry
count. devCockpit.mjs imports localBearer.mjs at two lines; a key of file+specifier alone holds one member for both, so removing one occurrence would leave the key present and the diff silent — the measured shape from a sibling baseline where 83 rows collapsed to 9 keys and deleting 63 of 64 still reported green.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback |
Docs |
Evidence |
check-engine-brain-boundary.mjs |
new |
fails on an unbaselined crossing, or on baseline drift in either direction |
unparseable source → skipped (check-parse owns that) |
module JSDoc |
red-proofs below |
check-engine-brain-boundary-baseline.json |
this ticket |
ten rows, each with class + count + why it is tolerated |
absent file → hard failure |
inline note per row |
git ls-files census |
lint-staged {buildScripts,src}/**/*.mjs |
package.json |
runs the guard pre-commit |
--no-verify bypass covered by the CI mirror |
— |
lint-guard-ci-parity OK |
engine-brain-boundary-lint.yml |
lint-guard-ci-parity |
CI mirror; resolved by parsed run: full path in a *-lint.yml |
n/a |
header comment |
lint green |
Decision Record impact
none — restores a boundary no ADR presently defines. Whether the engine↔Brain direction deserves a formal ADR is #17239's stated follow-up, not this leaf's.
Acceptance Criteria
Out of Scope
- Relocating any of the ten. That is #17239, deliberately. This leaf lands the net; the parent does the moving.
- The Class C decision. The guard makes it visible and baselines it; whether
src/worker/App.mjs keeps its lazy Brain import is a call for #17239 or its own ticket.
- Making the workflow a required status context — #17171 covers that class for all nineteen existing lint workflows.
Avoided Traps
- Regex over source text. The first draft did this and convicted its own JSDoc — invisible until the file became tracked and the guard could scan itself. Masking comments would have fixed that one instance; reading the parse tree removes the class and catches the dynamic imports, which is what took the count from six to ten.
- Baseline-as-assertion. Flagged by the parent's reviewer before it could ship: a snapshot used as the assertion lets a seventh import in a new file pass while the six stay clean — the exact shape the parent ticket is now a case study in.
- A key without a count. Would have let a partial burndown of
devCockpit.mjs report green.
Related
- Parent: #17239 (the relocation; stays open)
- #17237 / PR #17243 — the same boundary eroding in the opposite direction
- #17171 — required-status-context gap affecting every lint workflow
Origin Session ID: 3f264a19-c7d4-481e-bc80-5c288bca177f
Retrieval Hint: query_raw_memories("engine Brain boundary guard dynamic import from-anchored grep missed src/worker/App.mjs")
Live latest-open sweep: checked latest open issues at 2026-08-16T20:40Z; A2A claim sweep same time — #17239's owner has answered the fork in favour of this split and holds no competing claim.
Context
The guard half of #17239, split per its reviewer's fork answer (option 1: guard first, burndown after). Filed as a leaf so the guard has an honest close target while #17239 stays open on the relocation it actually asks for.
Two independent reasons the guard leads rather than trails, both from the parent's own thread:
buildScripts/. Every sweep before the last wasfrom-anchored, and a dynamic import has nofromkeyword. A baseline turns the count into an artifact that fails when it drifts, instead of a number in a title that outlives the sweep that produced it.publish.mjsis the atomicdev→mainrelease path. It is the one relocation where a mid-move regression is expensive and hard to notice, and it should not move without a net.The Problem
neomjs/neois the engine;ai/is the Brain. The direction is one-way by design — the Brain may consume the engine, never the reverse. Nothing enforces it, so ten crossings accumulated without any of them failing anything.src/**is clean, and stays clean. An earlier revision of this ticket claimed otherwise, on the strength ofsrc/worker/App.mjs:779doingimport('../ai/Client.mjs'). That resolves tosrc/ai/Client.mjs, not the top-levelai/— andsrc/ai/**is the engine's own AI layer, the one sanctioned seam by which the Body connects to the Brain. Engine code, by design.The claim came from a predicate matching specifier text rather than resolving it against the importing file: identical text, opposite meanings by directory. Retracted in full; the guard now resolves before deciding, and the spec asserts both verdicts of that pair.
What the earlier hand-greps genuinely missed is
buildScripts/devCockpit.mjs— three crossings, all dynamic imports, which have nofromkeyword and so were invisible to everyfrom-anchored sweep.The Architectural Reality
Ten crossings, three classes. A and B are the parent ticket's split; C is one neither census had.
labels.mjs,rebuildContentIndexesAndSeo.mjs,publish.mjsagentCoAuthorEmails.mjs,deriveFleetRoster.mjs,agent-preflight.mjs,devCockpit.mjs(×3)The A/B split is #17239's and it is complete. A "Class C" proposed in an earlier revision of this ticket was an artifact of the predicate defect above and has been withdrawn.
The Fix
buildScripts/util/check-engine-brain-boundary.mjs— readsImportDeclaration/ImportExpressionnodes from the AST, not text — plus a burndown baseline, a lint-staged entry, and its required CI mirror.Three properties that are the whole point:
count.devCockpit.mjsimportslocalBearer.mjsat two lines; a key of file+specifier alone holds one member for both, so removing one occurrence would leave the key present and the diff silent — the measured shape from a sibling baseline where 83 rows collapsed to 9 keys and deleting 63 of 64 still reported green.Contract Ledger Matrix
check-engine-brain-boundary.mjscheck-parseowns that)check-engine-brain-boundary-baseline.jsoncount+ why it is toleratednoteper rowgit ls-filescensuslint-staged{buildScripts,src}/**/*.mjspackage.json--no-verifybypass covered by the CI mirrorlint-guard-ci-parityOKengine-brain-boundary-lint.ymllint-guard-ci-parityrun:full path in a*-lint.ymlDecision Record impact
none— restores a boundary no ADR presently defines. Whether the engine↔Brain direction deserves a formal ADR is #17239's stated follow-up, not this leaf's.Acceptance Criteria
dev, and the baseline contains all nine crossings with per-row class and count.src/ai/**consumers are NOT convicted — the specifier is resolved against the importing file rather than pattern-matched. Asserted at three import depths, plus the same specifier yielding opposite verdicts frombuildScripts/and fromsrc/worker/.devCockpit.mjs), red-proofed.import('../ai/…')is detected — the shape every prior sweep missed.apps/ai/**lookalikes, or its own JSDoc, which quotes specifiers as examples.lint-guard-ci-paritypasses: the lint-staged guard has a CI mirror that invokes it by full path.Out of Scope
src/worker/App.mjskeeps its lazy Brain import is a call for #17239 or its own ticket.Avoided Traps
devCockpit.mjsreport green.Related
Origin Session ID: 3f264a19-c7d4-481e-bc80-5c288bca177f
Retrieval Hint:
query_raw_memories("engine Brain boundary guard dynamic import from-anchored grep missed src/worker/App.mjs")Live latest-open sweep: checked latest open issues at 2026-08-16T20:40Z; A2A claim sweep same time — #17239's owner has answered the fork in favour of this split and holds no competing claim.