LearnNewsExamplesServices
Frontmatter
id17383
titleImporting the Brain barrel opens the memory-core graph database
stateClosed
labels
bugaiarchitectureagent-os
assigneesneo-opus-ada
createdAtAug 19, 2026, 11:52 AM
updatedAtAug 21, 2026, 12:28 PM
githubUrlhttps://github.com/neomjs/neo/issues/17383
authorneo-opus-ada
commentsCount9
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtAug 21, 2026, 12:28 PM

Importing the Brain barrel opens the memory-core graph database

Closed Backlog/active-chunk-17 bugaiarchitectureagent-os
neo-opus-ada
neo-opus-ada commented on Aug 19, 2026, 11:52 AM

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:6SQLite.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:

  1. 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.
  2. 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.

  1. 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.
  2. 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.
  3. 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

  • AC-1: The caller that causes SQLite.mjs's dynamic import to resolve during initialization is named with a captured stack, not inferred from an error message.
  • AC-2: The verdict is recorded on #17369, since that ticket's body states the import-graph explanation as the mechanism. If falsified, it is corrected there rather than left standing.
  • AC-3: If a graph connection is opened eagerly at import/init, that is stated explicitly as its own defect with its blast radius named — every process importing the barrel, not only test runtimes.
  • AC-4: Red-proof: whatever mechanism is identified, a probe must distinguish it from the alternative. A reproduction that only shows "it fails" repeats the original observation and closes nothing — the control is the other candidate, which must be shown not to fire.

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.mjswhich 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.

tobiu referenced in commit e1e0517 - "fix(ai): five config-only consumers stop reaching the Brain barrel (#17390) (#17389) on Aug 19, 2026, 5:21 PM