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
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"test:watch": "vitest"
},
"devDependencies": {
"@objectstack/metadata-core": "workspace:*",
"@types/node": "^26.1.2",
"esbuild": "^0.28.1",
"typescript": "^6.0.3",
Expand Down
35 changes: 33 additions & 2 deletions packages/core/src/utils/migration-journal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,22 @@
*/

import { describe, it, expect, vi } from 'vitest';
// [#5855] The fake engine's write verbs route through the producer's OWN
// dispatch predicates (#4550 delete / #5480 update), so this double cannot
// accept a call `ObjectQL.<verb>` refuses. Imported from
// `@objectstack/metadata-core` and not from `@objectstack/objectql`: objectql
// depends on this package, so that import would close a dependency cycle turbo
// rejects — which is why both of this file's (file, verb) pairs sat in the
// gate's DEBT ledger until #5619 sank the two predicates into a package that
// depends on neither side. `@objectstack/metadata-core` is a devDependency
// here for exactly this import, and nothing else.
import {
assertEngineDeleteDispatch,
assertEngineUpdateDispatch,
type EngineDeleteDispatchInput,
type EngineUpdateDispatchData,
type EngineUpdateDispatchInput,
} from '@objectstack/metadata-core';
import {
runMigrationJournal,
resumeMigrationJournal,
Expand Down Expand Up @@ -72,8 +88,23 @@ class FakeEngine {
return (await this.find(objectName, query))[0] ?? null;
}

async update(): Promise<unknown> { throw new Error('not used'); }
async delete(): Promise<unknown> { throw new Error('not used'); }
// Neither verb is driven by anything this suite runs — the journal writes
// rows and reads them back. They stay `not used`, but the dispatch assert
// comes FIRST so the stub can never become the lax double of #4434 the day a
// test starts writing through it: a call the real engine rejects is rejected
// here, with the producer's own message, before `not used` is ever reached.
async update(
_objectName: string,
data: EngineUpdateDispatchData,
options?: EngineUpdateDispatchInput,
): Promise<unknown> {
assertEngineUpdateDispatch(data, options);
throw new Error('not used');
}
async delete(_objectName: string, options?: EngineDeleteDispatchInput): Promise<unknown> {
assertEngineDeleteDispatch(options);
throw new Error('not used');
}
async count(): Promise<number> { return 0; }
async aggregate(): Promise<unknown[]> { return []; }
getObject(name: string): unknown { return { name }; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';
// [#5855] The fake engine's write verbs route through the producer's OWN
// dispatch predicates (#4550 delete / #5480 update), so this double cannot
// accept a call `ObjectQL.<verb>` refuses. Imported from
// `@objectstack/metadata-core` (already a `dependencies` entry here) and not
// from `@objectstack/objectql`, which depends on this package — that import
// would close a dependency cycle turbo rejects, and is why both of this file's
// (file, verb) pairs sat in the gate's DEBT ledger until #5619 sank the two
// predicates into a package that depends on neither side.
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
import { migrateSysNotificationToEvent } from './migrate-sys-notification-to-event.js';

/** Columns the legacy (pre-ADR-0030) sys_notification table physically has. */
Expand Down Expand Up @@ -42,13 +51,17 @@ function fakeEngine() {
inserts.push({ object, row });
return { id: `${object}_${inserts.length}`, ...row };
},
async update(object: string, data: any) {
async update(object: string, data: any, options?: Record<string, unknown>) {
assertEngineUpdateDispatch(data, options);
updates.push({ object, data });
return data;
},
async find() { return []; },
async findOne() { return null; },
async delete() { return {}; },
async delete(_object?: string, options?: Record<string, unknown>) {
assertEngineDeleteDispatch(options);
return {};
},
async count() { return 0; },
async aggregate() { return []; },
} as any,
Expand Down
17 changes: 16 additions & 1 deletion packages/platform-objects/src/plugin.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect } from 'vitest';
// [#5855] The fake engine's `update` routes through the producer's OWN dispatch
// predicate (#5480), so this double cannot accept a call `ObjectQL.update`
// refuses. Imported from `@objectstack/metadata-core` (already a `dependencies`
// entry here) and not from `@objectstack/objectql`, which depends on this
// package — that import would close a dependency cycle turbo rejects, and is
// why this file's `update` entry sat in the gate's DEBT ledger until #5619 sank
// the predicate into a package that depends on neither side.
import { assertEngineUpdateDispatch } from '@objectstack/metadata-core';
import { PlatformObjectsPlugin } from './plugin.js';
import { SysMigration, SysMigrationJournal, SysSecret } from './system/index.js';

Expand Down Expand Up @@ -101,7 +109,14 @@ describe('PlatformObjectsPlugin: fresh-datastore attestation (#3438, ADR-0104)',
rows.push({ ...data });
return data;
},
update: async () => ({}),
// `attestFreshDatastore` never overwrites an existing flag row, so no
// path this suite drives reaches `update` — the assert comes first
// anyway, so the day one does, the double refuses what a real server
// refuses instead of silently accepting it (#4434).
update: async (_object: string, data: any, options?: Record<string, unknown>) => {
assertEngineUpdateDispatch(data, options);
return {};
},
rows,
};
if (createdFromEmpty !== undefined) {
Expand Down
14 changes: 13 additions & 1 deletion packages/platform-objects/src/system/migration-flag.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.

import { describe, it, expect, vi } from 'vitest';
// [#5855] The fake engine's `update` routes through the producer's OWN dispatch
// predicate (#5480), so this double cannot accept a call `ObjectQL.update`
// refuses. Imported from `@objectstack/metadata-core` (already a `dependencies`
// entry here) and not from `@objectstack/objectql`, which depends on this
// package — that import would close a dependency cycle turbo rejects, and is
// why this file's `update` entry sat in the gate's DEBT ledger until #5619 sank
// the predicate into a package that depends on neither side.
import { assertEngineUpdateDispatch } from '@objectstack/metadata-core';
import { CREATION_ATTESTED_MIGRATION_IDS } from '@objectstack/spec/system';
import {
readDataMigrationFlag,
Expand All @@ -26,7 +34,11 @@ function fakeEngine(rows: Array<Record<string, unknown>> = [], opts: { registere
(tables[object] ??= []).push({ ...data });
return data;
},
async update(object, data: any) {
async update(object, data: any, options) {
// `recordDataMigrationRun` updates an existing flag row by its `id` in
// the payload — the shape `ObjectQL.update` routes `by-id`. Asserting it
// here binds this double to that verdict instead of re-deciding it.
assertEngineUpdateDispatch(data, options);
const row = (tables[object] ?? []).find((r) => r.id === data.id);
if (row) Object.assign(row, data);
return row;
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading