Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/runtime-expected-read-refusal-noise.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
---
"@objectstack/runtime": patch
---

**Tests (log hygiene):** the sixteen remaining passing `@objectstack/runtime`
fixtures that printed expected `refused a read on` failures into the shared
shard log now **withhold and assert** that noise instead of emitting it
(#10629). No runtime behaviour changes and no test was skipped, loosened or
removed — the same 78 tests pass, and 268 lines of expected-failure output
(134 `[sql-driver] DATABASE_ERROR — the backend refused a read on '<table>'`
envelopes plus their 134 matching `ERROR Find operation failed` engine frames)
leave the `Test Core` log.

Why this is worth a release note at all: turbo interleaves package logs without
attribution, so an ERROR-shaped line from a **green** test is indistinguishable
from a real failure in a shard log. Lines of exactly this shape were once
lifted verbatim into a p1 flake signature (#10293) and sent a whole dispatch
cycle at the wrong mechanism. Expected-failure noise from a passing test is a
diagnosis tax on every future red shard.

Each fixture provokes a **fail-soft probe** — a read the runtime issues to find
out whether something is installed, and whose missing-table answer it swallows
by design: `resolveUserAuthzGrants`' six `sys_*` `tryFind`s,
`ObjectQL.probeInstallOrganizations`, `SeedLoaderService.resolveSoleOrganizationId`,
the lifecycle governance snapshot, `runBuildProbes`' view read, and the boot
metadata load. Every one of them was judged expected rather than diagnostic;
none was silenced on the strength of "it looks like noise".

⛔ This is not a mute. PR #10630 ruled the shape for this class on two files —
withhold only a line that names an expected table **and** carries that same
table's `no such table` reason, count what was withheld, and assert the counts —
and this applies that shape verbatim through one shared, test-only module,
`packages/runtime/src/expected-read-refusal-noise.ts`. A fixture that stopped
provoking its probe, or whose table started resolving, now goes **red** instead
of merely going quiet; the engine frame is withheld only when it sits directly
above a driver refusal the capture already recognised, so an identically-shaped
fault from any other cause still reaches the log with both halves intact.
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,6 +45,10 @@ import { join } from 'node:path';
import { ObjectQL } from '@objectstack/objectql';
import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol';
import { SqlDriver } from '@objectstack/driver-sql';
import {
captureExpectedReadRefusals,
type ExpectedReadRefusalCapture,
} from './expected-read-refusal-noise.js';
import { validationFailureDetails, resolveThrownHttpError } from '@objectstack/types';

const PARENT = { name: 'bd_parent', fields: { name: { type: 'text' } } };
Expand All@@ -63,14 +67,33 @@ const NOTE = {
},
};

/**
* [#10629] This fixture provisions its own business objects and nothing else,
* so the engine's single-tenant probe (`ObjectQL.probeInstallOrganizations`,
* memoised once per engine) reads a `sys_organization` that was never created.
* The probe is fail-soft by construction — it catches `isMissingTableError` and
* only that — but the driver and the engine each log the fault on the way out.
* Withheld and asserted rather than muted; `expected-read-refusal-noise.ts`
* says why.
*/
const ABSENT_TENANCY_TABLE = 'sys_organization';

describe('[#8502] a REAL driver fault is withheld from every batch row', () => {
/** [#10629] The expected-noise capture belonging to the latest rig. */
let noise: ExpectedReadRefusalCapture | null = null;
let dir: string | null = null;
let engine: ObjectQL | null = null;

afterEach(async () => {
try { await engine?.destroy(); } catch { /* noop */ }
engine = null;
if (dir) { rmSync(dir, { recursive: true, force: true }); dir = null; }
// [#10629] The capture is a PIN, not a mute — asserted after teardown so
// a failure here can never leave the engine running. Every test in this
// file rigs and writes, so the probe fires for each of them: this holds
// for a single `-t` run as well as for the whole file.
expect(noise?.silentChannels() ?? ['no capture was installed']).toEqual([]);
noise = null;
});

async function rig() {
Expand All@@ -80,6 +103,11 @@ describe('[#8502] a REAL driver fault is withheld from every batch row', () => {
connection: { filename: join(dir, 'data.sqlite') },
useNullAsDefault: true,
});
// [#10629] Installed on the REAL driver (the one that logs) before it
// runs a statement — the `Object.create(real)` wrapper below resolves
// `logger` through the prototype chain to this sink.
noise = captureExpectedReadRefusals([ABSENT_TENANCY_TABLE]);
noise.captureDriver(real);
await real.initObjects([PARENT, CHILD, NOTE]);

// Capture the RAW driver error at the seam and let it propagate
Expand All@@ -94,6 +122,7 @@ describe('[#8502] a REAL driver fault is withheld from every batch row', () => {
}

engine = new ObjectQL();
noise.captureEngine(engine);
engine.registerDriver(driver, true);
await engine.init();
for (const o of [PARENT, CHILD, NOTE]) {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,6 +42,10 @@ import { join } from 'node:path';
import { ObjectQL } from '@objectstack/objectql';
import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol';
import { SqlDriver } from '@objectstack/driver-sql';
import {
captureExpectedReadRefusals,
type ExpectedReadRefusalCapture,
} from './expected-read-refusal-noise.js';
import { resolveThrownHttpError, validationFailureDetails } from '@objectstack/types';

/** `maxLength` is what the real record validator rejects against. */
Expand All@@ -61,7 +65,29 @@ const CHILD = {
},
};

/**
* [#10629] This fixture provisions its three business objects and nothing else,
* so the engine's single-tenant probe (`ObjectQL.probeInstallOrganizations`,
* memoised once per engine) reads a `sys_organization` that was never created.
* The probe is fail-soft by construction — it catches `isMissingTableError` and
* only that — but the driver and the engine each log the fault on the way out.
*
* ⛔ Asserted in the three tests that WRITE rather than in `afterEach`: the
* probe runs on the system-write org resolution, so the not-found test (which
* only deletes a ghost id) never reaches it. An `afterEach` assertion would
* make that test red for a reason that has nothing to do with it.
* `expected-read-refusal-noise.ts` says why this withholds instead of muting.
*/
const ABSENT_TENANCY_TABLE = 'sys_organization';

/** [#10629] The capture is a PIN, not a mute — this is the assertion half. */
const expectExpectedNoiseWithheld = (noise: ExpectedReadRefusalCapture | null): void => {
expect(noise?.silentChannels() ?? ['no capture was installed']).toEqual([]);
};

describe('[#8570] a batch row carries the status its producer DECLARED — real driver', () => {
/** [#10629] The expected-noise capture belonging to the latest rig. */
let noise: ExpectedReadRefusalCapture | null = null;
let dir: string | null = null;
let engine: ObjectQL | null = null;

Expand All@@ -78,6 +104,11 @@ describe('[#8570] a batch row carries the status its producer DECLARED — real
connection: { filename: join(dir, 'data.sqlite') },
useNullAsDefault: true,
});
// [#10629] Installed on the REAL driver (the one that logs) before it
// runs a statement — the `Object.create(real)` wrapper below resolves
// `logger` through the prototype chain to this sink.
noise = captureExpectedReadRefusals([ABSENT_TENANCY_TABLE]);
noise.captureDriver(real);
await real.initObjects([TASK, PARENT, CHILD]);

// Capture the RAW error at the seam and let it propagate untouched, so
Expand All@@ -92,6 +123,7 @@ describe('[#8570] a batch row carries the status its producer DECLARED — real
}

engine = new ObjectQL();
noise.captureEngine(engine);
engine.registerDriver(driver, true);
await engine.init();
for (const o of [TASK, PARENT, CHILD]) {
Expand DownExpand Up@@ -141,6 +173,8 @@ describe('[#8570] a batch row carries the status its producer DECLARED — real

// …and the record is unchanged, so the refusal was a refusal.
expect((await engine!.findOne('hs_task', { where: { id: 'ok1' } }))?.name).toBe('ok');

expectExpectedNoiseWithheld(noise);
});

it('the same row in a MIXED batch — the asymmetry the card measured is gone', async () => {
Expand All@@ -164,6 +198,8 @@ describe('[#8570] a batch row carries the status its producer DECLARED — real
});
expect(res.results[1].errors[0].code).toBe('VALIDATION_FAILED');
expect(res.results[1].errors[0].httpStatus).toBe(400);

expectExpectedNoiseWithheld(noise);
});

it('a REAL driver fault still carries NO status — the over-broad direction', async () => {
Expand DownExpand Up@@ -195,6 +231,8 @@ describe('[#8570] a batch row carries the status its producer DECLARED — real
// causal row's text onto its siblings, so a row-only scan can miss a
// live path.
expect(JSON.stringify(res)).not.toContain('httpStatus');

expectExpectedNoiseWithheld(noise);
});

it('a not-found row still answers 404 — the population that already worked', async () => {
Expand Down
30 changes: 30 additions & 0 deletions packages/runtime/src/bulk-write-real-driver.integration.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,6 +16,10 @@ import { join } from 'node:path';
import { ObjectQL } from '@objectstack/objectql';
import { SeedLoaderService } from '@objectstack/metadata-protocol';
import { SqlDriver } from '@objectstack/driver-sql';
import {
captureExpectedReadRefusals,
type ExpectedReadRefusalCapture,
} from './expected-read-refusal-noise.js';

/**
* Wrap a real driver so specific methods can fail the way turso does. Hooks
Expand DownExpand Up@@ -93,22 +97,48 @@ function metadataFor(objects: any[]) {
} as any;
}

/**
* [#10629] This fixture provisions its own business objects and nothing else,
* so the engine's single-tenant probe (`ObjectQL.probeInstallOrganizations`,
* memoised once per engine) reads a `sys_organization` that was never created.
* The probe is fail-soft by construction — it catches `isMissingTableError` and
* only that — but the driver and the engine each log the fault on the way out.
* Withheld and asserted rather than muted; `expected-read-refusal-noise.ts`
* says why.
*/
const ABSENT_TENANCY_TABLE = 'sys_organization';

describe('bulk-write hardening on a REAL SqlDriver (framework#3147–#3152, #3172, #3173)', () => {
let dir: string | null = null;
let engine: ObjectQL | null = null;
/** [#10629] The expected-noise capture belonging to the latest boot. */
let noise: ExpectedReadRefusalCapture | null = null;

afterEach(async () => {
try { await engine?.destroy(); } catch { /* noop */ }
engine = null;
if (dir) { rmSync(dir, { recursive: true, force: true }); dir = null; }
// [#10629] The capture is a PIN, not a mute — asserted after teardown so a
// failure here can never leave the engine running. Every test in this file
// boots and writes, so the probe fires for each of them: this holds for a
// single `-t` run as well as for the whole file.
expect(noise?.silentChannels() ?? ['no capture was installed']).toEqual([]);
noise = null;
});

async function boot(objects: any[], plan: FaultPlan = {}) {
dir = mkdtempSync(join(tmpdir(), 'os-bulk-real-'));
const real = new SqlDriver({ client: 'better-sqlite3', connection: { filename: join(dir, 'data.sqlite') }, useNullAsDefault: true });
// [#10629] Installed on the REAL driver (the one that logs) before it runs
// a statement, and on the engine before it issues a read — the two sinks the
// expected refusal travels out on. `wrapDriver` prototype-delegates, so the
// wrapper resolves `logger` through the chain to this sink.
noise = captureExpectedReadRefusals([ABSENT_TENANCY_TABLE]);
noise.captureDriver(real);
await real.initObjects(objects); // create real tables + sequences
const driver = wrapDriver(real, plan);
engine = new ObjectQL();
noise.captureEngine(engine);
engine.registerDriver(driver, true);
await engine.init();
// Fast, deterministic summary-retry backoff (framework#3147).
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,6 +37,10 @@ import { join } from 'node:path';
import { ObjectQL } from '@objectstack/objectql';
import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol';
import { SqlDriver } from '@objectstack/driver-sql';
import {
captureExpectedReadRefusals,
type ExpectedReadRefusalCapture,
} from './expected-read-refusal-noise.js';

const ACCOUNT = { name: 'zz_account', fields: { name: { type: 'text' } } };

Expand DownExpand Up@@ -84,14 +88,33 @@ const SINGLE = {

const OWNER_PACKAGE = 'com.objectstack.test.9362';

/**
* [#10629] This fixture provisions its own business objects and nothing else,
* so the engine's single-tenant probe (`ObjectQL.probeInstallOrganizations`,
* memoised once per engine) reads a `sys_organization` that was never created.
* The probe is fail-soft by construction — it catches `isMissingTableError` and
* only that — but the driver and the engine each log the fault on the way out.
* Withheld and asserted rather than muted; `expected-read-refusal-noise.ts`
* says why.
*/
const ABSENT_TENANCY_TABLE = 'sys_organization';

describe('[#9362] REST DELETE on an object targeted by a multiple:true lookup — real driver', () => {
let dir: string | null = null;
let engine: ObjectQL | null = null;
/** [#10629] The expected-noise capture belonging to the latest rig. */
let noise: ExpectedReadRefusalCapture | null = null;

afterEach(async () => {
try { await engine?.destroy(); } catch { /* noop */ }
engine = null;
if (dir) { rmSync(dir, { recursive: true, force: true }); dir = null; }
// [#10629] The capture is a PIN, not a mute — asserted after teardown so
// a failure here can never leave the engine running. Every test in this
// file rigs and writes, so the probe fires for each of them: this holds
// for a single `-t` run as well as for the whole file.
expect(noise?.silentChannels() ?? ['no capture was installed']).toEqual([]);
noise = null;
});

async function rig(objects: unknown[]) {
Expand All@@ -101,8 +124,13 @@ describe('[#9362] REST DELETE on an object targeted by a multiple:true lookup
connection: { filename: join(dir, 'data.sqlite') },
useNullAsDefault: true,
});
// [#10629] Installed before the driver runs a statement and before the
// engine issues a read — the two sinks the expected refusal travels out on.
noise = captureExpectedReadRefusals([ABSENT_TENANCY_TABLE]);
noise.captureDriver(real);
await real.initObjects(objects as any);
engine = new ObjectQL();
noise.captureEngine(engine);
engine.registerDriver(real as any, true);
await engine.init();
for (const o of objects) engine.registry.registerObject(o as any, OWNER_PACKAGE);
Expand Down
22 changes: 22 additions & 0 deletions packages/runtime/src/default-datasource-plugin.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ import type { IDataEngine } from '@objectstack/spec/contracts';
import { Runtime } from './runtime.js';
import { DefaultDatasourcePlugin } from './default-datasource-plugin.js';
import { AppPlugin } from './app-plugin.js';
import { captureExpectedReadRefusals } from './expected-read-refusal-noise.js';

// [#10126] Pay the first transform of these dist-resolved workspace deps at MODULE
// LOAD. Each is reached below through a dynamic `import()` inside an `it()` body or a
Expand All@@ -24,6 +25,17 @@ import '@objectstack/service-datasource';
const BOOT_TIMEOUT = 60_000;
const ENV = 'OS_ALLOW_DRIVER_CONNECT_FAILURE';

/**
* [#10629] Exactly one case in this file writes through the booted engine, and
* that write runs the engine's single-tenant probe
* (`ObjectQL.probeInstallOrganizations`) against a `sys_organization` this
* composition never creates. The probe is fail-soft by construction — it
* catches `isMissingTableError` and only that — but the driver and the engine
* each log the fault on the way out. Withheld and asserted in that one case
* rather than muted; `expected-read-refusal-noise.ts` says why.
*/
const ABSENT_TENANCY_TABLE = 'sys_organization';

async function assemble(opts: {
driver?: string;
withAdminPlugin?: boolean;
Expand DownExpand Up@@ -158,6 +170,11 @@ describe('DefaultDatasourcePlugin — the default datasource as a declaration (#
const { createPrebuiltDriverFactory } = await import('@objectstack/service-datasource');
const { SqliteWasmDriver } = await import('@objectstack/driver-sqlite-wasm');
const hostBuilt = new SqliteWasmDriver({ filename: ':memory:' });
// [#10629] Installed before the driver runs a statement; the engine half
// is scoped after bootstrap, because the read it covers is the `insert`
// below rather than anything the boot itself does.
const noise = captureExpectedReadRefusals([ABSENT_TENANCY_TABLE]);
noise.captureDriver(hostBuilt);
const kernel = await assemble({
driver: 'turso', // a kind the SHARED factory does not support — proves dispatch
factory: createPrebuiltDriverFactory(hostBuilt, { driverId: 'turso' }),
Expand All@@ -168,6 +185,7 @@ describe('DefaultDatasourcePlugin — the default datasource as a declaration (#
});
try {
await kernel.bootstrap();
noise.captureEngine(kernel.getService<unknown>('objectql'));
const engine = kernel.getService<IDataEngine>('data');
const defaultName = engine.getDefaultDriverName?.();
expect(defaultName).toBeDefined();
Expand All@@ -176,6 +194,10 @@ describe('DefaultDatasourcePlugin — the default datasource as a declaration (#
await engine.insert('note', { title: 'through-the-adopted-default' });
const rows = await engine.find('note');
expect(rows.map((r: any) => r.title)).toContain('through-the-adopted-default');
// [#10629] The capture is a PIN, not a mute: the probe's two log lines
// are withheld from the shared shard log and asserted here instead, so
// a probe that stopped running goes red rather than merely quiet.
expect(noise.silentChannels()).toEqual([]);
} finally {
try { await (kernel as any)?.stop?.(); } catch { /* noop */ }
}
Expand Down
Loading
Loading