Uh oh!
There was an error while loading. Please reload this page.
test: hold the driver-double WHERE matchers to a combinator conformance battery - #8581
Conversation
…ce battery Closes#8494. #7620 corrected sixteen in-memory `matches(row, where)` doubles that short-circuited on `$or`, discarding every sibling equality key and turning a conjunction into a different query while the suites stayed green. Three lanes landed the fixes -- and nothing held them fixed: reinstating the early return failed nothing. `scripts/check-where-matcher-conformance.mjs` lifts every discovered matcher out of its test file and asks it four combinator questions. The criterion is one sentence: answer correctly, or refuse by throwing -- never answer silently wrong. Refusal conforms because the defect class is silence, not incompleteness. Behavioural rather than syntactic on purpose: the second failure shape #8494 binds this to cover is an ABSENCE -- a matcher with no combinator branch at all, which reads `$or` as a field name and silently drops the row -- which no pattern-match over source can see. Building it found five live instances of the early-return shape that all three correction lanes missed (four in objectql, one in plugin-security). Fixed here rather than baselined: a ledger that grandfathered the exact defect its gate exists to stop would be worth nothing. Shape (a) is therefore enforced with an empty ledger; the 78 pre-existing combinator-blind doubles are grandfathered in a shrink-only measured baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
os-zhuang
commented
Aug 13, 2026
PM review (
The no-changeset route is the one this gate's own log recommends — "If it releases nothing … apply the What I checked against the tree rather than the description:
⭐ The result worth recording beyond this card: building the guard turned up five live shape-(a) defects still on Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Closes#8494.
What this is
#7620 corrected sixteen in-memory
matches(row, where)doubles that short-circuited on$or, discarding every sibling equality key — so a real driver's conjunction became a different query while the suites stayed green. Three lanes landed the fixes (#7846, #8493, #7619). #8494 is the observation one level up: the doubles were made right, and nothing held them right.pnpm check:where-matcherlifts every discovered matcher out of its test file and asks it four combinator questions. The criterion is one sentence:Refusal counts as conforming on purpose: the defect class is silence, not incompleteness. A double that throws on an operator it does not implement makes the suite red the moment a combinator arrives. That is not this gate's invention —
packages/objectql/src/engine-autonumber-*.test.tsalready does it, with its own recorded reason, "silently ignoring an unknown operator would let a bad query pass as a good one." So the cheap correct answer for a double that only ever sees scalar equality is onethrow, not a combinator implementation.Why behavioural and not a lint rule
#8494 binds the guard to cover both failure shapes, and they share no source shape:
returns, dropping the sibling keys the loop had not reached.Object.entries(where).every(…)matcher that reads$oras an ordinary field name, comparesrow.$or(undefined) against the array, and silently excludes the row.Shape (b) is an absence, which no pattern-match over source can see, and the correct forms in this repo already vary too much to pin (a prelude of
if (…) return falsebefore the entries loop, and a combinator arm inside it, are both correct and share no shape).Discovery is structural, then behaviourally admitted: every candidate must pass a control probe (matches on equality, and really filters) or it is dropped as out of scope. So membership is decided by behaviour, never by parameter names — the ambiguity
check-engine-double-contractdocuments. It also stops a matcher passing vacuously by answeringtrueto everything.The four probes are load-bearing as pairs.
orConjoinedalone proves nothing — a shape-(b) matcher also answersfalsethere, for the opposite reason, and would ride through as "conjoins correctly". OnlyorRecognised === truemakes thatfalsemean conjoined rather than excluded by accident.⛔ No shared
matchesWhere— ruled NO on #7620, not re-litigated. This gate never gives a matcher an implementation; it only asks each independent double a question.Reverse verification — watched, not asserted
Reinstating the early return in
plugin-sharing/src/authored-row-write-deferral.test.ts(one of the sixteen, corrected by #8493):pnpm --filter @objectstack/plugin-sharing testpnpm check:where-matcherThat table is the whole card: the suite cannot see it, and now something can. Shape (b) was demonstrated the same way — deleting the combinator branch outright reddens the gate with a distinct attribution,
no combinator branch (combinator read as a field name), failing recognition rather than conjunction.Restored both times by checking the file back out of
HEAD(index and tree together);git hash-objectreturned26d2c2fb34e97066ad0885f5dee03282dab3647abefore and after each round,git status --porcelaincame back clean, and the gate returned to green.Five live defects this found
Building the guard surfaced five instances of shape (a) still live on
mainthat all three correction lanes missed — the lanes grepped a named file list, which is exactly the limitation this card exists to remove:packages/objectql/src/engine-author-state-query.test.tspackages/objectql/src/engine-findone-contract.test.tspackages/objectql/src/engine-unknown-option.test.tspackages/objectql/src/search-companion-read-projection-conformance.test.tspackages/plugins/plugin-security/src/store-fault-fail-closed.test.ts(both shapes at once)Fixed here rather than baselined. A ledger that grandfathered the exact defect its gate exists to stop would be worth nothing — so shape (a) is enforced with an empty ledger.
The ledger
168 matchers discovered: 89 conforming (4 of them by refusing), 78 silently wrong, 1 unjudged, across 75 grandfathered files. Every grandfathered entry is shape (b). Shrink-only, hand-edited, reconciled in both directions, with the key set checked against the merge base so a newly-added file cannot ride in matching its own count (the
SLOT_LOOKUP_UNSWEPTprecedent). Deliberately no--updateflag — a generator would admit a new silently-wrong double by "just run the update command".Sweeping those 78 is filed separately as #8582, not smuggled in here.
Tests and gates
pnpm --filter @objectstack/objectql test— 200 files / 3550 tests passpnpm --filter @objectstack/plugin-security test— 55 files / 1079 tests passpnpm --filter @objectstack/plugin-sharing test— 21 files / 569 tests passtypecheckon objectql + plugin-security — clean;eslint --no-inline-configon all changed files — cleanGreen:
check:where-matcher(incl.--self-test),check:nul-bytes,check:engine-double-contract,check:durability-log-level,check:query-options-erasure,check:i18n,check:type-check-coverage,check:type-check-debt,check:test-source-alias,check:type-source-resolution,check:cross-package-test-inputs,check:node-version,check:required-contexts,check:shard-attestation,check:workflow-status-functions.Two of those first reported a prerequisite failure rather than a verdict, and are green once the prerequisite is met — worth knowing because neither message is about the diff:
check:i18nneeds the workspace CLI built ("Nothing was checked"), andcheck:type-check-debtrefuses to re-measure without the full built closure.No changeset, and
skip-changesetapplied: the diff is test doubles, twoscripts/gates, the workflow and the rootpackage.json— nothing published changes, so an empty-frontmatter changeset would be rejected bycheck:empty-changesetand a package-bumping one would ship a release note about test internals. This also keeps the PR clear ofcheck:objectui-pin-fresh, which fires on any.changeset/*touch and is currently red as pre-existing repo state (.objectui-shabehind objectui main, #3340) — unrelated to this diff either way.Generated by Claude Code