4141 * boot and the module-scope slot stays empty — silently. A fix proved only on
4242 * the TS-config path would be a second escape with the same blind spot, so the
4343 * artifact boot is driven through the REAL `loadArtifactBundle` here.
44+ *
45+ * ## The backend, and why it is sqlite `:memory:`
46+ *
47+ * These tests need *a* store — nothing here is about any one driver's behaviour.
48+ * #5704 migrated this project's test backends to sqlite `:memory:` and ruled
49+ * that only the two files in `scripts/driver-memory-census.ledger.json` keep
50+ * `@objectstack/driver-memory`, each being one arm of a cross-family pin that
51+ * genuinely cannot run on SQL. This file is neither, so it boots on the migrated
52+ * backend. ⛔ Do not "simplify" it back onto the in-memory driver: that is an
53+ * unledgered arrival and `pnpm check:driver-memory-census` refuses it (#6664).
54+ * The card's own reproduction used the in-memory driver because that is what the
55+ * reporter had in hand — a manual probe, never a constraint on this rig.
4456 */
4557
4658import { describe , it , expect , vi , afterEach } from 'vitest' ;
@@ -51,11 +63,15 @@ import type { PluginContext } from '@objectstack/core';
5163import type { JobHandler } from '@objectstack/spec/contracts' ;
5264import { defineJob } from '@objectstack/spec/system' ;
5365import { ObjectQL } from '@objectstack/objectql' ;
54- import { InMemoryDriver } from '@objectstack/driver-memory ' ;
66+ import { SqlDriver } from '@objectstack/driver-sql ' ;
5567import { CronJobAdapter } from '@objectstack/service-job' ;
5668import { AppPlugin } from './app-plugin.js' ;
5769import { loadArtifactBundle } from './load-artifact-bundle.js' ;
5870import type { JobHandlerContext } from './job-handler-context.js' ;
71+ import {
72+ captureExpectedReadRefusals ,
73+ type ExpectedReadRefusalCapture ,
74+ } from './expected-read-refusal-noise.js' ;
5975
6076/** The record a scheduled sweep is supposed to be able to write. */
6177const NOTE = {
@@ -85,30 +101,78 @@ interface Harness {
85101 warnLogs : ( ) => string [ ] ;
86102}
87103
88- const live : Array < { engine ?: ObjectQL ; adapter ?: CronJobAdapter ; dir ?: string } > = [ ] ;
104+ const live : Array < {
105+ engine ?: ObjectQL ;
106+ adapter ?: CronJobAdapter ;
107+ driver ?: SqlDriver ;
108+ dir ?: string ;
109+ noise ?: ExpectedReadRefusalCapture ;
110+ /** The channels this test's path MUST have provoked — see `harness()`. */
111+ requiredChannels ?: readonly string [ ] ;
112+ } > = [ ] ;
113+
114+ /**
115+ * [#10629] This fixture provisions `sweep_note` and nothing else, so the
116+ * engine's own single-tenant probe (`ObjectQL.probeInstallOrganizations`) reads
117+ * a `sys_organization` that was never created. The probe is fail-soft by
118+ * construction, but the driver and the engine each log the fault on the way out.
119+ * Withheld and ASSERTED rather than muted — see `expected-read-refusal-noise.ts`.
120+ */
121+ const ABSENT_TENANCY_TABLE = 'sys_organization' ;
89122
90123afterEach ( async ( ) => {
91124 for ( const entry of live . splice ( 0 ) ) {
92125 try { await entry . adapter ?. destroy ( ) ; } catch { /* noop */ }
93126 try { await entry . engine ?. destroy ( ) ; } catch { /* noop */ }
127+ try { await entry . driver ?. disconnect ( ) ; } catch { /* noop */ }
94128 if ( entry . dir ) rmSync ( entry . dir , { recursive : true , force : true } ) ;
129+ // A capture nobody asserts is a mute. The probe is memoised behind the
130+ // FIRST data operation, so only the paths that actually touch the store
131+ // provoke it — `silentChannels(required)` is the API's own answer to a
132+ // table read on some of a file's paths and not others. The withholding
133+ // is unconditional either way; only the must-have-fired set narrows.
134+ if ( entry . noise ) {
135+ expect ( entry . noise . silentChannels ( entry . requiredChannels ?? [ ABSENT_TENANCY_TABLE ] ) ) . toEqual ( [ ] ) ;
136+ }
95137 }
96138} ) ;
97139
98- /** A real engine over the in-memory driver, with `sweep_note` registered. */
99- async function bootEngine ( ) : Promise < ObjectQL > {
100- const driver = new InMemoryDriver ( ) ;
140+ /**
141+ * A real engine over the MIGRATED test backend — sqlite `:memory:` (#5704) —
142+ * with `sweep_note` provisioned and registered.
143+ */
144+ async function bootEngine ( ) : Promise < { engine : ObjectQL ; driver : SqlDriver ; noise : ExpectedReadRefusalCapture } > {
145+ const driver = new SqlDriver ( {
146+ client : 'better-sqlite3' ,
147+ connection : { filename : ':memory:' } ,
148+ useNullAsDefault : true ,
149+ } ) ;
150+ // Installed before the driver runs a statement and before the engine issues
151+ // a read — the two sinks the expected refusal travels out on.
152+ const noise = captureExpectedReadRefusals ( [ ABSENT_TENANCY_TABLE ] ) ;
153+ noise . captureDriver ( driver ) ;
154+ await driver . initObjects ( [ NOTE as never ] ) ;
101155 const engine = new ObjectQL ( ) ;
156+ noise . captureEngine ( engine ) ;
102157 engine . registerDriver ( driver as never , true ) ;
103158 await engine . init ( ) ;
104159 engine . registry . registerObject ( NOTE as never ) ;
105- return engine ;
160+ return { engine, driver , noise } ;
106161}
107162
108- async function harness ( ) : Promise < Harness > {
109- const engine = await bootEngine ( ) ;
163+ /**
164+ * @param opts.touchesStore whether this test's path performs a data operation.
165+ * `true` (the default) requires the tenancy probe to have fired and been
166+ * withheld; a context-shape test that never reads or writes passes `false`,
167+ * which keeps the withholding and drops only the must-have-fired requirement.
168+ */
169+ async function harness ( opts : { touchesStore ?: boolean } = { } ) : Promise < Harness > {
170+ const { engine, driver, noise } = await bootEngine ( ) ;
110171 const adapter = new CronJobAdapter ( ) ;
111- live . push ( { engine, adapter } ) ;
172+ live . push ( {
173+ engine, adapter, driver, noise,
174+ requiredChannels : opts . touchesStore === false ? [ ] : [ ABSENT_TENANCY_TABLE ] ,
175+ } ) ;
112176
113177 const readyHooks : Array < ( ) => Promise < void > > = [ ] ;
114178 const ctx = {
@@ -181,7 +245,8 @@ describe('#14094 — a declarative job handler has data reach (TS-config path)',
181245 } ) ;
182246
183247 it ( 'the context is the pre-#14094 set PLUS exactly `ql` and `logger`' , async ( ) => {
184- const h = await harness ( ) ;
248+ // Reads nothing and writes nothing — this one is about the shape.
249+ const h = await harness ( { touchesStore : false } ) ;
185250 const seen : Array < Record < string , unknown > > = [ ] ;
186251
187252 const plugin = new AppPlugin ( {
@@ -209,7 +274,7 @@ describe('#14094 — a declarative job handler has data reach (TS-config path)',
209274 } ) ;
210275
211276 it ( '`data` from a manual trigger still reaches the handler beside the new members' , async ( ) => {
212- const h = await harness ( ) ;
277+ const h = await harness ( { touchesStore : false } ) ;
213278 const seen : Array < Record < string , unknown > > = [ ] ;
214279 const plugin = new AppPlugin ( {
215280 id : 'com.test.job-reach' ,
@@ -318,7 +383,7 @@ export const meta = { builtAt: '2026-09-01T00:00:00.000Z' };
318383
319384describe ( '#14094 — additivity (Zone 1.1)' , ( ) => {
320385 it ( 'a handler written against the PRE-#14094 context runs unchanged, byte for byte' , async ( ) => {
321- const h = await harness ( ) ;
386+ const h = await harness ( { touchesStore : false } ) ;
322387 const calls : Array < { jobId : string ; data ?: unknown } > = [ ] ;
323388
324389 // Verbatim the shape `IJobService`'s `JobHandler` declares — the type an
0 commit comments