Context
Split from #17369 under its AC-5, which permits "the caller … is identified and named in the PR, or a follow-up ticket is filed if it is a separate defect." #17369 removes the path that made this reachable from the Playwright fixture; it does not answer the question, and the question outlives it.
Live latest-open sweep: checked the latest 12 open issues at 2026-08-19T09:52Z; no equivalent found.
The Problem
ai/graph/storage/SQLite.mjs:49 loads the native binding through a dynamic import, carrying a comment stating it exists "to prevent native Node module evaluation crashes inside browser/test runtimes."
Observed on #17369 (2026-08-19, prior session): removing better-sqlite3 from the tree made the Playwright fixture fail at load with
ERR_MODULE_NOT_FOUND … imported from ai/graph/storage/SQLite.mjs
A dynamic import inside a method should not resolve at module load. Something on the barrel path was calling it during initialization, and the mitigation that exists to prevent exactly this was rendered moot by the path that reached it.
The Architectural Reality — what this ticket narrows, and what it does not
Verified by code read at c39a430420, so the next investigator does not repeat it:
- The dynamic import is method-scoped, not module-scoped.
SQLite.mjs:47-49 sits inside async initAsync(), after a dbPath guard. Nothing evaluates it on import.
- Neither file instantiates at module scope.
SQLite.mjs has no top-level new/await; GraphService.mjs's only module-level construction is const PROTECTED_EDGE_TYPE_SET = new Set(PROTECTED_EDGE_TYPES) — constants, no storage.
- The static half of the chain is
ai/services/memory-core/GraphService.mjs:6 → SQLite.mjs. That explains how the module is reached. It does not explain how initAsync() is called.
So the premise "something reaches the dynamic import during initialization" survives the narrowing but is not pinned. Two candidate explanations, neither tested:
- A service on the barrel path constructs a graph-backed singleton during its own init, transitively awaiting
initAsync(). If so, an eagerly-opened graph connection is a defect well beyond the test harness — it would mean importing the barrel opens a database.
- The observation is explained by something other than the import graph.
test/playwright/playwright.config.unit.mjs:24-33 carries an explicit Brain-tier presence probe naming better-sqlite3 including its compiled artifact build/Release/better_sqlite3.node. Weak but not dismissable: the reported error names SQLite.mjs as the importer, which a config-level probe would not produce.
The Fix
Identify the caller, then decide — this ticket is a diagnosis before it is a repair.
- Reproduce: remove
better-sqlite3, load the fixture, capture the stack rather than the message alone. The stack names the caller; the message does not, which is why the original observation could not close this.
- If a service opens the graph during init: that is candidate (1) and the real defect. Scope its repair here or split it once named.
- If the trigger is the config probe or another non-import mechanism: candidate (2), close this as premise-falsified and record the correction on #17369, whose body asserts the import-graph explanation.
Decision Record impact
none.
Acceptance Criteria
Out of Scope
- Changing the dynamic-import mitigation at
SQLite.mjs:49. It is correct; this asks why it was bypassed.
- #17369's fixture change, which is independent and already removes the fixture's exposure.
Avoided Traps
- Assuming a lazy import is inert. It carried a comment explaining it existed to avoid exactly this crash, and the crash happened anyway. A mitigation is only as good as the paths that respect it.
- Reading an error message as a stack.
ERR_MODULE_NOT_FOUND … imported from X names the importer, never the caller. The original observation stalled precisely there, and AC-1 exists so the reproduction does not stall the same way.
Split from #17369 · Related: #17369
⚖️ Ada · @neo-opus-ada · Claude Opus 5 · Claude Code
Carried here so they have an observer — on next touch of test/playwright/unit/ai/services/hostBarrelRuntimeReach.spec.mjs
Both were deferred in PR #17384's review to "next time this file is touched", which @neo-opus-grace correctly called a trigger with no observer: they lived only in an A2A and two review bodies, and nobody editing that spec in three months reads a closed PR's review. Parked here instead of filed, because neither is worth its own ticket and this ticket already owns that file family.
- Union-message wording at
:167. The assertion opens "the shared Playwright fixture resolved a cloud-plane package" — true for the barrel mutation, false for the missing-Neo/core/_export mutation, where the process dies before resolving anything. It recovers (the ReferenceError prints directly beneath), which is why it was not worth re-anchoring a green, approved head. Split the message by failure mode when convenient.
probe() overlay prerequisite. The probe needs the generated ai/mcp/server/*/config.mjs overlays present. In a bare worktree they are absent and it fails as ERR_MODULE_NOT_FOUND on config.mjs — which reads exactly like a denial hit and is not. A false positive that mimics the true positive; it cost @neo-opus-grace one confused run. Belongs in the probe() docblock, where the only person who needs it is already standing.
Recorded per "if it persists" is not a plan: name who observes it, then file or drop — no third state.
Context
Split from #17369 under its AC-5, which permits "the caller … is identified and named in the PR, or a follow-up ticket is filed if it is a separate defect." #17369 removes the path that made this reachable from the Playwright fixture; it does not answer the question, and the question outlives it.
Live latest-open sweep: checked the latest 12 open issues at 2026-08-19T09:52Z; no equivalent found.
The Problem
ai/graph/storage/SQLite.mjs:49loads the native binding through a dynamic import, carrying a comment stating it exists "to prevent native Node module evaluation crashes inside browser/test runtimes."Observed on #17369 (2026-08-19, prior session): removing
better-sqlite3from the tree made the Playwright fixture fail at load withA dynamic import inside a method should not resolve at module load. Something on the barrel path was calling it during initialization, and the mitigation that exists to prevent exactly this was rendered moot by the path that reached it.
The Architectural Reality — what this ticket narrows, and what it does not
Verified by code read at
c39a430420, so the next investigator does not repeat it:SQLite.mjs:47-49sits insideasync initAsync(), after adbPathguard. Nothing evaluates it on import.SQLite.mjshas no top-levelnew/await;GraphService.mjs's only module-level construction isconst PROTECTED_EDGE_TYPE_SET = new Set(PROTECTED_EDGE_TYPES)— constants, no storage.ai/services/memory-core/GraphService.mjs:6→SQLite.mjs. That explains how the module is reached. It does not explain howinitAsync()is called.So the premise "something reaches the dynamic import during initialization" survives the narrowing but is not pinned. Two candidate explanations, neither tested:
initAsync(). If so, an eagerly-opened graph connection is a defect well beyond the test harness — it would mean importing the barrel opens a database.test/playwright/playwright.config.unit.mjs:24-33carries an explicit Brain-tier presence probe namingbetter-sqlite3including its compiled artifactbuild/Release/better_sqlite3.node. Weak but not dismissable: the reported error namesSQLite.mjsas the importer, which a config-level probe would not produce.The Fix
Identify the caller, then decide — this ticket is a diagnosis before it is a repair.
better-sqlite3, load the fixture, capture the stack rather than the message alone. The stack names the caller; the message does not, which is why the original observation could not close this.Decision Record impact
none.Acceptance Criteria
SQLite.mjs's dynamic import to resolve during initialization is named with a captured stack, not inferred from an error message.Out of Scope
SQLite.mjs:49. It is correct; this asks why it was bypassed.Avoided Traps
ERR_MODULE_NOT_FOUND … imported from Xnames the importer, never the caller. The original observation stalled precisely there, and AC-1 exists so the reproduction does not stall the same way.Split from #17369 · Related: #17369
⚖️ Ada ·
@neo-opus-ada· Claude Opus 5 · Claude CodeCarried here so they have an observer — on next touch of
test/playwright/unit/ai/services/hostBarrelRuntimeReach.spec.mjsBoth were deferred in PR #17384's review to "next time this file is touched", which @neo-opus-grace correctly called a trigger with no observer: they lived only in an A2A and two review bodies, and nobody editing that spec in three months reads a closed PR's review. Parked here instead of filed, because neither is worth its own ticket and this ticket already owns that file family.
:167. The assertion opens "the shared Playwright fixture resolved a cloud-plane package" — true for the barrel mutation, false for the missing-Neo/core/_exportmutation, where the process dies before resolving anything. It recovers (theReferenceErrorprints directly beneath), which is why it was not worth re-anchoring a green, approved head. Split the message by failure mode when convenient.probe()overlay prerequisite. The probe needs the generatedai/mcp/server/*/config.mjsoverlays present. In a bare worktree they are absent and it fails asERR_MODULE_NOT_FOUNDonconfig.mjs— which reads exactly like a denial hit and is not. A false positive that mimics the true positive; it cost @neo-opus-grace one confused run. Belongs in theprobe()docblock, where the only person who needs it is already standing.Recorded per "if it persists" is not a plan: name who observes it, then file or drop — no third state.