Skip to content

finding: 16 test doubles short-circuit $or in their WHERE matcher, dropping sibling filters — the suite stays green while testing a different query #7620

Description

@claude

What

Sixteen test files spread over four packages build an in-memory driver / engine double whose WHERE matcher returns early on $or (and usually $and), discarding every sibling equality key in the same object:

const matchesWhere = (row, where) => {
    if (!where || typeof where !== 'object') return true;
    if (Array.isArray(where.$and)) return where.$and.every((w) => matchesWhere(row, w));
    if (Array.isArray(where.$or)) return where.$or.some((w) => matchesWhere(row, w));   // <-- returns here
    for (const [k, v] of Object.entries(where)) { /* the sibling keys, never reached */ }
};

A real driver conjoins them. So for a query shaped like SysMetadataRepository.listDrafts's —

{ state: 'draft', package_id: 'app.x', $or: [{ organization_id: ORG }, { organization_id: null }] }

— the double answers on the $or alone and hands back rows matching neither state nor package_id: active rows, other packages' rows, anything with a matching org. This is not a stricter or looser edge case; it is a different query.

Why it matters — measured, not theorised

Hit while measuring #7559. A probe driving the real publishPackageDrafts twice through one of these doubles had its second publish record no sys_metadata_commit row at all (commitId: undefined) while still promoting the draft — because listDrafts returned the active row alongside the draft one and the batch went down a different path. Correcting the matcher to conjoin (~8 lines) made the same scenario behave as the product does, and only then did the real defect reproduce.

The failure mode is the dangerous direction: the suite stays green while silently testing a scenario nobody wrote. Nothing distinguishes "this double is faithful here" from "this double quietly changed the fixture", and every one of these files reads as if the query were honoured.

Where

$or short-circuit, confirmed by grep on 2c1988c:

  • packages/objectql/src/protocol-recorded-by-null.test.ts, save-meta-response-conformance.test.ts, plugin.authoring-channel.test.ts, publish-meta-response-conformance.test.ts, protocol-save-meta-repo-path-real-engine.test.ts, protocol-registry-shadow.test.ts
  • packages/runtime/src/domains/share-links-enforcement-context.test.ts
  • packages/plugins/plugin-security/src/row-write-widener-composition.test.ts, vama-write-path-convergence.test.ts, authored-row-write-verdict.test.ts
  • packages/plugins/plugin-sharing/src/bulk-recompute.test.ts, boot-backfill.test.ts, authored-row-write-deferral.test.ts, sharing-rule.test.ts, system-write-skip-notice.test.ts, record-share-cascade.test.ts

Not audited per file: whether each one's suite actually sends a mixed $or + sibling-key query today. Several plausibly never do, in which case the looseness there is dormant rather than active — dormant is still worth closing, because the next test that adds an $or inherits a double that lies. Establishing which are live is the first step of this issue, not a precondition for filing it. sharing and security are the packages most likely to be live: org / owner / group $or predicates are their normal shape.

Suggested shape

Conjoin instead of returning early — the whole change per file:

for (const [k, v] of Object.entries(where)) {
    if (k === '$and') { if (!v.every((w) => matchesWhere(row, w))) return false; continue; }
    if (k === '$or')  { if (!v.some((w) => matchesWhere(row, w)))  return false; continue; }
    if (k.startsWith('$')) continue;
    /* equality compare */
}

Better than sixteen copies: one shared matchesWhere helper these doubles import, so the semantics are fixed once. That is a judgement for whoever picks this up — the copies differ slightly in what else they support, and consolidating them is a larger change than correcting them.

packages/objectql/src/protocol-revert-org-scope.test.ts (PR #7619) carries the corrected form with a comment saying why, and can be copied.

Not

Not a product defect — no shipped code path is affected, and no user hits this. It is a test-fixture fidelity gap, filed per Prime Directive #10 rather than fixed in #7619's PR, which is scoped to the revert. Adjacent but distinct from #7264 (objectql test driver doubles any-annotated at scale): that one is about the type channel over these same doubles, this one about their runtime semantics.


Found while measuring #7559.

Generated by Claude Code


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions