Context
⚠ PREMISE CORRECTION — 2026-08-08, by the author (@neo-opus-ada)
PR #16641 was CLOSED, never merged (merged=NEVER, closed 2026-08-08T03:14:09Z). The struck sentence below is retained because it is the intuitive reading and the next reader will reach for it too.
Neither the chromadb work nor its witness is on dev. bodyTierBarrelRuntime.spec.mjs was added in b1ff51434a on branch ada/16488-defer-chromadb-import; git merge-base --is-ancestor b1ff51434a origin/dev returns false and the path is absent at origin/dev. Any AC here that assumes that witness exists must build it, not extend it.
How this survived a correction sweep, recorded because it is the reusable part: I broadcast a [premise correction] at 16:05Z today and reported #16649 as corrected. It was not. What I had actually corrected was the description of this ticket's error inside #16710 — never this body. A recalled list of "tickets I fixed" is not a sweep; only a mechanical query over every artifact citing the dead ref is. Found on the re-sweep from #16644. Sibling instances: #16644, and #16710's Related line, both corrected the same hour.
#16488 / PR #16641 made chromadb genuinely demand-driven so ai/services.mjs could be imported in the Body install tier. That work landed a runtime witness — test/playwright/unit/ai/services/bodyTierBarrelRuntime.spec.mjs — which spawns a process with Brain-only packages denied by a resolve hook. [CORRECTED — see above. #16641 closed unmerged; the witness does not exist at origin/dev.]
That witness records a KNOWN STATE it deliberately did not fix: with the full Brain-only population denied, the barrel still fails, on better-sqlite3. #16488 bounded its claim to chromadb rather than overclaiming Body-tier importability. This ticket owns the remaining package.
The Problem
ai/graph/storage/SQLite.mjs:37-49 resolves better-sqlite3 inside initAsync():
async initAsync() {
await super.initAsync();
...
const Database = (await import('better-sqlite3')).default;A dynamic import inside initAsync() is not demand-lazy for an eager singleton. Neo.setupClass() instantiates the singleton at module load and core.Base schedules initAsync() on the very next microtask, so the import runs on barrel import. In the Body tier the rejection is unhandled and terminates the process. The comment above the import states the deferral intent, and the intent is not achieved — the failure changed phase, not ownership.
This is the identical defect @neo-gpt falsified on PR #16641 cycle 1, in a different module. It is worth stating plainly because the wrong version is the intuitive one: moving a require/static import into initAsync() looks like deferral and is not.
Measured, not inferred
Probe: resolve hook denying only better-sqlite3, import target, wait through singleton init.
| target |
result |
ai/services.mjs |
resolves better-sqlite3 |
ai/graph/storage/SQLite.mjs (alone) |
resolves better-sqlite3 |
ai/services/memory-core/SourceRegistryService.mjs |
resolves |
ai/services/memory-core/MemoryCoreRecorderService.mjs |
resolves |
ai/services/knowledge-base/KBRecorderService.mjs |
resolves |
ai/services/neural-link/RecorderService.mjs |
resolves |
ai/services/memory-core/CommunityBatchAdmissionService.mjs |
resolves |
Boundary of this measurement, stated because it changes the fix. Each of those five services also contains its own await import('better-sqlite3'), but most sit inside a try/catch that logs a warning and degrades (e.g. SourceRegistryService.mjs:117, whose catch emits "Failed to initialize SQLite connection"). A caught denial is not a fatal one. I have not isolated which reach is fatal versus which are absorbed — the shared ai/graph/storage/SQLite.mjs resolving on its own import makes it the prime candidate, and the per-service imports may be entirely survivable. Whoever takes this should measure that before deciding the blast radius, rather than assuming all seven sites need changing.
There is no static better-sqlite3 import anywhere under ai/services/ — every site is already await import(...). This is exclusively a when-does-it-run defect, not an import-shape one.
The Fix
The shape is established by #16488 and should be reused rather than re-invented: resolution moves to first actual use, memoized, behind an ensure…Ready()-style entry point that initAsync() never calls.
The relevant precedent, including the traps, is in ai/services/knowledge-base/ChromaManager.mjs and ai/services/memory-core/managers/ChromaManager.mjs:
initAsync() must touch no Brain-only package at all.
- The ensure must return early if the handle already exists, or documented mock seams break.
- Readiness that no longer implies a handle must be swept for every reader — production and test. On
#16488 two escaped a census that only enumerated production readers.
Contract Ledger
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Error Semantics |
Evidence |
ai/graph/storage/SQLite.mjs initAsync() |
this ticket |
Touches no Brain-only package |
unchanged lifecycle otherwise |
barrel survives full denial |
better-sqlite3 resolution |
this ticket |
Resolved on first actual DB use, memoized |
Absent package fails at that call, named |
runtime denial witness |
ai/services.mjs export surface |
existing |
unchanged |
n/a |
no export diff |
bodyTierBarrelRuntime.spec.mjs KNOWN STATE test |
#16488 |
Flips to asserting survival |
n/a |
the test is the AC |
Acceptance Criteria
Out of Scope
chromadb. Handled by #16488 / PR #16641.
#16523 (36 modules opening their own sqlite handle). Adjacent and genuinely related, but that is about handle ownership; this is about when the package resolves. Fixing this one does not close that one, and a shared-primitive refactor there could make this trivial — worth checking before starting.
- Splitting
ai/services.mjs. The barrel stays the single SDK entry point; its export surface does not change.
Avoided Traps
- Assuming
initAsync() is deferral. It is not, for an eager singleton. This is the whole defect.
- Trusting a static import walk. It reports clean here and the runtime property is false — that exact combination is why
#16488's original guard passed against broken code.
- Deleting the KNOWN STATE test when it turns green. It must flip to asserting survival; it is the only witness for this property.
- Assuming all seven reach sites are fatal. Several are inside
try/catch blocks that degrade. Measure before scoping.
- Full-install CI as evidence. It cannot reproduce this — CI has the Brain tier installed.
Related
#16488 / PR #16641 — the chromadb half, and the source of the witness and the fix pattern · #16523 — sqlite handle ownership, adjacent · #16364 / #16389 — the two-tier install split
Filed by Ada (@neo-opus-ada), unassigned — the measurement above is the hard part and it is already done.
Retrieval Hint: query_raw_memories("better-sqlite3 eager singleton boot barrel body tier initAsync not demand lazy")
Context
[CORRECTED — see above.#16488/ PR#16641madechromadbgenuinely demand-driven soai/services.mjscould be imported in the Body install tier. That work landed a runtime witness —test/playwright/unit/ai/services/bodyTierBarrelRuntime.spec.mjs— which spawns a process with Brain-only packages denied by a resolve hook.#16641closed unmerged; the witness does not exist atorigin/dev.]That witness records a KNOWN STATE it deliberately did not fix: with the full Brain-only population denied, the barrel still fails, on
better-sqlite3.#16488bounded its claim tochromadbrather than overclaiming Body-tier importability. This ticket owns the remaining package.The Problem
ai/graph/storage/SQLite.mjs:37-49resolvesbetter-sqlite3insideinitAsync():async initAsync() { await super.initAsync(); ... // Use dynamic imports to prevent native Node module evaluation crashes inside browser/test runtimes const Database = (await import('better-sqlite3')).default;A dynamic import inside
initAsync()is not demand-lazy for an eager singleton.Neo.setupClass()instantiates the singleton at module load andcore.BaseschedulesinitAsync()on the very next microtask, so the import runs on barrel import. In the Body tier the rejection is unhandled and terminates the process. The comment above the import states the deferral intent, and the intent is not achieved — the failure changed phase, not ownership.This is the identical defect
@neo-gptfalsified on PR#16641cycle 1, in a different module. It is worth stating plainly because the wrong version is the intuitive one: moving arequire/static import intoinitAsync()looks like deferral and is not.Measured, not inferred
Probe: resolve hook denying only
better-sqlite3, import target, wait through singleton init.ai/services.mjsbetter-sqlite3ai/graph/storage/SQLite.mjs(alone)better-sqlite3ai/services/memory-core/SourceRegistryService.mjsai/services/memory-core/MemoryCoreRecorderService.mjsai/services/knowledge-base/KBRecorderService.mjsai/services/neural-link/RecorderService.mjsai/services/memory-core/CommunityBatchAdmissionService.mjsBoundary of this measurement, stated because it changes the fix. Each of those five services also contains its own
await import('better-sqlite3'), but most sit inside atry/catchthat logs a warning and degrades (e.g.SourceRegistryService.mjs:117, whose catch emits "Failed to initialize SQLite connection"). A caught denial is not a fatal one. I have not isolated which reach is fatal versus which are absorbed — the sharedai/graph/storage/SQLite.mjsresolving on its own import makes it the prime candidate, and the per-service imports may be entirely survivable. Whoever takes this should measure that before deciding the blast radius, rather than assuming all seven sites need changing.There is no static
better-sqlite3import anywhere underai/services/— every site is alreadyawait import(...). This is exclusively a when-does-it-run defect, not an import-shape one.The Fix
The shape is established by
#16488and should be reused rather than re-invented: resolution moves to first actual use, memoized, behind anensure…Ready()-style entry point thatinitAsync()never calls.The relevant precedent, including the traps, is in
ai/services/knowledge-base/ChromaManager.mjsandai/services/memory-core/managers/ChromaManager.mjs:initAsync()must touch no Brain-only package at all.#16488two escaped a census that only enumerated production readers.Contract Ledger
ai/graph/storage/SQLite.mjsinitAsync()better-sqlite3resolutionai/services.mjsexport surfacebodyTierBarrelRuntime.spec.mjsKNOWN STATE test#16488Acceptance Criteria
ai/services.mjsand waiting through singleton init survives — the class assertion, not a per-module one.bodyTierBarrelRuntime.spec.mjsis rewritten to assert survival — not deleted. Its comment already prescribes this. Deleting it would remove the only guard for the property it was written to bound.@neo-gptraised this as non-blocking on PR#16641after approving it:chromaEnsureConcurrency.spec.mjsasserts the held public collection entry is not the escape-rejection, where it should assert the promise is exactly pending before release and resolved after. It was deliberately not pushed there — a new commit would have moved the head off a cross-family approval for a non-blocking item. It lands here because this ticket already edits the sibling spec in the same directory, so it costs one file-touch instead of a review cycle.Out of Scope
chromadb. Handled by#16488/ PR#16641.#16523(36 modules opening their own sqlite handle). Adjacent and genuinely related, but that is about handle ownership; this is about when the package resolves. Fixing this one does not close that one, and a shared-primitive refactor there could make this trivial — worth checking before starting.ai/services.mjs. The barrel stays the single SDK entry point; its export surface does not change.Avoided Traps
initAsync()is deferral. It is not, for an eager singleton. This is the whole defect.#16488's original guard passed against broken code.try/catchblocks that degrade. Measure before scoping.Related
#16488/ PR#16641— thechromadbhalf, and the source of the witness and the fix pattern ·#16523— sqlite handle ownership, adjacent ·#16364/#16389— the two-tier install splitFiled by Ada (
@neo-opus-ada), unassigned — the measurement above is the hard part and it is already done.Retrieval Hint:
query_raw_memories("better-sqlite3 eager singleton boot barrel body tier initAsync not demand lazy")