diff --git a/.changeset/engine-update-dispatch-predicate.md b/.changeset/engine-update-dispatch-predicate.md new file mode 100644 index 0000000000..348e25956c --- /dev/null +++ b/.changeset/engine-update-dispatch-predicate.md @@ -0,0 +1,21 @@ +--- +'@objectstack/objectql': patch +--- + +`ObjectQL.update` 的三分支派发抽成生产者侧唯一判定 `engine-update-dispatch.ts` + +`delete` 的派发决策自 #4550 起就是一份共享判定(`resolveEngineDeleteDispatch`),任何顶替引擎的测试替身都能 import 它,因此结构上不可能比引擎更宽松。`update` 的同款三分支——标量 `where.id` → 按 id;`options.multi` → `driver.updateMany`;否则抛错——此前只是 `engine.ts` 里的一个内联字面量 throw,既没有导出的常量也没有可复用的函数。后果不是理论上的:#5393 给 flow 的 `update_record` / `delete_record` 补真实契约测试时,delete 侧能把假引擎钉死在生产者契约上,update 侧只能退而断言执行器交出的 options 包,并在文件头写明「不对引擎会不会接受它发表第二份意见」——因为唯一的替代做法是在 fake 里手抄一遍判定,而手抄必然漏掉 `where: { id: { $in: [...] } }` 看着像 id 实为谓词这一半(#4434 正是这样带着全绿的测试发布了一条对每个调用者都回 500 的路由)。同一个执行器的两个写入动词,一个能被绑定到生产者契约、另一个结构上不能,而谓词 update 的破坏性并不低——它覆盖每一行匹配记录的字段。 + +本次新增: + +- `packages/objectql/src/engine-update-dispatch.ts`,导出 `resolveEngineUpdateDispatch` / `assertEngineUpdateDispatch` / `scalarUpdateId` / `ENGINE_UPDATE_REJECT_MESSAGE` / `ENGINE_UPDATE_DISPATCH_CASES`,均从 `@objectstack/objectql` 公开导出; +- `ObjectQL.update` **自身改用它**——生产者与判定必须是同一份,否则只是第二份副本。 + +这是**行为保持的重构**:三分支语义、`$in` 谓词判定、拒绝消息文本一字未改(`Update requires an ID or options.multi=true`,现在是导出常量 `ENGINE_UPDATE_REJECT_MESSAGE`)。判定里有两处刻意照抄而非「改良」了生产者的现状,并在模块头与测试中写明: + +1. `data.id` **不做标量测试**,只要为真就直接作为 id,且优先于 `where` 与 `multi`; +2. 分支按**真值**而非 `!== undefined`,所以 `where: { id: 0 }` 不走按 id 路径。 + +比生产者更「聪明」的判定就是第二份意见,正是 #4550 消除的东西;这两点该改的时候会在两个文件里一起改,现在那是一次编辑而不是两次。 + +新测试 `engine-update-dispatch.test.ts` 不去对照写在旁边的期望表,而是用记录型 driver 驱动**真实引擎**跑完 `ENGINE_UPDATE_DISPATCH_CASES`,逐例断言引擎的实际行为等于判定的裁决——两半唯一同处一室的地方。 diff --git a/packages/objectql/src/engine-update-dispatch.test.ts b/packages/objectql/src/engine-update-dispatch.test.ts new file mode 100644 index 0000000000..b3ee2a6e03 --- /dev/null +++ b/packages/objectql/src/engine-update-dispatch.test.ts @@ -0,0 +1,145 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. +// +// objectstack#5480 — the shared update-dispatch predicate must be the REAL +// engine's answer, not a second opinion that happens to agree today. +// +// Exactly the argument `engine-delete-dispatch.test.ts` makes for `delete`, +// and it matters more here rather than less: a shared predicate that drifted +// from `ObjectQL.update` would make every fake engine pinned to it confidently, +// uniformly wrong, while the gate over them reported success. So this file does +// not test the predicate against a table of expectations written next to it. It +// drives the **real engine** with a recording driver over +// `ENGINE_UPDATE_DISPATCH_CASES` and asserts the engine's observed behaviour +// equals the predicate's verdict, case by case. +// +// If someone changes the dispatch rule in `engine.ts` without changing +// `engine-update-dispatch.ts`, this goes red here — the one place where both +// halves are in the room together. + +import { describe, it, expect } from 'vitest'; +import { ObjectQL } from './engine.js'; +import { + ENGINE_UPDATE_DISPATCH_CASES, + ENGINE_UPDATE_REJECT_MESSAGE, + resolveEngineUpdateDispatch, + assertEngineUpdateDispatch, + scalarUpdateId, +} from './engine-update-dispatch.js'; + +/** Records which driver entry point the engine chose, if any. */ +function makeRecordingDriver() { + const calls: Array<{ fn: 'update' | 'updateMany'; arg: unknown }> = []; + const driver: any = { + name: 'recording', + version: '0.0.0', + supports: {}, + async connect() {}, async disconnect() {}, async checkHealth() { return true; }, async execute() { return null; }, + async find() { return []; }, + async findOne() { return null; }, + async create(_o: string, data: Record) { return { id: 'r1', ...data }; }, + async update(_o: string, id: string, data: Record) { calls.push({ fn: 'update', arg: id }); return { id, ...data }; }, + async updateMany(_o: string, ast: unknown) { calls.push({ fn: 'updateMany', arg: ast }); return 0; }, + async delete() { return true; }, + async deleteMany() { return 0; }, + async count() { return 0; }, + async bulkCreate() { return []; }, async bulkUpdate() { return []; }, async bulkDelete() {}, + async beginTransaction() { return { __trx: true, commit: async () => {}, rollback: async () => {} }; }, + async commit() {}, async rollback() {}, + }; + return { driver, calls }; +} + +async function makeEngine() { + const engine = new ObjectQL(); + const { driver, calls } = makeRecordingDriver(); + engine.registerDriver(driver, true); + await engine.init(); + engine.registry.registerObject({ + name: 'task', + fields: { title: { type: 'text' }, tenant: { type: 'text' } }, + } as any); + return { engine, calls }; +} + +/** What the real engine actually did with this `(data, options)` pair. */ +async function observeEngine(data: unknown, options: unknown): Promise<'by-id' | 'multi' | 'reject'> { + const { engine, calls } = await makeEngine(); + try { + await engine.update('task', data as any, options as any); + } catch (e) { + if ((e as Error).message === ENGINE_UPDATE_REJECT_MESSAGE) return 'reject'; + throw e; + } + if (calls.length !== 1) { + throw new Error(`expected exactly one driver call, saw ${JSON.stringify(calls)}`); + } + return calls[0].fn === 'update' ? 'by-id' : 'multi'; +} + +describe('engine update dispatch — the shared predicate IS the engine (#5480)', () => { + it('has cases on both sides of the guard (an empty or one-sided set proves nothing)', () => { + const kinds = new Set(ENGINE_UPDATE_DISPATCH_CASES.map((c) => c.expect)); + expect(kinds).toEqual(new Set(['by-id', 'multi', 'reject'])); + expect(ENGINE_UPDATE_DISPATCH_CASES.filter((c) => c.expect === 'reject').length).toBeGreaterThan(3); + }); + + for (const c of ENGINE_UPDATE_DISPATCH_CASES) { + it(`real engine agrees with the predicate: ${c.what} → ${c.expect}`, async () => { + expect(resolveEngineUpdateDispatch(c.data, c.options).kind, 'predicate').toBe(c.expect); + expect(await observeEngine(c.data, c.options), 'real ObjectQL.update').toBe(c.expect); + }); + } + + it('rejects with the exact message a fake must reproduce', () => { + expect(() => assertEngineUpdateDispatch({ title: 'x' }, { where: { tenant: 't1' } })) + .toThrow(ENGINE_UPDATE_REJECT_MESSAGE); + // …and returns the dispatch (never `reject`) when the call is legal. + expect(assertEngineUpdateDispatch({ title: 'x' }, { where: { id: 'a' } })).toEqual({ kind: 'by-id', id: 'a' }); + expect(assertEngineUpdateDispatch({ id: 'a' }, undefined)).toEqual({ kind: 'by-id', id: 'a' }); + expect(assertEngineUpdateDispatch({ title: 'x' }, { multi: true })).toEqual({ kind: 'multi' }); + }); + + it('scalarUpdateId treats operator objects and arrays as predicates, not ids', () => { + expect(scalarUpdateId({ where: { id: 'a' } })).toBe('a'); + expect(scalarUpdateId({ where: { id: 7 } })).toBe(7); + expect(scalarUpdateId({ where: { id: { $in: ['a'] } } })).toBeUndefined(); + expect(scalarUpdateId({ where: { id: ['a'] } })).toBeUndefined(); + expect(scalarUpdateId({ where: { id: null } })).toBeUndefined(); + expect(scalarUpdateId({ where: {} })).toBeUndefined(); + expect(scalarUpdateId(undefined)).toBeUndefined(); + }); + + // ── The two places `update` is NOT `delete`. Both are pinned here rather + // than left to the reader, because they are exactly what a hand-copied + // guard gets wrong in the OTHER direction: too strict, and the double + // then refuses a call the producer accepts. + it('data.id outranks where and multi, and is NOT scalar-tested (the producer\'s rule, verbatim)', () => { + expect(resolveEngineUpdateDispatch({ id: 'rec_1' }, { where: { id: { $in: ['a'] } }, multi: true })) + .toEqual({ kind: 'by-id', id: 'rec_1' }); + // An operator object parked in the PAYLOAD is taken as an id — the engine + // does exactly this today, so the predicate must say so too. Improving on + // the producer here would make this module a second opinion, which is the + // thing #4550 removed. Tracked as #5748; when it is fixed it is fixed in + // both files at once, which is now one edit instead of two, and this + // assertion is what tells the next author to turn BOTH halves over. + const operatorInPayload = resolveEngineUpdateDispatch({ id: { $in: ['a', 'b'] } }, { multi: true }); + expect(operatorInPayload.kind).toBe('by-id'); + }); + + it('branches on TRUTHINESS, so a falsy scalar id does not identify a row', () => { + expect(resolveEngineUpdateDispatch({ title: 'x' }, { where: { id: 0 } }).kind).toBe('reject'); + expect(resolveEngineUpdateDispatch({ title: 'x' }, { where: { id: '' } }).kind).toBe('reject'); + expect(resolveEngineUpdateDispatch({ id: 0, title: 'x' }, { multi: true }).kind).toBe('multi'); + // …while `scalarUpdateId` still reports the raw scalar it found. The two + // answer different questions and only `resolveEngineUpdateDispatch` + // answers the engine's. + expect(scalarUpdateId({ where: { id: 0 } })).toBe(0); + }); + + it('reads data UNGUARDED, exactly like the producer', () => { + // `ObjectQL.update` opens with `data.id`, so a missing payload is a + // TypeError there. A double kinder than the producer about it would hide + // the producer's behaviour. + expect(() => resolveEngineUpdateDispatch(undefined as any, { multi: true })).toThrow(TypeError); + }); +}); diff --git a/packages/objectql/src/engine-update-dispatch.ts b/packages/objectql/src/engine-update-dispatch.ts new file mode 100644 index 0000000000..800c05ae6c --- /dev/null +++ b/packages/objectql/src/engine-update-dispatch.ts @@ -0,0 +1,229 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * The **one** answer to "what does `ObjectQLEngine.update` do with this call?" + * — the twin of `engine-delete-dispatch.ts`, extracted for the same reason and + * on the same terms (objectstack#5480, from objectstack#4550 / objectstack#4434). + * + * ## Why this exists as a module + * + * `delete` got its shared predicate because #4434 shipped a dead REST route + * green: a fake engine accepted the one call shape the real `delete` refuses, + * so the suite proved nothing about the path it was written for. `update` has + * the *same* three-way dispatch and — until this module — none of the defence. + * #5393 hit the asymmetry from the consumer side: writing real contract tests + * for the flow `update_record` / `delete_record` executors, the delete half + * could bind its fake to `assertEngineDeleteDispatch` while the update half + * could only assert the options bag the executor hands over, "without a second + * opinion on whether the engine would accept it" — because the only + * alternative was hand-copying the rule into the fake, which is the failure + * mode this family of modules exists to remove. + * + * A double that *imports the producer's own decision* cannot be looser than + * the producer, ever. A hand-mirrored `if` can only stay honest until someone + * edits one side — and the half a copy drops is always the same one: + * `where: { id: { $in: [...] } }` looks like an id and is a multi-row + * predicate. + * + * ## The contract, normatively + * + * `update(object, data, options)` dispatches on exactly one question — *does + * this call identify a single row by primary key?* — asked of two places, in + * this order: + * + * - **`data.id`, taken verbatim when truthy** → `by-id`: routes to + * `driver.update(object, id, data, …)`. + * - otherwise, `options.where.id` is a **scalar** (`string` / `number` / + * `bigint`, not `null`) **and truthy** → `by-id`, same route. + * - otherwise, `options.multi` is truthy → `multi`: routes to + * `driver.updateMany` with the middleware-composed AST (#2982). + * - otherwise → **`reject`**. The call names neither one row nor a bulk + * intent, and the engine throws rather than rewriting every row it can see. + * + * Three things about that list are load-bearing and easy to get wrong when + * copying it by hand — which is the whole argument for importing it instead: + * + * 1. **The `where.id` scalar test.** `{ id: { $in: [...] } }` / `{ id: [...] }` + * / `{ id: null }` are predicates over many rows. Treating one as an id + * would bind the operator object literally into `driver.update(object, + * {$in: […]}, …)` **and** skip the #2982 row-scoping AST seeding. So they + * are `reject` unless the caller also said `multi`. + * 2. **`data.id` is NOT scalar-tested.** `ObjectQL.update` reads `data.id` + * first and uses it as-is whenever it is truthy, so an operator object + * parked there wins over everything below it — including an explicit + * `multi: true`. This module reports that verdict rather than quietly + * improving on it: a predicate that is *better* than the producer is a + * second opinion, which is exactly what #4550 removed. (The asymmetry + * itself is filed as objectstack#5748; when it is fixed, it is fixed **here + * and in `engine.ts` together**, which is now one edit instead of two.) + * 3. **Truthiness, not `!== undefined`.** The engine branches on + * `if (hookContext.input.id)`, so a falsy scalar id — `where: { id: 0 }`, + * `where: { id: '' }` — does **not** take the by-id route; it falls through + * to `multi`/`reject` like any other non-identifying call. + * + * ## What the predicate deliberately does NOT model + * + * `engine.ts`'s bulk branch reads `options.multi && driver.updateMany`, i.e. a + * driver with no `updateMany` turns a `multi` call into the same throw. That + * is a *driver capability*, not a property of the call, and a double answering + * for a fixture's own storage has no `driver.updateMany` to consult. Same + * choice `resolveEngineDeleteDispatch` makes for `driver.deleteMany`: this + * module classifies the CALL, and the engine keeps the capability check. + * + * @see ObjectQL.update in `engine.ts` — the only production caller. + * @see engine-delete-dispatch.ts — the twin, and the precedent. + * @see scripts/check-engine-double-contract.mjs — the gate that keeps doubles on both. + */ + +/** The message `update()` throws when a call identifies neither one row nor a bulk intent. */ +export const ENGINE_UPDATE_REJECT_MESSAGE = 'Update requires an ID or options.multi=true'; + +/** What `ObjectQLEngine.update` will do with a given `(data, options)` pair. */ +export type EngineUpdateDispatch = + /** A truthy `data.id`, or a truthy scalar `where.id` — `driver.update`. */ + | { readonly kind: 'by-id'; readonly id: unknown } + /** No single id but `options.multi` — `driver.updateMany` with the composed AST. */ + | { readonly kind: 'multi' } + /** Neither — the engine throws `ENGINE_UPDATE_REJECT_MESSAGE`. */ + | { readonly kind: 'reject'; readonly message: string }; + +/** The subset of `EngineUpdateOptions` the dispatch decision actually reads. */ +export interface EngineUpdateDispatchInput { + readonly where?: unknown; + readonly multi?: unknown; + readonly [k: string]: unknown; +} + +/** The subset of the update PAYLOAD the dispatch decision reads: `id`, and nothing else. */ +export interface EngineUpdateDispatchData { + readonly id?: unknown; + readonly [k: string]: unknown; +} + +/** + * Extract the SCALAR `where.id`, or `undefined` when the call's `where` does + * not name one row by primary key. + * + * Byte-for-byte the same rule as `scalarDeleteId` — `null`, `undefined`, + * arrays and operator objects (`{ $in: [...] }`, `{ $ne: … }`) all yield + * `undefined`, because they are predicates over many rows. + * + * Note this covers only the `where` half of the update decision; `data.id` + * outranks it and is taken verbatim (see the module header, point 2). Use + * {@link resolveEngineUpdateDispatch} for the whole answer. + */ +export function scalarUpdateId( + options?: EngineUpdateDispatchInput | null, +): string | number | bigint | undefined { + const where = options?.where; + if (!where || typeof where !== 'object') return undefined; + if (!('id' in (where as Record))) return undefined; + const whereId = (where as Record).id; + const t = typeof whereId; + if (whereId !== null && (t === 'string' || t === 'number' || t === 'bigint')) { + return whereId as string | number | bigint; + } + return undefined; +} + +/** + * Decide what `ObjectQLEngine.update` does with `(data, options)`, without + * doing it. + * + * Pure and side-effect free, so a test double can call it to *classify* a call + * and then implement `by-id` / `multi` however its fixture stores rows — while + * being bound to the real engine's `reject` surface for free. + * + * `data` is read UNGUARDED (`data.id`, no optional chaining) on purpose: + * `ObjectQL.update` reads it that way, so `update(object, undefined)` is a + * `TypeError` there and must be a `TypeError` here. A double that is kinder + * than the producer about a missing payload is a double that hides the + * producer's behaviour — the thing this module exists to prevent. + */ +export function resolveEngineUpdateDispatch( + data: EngineUpdateDispatchData, + options?: EngineUpdateDispatchInput | null, +): EngineUpdateDispatch { + // `let id = data.id; if (!id && ) id = whereId;` — the + // producer's own two lines, in the producer's own order. + let id: unknown = data.id; + if (!id) { + const fromWhere = scalarUpdateId(options); + if (fromWhere !== undefined) id = fromWhere; + } + // The engine branches on `if (hookContext.input.id)` — truthiness, so a + // falsy scalar id is not an identifying call. See header point 3. + if (id) return { kind: 'by-id', id }; + if (options?.multi) return { kind: 'multi' }; + return { kind: 'reject', message: ENGINE_UPDATE_REJECT_MESSAGE }; +} + +/** + * Throw exactly what `ObjectQLEngine.update` throws when a call is neither + * `by-id` nor `multi`; return the resolved dispatch otherwise. + * + * This is the line a fake engine's `update` opens with. One call pins the fake + * to the producer's rejection surface, and — unlike a mirrored `if` — it cannot + * drift when the producer's rule changes. + * + * ```ts + * async update(object: string, data: any, options?: any) { + * assertEngineUpdateDispatch(data, options); // refuses what a real server refuses + * … + * } + * ``` + */ +export function assertEngineUpdateDispatch( + data: EngineUpdateDispatchData, + options?: EngineUpdateDispatchInput | null, +): Exclude { + const dispatch = resolveEngineUpdateDispatch(data, options); + if (dispatch.kind === 'reject') throw new Error(dispatch.message); + return dispatch; +} + +/** + * The shared conformance case-set for the update dispatch — the same role + * `ENGINE_DELETE_DISPATCH_CASES` plays for `delete`, and the same role + * `packages/spec/src/data/*-conformance.ts` plays for drivers. + * + * Every case names a call shape and the verdict the **real engine** gives it. + * A double proved against these is proved against the producer, including the + * shapes that look like an id and are not — and the one that does not look + * like an id and is (`data.id`). + */ +export interface EngineUpdateDispatchCase { + /** What the shape is, in the words a failure message should use. */ + readonly what: string; + /** The payload handed to `update(object, data, options)`. */ + readonly data: EngineUpdateDispatchData; + /** The options bag handed to `update(object, data, options)`. */ + readonly options: EngineUpdateDispatchInput | undefined; + /** The verdict the engine gives it. */ + readonly expect: EngineUpdateDispatch['kind']; +} + +export const ENGINE_UPDATE_DISPATCH_CASES: readonly EngineUpdateDispatchCase[] = [ + // ── by-id via `where`. + { what: 'scalar string where.id', data: { title: 'x' }, options: { where: { id: 'rec_1' } }, expect: 'by-id' }, + { what: 'scalar number where.id', data: { title: 'x' }, options: { where: { id: 42 } }, expect: 'by-id' }, + { what: 'scalar where.id alongside other predicates', data: { title: 'x' }, options: { where: { id: 'rec_1', tenant: 't1' } }, expect: 'by-id' }, + // ── by-id via the PAYLOAD, which outranks `where` and `multi` alike. + { what: 'id carried in the data payload, no where at all', data: { id: 'rec_1', title: 'x' }, options: undefined, expect: 'by-id' }, + { what: 'data.id wins over an explicit multi:true', data: { id: 'rec_1', title: 'x' }, options: { where: { tenant: 't1' }, multi: true }, expect: 'by-id' }, + // ── multi. + { what: 'multi with a predicate', data: { title: 'x' }, options: { where: { tenant: 't1' }, multi: true }, expect: 'multi' }, + { what: 'multi with no predicate at all', data: { title: 'x' }, options: { multi: true }, expect: 'multi' }, + { what: 'multi alongside an $in id set', data: { title: 'x' }, options: { where: { id: { $in: ['a', 'b'] } }, multi: true }, expect: 'multi' }, + { what: 'multi with a FALSY data.id (0 does not identify a row)', data: { id: 0, title: 'x' }, options: { multi: true }, expect: 'multi' }, + // ── The rejects. Every one of these is a call a fake that mirrors the rule + // by hand tends to accept, and a running server answers 500 to. + { what: 'predicate on a non-id column, no multi', data: { title: 'x' }, options: { where: { tenant: 't1' } }, expect: 'reject' }, + { what: '$in over ids, no multi (an operator object is NOT an id)', data: { title: 'x' }, options: { where: { id: { $in: ['a', 'b'] } } }, expect: 'reject' }, + { what: 'array id, no multi', data: { title: 'x' }, options: { where: { id: ['a', 'b'] } }, expect: 'reject' }, + { what: 'null id, no multi', data: { title: 'x' }, options: { where: { id: null } }, expect: 'reject' }, + { what: 'falsy scalar where.id (0), no multi', data: { title: 'x' }, options: { where: { id: 0 } }, expect: 'reject' }, + { what: 'empty where, no multi', data: { title: 'x' }, options: { where: {} }, expect: 'reject' }, + { what: 'no options at all', data: { title: 'x' }, options: undefined, expect: 'reject' }, + { what: 'multi explicitly false with a predicate', data: { title: 'x' }, options: { where: { tenant: 't1' }, multi: false }, expect: 'reject' }, +]; diff --git a/packages/objectql/src/engine.ts b/packages/objectql/src/engine.ts index 3321bf75fe..548b321687 100644 --- a/packages/objectql/src/engine.ts +++ b/packages/objectql/src/engine.ts @@ -96,6 +96,12 @@ import { ENGINE_DELETE_REJECT_MESSAGE, type EngineDeleteDispatchInput, } from './engine-delete-dispatch.js'; +import { + resolveEngineUpdateDispatch, + ENGINE_UPDATE_REJECT_MESSAGE, + type EngineUpdateDispatchData, + type EngineUpdateDispatchInput, +} from './engine-update-dispatch.js'; import { applyHaving } from './having-filter.js'; import { auditDanglingReferences, @@ -5346,14 +5352,19 @@ export class ObjectQL implements IObjectQLEngine { // (e.g. `WHERE id = {"$in":[...]}`, which SQLite rejects). Leave `id` // undefined in that case so the call routes to updateMany (requires // options.multi=true), where applyFilters compiles the operator. - let id = data.id; - if (!id && options?.where && typeof options.where === 'object' && 'id' in options.where) { - const whereId = (options.where as Record).id; - const t = typeof whereId; - if (whereId !== null && (t === 'string' || t === 'number' || t === 'bigint')) { - id = whereId; - } - } + // + // [#5480] The decision lives in `engine-update-dispatch.ts` — the twin of + // the `delete` extraction below (#4550) — so the fake engines that stand + // in for this method import it instead of re-deriving it. Same argument, + // same failure mode: a double looser than the producer converts a green + // suite into no suite at all on exactly the path the double was written + // for (#4434), and a predicate update is no less destructive than a + // predicate delete — it rewrites every matching row's fields. + const dispatch = resolveEngineUpdateDispatch( + data as EngineUpdateDispatchData, + options as EngineUpdateDispatchInput | undefined, + ); + const id: any = dispatch.kind === 'by-id' ? dispatch.id : undefined; const opCtx: OperationContext = { object, @@ -5679,7 +5690,12 @@ export class ObjectQL implements IObjectQLEngine { result = await driver.updateMany(object, ast, hookContext.input.data as Record, hookContext.input.options as any); isPredicateWrite = true; } else { - throw new Error('Update requires an ID or options.multi=true'); + // [#5480] The `reject` verdict of resolveEngineUpdateDispatch, + // re-asked here because a beforeUpdate hook may have cleared the + // id since — the same shape delete()'s branch below carries, and + // the reason the wording lives in one exported constant either + // way. + throw new Error(ENGINE_UPDATE_REJECT_MESSAGE); } hookContext.event = 'afterUpdate'; diff --git a/packages/objectql/src/index.ts b/packages/objectql/src/index.ts index 0bc905d0f6..cf8dc5fed9 100644 --- a/packages/objectql/src/index.ts +++ b/packages/objectql/src/index.ts @@ -87,6 +87,25 @@ export type { EngineDeleteDispatchCase, } from './engine-delete-dispatch.js'; +// [#5480] The update-dispatch contract, on exactly the same terms — `update` +// has the same three-way dispatch, and a predicate write is no less +// destructive than a predicate delete (it rewrites every matching row's +// fields). Until this existed, a double could be bound to the producer for one +// write verb and structurally could not be for the other (#5393). +export { + resolveEngineUpdateDispatch, + assertEngineUpdateDispatch, + scalarUpdateId, + ENGINE_UPDATE_REJECT_MESSAGE, + ENGINE_UPDATE_DISPATCH_CASES, +} from './engine-update-dispatch.js'; +export type { + EngineUpdateDispatch, + EngineUpdateDispatchInput, + EngineUpdateDispatchData, + EngineUpdateDispatchCase, +} from './engine-update-dispatch.js'; + // Export in-memory aggregation fallback (used by engine.aggregate when the // driver lacks native groupBy/aggregations support; also useful for tests). export { applyInMemoryAggregation, bucketDateValue } from './in-memory-aggregation.js'; diff --git a/packages/objectql/src/layered-overlay-integration.test.ts b/packages/objectql/src/layered-overlay-integration.test.ts index e82fb92eec..2c0496953a 100644 --- a/packages/objectql/src/layered-overlay-integration.test.ts +++ b/packages/objectql/src/layered-overlay-integration.test.ts @@ -20,6 +20,7 @@ import { LayeredRepository, InMemoryRepository, hashSpec } from '@objectstack/me import type { MetaRef } from '@objectstack/metadata-core'; import { SysMetadataRepository } from '@objectstack/metadata-protocol'; import { assertEngineDeleteDispatch } from './engine-delete-dispatch.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; interface Row { id: string; @@ -85,6 +86,11 @@ function makeFakeEngine() { return { id: row.id }; }, async update(_t: string, data: Record, opts: { where: Record }) { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate, the + // twin of the delete pin below and on the same argument: this file + // could bind one write verb to the producer and not the other only + // because `update` had no shared predicate to bind to. + assertEngineUpdateDispatch(data, opts); const found = findRow(opts.where); if (!found) return { id: null }; rows.set(found.key, { ...found.row, ...(data as any) }); diff --git a/packages/objectql/src/protocol-boot-hydration-scoped.test.ts b/packages/objectql/src/protocol-boot-hydration-scoped.test.ts index 4c9f372dab..59db262964 100644 --- a/packages/objectql/src/protocol-boot-hydration-scoped.test.ts +++ b/packages/objectql/src/protocol-boot-hydration-scoped.test.ts @@ -22,6 +22,7 @@ import { describe, it, expect } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; import { SchemaRegistry } from './registry.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; const PKG_A = 'com.acme.a'; const PKG_B = 'com.acme.b'; @@ -65,7 +66,15 @@ function makeEngine(registry: SchemaRegistry, rows: Row[]) { return rows.find((r) => matches(r, opts.where)) ?? null; }, async insert() { return { id: 'x' }; }, - async update() { return { id: 'x' }; }, + async update(_t: string, data: Record, opts?: Record) { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate — the twin of + // the delete pin, on the same argument: a double looser than the engine it + // stands in for is how #4434 shipped a REST route that 500'd for every + // caller with its suite green, and a predicate update is no less + // destructive than a predicate delete. + assertEngineUpdateDispatch(data, opts); + return { id: 'x' }; + }, async delete() { return { deleted: 0 }; }, }; return engine; diff --git a/packages/objectql/src/protocol-publish-package-drafts.test.ts b/packages/objectql/src/protocol-publish-package-drafts.test.ts index 4819328b43..127dd04d38 100644 --- a/packages/objectql/src/protocol-publish-package-drafts.test.ts +++ b/packages/objectql/src/protocol-publish-package-drafts.test.ts @@ -2,6 +2,7 @@ import { describe, it, expect, vi } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; /** * ADR-0033 / ADR-0067 D2 — `publishPackageDrafts` promotes every pending @@ -349,7 +350,15 @@ describe('protocol.applySeedBodies — real loader smoke test', () => { inserted.push({ object, record: data }); return { id: `${object}_${inserted.length}` }; }, - update: async () => ({}), + update: async (_o: string, data: any, opts?: any) => { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate — the twin of + // the delete pin, on the same argument: a double looser than the engine it + // stands in for is how #4434 shipped a REST route that 500'd for every + // caller with its suite green, and a predicate update is no less + // destructive than a predicate delete. + assertEngineUpdateDispatch(data, opts); + return {}; + }, }; (protocol as any).getMetaItem = async ({ name }: any) => ({ item: { name, fields: { name: { type: 'text' } } }, diff --git a/packages/objectql/src/protocol-publish-rollback.test.ts b/packages/objectql/src/protocol-publish-rollback.test.ts index 91e0d77898..b14e78d0ac 100644 --- a/packages/objectql/src/protocol-publish-rollback.test.ts +++ b/packages/objectql/src/protocol-publish-rollback.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; import { assertEngineDeleteDispatch } from './engine-delete-dispatch.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; /** * Protocol-level coverage for the per-item draft / publish / rollback / @@ -125,6 +126,11 @@ function makeStubEngine() { return { id: row.id }; }, async update(_t: string, data: Record, opts: { where: Record }) { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate, the + // twin of the delete pin below and on the same argument: this file + // could bind one write verb to the producer and not the other only + // because `update` had no shared predicate to bind to. + assertEngineUpdateDispatch(data, opts); const found = findRow(opts.where); if (!found) return { id: null }; const merged = { ...found.row, ...(data as any) }; diff --git a/packages/objectql/src/protocol-registry-shadow.test.ts b/packages/objectql/src/protocol-registry-shadow.test.ts index 2c1adfb25d..7735eaa393 100644 --- a/packages/objectql/src/protocol-registry-shadow.test.ts +++ b/packages/objectql/src/protocol-registry-shadow.test.ts @@ -26,6 +26,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; import { ObjectQL } from './engine.js'; import { SchemaRegistry } from './registry.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; const PKG = 'com.objectstack.test-pkg'; @@ -257,7 +258,15 @@ describe('registry shadow — scoped-kernel lock enforcement is shadow-immune', find: async () => [], findOne: async () => null, insert: async () => ({ id: 'x' }), - update: async () => ({ id: 'x' }), + update: async (_o: string, data: any, opts?: any) => { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate — the twin of + // the delete pin, on the same argument: a double looser than the engine it + // stands in for is how #4434 shipped a REST route that 500'd for every + // caller with its suite green, and a predicate update is no less + // destructive than a predicate delete. + assertEngineUpdateDispatch(data, opts); + return { id: 'x' }; + }, delete: async () => ({ deleted: 1 }), }; const protocol = new ObjectStackProtocolImplementation( diff --git a/packages/objectql/src/protocol-save-meta-repo-path.test.ts b/packages/objectql/src/protocol-save-meta-repo-path.test.ts index fd67fe0a23..46182cc3f0 100644 --- a/packages/objectql/src/protocol-save-meta-repo-path.test.ts +++ b/packages/objectql/src/protocol-save-meta-repo-path.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from 'vitest'; import { hashSpec } from '@objectstack/metadata-core'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; import { assertEngineDeleteDispatch } from './engine-delete-dispatch.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; /** * Repository write-path coverage (post PR-10d.6). @@ -65,6 +66,11 @@ function makeStubEngine() { return { id: row.id }; }, async update(_t: string, data: Record, opts: { where: Record }) { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate, the + // twin of the delete pin below and on the same argument: this file + // could bind one write verb to the producer and not the other only + // because `update` had no shared predicate to bind to. + assertEngineUpdateDispatch(data, opts); const found = findRow(opts.where); if (!found) return { id: null }; rows.set(found.key, { ...found.row, ...(data as any) }); diff --git a/packages/objectql/src/protocol-view-identity-overlay.test.ts b/packages/objectql/src/protocol-view-identity-overlay.test.ts index 475acb958f..08d225fc89 100644 --- a/packages/objectql/src/protocol-view-identity-overlay.test.ts +++ b/packages/objectql/src/protocol-view-identity-overlay.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'; import { ObjectStackProtocolImplementation } from '@objectstack/metadata-protocol'; import { assertEngineDeleteDispatch } from './engine-delete-dispatch.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; /** * #2555 — a console personalization PUT (grid column sort, inline edit, …) @@ -82,6 +83,11 @@ function makeStubEngine(registryViews: Record = {}) { return { id: row.id }; }, async update(_t: string, data: Record, opts: { where: Record }) { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate, the + // twin of the delete pin below and on the same argument: this file + // could bind one write verb to the producer and not the other only + // because `update` had no shared predicate to bind to. + assertEngineUpdateDispatch(data, opts); const found = findRow(opts.where); if (!found) return { id: null }; rows.set(found.key, { ...found.row, ...(data as any) }); diff --git a/packages/objectql/src/seed-loader-org-fallback.test.ts b/packages/objectql/src/seed-loader-org-fallback.test.ts index bebc90d84e..ec77115632 100644 --- a/packages/objectql/src/seed-loader-org-fallback.test.ts +++ b/packages/objectql/src/seed-loader-org-fallback.test.ts @@ -10,6 +10,7 @@ import { describe, it, expect } from 'vitest'; import { SeedLoaderService } from '@objectstack/metadata-protocol'; import { SeedLoaderConfigSchema } from '@objectstack/spec/data'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; function harness(orgRows: Array<{ id: string }>) { const inserted: Array<{ object: string; record: Record }> = []; @@ -20,7 +21,15 @@ function harness(orgRows: Array<{ id: string }>) { inserted.push({ object, record }); return { id: `${object}_${inserted.length}` }; }, - update: async () => ({}), + update: async (_o: string, data: any, opts?: any) => { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate — the twin of + // the delete pin, on the same argument: a double looser than the engine it + // stands in for is how #4434 shipped a REST route that 500'd for every + // caller with its suite green, and a predicate update is no less + // destructive than a predicate delete. + assertEngineUpdateDispatch(data, opts); + return {}; + }, }; const metadata = { // A single text field → no lookup/master_detail references to resolve. diff --git a/packages/objectql/src/seed-loader-org-stamp.test.ts b/packages/objectql/src/seed-loader-org-stamp.test.ts index 28eabec546..334da1ab94 100644 --- a/packages/objectql/src/seed-loader-org-stamp.test.ts +++ b/packages/objectql/src/seed-loader-org-stamp.test.ts @@ -22,6 +22,7 @@ import { describe, it, expect } from 'vitest'; import { SeedLoaderService } from '@objectstack/metadata-protocol'; import { SeedLoaderConfigSchema } from '@objectstack/spec/data'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; /** * Harness whose business object declares `organization_id` as the engine @@ -39,7 +40,15 @@ function harness() { inserted.push({ object, record }); return { id: `${object}_${inserted.length}` }; }, - update: async () => ({}), + update: async (_o: string, data: any, opts?: any) => { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate — the twin of + // the delete pin, on the same argument: a double looser than the engine it + // stands in for is how #4434 shipped a REST route that 500'd for every + // caller with its suite green, and a predicate update is no less + // destructive than a predicate delete. + assertEngineUpdateDispatch(data, opts); + return {}; + }, }; const metadata = { getObject: async (name: string) => ({ diff --git a/packages/objectql/src/sys-metadata-repository.test.ts b/packages/objectql/src/sys-metadata-repository.test.ts index fa9dbd63dc..789ab46803 100644 --- a/packages/objectql/src/sys-metadata-repository.test.ts +++ b/packages/objectql/src/sys-metadata-repository.test.ts @@ -12,6 +12,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { ConflictError, hashSpec } from '@objectstack/metadata-core'; import { SysMetadataRepository } from '@objectstack/metadata-protocol'; import { assertEngineDeleteDispatch } from './engine-delete-dispatch.js'; +import { assertEngineUpdateDispatch } from './engine-update-dispatch.js'; interface Row { id: string; @@ -114,6 +115,12 @@ function makeFakeEngine() { return { id: row.id }; }, async update(_t: string, data: Record, opts: { where: Record }) { + // [#5480] Pinned to ObjectQL.update's OWN dispatch predicate — the twin of + // the delete pin, on the same argument: a double looser than the engine it + // stands in for is how #4434 shipped a REST route that 500'd for every + // caller with its suite green, and a predicate update is no less + // destructive than a predicate delete. + assertEngineUpdateDispatch(data, opts); const found = findRow(opts.where); if (!found) throw new Error('not found'); rows.set(found.key, { ...found.row, ...(data as any) }); diff --git a/scripts/check-engine-double-contract.mjs b/scripts/check-engine-double-contract.mjs index f0bde88e09..127f490888 100644 --- a/scripts/check-engine-double-contract.mjs +++ b/scripts/check-engine-double-contract.mjs @@ -1,9 +1,10 @@ #!/usr/bin/env node // Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. // -// check-engine-double-contract -- a fake ObjectQL engine's `delete` must be +// check-engine-double-contract -- a fake ObjectQL engine's WRITE VERBS must be // pinned to the real engine's dispatch contract, not a looser hand-written -// approximation of it (objectstack#4550, from objectstack#4434). +// approximation of it (objectstack#4550, from objectstack#4434; the `update` +// slice added by objectstack#5480). // // node scripts/check-engine-double-contract.mjs // node scripts/check-engine-double-contract.mjs --self-test @@ -26,16 +27,30 @@ // introduced for -- which are the paths that were hard to test, which are // usually the paths where the contract is densest. // -// ## Slice: `delete` dispatch only, and only on ENGINE doubles +// ## Slices: the WRITE-VERB dispatches, and only on ENGINE doubles // -// #4550 lists four instances of the family. This gate takes exactly one of -// them, the one whose criterion is mechanically decidable with no judgment -// call: the engine's delete dispatch is a total function from an options bag to -// one of three verdicts, so "is this double looser?" has a yes/no answer that -// does not depend on reading the test's intent. +// #4550 lists four instances of the family. This gate takes the ones whose +// criterion is mechanically decidable with no judgment call: a write verb's +// dispatch is a total function from a call to one of three verdicts, so "is +// this double looser?" has a yes/no answer that does not depend on reading the +// test's intent. // -// Deliberately NOT covered, and why (each wants its own gate, not a vaguer -// version of this one -- a gate whose scope is fuzzy is indistinguishable from +// - `delete` -- `assertEngineDeleteDispatch` / `resolveEngineDeleteDispatch` +// (#4550, from #4434). +// - `update` -- `assertEngineUpdateDispatch` / `resolveEngineUpdateDispatch` +// (#5480). Same three-way dispatch, same destructiveness: a predicate +// update rewrites every matching row's fields. It sat in the "not covered" +// list below until #5480 extracted the producer-side predicate it needed, +// which is the ONLY thing that was ever missing -- the criterion, the +// scanner and the ledger are shared verbatim. +// +// A slice is exactly two facts: which member of the double to look at, and +// which producer-side predicate that member must reach. Everything else -- +// engine-vs-driver attribution, the one-helper-deep indirection, the ledger, +// the both-directions reconciliation -- is one implementation serving both. +// +// Deliberately NOT covered, and why (each wants its own slice, not a vaguer +// version of these -- a gate whose scope is fuzzy is indistinguishable from // no gate to everyone downstream of it): // // - fixtures that disable a platform constraint in prose (`// FK enforcement @@ -45,27 +60,34 @@ // - stubbing the very thing under assertion (objectui#3129) and missing // counterparts (objectui#3134). Both live in the `objectui` repo, which // this script cannot see, and #3134 names no double at all. -// - every other method a fake engine offers (`find` filter semantics, -// `update`'s twin dispatch, unknown-option rejection). Same family, but -// each needs its own producer-side predicate extracted first. `delete` had -// one available because #4434 already paid for it. +// - the READ side and the option surface (`find` filter semantics, +// unknown-option rejection). Same family, but each needs its own +// producer-side predicate extracted first -- the two write verbs have one +// because #4434 and #5480 paid for them. // // ## Invariants // +// Each holds PER SLICE -- a green `delete` slice says nothing about `update`, +// and the ledger is keyed on (file, verb) for the same reason. +// // DISCOVERED the scan found engine doubles at all. Zero is not "a clean // repo", it is a broken scan: PINNED iterates the discovered set, // so a discovery that silently stops matching makes this script // print OK while checking nothing -- the #4868 family, where a // check runs, is green, and structurally cannot reach its subject. -// PINNED every discovered engine double's `delete` routes through -// `assertEngineDeleteDispatch` / `resolveEngineDeleteDispatch` -// from `@objectstack/objectql` -- the predicate the real -// `ObjectQL.delete` itself uses -- or its file carries a measured -// baseline entry. +// PINNED every discovered engine double's verb routes through that +// slice's `assert…Dispatch` / `resolve…Dispatch` from +// `@objectstack/objectql` -- the predicate the real +// `ObjectQL.` itself uses -- or its file carries a measured +// baseline entry for that verb. // RECONCILED in both directions. A baseline entry for a file with no // unguarded doubles left, for a file that no longer exists, or // whose count is now lower, is an error. A ratchet that can only // accrete rots into a list nobody trusts. +// DECLARED every baseline entry names a `verb` this script actually +// scans. Without it a typo'd or retired verb makes an entry +// unreachable -- it would reconcile against nothing, forever, +// and read as a live exemption. // // ## Why "routes through the shared predicate" and not "mirrors the guard" // @@ -78,6 +100,13 @@ // imports the decision cannot be looser than the decision. Same reasoning as // objectstack#4455 -- the scan and the validator must answer with ONE predicate. // +// `update` adds a second way a copy goes wrong, in the opposite direction: its +// id also comes from the PAYLOAD (`data.id`, taken verbatim when truthy, ahead +// of `where` and ahead of `multi`), so a copyist who "improves" the rule by +// scalar-testing `data.id` writes a double STRICTER than the producer, which +// fails calls a running server accepts. Looser hides bugs, stricter invents +// them; importing the decision is the only spelling that does neither. +// // ## What this deliberately does NOT claim // // It checks that the shared predicate is CALLED, not that the double's by-id @@ -95,23 +124,61 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..'); const BASELINE_PATH = join(ROOT, 'scripts', 'engine-double-contract.baseline.json'); const SCAN_ROOTS = ['packages', 'examples']; -/** The producer-side predicate a double must route through. */ -const PINNED_SYMBOLS = new Set(['assertEngineDeleteDispatch', 'resolveEngineDeleteDispatch']); -/** Where it may legitimately come from: the public export, or objectql's own relative path. */ -const PINNED_MODULES = [/^@objectstack\/objectql$/, /engine-delete-dispatch(\.js)?$/]; +/** + * The slices. Each names ONE member of an engine double and the producer-side + * predicate that member must reach; everything else in this file is shared. + * + * `symbols` is the pair the producer exports (`assert…` throws, `resolve…` + * classifies -- a double may legitimately use either), and `modules` is where + * they may come from: the public export, or objectql's own relative path for + * objectql's own tests. + */ +const SLICES = [ + { + verb: 'delete', + producer: 'ObjectQL.delete', + symbols: new Set(['assertEngineDeleteDispatch', 'resolveEngineDeleteDispatch']), + modules: [/^@objectstack\/objectql$/, /engine-delete-dispatch(\.js)?$/], + pinCall: 'assertEngineDeleteDispatch(options)', + origin: '#4434', + }, + { + verb: 'update', + producer: 'ObjectQL.update', + symbols: new Set(['assertEngineUpdateDispatch', 'resolveEngineUpdateDispatch']), + modules: [/^@objectstack\/objectql$/, /engine-update-dispatch(\.js)?$/], + pinCall: 'assertEngineUpdateDispatch(data, options)', + origin: '#5480', + }, +]; + +/** The verbs the ledger may name -- see the DECLARED invariant. */ +const SCANNED_VERBS = new Set(SLICES.map((s) => s.verb)); /** * Members that mark an object literal as standing in for the ENGINE. * - * The engine and the driver both have `delete`, so the sibling set is what + * The engine and the driver share every name here, so the sibling set is what * separates them alongside the parameter test below: drivers speak * `create`/`bulkCreate`/`checkHealth`, the engine speaks `insert`/`findOne`. + * + * A slice never counts its OWN verb as a sibling (`scanSource` filters it), + * which is why `delete` can sit in this set without changing the delete + * slice's discovery by one file: it is evidence for the `update` slice -- + * `{ find, update, delete }` is an engine-shaped trio -- and self-excluded + * for its own. */ const ENGINE_SIBLINGS = new Set([ - 'find', 'findOne', 'insert', 'update', 'count', 'aggregate', 'getSchema', 'registry', 'insertMany', + 'find', 'findOne', 'insert', 'update', 'delete', 'count', 'aggregate', 'getSchema', 'registry', + 'insertMany', ]); -/** Parameter names that mean "this is the DRIVER's delete(object, id, options)". */ +/** + * Parameter names that mean "this is the DRIVER's signature": the primary key + * sits in the second position for both write verbs -- `delete(object, id, + * options)` and `update(object, id, data, options)` -- where the engine takes + * an options bag and a payload respectively. + */ const ID_PARAM = /^_*(id|recordId|ids|pk)$/i; /** @@ -192,12 +259,17 @@ function memberName(member) { } /** - * Is this `delete(a, b, …)` the ENGINE's shape (`object, options`) rather than - * the DRIVER's (`object, id, options`)? + * Is this `(a, b, …)` the ENGINE's shape rather than the DRIVER's? + * (Named `isEngineDeleteShape` until #5480 made it serve both write verbs; the + * body never was delete-specific.) * - * The second parameter is the whole question: the engine takes an options bag - * there, the driver takes a primary key. Judged on the name first (the repo - * writes `id` when it means one) and on a scalar type annotation second. + * engine `delete(object, options)` `update(object, data, options)` + * driver `delete(object, id, options)` `update(object, id, data, options)` + * + * The second parameter is the whole question for both: the engine takes an + * options bag / a payload there, the driver takes a primary key. Judged on the + * name first (the repo writes `id` when it means one) and on a scalar type + * annotation second. * * ## When there IS no second parameter (#5629) * @@ -225,12 +297,27 @@ function memberName(member) { * the native-aggregate driver above, and "no driver members" alone admits any * `{ find, findOne, update, delete }` store mock that is neither contract. */ -function isEngineDeleteShape(fn, memberNames = new Set()) { +function isEngineVerbShape(fn, memberNames = new Set()) { const params = fn.parameters ?? []; + // The DRIVER veto outranks everything, at every arity (#5480). + // + // It used to run only when arity could not answer. That was survivable while + // the only verb was `delete`, whose driver spelling puts a parameter the repo + // consistently names `id` in second position — but it does not survive + // `update`. `plugin.integration.test.ts` writes its fake DRIVERS as + // `update: async (_o: string, _i: any, d: any) => d`: `_i` IS the primary + // key, it just is not spelled `id`, so the parameter test reads the payload + // position as an options bag and admits 19 driver doubles in one file as + // engine doubles. A ledger that records false positives is worse than a + // narrow one — it teaches readers the gate does not know what it is looking + // at. Declaring `connect`/`create`/`syncSchema`/`updateMany` is positive + // evidence of the DRIVER contract at ANY arity (`IDataEngine` declares none + // of them), so it decides first. Same precedence the arity path always used, + // now applied uniformly. + for (const n of memberNames) if (DRIVER_ONLY_MEMBERS.has(n)) return false; if (params.length < 2) { let engineEvidence = false; for (const n of memberNames) { - if (DRIVER_ONLY_MEMBERS.has(n)) return false; if (ENGINE_ONLY_MEMBERS.has(n)) engineEvidence = true; } return engineEvidence; @@ -259,25 +346,27 @@ function calleesIn(node) { } /** - * LOCAL names in this file that are bound to one of the pinned predicates by an - * import from the producer. + * LOCAL names in this file that are bound to THIS SLICE's pinned predicates by + * an import from the producer. * * Keyed on the local binding (so `import { assertEngineDeleteDispatch as guard }` - * still counts) but only when the IMPORTED name is one of the pinned symbols — + * still counts) but only when the IMPORTED name is one of the slice's symbols — * a same-named local look-alike must not qualify, since the whole property is - * that one predicate answers. + * that one predicate answers. Per slice, so a file that pins `delete` and not + * `update` is credited for exactly the one it pinned, which is the state most + * of this repo's doubles are in the day #5480 lands. */ -function pinnedImportsOf(sourceFile) { +function pinnedImportsOf(sourceFile, slice) { const found = new Set(); for (const st of sourceFile.statements) { if (!ts.isImportDeclaration(st) || !ts.isStringLiteral(st.moduleSpecifier)) continue; const spec = st.moduleSpecifier.text; - if (!PINNED_MODULES.some((re) => re.test(spec))) continue; + if (!slice.modules.some((re) => re.test(spec))) continue; const named = st.importClause?.namedBindings; if (named && ts.isNamedImports(named)) { for (const el of named.elements) { const imported = (el.propertyName ?? el.name).text; - if (PINNED_SYMBOLS.has(imported)) found.add(el.name.text); + if (slice.symbols.has(imported)) found.add(el.name.text); } } } @@ -304,16 +393,21 @@ function localFunctions(sourceFile) { } /** - * Every engine double in one file, with a verdict on whether its `delete` is - * pinned to the shared predicate. + * Every engine double in one file, with a verdict on whether the SLICE's verb + * is pinned to that slice's shared predicate. * * Pinning is accepted one level of indirection deep: a fake that opens with a * local `assertDeletable(opts)` helper which itself calls the shared predicate * is pinned. Two hops is not — at that point the gate would be guessing. + * + * One double may be pinned for one verb and not the other; the scan answers + * per slice and the ledger records per slice, because a fake bound to the + * producer on `delete` and hand-waving on `update` is exactly the asymmetry + * #5393 hit and #5480 removed the excuse for. */ -function scanSource(fileName, text) { +function scanSource(fileName, text, slice = SLICES[0]) { const sf = ts.createSourceFile(fileName, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX); - const pinnedNames = pinnedImportsOf(sf); + const pinnedNames = pinnedImportsOf(sf, slice); const locals = localFunctions(sf); const doubles = []; @@ -331,19 +425,21 @@ function scanSource(fileName, text) { const consider = (members, node) => { const names = new Set(); - let del = null; + let target = null; for (const m of members) { const n = memberName(m); if (!n) continue; names.add(n); - if (n === 'delete') del = implOf(m); + if (n === slice.verb) target = implOf(m); } - if (!del) return; - const siblings = [...names].filter((n) => ENGINE_SIBLINGS.has(n)); + if (!target) return; + // The verb under test is never its own sibling: `{ find, update }` is two + // pieces of engine evidence for the update slice, `{ update }` is none. + const siblings = [...names].filter((n) => ENGINE_SIBLINGS.has(n) && n !== slice.verb); if (siblings.length < 2) return; - if (!isEngineDeleteShape(del, names)) return; + if (!isEngineVerbShape(target, names)) return; const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1; - doubles.push({ line, siblings: siblings.sort(), pinned: bodyIsPinned(del) }); + doubles.push({ line, siblings: siblings.sort(), pinned: bodyIsPinned(target) }); }; const visit = (n) => { @@ -364,91 +460,128 @@ function readBaseline() { // ── Audit ─────────────────────────────────────────────────────────────────── -function audit() { - const baseline = readBaseline(); - const byFile = new Map(baseline.entries.map((e) => [e.file, e])); - const errors = []; +/** One slice's scan over the whole tree. */ +function scanSlice(slice) { const found = []; - for (const abs of testFiles()) { const rel = relative(ROOT, abs).split(sep).join('/'); const text = readFileSync(abs, 'utf8'); - // Cheap pre-filter: no `delete` member, nothing to parse. - if (!/\bdelete\s*[(:]/.test(text)) continue; - const doubles = scanSource(abs, text); + // Cheap pre-filter: no member with this verb's name, nothing to parse. + if (!new RegExp(`\\b${slice.verb}\\s*[(:]`).test(text)) continue; + const doubles = scanSource(abs, text, slice); if (doubles.length === 0) continue; found.push({ file: rel, doubles }); } + return found; +} - // DISCOVERED - if (found.length === 0) { - errors.push( - 'DISCOVERED: the scan found no engine doubles anywhere. That is not a clean repo, it is a ' - + 'broken scan — PINNED iterates this set, so every other invariant passes vacuously and ' - + 'this script reports OK while reading nothing. Fix the discovery before trusting a green run.', - ); +function audit() { + const baseline = readBaseline(); + const errors = []; + const slices = []; + + // DECLARED — before anything reconciles, every entry must name a verb this + // script scans. An entry whose verb nothing scans reconciles against nothing + // and reads as a live exemption forever. + for (const entry of baseline.entries) { + if (!SCANNED_VERBS.has(entry.verb)) { + errors.push( + `DECLARED: baseline entry for ${entry.file} names verb ${JSON.stringify(entry.verb)}, which ` + + `no slice scans (known: ${[...SCANNED_VERBS].join(', ')}). An entry no slice reaches is ` + + 'an exemption nothing can ever retire — fix the verb or delete the entry.', + ); + } } - const seen = new Set(); - for (const { file, doubles } of found) { - const unguarded = doubles.filter((d) => !d.pinned); - const entry = byFile.get(file); - if (entry) seen.add(file); + for (const slice of SLICES) { + const found = scanSlice(slice); + const byFile = new Map( + baseline.entries.filter((e) => e.verb === slice.verb).map((e) => [e.file, e]), + ); + slices.push({ slice, found }); + + // DISCOVERED + if (found.length === 0) { + errors.push( + `DISCOVERED: the ${slice.verb} scan found no engine doubles anywhere. That is not a clean ` + + 'repo, it is a broken scan — PINNED iterates this set, so every other invariant passes ' + + 'vacuously and this script reports OK while reading nothing. Fix the discovery before ' + + 'trusting a green run.', + ); + } - if (unguarded.length === 0) { - if (entry) { + const seen = new Set(); + for (const { file, doubles } of found) { + const unguarded = doubles.filter((d) => !d.pinned); + const entry = byFile.get(file); + if (entry) seen.add(file); + + if (unguarded.length === 0) { + if (entry) { + errors.push( + `RECONCILED [${slice.verb}]: ${file} has no unguarded engine double left, but the ` + + `baseline still records ${entry.unguarded}. Delete the entry in the same PR that ` + + 'fixed it.', + ); + } + continue; + } + if (!entry) { errors.push( - `RECONCILED: ${file} has no unguarded engine double left, but the baseline still ` - + `records ${entry.unguarded}. Delete the entry in the same PR that fixed it.`, + `PINNED [${slice.verb}]: ${file} declares ${unguarded.length} engine double(s) whose ` + + `${slice.verb}() does not route through ${[...slice.symbols][0]} ` + + `(line${unguarded.length > 1 ? 's' : ''} ${unguarded.map((d) => d.line).join(', ')}). ` + + `A fake looser than ${slice.producer} is how #4434 shipped a dead REST route with its ` + + `suite green. Open the fake's ${slice.verb} with \`${slice.pinCall}\` from ` + + "'@objectstack/objectql' (add it as a devDependency if the package lacks it), or add a " + + 'MEASURED entry to scripts/engine-double-contract.baseline.json saying why not — with ' + + `"verb": ${JSON.stringify(slice.verb)}.`, + ); + continue; + } + if (unguarded.length > entry.unguarded) { + errors.push( + `PINNED [${slice.verb}]: ${file} now has ${unguarded.length} unguarded engine double(s), ` + + `baseline records ${entry.unguarded}. The baseline is shrink-only — pin the new one ` + + 'rather than raising it.', + ); + } else if (unguarded.length < entry.unguarded) { + errors.push( + `RECONCILED [${slice.verb}]: ${file} is down to ${unguarded.length} unguarded engine ` + + `double(s) from the baseline's ${entry.unguarded}. Lower the number in the same PR, so ` + + 'the ratchet holds.', ); } - continue; - } - if (!entry) { - errors.push( - `PINNED: ${file} declares ${unguarded.length} engine double(s) whose delete() does not route ` - + `through assertEngineDeleteDispatch (line${unguarded.length > 1 ? 's' : ''} ` - + `${unguarded.map((d) => d.line).join(', ')}). A fake looser than ObjectQL.delete is how ` - + '#4434 shipped a dead REST route with its suite green. Open the fake\'s delete with ' - + "`assertEngineDeleteDispatch(options)` from '@objectstack/objectql' (add it as a " - + 'devDependency if the package lacks it), or add a MEASURED entry to ' - + 'scripts/engine-double-contract.baseline.json saying why not.', - ); - continue; } - if (unguarded.length > entry.unguarded) { - errors.push( - `PINNED: ${file} now has ${unguarded.length} unguarded engine double(s), baseline records ` - + `${entry.unguarded}. The baseline is shrink-only — pin the new one rather than raising it.`, - ); - } else if (unguarded.length < entry.unguarded) { + + for (const entry of baseline.entries) { + if (entry.verb !== slice.verb || seen.has(entry.file)) continue; errors.push( - `RECONCILED: ${file} is down to ${unguarded.length} unguarded engine double(s) from the ` - + `baseline's ${entry.unguarded}. Lower the number in the same PR, so the ratchet holds.`, + `RECONCILED [${slice.verb}]: baseline entry for ${entry.file}, which declares no engine ` + + `double with a ${slice.verb} any more (file deleted, fake removed, or the shape ` + + 'changed). Delete the entry.', ); } } - for (const entry of baseline.entries) { - if (seen.has(entry.file)) continue; - errors.push( - `RECONCILED: baseline entry for ${entry.file}, which declares no engine double any more ` - + '(file deleted, fake removed, or the shape changed). Delete the entry.', - ); - } - - return { found, baseline, errors }; + return { slices, baseline, errors }; } function report() { - const { found, baseline, errors } = audit(); - const doubles = found.reduce((n, f) => n + f.doubles.length, 0); - const pinned = found.reduce((n, f) => n + f.doubles.filter((d) => d.pinned).length, 0); + const { slices, baseline, errors } = audit(); - console.log( - `\nengine doubles: ${doubles} in ${found.length} test file(s) — ${pinned} pinned to ` - + `ObjectQL.delete's dispatch predicate, ${doubles - pinned} in the shrink-only baseline.\n`, - ); + console.log(''); + let totalPinned = 0; + for (const { slice, found } of slices) { + const doubles = found.reduce((n, f) => n + f.doubles.length, 0); + const pinned = found.reduce((n, f) => n + f.doubles.filter((d) => d.pinned).length, 0); + totalPinned += pinned; + console.log( + `${slice.verb} doubles: ${doubles} in ${found.length} test file(s) — ${pinned} pinned to ` + + `${slice.producer}'s dispatch predicate, ${doubles - pinned} in the shrink-only baseline.`, + ); + } + console.log(''); if (errors.length) { for (const e of errors) console.error(` x ${e}`); @@ -456,8 +589,10 @@ function report() { process.exit(1); } - for (const f of found.filter((f) => f.doubles.some((d) => d.pinned))) { - console.log(` pinned ${f.file}`); + for (const { slice, found } of slices) { + for (const f of found.filter((f) => f.doubles.some((d) => d.pinned))) { + console.log(` pinned [${slice.verb}] ${f.file}`); + } } // Print the EXEMPT reasons, not only the count. An entry whose justification @@ -466,13 +601,13 @@ function report() { const exempt = baseline.entries.filter((e) => e.kind === 'EXEMPT'); if (exempt.length) console.log(''); for (const e of exempt) { - console.log(` EXEMPT ${e.file}`); + console.log(` EXEMPT [${e.verb}] ${e.file}`); console.log(` ${e.why}`); } console.log(''); const debt = baseline.entries.filter((e) => e.kind !== 'EXEMPT').length; console.log( - `check-engine-double-contract: OK — ${pinned} pinned, ${debt} in the DEBT ledger, ` + `check-engine-double-contract: OK — ${totalPinned} pinned, ${debt} in the DEBT ledger, ` + `${exempt.length} exempt.\n`, ); } @@ -665,18 +800,164 @@ const store = { expect('a file with no fakes yields no doubles', scanSource('empty.test.ts', 'export const x = 1;\n').length === 0); - // Discovery must reach the real tree, and specifically must reach the fake - // #4434 was shipped past. Everything above is synthetic; this is the wiring. + // ── The `update` slice (#5480). + // + // Same detector, second verb. Driven on both sides of every decision again + // rather than trusted to generalise: the two slices differ in exactly the + // places that could silently mis-fire — `update` IS one of the engine + // siblings (so it must not count itself), and the driver's `update` carries + // its primary key in the same second position `delete`'s does but with a + // payload behind it. + const U = SLICES.find((s) => s.verb === 'update'); + const UIMPORT = "import { assertEngineUpdateDispatch } from '@objectstack/objectql';\n"; + const engineFakeU = (updateBody, header = '') => `${header} +function makeEngine() { + return { + async find(o: string, opts?: any) { return []; }, + async insert(o: string, data: any) { return data; }, + async delete(o: string, opts?: any) { return true; }, + async update(o: string, data: any, opts?: any) { ${updateBody} }, + }; +} +`; + + d = scanSource('u.test.ts', engineFakeU('return data;'), U); + expect('finds an unpinned engine update double', d.length === 1 && d[0].pinned === false); + + d = scanSource('u.test.ts', engineFakeU('assertEngineUpdateDispatch(data, opts); return data;', UIMPORT), U); + expect('a directly pinned update double is not flagged', d.length === 1 && d[0].pinned === true); + + d = scanSource('u.test.ts', engineFakeU('assertUpdatable(data, opts); return data;', + UIMPORT + 'function assertUpdatable(dd: any, o: any) { assertEngineUpdateDispatch(dd, o); }\n'), U); + expect('a local helper that calls the update predicate counts as pinned', + d.length === 1 && d[0].pinned === true); + + d = scanSource('u.test.ts', engineFakeU('assertUpdatable(data, opts); return data;', + UIMPORT + 'function assertUpdatable(dd: any, o: any) { if (!dd?.id && !o?.where?.id && !o?.multi) throw new Error("x"); }\n'), U); + expect('a HAND-MIRRORED update helper does not count as pinned', + d.length === 1 && d[0].pinned === false); + + d = scanSource('u.test.ts', engineFakeU('return data;', UIMPORT), U); + expect('an unused update import is not pinning', d.length === 1 && d[0].pinned === false); + + // The slices must not cross-credit. This is the whole reason the ledger is + // keyed on (file, verb): #5393's fake is the live specimen — pinned on + // `delete`, hand-waving on `update` — and a scan that let the delete pin + // vouch for the update would report the asymmetry as fixed. + const pinnedDeleteOnly = `${IMPORT} +const engine = { + async find(o: string, opts?: any) { return []; }, + async insert(o: string, data: any) { return data; }, + async update(o: string, data: any, opts?: any) { return data; }, + async delete(o: string, opts?: any) { assertEngineDeleteDispatch(opts); return true; }, +}; +`; + expect('a delete-pinned fake is still unpinned for update', + scanSource('x.test.ts', pinnedDeleteOnly, U).length === 1 + && scanSource('x.test.ts', pinnedDeleteOnly, U)[0].pinned === false); + expect('…and the same fake IS pinned for delete', + scanSource('x.test.ts', pinnedDeleteOnly)[0].pinned === true); + // The sharp case, and the one a copy-paste actually produces: the WRONG + // slice's predicate called from inside the right verb's body. It reads as a + // pin, it imports from the producer, and it answers a different question — + // `assertEngineDeleteDispatch(opts)` never looks at `data.id`, so a double + // guarded by it rejects `update(o, { id: 'r1' })`, which the engine accepts. + // Nothing above catches this: the earlier fixtures differ in which BODY calls + // the predicate, so a scan that credited symbols across slices would still + // pass them. + const crossSlicePredicate = `${IMPORT} +const engine = { + async find(o: string, opts?: any) { return []; }, + async insert(o: string, data: any) { return data; }, + async delete(o: string, opts?: any) { return true; }, + async update(o: string, data: any, opts?: any) { assertEngineDeleteDispatch(opts); return data; }, +}; +`; + expect("delete's predicate inside update() does not pin the update slice", + scanSource('xs.test.ts', crossSlicePredicate, U)[0].pinned === false); + // The mirror image, so neither direction is the one that happens to work. + const pinnedUpdateOnly = `${UIMPORT} +const engine = { + async find(o: string, opts?: any) { return []; }, + async insert(o: string, data: any) { return data; }, + async update(o: string, data: any, opts?: any) { assertEngineUpdateDispatch(data, opts); return data; }, + async delete(o: string, opts?: any) { return true; }, +}; +`; + expect('an update-pinned fake is still unpinned for delete', + scanSource('x.test.ts', pinnedUpdateOnly)[0].pinned === false); + expect('…and the same fake IS pinned for update', + scanSource('x.test.ts', pinnedUpdateOnly, U)[0].pinned === true); + + // Scope: the DRIVER's update(object, id, data, options) is a different + // contract — the primary key sits where the engine takes the payload. + const driverUpdate = ` +const driver = { + async find(o: string) { return []; }, + async create(o: string, d: any) { return d; }, + async delete(object: string, id: string) { return true; }, + async update(object: string, id: string, data: any) { return data; }, +}; +`; + expect('a driver double (update by scalar id) is out of scope for the update slice', + scanSource('du.test.ts', driverUpdate, U).length === 0); + + // Arity, both halves, exactly as for delete: sibling evidence decides when + // the parameter list cannot. + const zeroArityEngineU = ` +const engine = { + async find(o: string) { return []; }, + async findOne(o: string) { return null; }, + async insert(o: string, d: any) { return d; }, + async delete(o: string, opts?: any) { return true; }, + async update() { return null; }, +}; +`; + expect('a zero-parameter engine update is in scope', + scanSource('zu.test.ts', zeroArityEngineU, U).length === 1); + + const zeroArityDriverU = ` +const driver = { + async find(o: string) { return []; }, + async findOne(o: string) { return null; }, + async create(o: string, d: any) { return d; }, + async checkHealth() { return true; }, + async update() { return null; }, +}; +`; + expect('a zero-parameter DRIVER update stays out of scope', + scanSource('zdu.test.ts', zeroArityDriverU, U).length === 0); + + // `update` must not count itself as its own sibling, or a lone `update` on + // any object literal becomes a finding. + const loneUpdate = 'const store = { update(k: string, v: any) { return v; } };\n'; + expect('an object whose only engine member IS update is out of scope', + scanSource('lu.test.ts', loneUpdate, U).length === 0); + + // objectql's own tests import by relative path; that IS the producer — and + // the update slice must accept its OWN module, not delete's. + d = scanSource('su.test.ts', engineFakeU('assertEngineUpdateDispatch(data, opts); return data;', + "import { assertEngineUpdateDispatch } from './engine-update-dispatch.js';\n"), U); + expect("objectql's relative import of the update producer counts", + d.length === 1 && d[0].pinned === true); + + // Discovery must reach the real tree, for EVERY slice, and specifically must + // reach the fake #4434 was shipped past. Everything above is synthetic; this + // is the wiring. // // Deliberately NOT asserted here: that the real tree is clean, or that any // particular fake is pinned. That is the job of the run this self-test gates, // and duplicating it would make a genuine violation surface as a self-test // failure — the least legible message available. - const { found } = audit(); - expect('discovers engine doubles in the real tree', found.length > 0); + const { slices } = audit(); + expect('every slice is exercised', slices.length === SLICES.length); + for (const { slice, found } of slices) { + expect(`discovers engine doubles in the real tree [${slice.verb}]`, found.length > 0); + } expect( 'discovery reaches the #4434 fake', - found.some((f) => f.file === 'packages/plugins/plugin-sharing/src/sharing-rule.test.ts'), + slices.find((s) => s.slice.verb === 'delete').found + .some((f) => f.file === 'packages/plugins/plugin-sharing/src/sharing-rule.test.ts'), ); if (failures.length) { @@ -685,10 +966,11 @@ const store = { process.exit(1); } console.log( - 'OK self-test: separates engine doubles from driver doubles, admits a delete that declares ' - + 'fewer than two parameters only on engine-vs-driver sibling evidence, accepts only the ' - + 'producer\'s predicate (direct or one helper deep), rejects unused imports, hand-mirrored ' - + 'guards and look-alikes, and proves discovery reaches the real tree.', + 'OK self-test: separates engine doubles from driver doubles on BOTH write verbs, admits a ' + + 'verb that declares fewer than two parameters only on engine-vs-driver sibling evidence, ' + + "accepts only that slice's producer predicate (direct or one helper deep) and never the " + + 'other slice\'s, rejects unused imports, hand-mirrored guards and look-alikes, and proves ' + + 'discovery reaches the real tree for every slice.', ); } diff --git a/scripts/engine-double-contract.baseline.json b/scripts/engine-double-contract.baseline.json index 3c593a135c..35797e3b5e 100644 --- a/scripts/engine-double-contract.baseline.json +++ b/scripts/engine-double-contract.baseline.json @@ -1,17 +1,24 @@ { "$comment": [ - "Measured baseline for scripts/check-engine-double-contract.mjs (#4550).", + "Measured baseline for scripts/check-engine-double-contract.mjs (#4550; the update slice #5480).", "", - "Each entry is a test file that declares a fake ObjectQL engine whose `delete` does NOT", - "route through `assertEngineDeleteDispatch` from @objectstack/objectql — so it may accept", - "a call the real engine refuses, which is exactly how #4434 shipped a dead REST route with", - "its suite green.", + "Each entry is ONE (file, verb) pair: a test file declaring a fake ObjectQL engine whose", + "`verb` does NOT route through that slice's producer-side predicate from @objectstack/objectql", + "— so it may accept a call the real engine refuses, which is exactly how #4434 shipped a dead", + "REST route with its suite green.", "", - "SHRINK-ONLY and hand-edited under review. The checker fails in BOTH directions: a file", - "whose count went down must have its entry lowered or deleted in the same PR, and a file", - "with no unguarded double left must lose its entry entirely. There is deliberately no", - "`--fix`/`--update` flag — a generator would let a new lax fake be admitted by 'just run", - "the update command', which is precisely how a gate stops meaning anything.", + "`verb` is REQUIRED and must name a slice the checker actually scans (currently `delete` and", + "`update` — the DECLARED invariant). It is not defaulted: an entry that inherited a verb would", + "be an exemption whose scope nobody could read, and the two verbs genuinely differ — see the", + "script header on `data.id`, which `update` takes verbatim and `delete` has no analogue for.", + "A file may therefore carry two entries, one per verb, and a fake pinned on `delete` earns", + "nothing for `update`; that asymmetry is the whole finding #5480 came from (#5393).", + "", + "SHRINK-ONLY and hand-edited under review. The checker fails in BOTH directions: a (file, verb)", + "whose count went down must have its entry lowered or deleted in the same PR, and one with no", + "unguarded double left must lose its entry entirely. There is deliberately no `--fix`/`--update`", + "flag — a generator would let a new lax fake be admitted by 'just run the update command', which", + "is precisely how a gate stops meaning anything.", "", "`unguarded` is measured by the scan, not asserted by hand. `why` says what stands between", "the file and being pinned; `closes` says what removes the entry.", @@ -19,146 +26,321 @@ "WHAT THESE ENTRIES DO AND DO NOT CLAIM. They record that the double is structurally", "looser than the contract. They do NOT claim the looseness is currently harmless — proving", "that per file means running each suite with the guard installed, and the one package where", - "that was done wholesale in this PR (@objectstack/spec, 295 files) passed. The rest is", + "that was done wholesale in #4550 (@objectstack/spec, 295 files) passed. The rest is", "unproven either way, which is the honest state and the reason each entry is DEBT rather", - "than EXEMPT." + "than EXEMPT. The `update` entries are weaker still and say so individually: they carry no", + "per-file dormancy probe at all, because the update slice landed with 116 unguarded doubles", + "in one act — there was no producer-side predicate to route through before #5480, so none of", + "them could have been pinned however carefully they were written." ], "entries": [ { "file": "packages/cli/src/commands/serve-email-appname-precedence.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 164. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/cli/src/commands/serve-email-appname-precedence.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 164. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/cli/src/commands/serve-email-persist.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 121. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/cli/src/commands/serve-email-persist.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 121. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/cloud-connection/src/marketplace-install-local-seed-lookup.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 81. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/cloud-connection --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/cloud-connection/src/marketplace-install-local-seed-lookup.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 81. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/cloud-connection in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/cloud-connection/src/marketplace-install-local-state-machine-exempt.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 100. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/cloud-connection --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/cloud-connection/src/marketplace-install-local-state-machine-exempt.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 100. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/cloud-connection in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/core/src/utils/migration-journal.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 34. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/core, both `dependencies`), so any reverse edge closes a cycle by construction. Measured on this branch: the edge was added to @objectstack/core's devDependencies and turbo 2.10.7 refused the graph outright — `WARNING Circular package dependency detected: @objectstack/driver-sql, @objectstack/driver-sqlite-wasm, @objectstack/metadata, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/core` and `x Cyclic dependency detected:` from `turbo run build --filter=@objectstack/core --dry` — then the edge was reverted. Same route, same refusal the #4987 and #5206 entries in this ledger already record for metadata-protocol.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/core/src/utils/migration-journal.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 34. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/core in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/migrations/recorded-by-sentinel.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 35. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol, both `dependencies`), so any reverse edge closes a cycle by construction. Measured twice already, and this entry does not re-measure: the #4987 and #5206 entries in this ledger added the edge to @objectstack/metadata-protocol's devDependencies and recorded turbo's outright refusal. This package is also named in the cycle turbo printed on THIS branch when the same edge was added to @objectstack/core.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata-protocol/src/migrations/recorded-by-sentinel.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 35. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol-publish-drafts-endpoint-gate.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5206): the devDependency route this ledger's other metadata-protocol entries prescribe is CYCLIC, re-measured on this branch rather than cited. @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies` (`workspace:*`), so the reverse edge was added, `pnpm install` run, and turbo refused the graph outright — `WARNING Circular package dependency detected: @objectstack/objectql, @objectstack/metadata-protocol` / `x Cyclic dependency detected: @objectstack/objectql#build, @objectstack/metadata-protocol#build` (turbo 2.10.7, `turbo run build --filter=@objectstack/metadata-protocol --dry`) — then the edge and lockfile were reverted. Same cycle, same measurement method as the #4867 and #4981 entries below; this is the sixth metadata-protocol file to hit the route those entries already recorded as closed. The fake's delete was probed by replacing it with a throw: it IS exercised (the promote drops the published draft row) and only ever as `{ where: { id } }`, a scalar by-id delete routed through SysMetadataRepository.delete, so removing the method to escape the scan was not available either. But that is an argument about this file, not about the contract, so the entry stays DEBT rather than EXEMPT per this ledger's own rule.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #4987 — then open the fake's delete with it; the devDependency route is closed by the cycle above, for this file and for the five sibling metadata-protocol entries alike" }, + { + "file": "packages/metadata-protocol/src/protocol-publish-drafts-endpoint-gate.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 122. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#4987): the devDependency route this entry used to prescribe DOES NOT EXIST — it is cyclic, not merely unreviewed. @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies` (`workspace:*`), so any reverse edge closes a cycle by construction. Re-measured on #4987's branch rather than cited: the edge was added to metadata-protocol's devDependencies and turbo 2.10.7 refused BOTH task graphs outright — `WARNING Circular package dependency detected: @objectstack/objectql, @objectstack/metadata-protocol` / `x Cyclic dependency detected: @objectstack/objectql#build, @objectstack/metadata-protocol#build`, exit 1 from `turbo run build --filter=@objectstack/metadata-protocol --dry` and from the same command with `test` — then the edge was reverted. This is exactly the criterion the `packages/spec/src/contracts/data-engine.test.ts` EXEMPT entry below already states ('it cannot be pinned even in principle ... the import would invert the dependency'); it was simply never applied to the metadata-protocol entries. The entry stays DEBT and not EXEMPT because what cannot exist is the devDependency ROUTE, while the entry itself is closable by sinking the predicate — see `closes`. Whether this file's own fake delete is currently exercised was NOT probed: #4987's file face is this ledger's text only. Per this ledger's own rule that changes nothing, since it would be an argument about this file rather than about the contract.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. Verified available on #4987's branch: @objectstack/objectql and @objectstack/metadata-protocol both already depend on @objectstack/metadata-core (`workspace:*`), whose own `dependencies` are just @objectstack/spec + zod and do NOT include objectql, so the sink adds no new edge; the producer `packages/objectql/src/engine-delete-dispatch.ts` has zero imports, so this is a move and not a refactor. @objectstack/spec/contracts is the other candidate, but only if the predicate belongs to the contract layer — do not pick it by default. The devDependency route is closed by the cycle recorded in `why`, for this file and for every other metadata-protocol entry in this ledger alike." }, + { + "file": "packages/metadata-protocol/src/protocol-publish-drafts-org-scope.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 119. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.code-only-types.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 88. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol, both `dependencies`), so any reverse edge closes a cycle by construction. Measured twice already, and this entry does not re-measure: the #4987 and #5206 entries in this ledger added the edge to @objectstack/metadata-protocol's devDependencies and recorded turbo's outright refusal. This package is also named in the cycle turbo printed on THIS branch when the same edge was added to @objectstack/core.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata-protocol/src/protocol.code-only-types.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 88. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.read-decorations.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 60. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol, both `dependencies`), so any reverse edge closes a cycle by construction. Measured twice already, and this entry does not re-measure: the #4987 and #5206 entries in this ledger added the edge to @objectstack/metadata-protocol's devDependencies and recorded turbo's outright refusal. This package is also named in the cycle turbo printed on THIS branch when the same edge was added to @objectstack/core.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata-protocol/src/protocol.read-decorations.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 60. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#4987): the devDependency route this entry used to prescribe DOES NOT EXIST — it is cyclic, not merely unreviewed. @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies` (`workspace:*`), so any reverse edge closes a cycle by construction. Re-measured on #4987's branch rather than cited: the edge was added to metadata-protocol's devDependencies and turbo 2.10.7 refused BOTH task graphs outright — `WARNING Circular package dependency detected: @objectstack/objectql, @objectstack/metadata-protocol` / `x Cyclic dependency detected: @objectstack/objectql#build, @objectstack/metadata-protocol#build`, exit 1 from `turbo run build --filter=@objectstack/metadata-protocol --dry` and from the same command with `test` — then the edge was reverted. This is exactly the criterion the `packages/spec/src/contracts/data-engine.test.ts` EXEMPT entry below already states ('it cannot be pinned even in principle ... the import would invert the dependency'); it was simply never applied to the metadata-protocol entries. The entry stays DEBT and not EXEMPT because what cannot exist is the devDependency ROUTE, while the entry itself is closable by sinking the predicate — see `closes`. Whether this file's own fake delete is currently exercised was NOT probed: #4987's file face is this ledger's text only. Per this ledger's own rule that changes nothing, since it would be an argument about this file rather than about the contract.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. Verified available on #4987's branch: @objectstack/objectql and @objectstack/metadata-protocol both already depend on @objectstack/metadata-core (`workspace:*`), whose own `dependencies` are just @objectstack/spec + zod and do NOT include objectql, so the sink adds no new edge; the producer `packages/objectql/src/engine-delete-dispatch.ts` has zero imports, so this is a move and not a refactor. @objectstack/spec/contracts is the other candidate, but only if the predicate belongs to the contract layer — do not pick it by default. The devDependency route is closed by the cycle recorded in `why`, for this file and for every other metadata-protocol entry in this ledger alike." }, + { + "file": "packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 86. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.save-flow-canonicalization.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#4987): the devDependency route this entry used to prescribe DOES NOT EXIST — it is cyclic, not merely unreviewed. @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies` (`workspace:*`), so any reverse edge closes a cycle by construction. Re-measured on #4987's branch rather than cited: the edge was added to metadata-protocol's devDependencies and turbo 2.10.7 refused BOTH task graphs outright — `WARNING Circular package dependency detected: @objectstack/objectql, @objectstack/metadata-protocol` / `x Cyclic dependency detected: @objectstack/objectql#build, @objectstack/metadata-protocol#build`, exit 1 from `turbo run build --filter=@objectstack/metadata-protocol --dry` and from the same command with `test` — then the edge was reverted. This is exactly the criterion the `packages/spec/src/contracts/data-engine.test.ts` EXEMPT entry below already states ('it cannot be pinned even in principle ... the import would invert the dependency'); it was simply never applied to the metadata-protocol entries. The entry stays DEBT and not EXEMPT because what cannot exist is the devDependency ROUTE, while the entry itself is closable by sinking the predicate — see `closes`. Whether this file's own fake delete is currently exercised was NOT probed: #4987's file face is this ledger's text only. Per this ledger's own rule that changes nothing, since it would be an argument about this file rather than about the contract.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. Verified available on #4987's branch: @objectstack/objectql and @objectstack/metadata-protocol both already depend on @objectstack/metadata-core (`workspace:*`), whose own `dependencies` are just @objectstack/spec + zod and do NOT include objectql, so the sink adds no new edge; the producer `packages/objectql/src/engine-delete-dispatch.ts` has zero imports, so this is a move and not a refactor. @objectstack/spec/contracts is the other candidate, but only if the predicate belongs to the contract layer — do not pick it by default. The devDependency route is closed by the cycle recorded in `why`, for this file and for every other metadata-protocol entry in this ledger alike." }, + { + "file": "packages/metadata-protocol/src/protocol.save-flow-canonicalization.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 89. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.save-union-issues.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 45. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol, both `dependencies`), so any reverse edge closes a cycle by construction. Measured twice already, and this entry does not re-measure: the #4987 and #5206 entries in this ledger added the edge to @objectstack/metadata-protocol's devDependencies and recorded turbo's outright refusal. This package is also named in the cycle turbo printed on THIS branch when the same edge was added to @objectstack/core.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata-protocol/src/protocol.save-union-issues.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 45. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.stored-conversions.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 51. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol, both `dependencies`), so any reverse edge closes a cycle by construction. Measured twice already, and this entry does not re-measure: the #4987 and #5206 entries in this ledger added the edge to @objectstack/metadata-protocol's devDependencies and recorded turbo's outright refusal. This package is also named in the cycle turbo printed on THIS branch when the same edge was added to @objectstack/core.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata-protocol/src/protocol.stored-conversions.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 51. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/protocol.stored-migration.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 73. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol, both `dependencies`), so any reverse edge closes a cycle by construction. Measured twice already, and this entry does not re-measure: the #4987 and #5206 entries in this ledger added the edge to @objectstack/metadata-protocol's devDependencies and recorded turbo's outright refusal. This package is also named in the cycle turbo printed on THIS branch when the same edge was added to @objectstack/core.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata-protocol/src/protocol.stored-migration.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 73. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/sys-metadata-repository.draft-drain.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#4981): identical to the sibling `sys-metadata-repository.history-counters.test.ts` entry below, and closed by the same route. The devDependency this ledger's other metadata-protocol entries prescribe is CYCLIC, not merely unreviewed: @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies` (re-verified statically on this branch), so adding objectql to metadata-protocol's devDependencies makes turbo refuse the graph outright — `Cyclic dependency detected: @objectstack/metadata-protocol#build, @objectstack/objectql#build`, measured in #4867 by adding the edge and reverting it, and deliberately NOT re-run here. The fake's delete is exercised only by the #4981 drain path and is a by-id delete routed through SysMetadataRepository.delete, but that is an argument about this file, not about the contract, so the entry stays DEBT rather than EXEMPT per this ledger's own rule.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on (@objectstack/metadata-core is the common dep; @objectstack/spec/contracts is the other candidate) — tracked as #4987 — then open the fake's delete with it; the devDependency route is closed by the cycle above, for this file and for the five sibling metadata-protocol entries alike" }, + { + "file": "packages/metadata-protocol/src/sys-metadata-repository.draft-drain.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 77. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/sys-metadata-repository.history-counters.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#4867): the devDependency this ledger's sibling entries prescribe is not available here — it is CYCLIC, not merely unreviewed. @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies`, so adding objectql to metadata-protocol's devDependencies makes turbo refuse the graph outright: `Cyclic dependency detected: @objectstack/metadata-protocol#build, @objectstack/objectql#build` (turbo 2.10.7, `turbo run test --filter=@objectstack/metadata-protocol --dry`, measured by adding the edge and reverting it). The fake's delete is exercised by one test (the #4867 delete path) and is a by-id delete routed through SysMetadataRepository.delete, but that is an argument about this file, not about the contract, so the entry stays DEBT rather than EXEMPT per this ledger's own rule.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on (@objectstack/metadata-core is the common dep; @objectstack/spec/contracts is the other candidate), then open the fake's delete with it — the devDependency route is closed by the cycle above, for this file and for the four sibling metadata-protocol entries alike" }, + { + "file": "packages/metadata-protocol/src/sys-metadata-repository.history-counters.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 91. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata-protocol/src/sys-metadata-repository.recorded-by.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#4987): the devDependency route this entry used to prescribe DOES NOT EXIST — it is cyclic, not merely unreviewed. @objectstack/objectql depends on @objectstack/metadata-protocol in `dependencies` (`workspace:*`), so any reverse edge closes a cycle by construction. Re-measured on #4987's branch rather than cited: the edge was added to metadata-protocol's devDependencies and turbo 2.10.7 refused BOTH task graphs outright — `WARNING Circular package dependency detected: @objectstack/objectql, @objectstack/metadata-protocol` / `x Cyclic dependency detected: @objectstack/objectql#build, @objectstack/metadata-protocol#build`, exit 1 from `turbo run build --filter=@objectstack/metadata-protocol --dry` and from the same command with `test` — then the edge was reverted. This is exactly the criterion the `packages/spec/src/contracts/data-engine.test.ts` EXEMPT entry below already states ('it cannot be pinned even in principle ... the import would invert the dependency'); it was simply never applied to the metadata-protocol entries. The entry stays DEBT and not EXEMPT because what cannot exist is the devDependency ROUTE, while the entry itself is closable by sinking the predicate — see `closes`. Whether this file's own fake delete is currently exercised was NOT probed: #4987's file face is this ledger's text only. Per this ledger's own rule that changes nothing, since it would be an argument about this file rather than about the contract.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. Verified available on #4987's branch: @objectstack/objectql and @objectstack/metadata-protocol both already depend on @objectstack/metadata-core (`workspace:*`), whose own `dependencies` are just @objectstack/spec + zod and do NOT include objectql, so the sink adds no new edge; the producer `packages/objectql/src/engine-delete-dispatch.ts` has zero imports, so this is a move and not a refactor. @objectstack/spec/contracts is the other candidate, but only if the predicate belongs to the contract layer — do not pick it by default. The devDependency route is closed by the cycle recorded in `why`, for this file and for every other metadata-protocol entry in this ledger alike." }, + { + "file": "packages/metadata-protocol/src/sys-metadata-repository.recorded-by.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 49. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata-protocol in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/metadata/src/migrations/migrate-sys-notification-to-event.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 40. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it (@objectstack/objectql -> @objectstack/metadata-protocol -> @objectstack/metadata, every edge `dependencies`), so any reverse edge closes a cycle by construction. This package's own edge was NOT probed separately on this branch, and does not need to be: @objectstack/metadata is named IN the cycle turbo 2.10.7 printed when the same edge was added to @objectstack/core here — `@objectstack/driver-sql, @objectstack/driver-sqlite-wasm, @objectstack/metadata, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/core`. Stated plainly so the next reader knows which measurement this rests on.", "closes": "sink assertEngineDeleteDispatch into a package BOTH sides already depend on — tracked as #5619 — then open the fake's delete with it. The devDependency route is closed by the cycle recorded in `why`, exactly as for the metadata-protocol entries in this ledger." }, + { + "file": "packages/metadata/src/migrations/migrate-sys-notification-to-event.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 40. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. This entry does not re-measure — the delete-slice entries for @objectstack/metadata in this same ledger added the edge and recorded turbo's outright refusal, and the blocker is a property of the dependency graph, not of the verb. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, { "file": "packages/objectql/src/protocol-boot-hydration-scoped.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 59. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. This IS the producer's own package: `./engine-delete-dispatch.js` is a relative import away, exactly as objectql's already-pinned tests import it. A one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, not adoption.", @@ -166,325 +348,1027 @@ }, { "file": "packages/objectql/src/protocol-registry-shadow.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 255. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. This IS the producer's own package: `./engine-delete-dispatch.js` is a relative import away, exactly as objectql's already-pinned tests import it. A one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, not adoption.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) imported from ./engine-delete-dispatch.js, and run the package's suite" }, + { + "file": "packages/platform-objects/src/plugin.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 96. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. Measured on this branch, not cited: the edge was added to @objectstack/platform-objects's devDependencies and turbo 2.10.7 refused the graph — `WARNING Circular package dependency detected: @objectstack/metadata, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/platform-objects` and `x Cyclic dependency detected:` from `turbo run build --filter=@objectstack/platform-objects --dry` — then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, + { + "file": "packages/platform-objects/src/system/migration-flag.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 18. The devDependency route DOES NOT EXIST for this package: @objectstack/objectql depends on it, so any reverse edge closes a cycle by construction. Measured on this branch, not cited: the edge was added to @objectstack/platform-objects's devDependencies and turbo 2.10.7 refused the graph — `WARNING Circular package dependency detected: @objectstack/metadata, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/platform-objects` and `x Cyclic dependency detected:` from `turbo run build --filter=@objectstack/platform-objects --dry` — then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "sink assertEngineUpdateDispatch into a package BOTH sides already depend on — the same blocker #5619 tracks for assertEngineDeleteDispatch, and one move serves both slices — then open the fake's update with it. The devDependency route is closed by the cycle recorded in `why`." + }, + { + "file": "packages/plugins/plugin-approvals/src/admin-exemption-retired.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 101. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-actor-impersonation.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-actor-impersonation.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 65. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-node-degradation.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 24. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. @objectstack/objectql is already in this package's devDependencies (added when an earlier double in the same package was pinned), so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, not adoption.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-node-degradation.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 24. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-node.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-node.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 26. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-override-audit.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-override-audit.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 47. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-restart-resume.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-restart-resume.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 46. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-revise.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-revise.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 44. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approval-service.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/plugins/plugin-approvals/src/approval-service.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 60. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/approver-cross-org.integration.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 45. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. @objectstack/objectql is already in this package's devDependencies (added when an earlier double in the same package was pinned), so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, not adoption.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/plugins/plugin-approvals/src/approver-cross-org.integration.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 45. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-approvals/src/stranded-request-inspection.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 42. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. @objectstack/objectql is already in this package's devDependencies (added when an earlier double in the same package was pinned), so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, not adoption.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/plugins/plugin-approvals/src/stranded-request-inspection.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 42. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-auth/src/auth-manager.jwt-eddsa-fallback.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 43. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-auth/src/auth-manager.optional-plugin-isolation.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#3585): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/plugin-auth's devDependencies when the sibling auth-manager.jwt-eddsa-fallback.test.ts was pinned, so what is left here is a one-line pin. Deferred only because #3585's PR is a JWT-algorithm fix and flipping an unmeasured suite red belongs in its own PR, not because anything structural stands in the way.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/plugins/plugin-auth/src/auth-manager.optional-plugin-isolation.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 77. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-auth/src/org-create-posture-gate.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 99. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-auth/src/session-of-record.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 80. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-email/src/attachment-reclaim.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 53. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 32. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-email/src/email-plugin.attachment-storage.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 53. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-email/src/email-plugin.mail-settings.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 54. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-email/src/email-plugin.outbox-sweep.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 62. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-email/src/email-plugin.queue-delivery.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 54. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-pinyin-search/src/companion-projection.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 38. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-reports/src/report-export-axis.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/plugin-reports does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/plugins/plugin-reports/src/report-export-axis.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 27. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-reports's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-reports --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/plugins/plugin-reports/src/report-service.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/plugin-reports does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/plugins/plugin-reports/src/report-service.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 22. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-reports's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-reports --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-security/src/audience-anchors.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 12. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/plugins/plugin-security/src/auto-org-admin-grant.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "HAND-MIRRORS the guard already (a copy of the #4434 fix's `if`), which is the second copy of the contract this gate exists to remove — but @objectstack/plugin-security does not depend on @objectstack/objectql, so replacing the copy with the producer's predicate needs a devDependency change.", "closes": "add @objectstack/objectql to devDependencies, then replace the mirrored `if` with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 10. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-security/src/bootstrap-declared-permissions.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 12. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-security/src/bootstrap-declared-positions.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 21. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-security/src/bootstrap-platform-admin.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 25. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-security/src/bootstrap-system-capabilities.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 9. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/plugins/plugin-security/src/permission-set-projection.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/plugin-security does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/plugins/plugin-security/src/permission-set-projection.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 35. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-security/src/security-plugin.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 3611. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/plugins/plugin-security/src/suggested-audience-bindings.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/plugin-security does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/plugins/plugin-security/src/suggested-audience-bindings.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 27. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-security's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-security --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/plugins/plugin-sharing/src/boot-backfill.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 46. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-sharing/src/bulk-recompute.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 80. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-sharing/src/record-share-cascade.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 83. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-sharing/src/share-link-service.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 20. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/plugins/plugin-sharing/src/share-link-service.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 20. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-sharing/src/sharing-rule-provenance.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 40. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-sharing/src/sharing-rule.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 47. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/plugins/plugin-sharing/src/sharing-service.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 40. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/plugins/plugin-webhooks/src/auto-enqueuer.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/plugin-webhooks does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/plugins/plugin-webhooks/src/auto-enqueuer.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 74. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-webhooks's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-webhooks --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 34. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-webhooks --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 34. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/plugin-webhooks's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/plugin-webhooks --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/runtime/src/action-body-identity.test.ts", + "verb": "delete", "unguarded": 2, "kind": "DEBT", "why": "The package DOES depend on @objectstack/objectql, so this is a one-line pin — deferred only because this PR's slice is the gate plus the package #4434 came from, and an unmeasured suite flipping red belongs in its own PR. RE-MEASURED (#5629): this entry's count moves 1 -> 2 without a line of test code changing — 1 further double in this file (line 71) became visible when #5629 stopped discarding deletes that declare no parameters. Not a regression and not a raised ratchet: the doubles were always here, the scan could not reach them. The second double is not an independent fake: it is the `createContext().object(name)` scoped facade whose `delete` forwards to the SAME fake engine already recorded by this entry. Pinning the outer fake closes both, so this count returning to 1 and then 0 is the expected shape. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite" }, + { + "file": "packages/runtime/src/action-body-identity.test.ts", + "verb": "update", + "unguarded": 2, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at lines 46, 71. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/runtime/src/action-execution-calldata-not-found.test.ts", + "verb": "update", + "unguarded": 2, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at lines 86, 126. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/runtime/src/dispatcher-plugin.anonymous-gate.integration.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 65. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/runtime/src/dispatcher-plugin.anonymous-gate.integration.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 65. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/runtime/src/http-dispatcher.keys.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 18. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/runtime/src/http-dispatcher.keys.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 18. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/runtime/src/http-dispatcher.mcp-oauth.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 44. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/runtime/src/http-dispatcher.mcp-oauth.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 44. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/runtime/src/http-dispatcher.mcp.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 34. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/runtime/src/http-dispatcher.mcp.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 34. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/runtime/src/http-dispatcher.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 3693. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package DOES depend on @objectstack/objectql, so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, and flipping ~three dozen unmeasured suites red belongs in the per-package batches that follow.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite" }, + { + "file": "packages/runtime/src/http-dispatcher.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 3693. The package already depends on @objectstack/objectql (`dependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-automation/src/builtin/crud-bulk-intent.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 82. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/builtin/crud-config-aliases.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/builtin/crud-config-aliases.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 35. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/builtin/crud-filter-guard.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR. One extra note for whoever pins it: several of its #3810 fixtures assert success on a PREDICATE delete, which the real engine refuses — since #5393 that is expressible, so those fixtures gain `multi: true` rather than being deleted.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/builtin/crud-filter-guard.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 31. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-automation/src/builtin/crud-output-var.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 22. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/builtin/crud-runas.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/builtin/crud-runas.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 34. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/fault-edge-guard-containment.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/plugin-startup-log-cause.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480), and the one entry in this batch that is NOT part of the one-act landing the other update entries describe: this file did not exist when the update slice was measured. It arrived on main in #5738 (from #5661) AFTER this branch's measurement window and while main still had no update slice, so its own CI could not have flagged it and this ledger could not have recorded it — a base crossing, not a regression on either side. Re-measured on the merge with origin/main: 1 unguarded engine double, discovered at line 170 (the `fakeDataEngine` helper, whose `update` writes the row straight into the fixture Map and returns it, consulting nothing). The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin — but the file lives under `packages/services/**`, which #5480 is scoped out of, so it belongs to the services lane's own batch alongside the `crud-bulk-intent.test.ts` upgrade that #5480 step 3 leaves to it. WHAT THIS ENTRY DOES NOT CLAIM: like every other update entry and unlike the #5629 delete batch, it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/plugin-suspended-run-wiring.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/plugin-suspended-run-wiring.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 29. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/record-lookup-expand.integration.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/record-lookup-expand.integration.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 33. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/run-summary.test.ts", + "verb": "delete", "unguarded": 5, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at lines 188, 332, 360, 384, 793. This is #5629's origin specimen. #5197 pinned the ONE double in this file whose delete is actually driven (the sweep behind #5225's `showcase_inquiry_purge`, which answered `acted: 0` in production while this suite stayed green); these are the remaining zero-parameter siblings, which #5197 correctly left alone because nothing calls them. They are also the control for every dormancy claim in this batch: the pinned delete printed its marker, these five did not. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. @objectstack/objectql is already in this package's devDependencies (added when an earlier double in the same package was pinned), so this is a one-line pin whenever a batch takes it — deferred here because #5629's first batch is the criterion plus the ledger, not adoption.", "closes": "open the fake's delete with assertEngineDeleteDispatch(options) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/run-summary.test.ts", + "verb": "update", + "unguarded": 5, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at lines 188, 332, 360, 384, 793. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/runas-grant-resolution.integration.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5393): the devDependency this entry used to cite as the blocker now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when the sibling `builtin/crud-bulk-intent.test.ts` was pinned. It is not cyclic: objectql's transitive dependency closure (12 packages) does not contain service-automation, and `turbo run build --filter=@objectstack/service-automation --dry` (turbo 2.10.7) resolved the graph without complaint. So what is left here is a one-line pin, deferred only because #5393's PR is a spec/executor change and flipping an unmeasured suite red belongs in its own PR.", "closes": "open the fake's delete with assertEngineDeleteDispatch(opts) and run the package's suite — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/runas-grant-resolution.integration.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 36. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-automation/src/suspended-run-store.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "HAND-MIRRORS the guard already, which is the second copy of the contract this gate exists to remove. MEASURED (#5393): the devDependency that used to block replacing the copy now EXISTS — @objectstack/objectql was added to @objectstack/service-automation's devDependencies when `builtin/crud-bulk-intent.test.ts` was pinned, and the graph is acyclic (see the sibling entries). What is left is replacing the mirrored `if` with the producer's predicate.", "closes": "replace the mirrored `if` with assertEngineDeleteDispatch(options) — the devDependency is already declared" }, + { + "file": "packages/services/service-automation/src/suspended-run-store.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 27. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/services/service-datasource/src/__tests__/datasource-admin-plugin.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/service-datasource does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/services/service-datasource/src/__tests__/datasource-admin-plugin.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 244. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/service-datasource's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-datasource --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-datasource/src/__tests__/datasource-secret-binder.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "@objectstack/service-datasource does not depend on @objectstack/objectql. Pinning needs a devDependency + lockfile change, which is a separate reviewable act.", "closes": "add @objectstack/objectql to devDependencies, then open the fake's delete with assertEngineDeleteDispatch(opts)" }, + { + "file": "packages/services/service-job/src/db-job-adapter.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 8. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/service-job's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-job --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/services/service-job/src/job-service-plugin.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 22. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. Measured on this branch, not cited: the edge was added to @objectstack/service-job's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-job --dry`, no circular-dependency warning), then the edge was reverted. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/email-channel.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 33. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/email-channel.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 33. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/inbox-channel.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 37. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/inbox-channel.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 37. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/messaging-service-plugin.test.ts", + "verb": "delete", "unguarded": 2, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at lines 23, 105. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/messaging-service-plugin.test.ts", + "verb": "update", + "unguarded": 2, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at lines 23, 105. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/messaging-service.test.ts", + "verb": "delete", "unguarded": 6, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at lines 37, 173, 226, 375, 410, 442. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/messaging-service.test.ts", + "verb": "update", + "unguarded": 6, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at lines 37, 173, 226, 375, 410, 442. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/preference-resolver.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 18. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/preference-resolver.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 18. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/recipient-resolver.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 24. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/recipient-resolver.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 24. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/sms-channel.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 33. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/sms-channel.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 33. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/sql-outbox-audit-columns.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 39. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/sql-outbox-audit-columns.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 39. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, { "file": "packages/services/service-messaging/src/template-renderer.test.ts", + "verb": "delete", "unguarded": 1, "kind": "DEBT", "why": "MEASURED (#5629): newly VISIBLE, not newly written — the fake's `delete` declares no parameters, and the gate's arity test (`params.length < 2` was the first line of `isEngineDeleteShape`) discarded such deletes before any other criterion ran. So this double reached neither PINNED nor this ledger and produced no output at all: the #4868 shape the DISCOVERED invariant above is written against. Discovered at line 70. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses. The package does not depend on @objectstack/objectql yet, and — unlike the metadata-protocol family in this ledger — the devDependency route is AVAILABLE here rather than cyclic. Measured on this branch, not cited: the edge was added to this package's devDependencies and turbo 2.10.7 accepted the graph (`turbo run build --filter=@objectstack/service-messaging --dry`, no circular-dependency warning), then the edge was reverted.", "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic on this branch — see `why`), then open the fake's delete with assertEngineDeleteDispatch(options)" }, + { + "file": "packages/services/service-messaging/src/template-renderer.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 70. The package does not depend on @objectstack/objectql yet, and the devDependency route is AVAILABLE rather than cyclic. This entry does not re-measure — the delete-slice entries for @objectstack/service-messaging in this same ledger added the edge, recorded turbo's acceptance and reverted it, and the graph does not depend on which verb the pin is for. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "add @objectstack/objectql to this package's devDependencies (verified acyclic — see `why`), then open the fake's update with assertEngineUpdateDispatch(data, options)" + }, + { + "file": "packages/services/service-queue/src/db-queue-adapter.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 27. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-queue/src/job-queue-retention.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 64. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-storage/src/attachment-lifecycle.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 37. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-storage/src/backfill-file-references.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 27. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-storage/src/file-reference-lifecycle.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 56. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-storage/src/files-to-references-migration.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 28. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-storage/src/metadata-store.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 61. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, + { + "file": "packages/services/service-storage/src/storage-routes.metadata-outage.test.ts", + "verb": "update", + "unguarded": 1, + "kind": "DEBT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at line 81. The package already depends on @objectstack/objectql (`devDependencies`), so this is a one-line pin whenever a batch takes it — deferred here because #5480's slice is the producer-side predicate plus the gate that reads it, and flipping ~100 unmeasured suites red belongs in the per-package batches that follow, exactly as #5629 did for delete. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "open the fake's update with assertEngineUpdateDispatch(data, options) and run the package's suite" + }, { "file": "packages/spec/src/contracts/data-engine.test.ts", + "verb": "delete", "unguarded": 5, "kind": "EXEMPT", "why": "Not a stand-in that code under test drives — it is a TYPE-CONFORMANCE witness that IDataEngine is implementable, asserting only `typeof engine.delete === 'function'`. And it cannot be pinned even in principle: @objectstack/objectql depends on @objectstack/spec, so the import would invert the dependency. Ran clean under the guard anyway (spec: 295/295 files passed with the dispatch guard installed in every engine double). RE-MEASURED (#5629): this entry's count moves 1 -> 5 without a line of test code changing — 4 further doubles in this file (lines 46, 82, 119, 152) became visible when #5629 stopped discarding deletes that declare no parameters. Not a regression and not a raised ratchet: the doubles were always here, the scan could not reach them. The four newly visible doubles are the same kind as the one this entry already records: `const engine: IDataEngine = { … }` type-conformance witnesses inside the contract test for that interface. Each is built to exercise or declare something OTHER than delete — reads through `find`/`findOne`/`count`, the trailing options argument on every read, the optional `execute` and `vectorFind` members — and none of them calls `delete`, which is present only because the interface requires the member. EXEMPT for the reason already stated, which applies to each of them unchanged. Dormant looseness, probed rather than assumed: a `process.stderr.write` marker injected as the first statement of this delete printed NOTHING while the file's suite passed, so no path this suite drives calls it. The control that makes that silence evidence instead of a broken probe: the same injection in `run-summary.test.ts`'s PINNED delete DID print, in the same run of the same harness. Dormant is not harmless — it means the looseness is unexercised today, so a future test that starts deleting through this fake inherits a double that accepts what ObjectQL.delete refuses.", "closes": "nothing — permanent" + }, + { + "file": "packages/spec/src/contracts/data-engine.test.ts", + "verb": "update", + "unguarded": 5, + "kind": "EXEMPT", + "why": "MEASURED (#5480): the `update` slice of this gate is NEW — `resolveEngineUpdateDispatch` did not exist before #5480, so no double in the repo could route through it and the whole discovered set enters this ledger in one act. Not newly written looseness and not a raised ratchet: it is the first measurement of a contract that had no producer-side predicate to measure against, which is exactly what this script's header used to list under deliberately-not-covered (\"update's twin dispatch ... needs its own producer-side predicate extracted first\"). Discovered at lines 24, 46, 82, 119, 152. EXEMPT for the same reason the delete-slice entry for this file is, restated for the update verb because a per-verb ledger may not inherit a verdict: these are not stand-ins that code under test drives, they are TYPE-CONFORMANCE witnesses that `IDataEngine` is implementable, and `update` is present on each only because the interface requires the member. They cannot be pinned even in principle — @objectstack/objectql depends on @objectstack/spec, so the import would invert the dependency. WHAT THIS ENTRY DOES NOT CLAIM: unlike the #5629 delete batch above it carries NO per-file dormancy probe. Nothing here says the looseness is unexercised — only that the double is structurally free to be looser than ObjectQL.update on the shape a hand-written guard always drops (`where: { id: { $in: [...] } }` looks like an id and is a multi-row predicate) and stricter on the one it invents (`data.id`, which the producer takes verbatim when truthy, ahead of both `where` and `multi`).", + "closes": "nothing — permanent" } ] }