From 95d6484dabd040006ab564035ea53e9b3d8bc5a1 Mon Sep 17 00:00:00 2001 From: os-musk Date: Wed, 2 Sep 2026 02:15:40 +0000 Subject: [PATCH] test(objectql): type the #13357 negative pin's witness seams against the driver contract MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The filter-array lowering pin proves a refused filter shape cannot reach the reference matcher, and its evidence is the recording driver's call log. Both halves of that evidence were `any`, so a drifted `IDataDriver` signature — or a drifted shape of the recorded AST — could not redden the file. - `interface SeenRead { ast: any }` -> `ast: DriverQuery`, the driver contract's own `Omit`. `lastWhere()` is what every assertion reads. - `const driver: any` -> `const driver: RecordingDriver`, an interface that extends `IDataDriver` and declares the one verb the engine reaches by duck-typing (`aggregate`). Annotating it surfaced five required members the double never had (`upsert`, `bulkUpdate`, `bulkDelete`, `syncSchema`, `dropTable`); each is supplied as a loud `unexercised()` stub rather than a silent no-op, so the double stays a witness. None is dispatched by this pin. No runtime change: same cases, same assertions, 52 passed before and after. No `tsconfig.test.json` change, no `@ts-expect-error` (still 0), no new `any` (`: any` 20 -> 11 lines), no ledger row — the file still reports 0 errors under `tsc -p packages/objectql/tsconfig.test.json`. The 35 bare `as any` call sites are deliberately untouched: they feed off-contract shapes by declaration, which is the point of a negative pin. Fixes #14117 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68 --- .../src/engine-filter-array-lowering.test.ts | 68 ++++++++++++++++--- 1 file changed, 59 insertions(+), 9 deletions(-) diff --git a/packages/objectql/src/engine-filter-array-lowering.test.ts b/packages/objectql/src/engine-filter-array-lowering.test.ts index 1b4124f207..45f6e2dd0e 100644 --- a/packages/objectql/src/engine-filter-array-lowering.test.ts +++ b/packages/objectql/src/engine-filter-array-lowering.test.ts @@ -27,10 +27,12 @@ import { describe, it, expect, beforeEach } from 'vitest'; import type { + DriverOptions, EngineAggregateOptions, EngineCountOptions, EngineQueryOptions, } from '@objectstack/spec/data'; +import type { DriverQuery, IDataDriver } from '@objectstack/spec/contracts'; import { ObjectQL } from './engine.js'; /** @@ -65,7 +67,49 @@ const deal = { }, }; -interface SeenRead { ast: any } +/** + * WITNESS SEAM 1 — what the driver was handed. + * + * `ast` is the driver contract's own {@link DriverQuery} + * (`Omit`, `@objectstack/spec/contracts`), NOT `any`. Every + * assertion in this file reads `lastWhere()` off this record, so the record IS + * the unreachability evidence — and evidence typed `any` is checked by nothing: + * `ast.where` could be renamed, retyped or dropped at the engine→driver + * boundary and this file would keep passing while proving less than it says. + * Naming the real type is what makes a drift there redden the pin. + */ +interface SeenRead { ast: DriverQuery } + +/** + * WITNESS SEAM 2 — what the double is standing in for. + * + * The recording double is annotated against the REAL driver contract rather + * than left `any`, for the reason `engine-primary-datasource.test.ts` states + * for its own fixture: `registerDriver` takes `IDataDriver`, so an un-annotated + * double is checked nowhere and drifts silently as the interface grows. With + * the annotation THIS declaration is what fails when a member is added or a + * signature moves. + * + * The one extension: `aggregate` is NOT on `IDataDriver`. The engine reaches it + * by duck-typing (`typeof drv.aggregate === 'function'`, `engine.ts`), so it is + * declared here explicitly — the pin exercises that verb and must keep + * witnessing it. ⚠️ Declared here does NOT make it contractual; it records that + * this double answers a call the interface does not describe. + */ +interface RecordingDriver extends IDataDriver { + aggregate(object: string, query: DriverQuery, options?: DriverOptions): Promise[]>; +} + +/** + * A verb the pin does not exercise, present only because `IDataDriver` requires + * it. Throwing rather than no-op'ing keeps the double a WITNESS: if the engine + * ever routes one of these paths, this file says so instead of passing on a + * silent stub. Nothing here is reached today — the suite's 52 passing cases are + * the reading. + */ +const unexercised = (verb: string): never => { + throw new Error(`recording driver: ${verb}() is not exercised by this pin (#5158/#13357)`); +}; /** * Minimal driver that records every AST it is handed and executes only the @@ -104,17 +148,17 @@ function makeRecordingDriver() { } return true; }; - const run = (ast: any) => { + const run = (ast: DriverQuery | undefined) => { const out = [...rows.values()].filter((r) => matches(r, ast?.where)); return typeof ast?.limit === 'number' && ast.limit > 0 ? out.slice(0, ast.limit) : out; }; - const driver: any = { + const driver: RecordingDriver = { name: 'recording', version: '0.0.0', supports: {}, async connect() {}, async disconnect() {}, async checkHealth() { return true; }, async execute() { return null; }, - async find(_o: string, ast: any) { reads.push({ ast }); return run(ast); }, - async findOne(_o: string, ast: any) { reads.push({ ast }); return run(ast)[0] ?? null; }, - async count(_o: string, ast: any) { reads.push({ ast }); return run(ast).length; }, - async aggregate(_o: string, ast: any) { reads.push({ ast }); return run(ast); }, + async find(_o: string, ast: DriverQuery) { reads.push({ ast }); return run(ast); }, + async findOne(_o: string, ast: DriverQuery) { reads.push({ ast }); return run(ast)[0] ?? null; }, + async count(_o: string, ast: DriverQuery) { reads.push({ ast }); return run(ast).length; }, + async aggregate(_o: string, ast: DriverQuery) { reads.push({ ast }); return run(ast); }, async create(_o: string, data: Record) { const id = (data.id as string) ?? `r_${rows.size + 1}`; const row = { ...data, id }; rows.set(id, row); return row; @@ -123,14 +167,14 @@ function makeRecordingDriver() { const cur = rows.get(id); if (!cur) throw new Error(`nf ${id}`); const up = { ...cur, ...data, id }; rows.set(id, up); return up; }, - async updateMany(_o: string, ast: any, data: Record) { + async updateMany(_o: string, ast: DriverQuery, data: Record) { writes.push({ ast }); const hit = run(ast); for (const r of hit) rows.set(r.id as string, { ...r, ...data }); return hit.length; }, async delete(_o: string, id: string) { return rows.delete(id); }, - async deleteMany(_o: string, ast: any) { + async deleteMany(_o: string, ast: DriverQuery) { writes.push({ ast }); const hit = run(ast); for (const r of hit) rows.delete(r.id as string); @@ -141,6 +185,12 @@ function makeRecordingDriver() { }, async beginTransaction() { return { commit: async () => {}, rollback: async () => {} }; }, async commit() {}, async rollback() {}, + // ── required by `IDataDriver`, never reached by this pin ────────────── + async upsert() { return unexercised('upsert'); }, + async bulkUpdate() { return unexercised('bulkUpdate'); }, + async bulkDelete() { return unexercised('bulkDelete'); }, + async syncSchema() { return unexercised('syncSchema'); }, + async dropTable() { return unexercised('dropTable'); }, }; return { driver, reads, writes }; }