Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/lint.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -738,6 +738,25 @@ jobs:
- name: Engine test-double contract gate
run: pnpm check:engine-double-contract

# WHERE-matcher conformance gate (#8494, from #7620). The read-side
# sibling of the gate above, and the half its header lists as not
# covered. #7620 corrected sixteen in-memory `matches(row, where)`
# doubles that short-circuited on `$or` and discarded every sibling
# equality key — turning a conjunction into a different query while the
# suites stayed green — and #8494 is the observation one level up:
# nothing held them corrected, so reinstating the early return failed
# nothing. This lifts each discovered matcher out of its file and asks
# it four combinator questions; it must answer correctly or throw, never
# answer silently wrong. Behavioural rather than syntactic on purpose —
# the second failure shape is an ABSENCE (no combinator branch at all),
# which no pattern-match over source can see. Pre-existing combinator-
# blind doubles are in a shrink-only measured baseline. Runs its own
# --self-test first: the detector can be broken while every double is
# fine, and a scan that quietly stops matching would report OK while
# reading nothing (#4868's family).
- name: WHERE-matcher conformance gate
run: pnpm check:where-matcher

# Paired kernel-hook pin gate (#5282, from #5170 / #5257 / #5274). The two
# kernels — ObjectKernel (production) and LiteKernel (vitest / serverless /
# edge) — run the same plugin code and the same hook vocabulary, but do NOT
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,6 +92,7 @@
"check:driver-conformance": "node scripts/check-driver-conformance.mjs --self-test && node scripts/check-driver-conformance.mjs",
"check:driver-memory-census": "node scripts/check-driver-memory-census.mjs --self-test && node scripts/check-driver-memory-census.mjs",
"check:engine-double-contract": "node scripts/check-engine-double-contract.mjs --self-test && node scripts/check-engine-double-contract.mjs",
"check:where-matcher": "node scripts/check-where-matcher-conformance.mjs --self-test && node scripts/check-where-matcher-conformance.mjs",
"check:resume-authority-declared": "node scripts/check-resume-authority-declared.mjs --self-test && node scripts/check-resume-authority-declared.mjs",
"check:spec-parsed-alias": "node scripts/check-spec-parsed-alias.mjs --self-test && node scripts/check-spec-parsed-alias.mjs",
"check:tenant-chokepoint": "node scripts/check-tenant-chokepoint.mjs --self-test && node scripts/check-tenant-chokepoint.mjs",
Expand Down
13 changes: 11 additions & 2 deletions packages/objectql/src/engine-author-state-query.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -64,8 +64,17 @@ function makeRecordingDriver() {
const matches = (row: any, where: any): boolean => {
if (!where || typeof where !== 'object') return true;
for (const [k, v] of Object.entries(where)) {
if (k === '$and') return (v as any[]).every((w) => matches(row, w));
if (k === '$or') return (v as any[]).some((w) => matches(row, w));
// `$and` / `$or` are CONJOINED with their sibling keys, the way a
// real driver reads them — never `return`ed, which would discard
// every sibling the loop has not reached yet (#7620 / #8494).
if (k === '$and') {
if (!(v as any[]).every((w) => matches(row, w))) return false;
continue;
}
if (k === '$or') {
if (!(v as any[]).some((w) => matches(row, w))) return false;
continue;
}
if (k.startsWith('$')) continue;
if (v && typeof v === 'object' && '$in' in (v as any)) {
if (!(v as any).$in.map(String).includes(String(row[k]))) return false;
Expand Down
13 changes: 11 additions & 2 deletions packages/objectql/src/engine-findone-contract.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -65,8 +65,17 @@ function makeRecordingDriver() {
const matches = (row: any, where: any): boolean => {
if (!where || typeof where !== 'object') return true;
for (const [k, v] of Object.entries(where)) {
if (k === '$and') return (v as any[]).every((w) => matches(row, w));
if (k === '$or') return (v as any[]).some((w) => matches(row, w));
// `$and` / `$or` are CONJOINED with their sibling keys, the way a
// real driver reads them — never `return`ed, which would discard
// every sibling the loop has not reached yet (#7620 / #8494).
if (k === '$and') {
if (!(v as any[]).every((w) => matches(row, w))) return false;
continue;
}
if (k === '$or') {
if (!(v as any[]).some((w) => matches(row, w))) return false;
continue;
}
if (k.startsWith('$')) continue;
// [#7641] `$icontains` — the operator `$search` compiles to. This
// arm folded BOTH sides while it was still keyed on `$contains`,
Expand Down
12 changes: 11 additions & 1 deletion packages/objectql/src/engine-unknown-option.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -51,7 +51,17 @@ function makeDriver() {
const matches = (row: any, where: any): boolean => {
if (!where || typeof where !== 'object') return true;
for (const [k, v] of Object.entries(where)) {
if (k === '$and') return (v as any[]).every((w) => matches(row, w));
// `$and` / `$or` are CONJOINED with their sibling keys, the way a
// real driver reads them — never `return`ed, which would discard
// every sibling the loop has not reached yet (#7620 / #8494).
if (k === '$and') {
if (!(v as any[]).every((w) => matches(row, w))) return false;
continue;
}
if (k === '$or') {
if (!(v as any[]).some((w) => matches(row, w))) return false;
continue;
}
if (k.startsWith('$')) continue;
const exp = (v && typeof v === 'object' && '$in' in (v as any)) ? (v as any).$in : v;
if (Array.isArray(exp)) { if (!exp.includes(row[k])) return false; }
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -88,8 +88,17 @@ function makeStoreDriver(): { driver: unknown } & StoreDriver {
const matches = (row: Row, where: Record<string, unknown> | undefined): boolean => {
if (!where) return true;
for (const [k, v] of Object.entries(where)) {
if (k === '$and') return (v as Array<Record<string, unknown>>).every((w) => matches(row, w));
if (k === '$or') return (v as Array<Record<string, unknown>>).some((w) => matches(row, w));
// `$and` / `$or` are CONJOINED with their sibling keys, the way a real
// driver reads them — never `return`ed, which would discard every
// sibling the loop has not reached yet (#7620 / #8494).
if (k === '$and') {
if (!(v as Array<Record<string, unknown>>).every((w) => matches(row, w))) return false;
continue;
}
if (k === '$or') {
if (!(v as Array<Record<string, unknown>>).some((w) => matches(row, w))) return false;
continue;
}
if (k.startsWith('$')) continue;
if (v !== null && typeof v === 'object') {
const cmp = v as Record<string, unknown>;
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -125,8 +125,13 @@ async function boot(options: BootOptions = {}) {
const rows: Record<string, Row[]> = options.rows ?? {};
const matches = (row: Row, where: any): boolean => {
if (!where || typeof where !== 'object') return true;
if (Array.isArray(where.$and)) return where.$and.every((w: any) => matches(row, w));
// `$and` / `$or` are CONJOINED with their sibling keys, the way a real
// driver reads them — never `return`ed, which would discard every sibling
// equality key in the same filter object (#7620 / #8494).
if (Array.isArray(where.$and) && !where.$and.every((w: any) => matches(row, w))) return false;
if (Array.isArray(where.$or) && !where.$or.some((w: any) => matches(row, w))) return false;
return Object.entries(where).every(([k, v]) => {
if (k === '$and' || k === '$or') return true;
if (v && typeof v === 'object' && Array.isArray((v as any).$in)) {
return (v as any).$in.map(String).includes(String(row[k]));
}
Expand Down
13 changes: 9 additions & 4 deletions scripts/check-engine-double-contract.mjs
Original file line numberDiff line numberDiff line change
Expand Up@@ -91,10 +91,15 @@
// - 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.
// - 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.
// - the option surface (unknown-option rejection). Same family, but it needs
// its own producer-side predicate extracted first -- the two write verbs
// have one because #4434 and #5480 paid for them.
// - the READ side (`find` filter semantics) is no longer uncovered:
// `scripts/check-where-matcher-conformance.mjs` (#8494) holds it. It could
// not follow this gate's pattern, because extracting the producer-side
// predicate a read slice would need -- a shared `matchesWhere` -- was ruled
// NO on #7620 for these doubles specifically. So it asks each independent
// double a behavioural question instead of handing it an implementation.
// - a scoped repository that declares NO repository-only member. Measured on
// the corpus this landed against: `packages/runtime/src/action-body-identity
// .test.ts:71` is a real scoped facade (`createContext().object(name)`)
Expand Down
Loading
Loading