test(objectql): type the #13357 negative pin's witness seams against the driver contract - #14344
Merged
os-musk merged 1 commit intoSep 2, 2026
Merged
Conversation
…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
Contributor
📓 Docs Drift CheckNothing 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
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
This was referenced Sep 2, 2026
os-musk
marked this pull request as ready for review
September 2, 2026 03:16
os-musk
enabled auto-merge
September 2, 2026 03:17
Collaborator
Author
|
Provenance for the ready flip + auto-merge: ACCEPT on Generated by Claude Code |
os-musk
deleted the
claude/issue-14117-filter-lowering-pin-witness-types
branch
September 2, 2026 03:41
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 wereany.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 nowast: DriverQuery— the driver contract's own type from@objectstack/spec/contracts(QueryAST minus its top-levelobject). Every assertion in the file readslastWhere()off this record, so the record IS the unreachability evidence, and evidence typedanyis checked by nothing.2. What the double is standing in for.
const driver: anyis nowconst driver: RecordingDriver, a local interface that extendsIDataDriverand declares the one verb the engine reaches by duck-typing.aggregateis NOT onIDataDriver—engine.tscalls it throughtypeof 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 inpackages/objectql/src/engine-primary-datasource.test.ts, whose own comment says why:registerDrivertakes 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/bulkDeleteappear nowhere inengine.ts;syncSchemais reached only fromsyncSchemas()/syncObjectSchema()anddropTableonly fromdropObjectStorage(), none of which this test calls).What was deliberately NOT done
as anycall 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.jsonis unchanged.@ts-expect-error/@ts-ignorecount stays at 0. No newany—: anygoes 20 lines to 11.packages/objectql/test-typecheck-debt.jsonis untouched, and the file stays deliberately absent from it, as its_noterequires.IDataDriverandDriverQueryalready ship from@objectstack/spec/contracts;DriverOptionsfrom@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:The zero is instrumented rather than assumed: the same grep reports 102 errors for
src/engine.test.tsin the same run (the counttsconfig.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.--listFilesconfirms the file is in the program at95d6484da: 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 andgit diff HEADempty. 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.A — rename an exercised verb (
counttocountt) → 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
limitonly → 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 islastWhere())…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, dropTableC also partly falsifies the dispatching assumption. The ledger
_notesays 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 suggestsIDataDriveritself 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 addedcheck:query-options-erasure,check:type-check-coverage,check:type-check-debtandcheck: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-typecheck—OK — @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-debt—OK — 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-resolutionand the rest: exit 0.pnpm --filter @objectstack/objectql typecheck: exit 0 (whole script, including the two sibling tsc programs).pnpm lint(repo-wideeslint . --no-inline-config): exit 0. Not narrowed.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.mjsrefused on a shallow clone (deepened withgit fetch --shallow-since, then green: 98.0% ratio);check:dual-build-cjs-loadssaid PREREQUISITE NOT MET before the full build, green after;check-test-completeness.mjsexits 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 (
grepfor importers: 0; the 19 other files with amakeRecordingDrivereach define their own). The whole test layer was still compiled bycheck:test-typecheck. CI runs the shards.Scope
One file changed:
packages/objectql/src/engine-filter-array-lowering.test.ts. Tests-only, soskip-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