Skip to content

test(objectql): type the #13357 negative pin's witness seams against the driver contract - #14344

Merged
os-musk merged 1 commit into
mainfrom
claude/issue-14117-filter-lowering-pin-witness-types
Sep 2, 2026
Merged

test(objectql): type the #13357 negative pin's witness seams against the driver contract#14344
os-musk merged 1 commit into
mainfrom
claude/issue-14117-filter-lowering-pin-witness-types

Conversation

@os-musk

@os-musk os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes #14117

#13676 (PR #14115) put packages/objectql's test layer in front of tsc. That was the precondition for checking the #13357 point-3 negative pin, not the check: the pin reported zero errors and the zero carried almost no information, because both halves of its evidence were any.

This types the two witness seams. It is a type-witness repair only — no runtime behaviour changes.

The two seams

1. What the driver was handed. interface SeenRead { ast: any } is now ast: DriverQuery — the driver contract's own type from @objectstack/spec/contracts (QueryAST minus its top-level object). Every assertion in the file reads lastWhere() off this record, so the record IS the unreachability evidence, and evidence typed any is checked by nothing.

2. What the double is standing in for. const driver: any is now const driver: RecordingDriver, a local interface that extends IDataDriver and declares the one verb the engine reaches by duck-typing. aggregate is NOT on IDataDriverengine.ts calls it through typeof drv.aggregate === 'function' — so it is declared explicitly here, with a comment recording that declaring it does not make it contractual. This follows the fixture in packages/objectql/src/engine-primary-datasource.test.ts, whose own comment says why: registerDriver takes the real contract, so an un-annotated double is checked nowhere and drifts silently as the interface grows.

Annotating the double surfaced exactly one diagnostic — five required members the double never had. Each is supplied as a loud unexercised() stub rather than a silent no-op, so the double stays a witness: if the engine ever routes one of these paths, the file says so instead of passing on a stub. None is dispatched by this pin (upsert / bulkUpdate / bulkDelete appear nowhere in engine.ts; syncSchema is reached only from syncSchemas() / syncObjectSchema() and dropTable only from dropObjectStorage(), none of which this test calls).

What was deliberately NOT done

  • The 35 bare as any call sites are untouched. Many feed shapes that are off-contract by declaration — that is the point of a negative pin, and the file's own header says so. The narrow claim was always the witness side.
  • packages/objectql/tsconfig.test.json is unchanged.
  • @ts-expect-error / @ts-ignore count stays at 0. No new any: any goes 20 lines to 11.
  • No ledger row. packages/objectql/test-typecheck-debt.json is untouched, and the file stays deliberately absent from it, as its _note requires.
  • No new export. IDataDriver and DriverQuery already ship from @objectstack/spec/contracts; DriverOptions from @objectstack/spec/data. Nothing widens.

Measured (union run at 95d6484da; working tree clean at that sha)

Before and after, tsc -p packages/objectql/tsconfig.test.json:

reading before after
errors attributed to this file 0 0
program total 242 242
vitest cases 52 passed 52 passed

The zero is instrumented rather than assumed: the same grep reports 102 errors for src/engine.test.ts in the same run (the count tsconfig.test.json's own header records), and it reports this file's errors under each ablation below. An earlier reading used a path prefix that could never match — it was replaced and re-measured; both numbers above come from the corrected instrument.

--listFiles confirms the file is in the program at 95d6484da: 1 hit, /…/packages/objectql/src/engine-filter-array-lowering.test.ts.

Reverse verification

Directions were stated before running. Each ablation: mutate, prove the mutation reached disk by anchor counts, run tsc, restore with git checkout HEAD -- ABSOLUTE_PATH, prove the restore by blob hash equal to the HEAD blob and git diff HEAD empty. All three restores proved clean (45f6e2dd0e4e2754daf5657298d9b99fe39567dd). One earlier attempt failed its anchor check and refused to run rather than measuring nothing. No rebuild is involved: the mutated file IS the file under check and tsc reads it from disk.

⚠️ Angle brackets are transliterated to square brackets in the quoted diagnostics below — GitHub's body sanitizer eats tag-shaped fragments, including inside backticks.

A — rename an exercised verb (count to countt) → predicted RED, observed RED, 1 diagnostic:

packages/objectql/src/engine-filter-array-lowering.test.ts(160,11): error TS2561: Object literal may only specify known properties, but 'countt' does not exist in type 'RecordingDriver'. Did you mean to write 'count'?

B — narrow the recorded AST to a Pick of DriverQuery down to limit only → predicted RED, observed RED, 3 diagnostics, all on the assertion path:

  • …test.ts(218,57): error TS2339: Property 'where' does not exist on type 'Pick[DriverQuery, "limit"]'. (this is lastWhere())
  • …test.ts(310,44): error TS2339: Property 'where' does not exist on type 'Pick[DriverQuery, "limit"]'.
  • …test.ts(313,44): error TS2339: Property 'where' does not exist on type 'Pick[DriverQuery, "limit"]'.

C — remove the five stubs and the helper, i.e. what does annotating the double actually surface → predicted RED, observed RED, 1 diagnostic:

packages/objectql/src/engine-filter-array-lowering.test.ts(152,9): error TS2739: Type '{ … }' is missing the following properties from type 'RecordingDriver': upsert, bulkUpdate, bulkDelete, syncSchema, dropTable

C also partly falsifies the dispatching assumption. The ledger _note says the bulk of the package's TS2345 residue is "a driver double that does not satisfy IDataDriver", and the dispatch expected signature mismatches here. For this file there are zero TS2345: every verb the double already implemented matches the contract as written, and the whole gap was five absent required members. Nothing suggests IDataDriver itself is wrong — no contract finding.

Gates

Family re-derived from the change set with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed; it took the change set from the merge base itself): 27 families, 23 by path plus 6 by change kind, 2 reached both ways. The dispatch hint listed the 23 matched ones; the convention block added check:query-options-erasure, check:type-check-coverage, check:type-check-debt and check:engine-double-contract.

