From 48c53e3be69fd1f14a464a68cb3c091bf4c65e68 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Aug 2026 20:29:06 +0000 Subject: [PATCH] fix(runtime): route #5138 test engine doubles through assertEngineDeleteDispatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR #5584(#5138)新增的 packages/runtime/src/action-execution-calldata-not-found.test.ts 里两个 fake ObjectQL 引擎的 delete() 没有走 @objectstack/objectql 的 assertEngineDeleteDispatch,合入 main 后 check:engine-double-contract (挂在 ESLint job)转红,阻塞后续所有 PR 的 ESLint。 按门禁处方第一条修:两处 delete 以 assertEngineDeleteDispatch(opts) 开头, 并用它返回的 by-id dispatch 里的 id 作为 store 键,而不是自己再从 opts.where.id 取一次。@objectstack/objectql 已是 @objectstack/runtime 的 dependencies 条目,无需新增 devDependency,也不需要 MEASURED 例外。 这与 #5138 的取舍一致且互补:那个 PR 刻意不读 ql.delete 的返回值(引擎侧 IDataEngine.delete 只声明 Promise),而这里收紧的是 fake 的入口谓词 —— 断言顺带证明了 callData 兜底发出的是标量 by-id 删除,即真引擎会执行的形状。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh --- ...ction-execution-calldata-not-found.test.ts | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/action-execution-calldata-not-found.test.ts b/packages/runtime/src/action-execution-calldata-not-found.test.ts index 32e126e3af..376133a001 100644 --- a/packages/runtime/src/action-execution-calldata-not-found.test.ts +++ b/packages/runtime/src/action-execution-calldata-not-found.test.ts @@ -37,6 +37,13 @@ import { describe, it, expect } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; +// [#4550, from #4434] The REAL engine's delete-dispatch predicate. A fake whose +// `delete` is looser than the implementation it stands in for turns a green +// suite into no suite at all; importing the producer's decision (rather than +// mirroring it) is what makes that impossible. `@objectstack/objectql` is +// already a `dependencies` entry of `@objectstack/runtime`, so no manifest +// change is needed to reach it. +import { assertEngineDeleteDispatch } from '@objectstack/objectql'; import { ApiEndpointSchema } from '@objectstack/spec/api'; import type { ApiEndpoint } from '@objectstack/spec/api'; @@ -81,7 +88,14 @@ function fallbackHarness(store = rows()) { return store.get(id); }, delete: async (_o: string, opts: any) => { - const id = String(opts?.where?.id); + // [#4550, from #4434] Open on the producer's own dispatch predicate, + // so this double cannot accept a call the real `ObjectQL.delete` + // refuses. It also earns its keep here: the assertion is what proves + // `callData`'s fallback issues a SCALAR by-id delete — the shape a + // running engine executes — rather than a predicate-shaped one that + // would 500 in production while this suite stayed green. + const dispatch = assertEngineDeleteDispatch(opts); + const id = String((dispatch as { kind: 'by-id'; id: string | number | bigint }).id); deleted.push(id); return store.delete(id); }, @@ -109,7 +123,13 @@ function protocolHarness(store = rows()) { store.set(id, { ...store.get(id), ...data }); return store.get(id); }, - delete: async (_o: string, opts: any) => store.delete(String(opts?.where?.id)), + // [#4550] Same pinning as the fallback harness above — this engine sits + // under the REAL protocol implementation, so its `delete` is reached by + // `deleteData`'s own by-id call and must be held to the same contract. + delete: async (_o: string, opts: any) => { + const dispatch = assertEngineDeleteDispatch(opts); + return store.delete(String((dispatch as { kind: 'by-id'; id: string | number | bigint }).id)); + }, }; const services: Record = { metadata: { getObject: async () => ({ name: 'task', fields: {} }) },