Skip to content

Commit 5e5df6a

Browse files
os-zhuangclaude
andauthored
fix(devx): resolve engine doubles bound to a local name, and walk one root constant on both scans (#10614)
check-engine-double-contract could not read two mock-initializer spellings, so the constructs behind them were in NEITHER the pinned population nor the ledger -- absent, which reads to every consumer of this gate's output as clean. delete: del with `const del = vi.fn(async (…) => …)` above it { registry, insert, findOne, update } the shorthand member `fnInitializer` now resolves an IDENTIFIER through the file's own scope chain and hands the result back to itself, so the spelling composes with every other unwrap instead of forking a second, narrower one. Resolution lives in `implOf` itself rather than in a walk, which is what keeps discovery and the #9747 census from disagreeing: there is no second call site to apply it to. It refuses a parameter, an imported binding and a cycle -- answering null leaves the construct in the census, where it is printed and counted. The shared pre-filter is one function now (`mentionsVerb`): discovery's used `\bverb\s*[(:]`, which cannot match a shorthand, so discovery and the census were scoped differently on exactly the spelling this card is about. #10496, folded in: the CONSUMER SEAM scan walked `packages/` alone while the test-double side walked SCAN_ROOTS (`packages`, `examples`), and nothing stated the narrower scope as a decision. Both read SCAN_ROOTS now. Measured cost today: zero rows -- with a planted-seam positive control, since a walk that stopped working returns the same zero. Measured on this tree (before -> after): UNRECOGNISED census 21 -> 0 SCOPED OUT 119 -> 119 (unchanged) delete doubles 240 -> 246 update doubles 276 -> 291 pinned ledger rows 337 -> 353 16 added, 0 lost, 0 baseline raises The 16 newly discovered doubles that were unguarded are pinned AT SOURCE in nine test files; none of them needed the shrink-only baseline raised. Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt Co-authored-by: Claude <noreply@anthropic.com>
1 parent af94015 commit 5e5df6a

11 files changed

Lines changed: 469 additions & 41 deletions

packages/metadata-protocol/src/protocol.batch-atomic.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// is already in the database.
1515

1616
import { describe, it, expect, vi } from 'vitest';
17+
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
1718
import { ObjectStackProtocolImplementation } from './protocol.js';
1819

1920
const SCHEMA = {
@@ -42,11 +43,15 @@ function makeTransactionalEngine(opts: { driverCanTransact?: boolean } = {}) {
4243
return { id: `rec-${insert.mock.calls.length}`, ...data };
4344
});
4445
const update = vi.fn(async (_object: string, data: any, options?: any) => {
46+
assertEngineUpdateDispatch(data, options);
4547
if (data?.title === POISON) throw new Error('update exploded');
4648
return { id: options?.where?.id, ...data };
4749
});
4850
const findOne = vi.fn(async (_object: string, options?: any) => ({ id: options?.where?.id }));
49-
const del = vi.fn(async () => ({ deleted: 1 }));
51+
const del = vi.fn(async (_object: string, options?: any) => {
52+
assertEngineDeleteDispatch(options);
53+
return { deleted: 1 };
54+
});
5055

5156
const engine: any = {
5257
registry: { getObject: () => SCHEMA },

packages/metadata-protocol/src/protocol.batch-not-attempted.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
*/
4141

4242
import { describe, it, expect, vi } from 'vitest';
43+
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
4344
import { ObjectStackProtocolImplementation } from './protocol.js';
4445

4546
const SCHEMA = {
@@ -85,6 +86,7 @@ function makeStoreEngine() {
8586
return rec;
8687
});
8788
const update = vi.fn(async (_object: string, data: any, options?: any) => {
89+
assertEngineUpdateDispatch(data, options);
8890
const id = options?.where?.id;
8991
if (data?.title === POISON) throw validationFailure();
9092
const next = { ...rows.get(id), ...data };
@@ -93,6 +95,7 @@ function makeStoreEngine() {
9395
});
9496
// Contract per #4435: `false` is the positive not-found value.
9597
const del = vi.fn(async (_object: string, options?: any) => {
98+
assertEngineDeleteDispatch(options);
9699
const id = options?.where?.id;
97100
if (!rows.has(id)) return false;
98101
rows.delete(id);

packages/metadata-protocol/src/protocol.bulk-record-not-found.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
*/
3333

3434
import { describe, it, expect, vi } from 'vitest';
35+
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
3536
import { ObjectStackProtocolImplementation } from './protocol.js';
3637

3738
const SCHEMA = {
@@ -68,6 +69,7 @@ function makeStoreEngine() {
6869
const handle = { id: 'trx-1' };
6970

7071
const update = vi.fn(async (_object: string, data: any, options?: any) => {
72+
assertEngineUpdateDispatch(data, options);
7173
const id = options?.where?.id;
7274
// The write pipeline, reached for an id that names no row: the hook
7375
// condition evaluates against a payload-only record and #4775 aborts.
@@ -78,6 +80,7 @@ function makeStoreEngine() {
7880
});
7981
// Contract per #4435: `false` is the positive not-found value.
8082
const del = vi.fn(async (_object: string, options?: any) => {
83+
assertEngineDeleteDispatch(options);
8184
const id = options?.where?.id;
8285
if (!rows.has(id)) return false;
8386
rows.delete(id);

packages/metadata-protocol/src/protocol.dropped-fields.bulk.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
// response has no per-row slot; the insert strip is schema-uniform).
1313

1414
import { describe, it, expect, vi } from 'vitest';
15+
import { assertEngineUpdateDispatch } from '@objectstack/metadata-core';
1516
import { ObjectStackProtocolImplementation } from './protocol.js';
1617

1718
const SCHEMA = {
@@ -25,6 +26,7 @@ const SCHEMA = {
2526
describe('updateManyData — per-row droppedFields + context threading (#3455)', () => {
2627
it('surfaces per-row engine strips and threads the caller context to each update', async () => {
2728
const update = vi.fn(async (object: string, data: any, options?: any) => {
29+
assertEngineUpdateDispatch(data, options);
2830
// Only the second row forges the readonly field → only it drops.
2931
if (data.approval_status !== undefined) {
3032
options?.onFieldsDropped?.({ object, fields: ['approval_status'], reason: 'readonly' });
@@ -179,6 +181,7 @@ describe('batchData — per-row droppedFields + context threading (#3455)', () =
179181

180182
it('update rows surface the engine strip and keep droppedFields when returnRecords=false', async () => {
181183
const update = vi.fn(async (object: string, _data: any, options?: any) => {
184+
assertEngineUpdateDispatch(_data, options);
182185
options?.onFieldsDropped?.({ object, fields: ['approval_status'], reason: 'readonly' });
183186
return { id: options.where.id };
184187
});

packages/metadata-protocol/src/protocol.many-data-atomic.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
// test reads the store back afterwards.
2222

2323
import { describe, it, expect, vi } from 'vitest';
24+
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
2425
import { ObjectStackProtocolImplementation } from './protocol.js';
2526

2627
const SCHEMA = { name: 'invoice', fields: { title: { name: 'title', type: 'text' } } };
@@ -47,6 +48,7 @@ function makeStoreEngine(opts: { driverCanTransact?: boolean; hasTransaction?: b
4748
const handle = { id: 'trx-1' };
4849

4950
const update = vi.fn(async (_object: string, data: any, options?: any) => {
51+
assertEngineUpdateDispatch(data, options);
5052
const id = options?.where?.id;
5153
const current = rows.get(id);
5254
if (!current) throw new Error(`no such record: ${id}`);
@@ -57,6 +59,7 @@ function makeStoreEngine(opts: { driverCanTransact?: boolean; hasTransaction?: b
5759
});
5860
// Contract per #4435: `false` is the positive not-found value.
5961
const del = vi.fn(async (_object: string, options?: any) => {
62+
assertEngineDeleteDispatch(options);
6063
const id = options?.where?.id;
6164
if (!rows.has(id)) return false;
6265
rows.delete(id);

packages/metadata-protocol/src/protocol.record-not-found.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
*/
2222

2323
import { describe, it, expect, vi } from 'vitest';
24+
import { assertEngineDeleteDispatch, assertEngineUpdateDispatch } from '@objectstack/metadata-core';
2425
import { ObjectStackProtocolImplementation } from './protocol.js';
2526

2627
const SCHEMA = { name: 'task', fields: { title: { name: 'title', type: 'text' } } };
@@ -34,13 +35,17 @@ function makeProtocol(rows: Record<string, any> = {}) {
3435
const store = new Map<string, any>(Object.entries(rows));
3536
const findOne = vi.fn(async (_object: string, opts: any) => store.get(String(opts?.where?.id)) ?? null);
3637
const update = vi.fn(async (_object: string, data: any, opts: any) => {
38+
assertEngineUpdateDispatch(data, opts);
3739
const id = String(opts?.where?.id);
3840
if (!store.has(id)) return null;
3941
const next = { ...store.get(id), ...data };
4042
store.set(id, next);
4143
return next;
4244
});
43-
const del = vi.fn(async (_object: string, opts: any) => store.delete(String(opts?.where?.id)));
45+
const del = vi.fn(async (_object: string, opts: any) => {
46+
assertEngineDeleteDispatch(opts);
47+
return store.delete(String(opts?.where?.id));
48+
});
4449
const engine = {
4550
registry: { getObject: (n: string) => (n === 'task' ? SCHEMA : undefined) },
4651
findOne, update, delete: del,

packages/metadata-protocol/src/protocol.upsert-existence.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
*/
2727

2828
import { describe, it, expect, vi } from 'vitest';
29+
import { assertEngineUpdateDispatch } from '@objectstack/metadata-core';
2930
import { ObjectStackProtocolImplementation } from './protocol.js';
3031

3132
const SCHEMA = {
@@ -59,6 +60,7 @@ function makeRlsEngine() {
5960
return visible(row, options?.context) ? row : null;
6061
});
6162
const update = vi.fn(async (_object: string, data: any, options?: any) => {
63+
assertEngineUpdateDispatch(data, options);
6264
const id = options?.where?.id;
6365
const row = rows.get(id);
6466
if (!row || !visible(row, options?.context)) {

packages/plugins/plugin-auth/src/admin-import-users.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { describe, it, expect, vi } from 'vitest';
4+
import { assertEngineUpdateDispatch } from '@objectstack/objectql';
45
import { runAdminImportUsers, IMPORT_USERS_MAX_ROWS, type IdentityImportDeps } from './admin-import-users.js';
56
import type { AdminActor } from './admin-user-endpoints.js';
67

@@ -35,7 +36,10 @@ function makeDeps(opts: {
3536
const where = q?.where ?? {};
3637
return existing.filter((u) => Object.entries(where).every(([k, v]) => { if (k.startsWith('$')) throw new Error(`fake driver: unsupported operator ${k}`); return u[k] === v; }));
3738
});
38-
const update = vi.fn(async () => ({}));
39+
const update = vi.fn(async (_obj: string, data: any, options?: any) => {
40+
assertEngineUpdateDispatch(data, options);
41+
return {};
42+
});
3943
const insert = vi.fn(async () => ({}));
4044
const warn = vi.fn();
4145
const noteMustChangePasswordIssued = vi.fn();

packages/plugins/plugin-auth/src/admin-user-endpoints.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { describe, it, expect, vi } from 'vitest';
4+
import { assertEngineUpdateDispatch } from '@objectstack/objectql';
45
import {
56
runAdminCreateUser,
67
runAdminSetUserPassword,
@@ -338,7 +339,10 @@ describe('runAdminCreateUser', () => {
338339
}
339340
return [];
340341
});
341-
const engineUpdate = vi.fn(async () => ({}));
342+
const engineUpdate = vi.fn(async (_obj: string, data: any, options?: any) => {
343+
assertEngineUpdateDispatch(data, options);
344+
return {};
345+
});
342346
const engineInsert = vi.fn(async () => ({}));
343347
const m = makeDeps({
344348
getDataEngine: () => ({ update: engineUpdate, insert: engineInsert, find }),

0 commit comments

Comments
 (0)