26 of the 27 exit 0. The 27th (check-test-completeness.mjs) is NOT MEASURED locally by its own design, not green and not red — see below. Exit codes were captured before any pipe, redirect-then-capture. Verdict lines from the two that matter most here:

  • check:test-typecheckOK — @objectstack/objectql's test layer compiles under packages/objectql/tsconfig.test.json; 44 file(s) / 242 error(s) / 69 pinned signature(s) held in test-typecheck-debt.json — identical to the seeded state, so this file still contributes nothing to the ledger.
  • check:type-check-debtOK — 27 ledger entr(ies) re-measured in 182.2s, 1217 raw tsc error(s) total, none above its recorded number. surplus: none.
  • check:engine-double-contract, check:where-matcher, check:objectql-double-limit, check:query-options-erasure, check:type-check-coverage, check:cross-package-test-inputs, check:test-source-alias, check:type-source-resolution and the rest: exit 0.
  • pnpm --filter @objectstack/objectql typecheck: exit 0 (whole script, including the two sibling tsc programs).
  • pnpm lint (repo-wide eslint . --no-inline-config): exit 0. Not narrowed.
  • Full workspace build before the ratchet gates: pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/* — 70/70 tasks, exit 0.

Three gates initially returned non-zero for environment reasons, none a finding: check-engine-split-ratio.mjs refused on a shallow clone (deepened with git fetch --shallow-since, then green: 98.0% ratio); check:dual-build-cjs-loads said PREREQUISITE NOT MET before the full build, green after; check-test-completeness.mjs exits 3 with no turbo log to read and documents that as NOT MEASURED when run locally, so it is recorded that way rather than as a pass.

One declared narrowing: the objectql package's full 251-file vitest run was not executed locally; the targeted run named in the dispatch was (52 passed). The change is one test file, it exports nothing, and no file imports it (grep for importers: 0; the 19 other files with a makeRecordingDriver each define their own). The whole test layer was still compiled by check:test-typecheck. CI runs the shards.

Scope

One file changed: packages/objectql/src/engine-filter-array-lowering.test.ts. Tests-only, so skip-changeset. Clause ② self-reading from the diff: no — no contract accept/reject behaviour moves, no public surface widens.


🤖 Generated with Claude Code

https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68


Generated by Claude Code

…the driver contract

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<QueryAST, 'object'>`. `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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
@github-actions github-actions Bot added the size/s label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 72adb7fcb6b6f48b656047835ed3852f314d9de1packageMentionDocs.

os-musk commented Sep 2, 2026

Copy link
Copy Markdown
Collaborator Author

Provenance for the ready flip + auto-merge: ACCEPT on 95d6484d (issue comment 5503608975 on #14117); tests-only, skip-changeset, Clause-② no; every check run on this head completed success/skipped at 03:06Z. Queue landing (squash) follows.


Generated by Claude Code

@os-musk
os-musk added this pull request to the merge queue Sep 2, 2026
Merged via the queue into main with commit ed9e699 Sep 2, 2026
42 checks passed
@os-musk
os-musk deleted the claude/issue-14117-filter-lowering-pin-witness-types branch September 2, 2026 03:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

1 participant