Skip to content

test(objectql): conjoin $or/$and with sibling filters in six driver doubles - #7846

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-7620-objectql-or-matcher-conjoin
Aug 11, 2026
Merged

test(objectql): conjoin $or/$and with sibling filters in six driver doubles#7846
huangyiirene merged 1 commit into
mainfrom
claude/issue-7620-objectql-or-matcher-conjoin

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Part of #7620

⚠️ Part of, not Fixes, and deliberately so. #7620 names 16 files across four packages. This PR carries the six in packages/objectql/src — the domain:engine-core lane, per the claim comment on the card. The remaining ten (6 plugin-sharing, 3 plugin-security, 1 runtime) belong to two other seats and are routed separately, so the card must survive this merge.

What

Six in-memory driver doubles built a WHERE matcher that returned early on $and/$or, discarding every sibling equality key in the same object:

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));
for (const [k, v] of Object.entries(where)) { /* the siblings, never reached */ }

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

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

— would have been answered on the $or alone, handing back rows matching neither state nor package_id. Not a stricter or looser edge case: a different query, with the suite staying green while testing it.

The fix folds $and/$or into the entries loop so they compose with their siblings, matching the corrected form protocol-revert-org-scope.test.ts already carries from #7619 (re-verified intact on current main).

file matcher calls observed $or/$and seen
publish-meta-response-conformance.test.ts 60 0
protocol-recorded-by-null.test.ts 44 0
protocol-save-meta-repo-path-real-engine.test.ts 21 0
protocol-registry-shadow.test.ts 7 0
save-meta-response-conformance.test.ts 0 0
plugin.authoring-channel.test.ts 0 0

Live vs dormant — measured, not assumed

The card names establishing this as step one. A probe was installed in each of the six matchers, logging every where it was handed, and the suites run:

132 matcher calls across the six suites, and not one $or or $and. All six are dormant.

The two zero-call files never invoke their matcher at all — those suites drive writes, not reads. The control that makes the silence evidence rather than a dead probe is the 132 plain-equality calls the same instrumentation did record.

Consequence: no existing test outcome changes, and none should. There was no correction-induced failure to report — the interesting result this card could have produced (a test that was passing against a query nobody wrote) did not materialise in this lane. The card's own note that sharing and security are the likeliest to be live is unaffected by this measurement and still worth the other seats' attention.

Dormant is still worth closing: nothing distinguished "this double is faithful here" from "this double quietly changed the fixture", and the next test to add an $or would have inherited a matcher that lies.

Capability preservation

Each matcher keeps exactly the operator surface it already had — $eq unwrapping, the undefinednull comparison normalisation, and the skip for any other $-prefixed key. A non-array $and/$or still falls through to that skip, exactly as before. Measured first: the six proved semantically identical, differing only in indentation (one is 2-space) and an intermediate rowVal variable, so there was no lowest-common-denominator to flatten anything to.

No shared helper — stated deliberately

A helper local to packages/objectql would genuinely have reduced six to one. Rejected anyway, for three reasons:

  1. publish-meta-response-conformance.test.ts carries the repo's own written rationale against it: "a gate that imports its own substrate from another gate's file couples two tripwires that must be able to fail independently."
  2. fix(metadata-protocol,metadata): revert reads the history row under the key the writer stored it with (#7559) #7619's reference correction is inline for the same reason, and it is the template this card says to copy.
  3. An objectql-local helper cannot serve the ten remaining files in plugin-sharing / plugin-security / runtime — a cross-package one is out of scope by construction — so it would add a second convention rather than consolidate to one.

Verification

All run locally in a dedicated worktree at b54aaab.

gate result
packages/objectql vitest (full) 185 files / 3274 tests passed, 0 failed
pnpm check:query-options-erasure exit 0 — 67 non-test sites, none new; baseline key set verified against b54aaab, no files added
pnpm check:type-check-debt exit 0 — 33 ledger entries re-measured, none above its recorded number
pnpm check:durability-log-level exit 0
pnpm check:engine-double-contract exit 0 — 155 pinned, unchanged
node scripts/check-engine-split-ratio.mjs exit 0
eslint on the six clean

Gate list derived with node scripts/pm/dispatch-gates.mjs <the six paths>, not enumerated by hand. Neither ratchet baseline was raised. Nothing here let me lower a ledger entry: packages/objectql is fully type-checked and carries no entry, and the surplus the debt gate reports sits in unrelated packages.

Not in this PR


Generated by Claude Code

…er doubles (part of #7620)

Six in-memory driver doubles in `packages/objectql/src` returned early on
`$and`/`$or`, discarding every sibling equality key in the same `where`
object. A real driver ANDs them, so a query like

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

was answered on the `$or` alone — a different query than the one written,
with the suite still green.

Fold `$and`/`$or` into the entries loop, matching the corrected form
`protocol-revert-org-scope.test.ts` already carries from #7619. Each
matcher keeps exactly the operator surface it had: `$eq` unwrapping, the
`undefined`->`null` comparison normalisation, and the skip for any other
`$`-prefixed key (which a non-array `$and`/`$or` still falls through to).

Measured, not assumed: a probe in each matcher logging every `where` it
was handed recorded 132 calls across the six suites and not one `$or` or
`$and` — all six are dormant. The 132 plain-equality calls are the
control proving the probe was live. No existing outcome changes;
packages/objectql is 185 files / 3274 tests passing before and after.

Deliberately not extracted into a shared helper: the repo's own rationale
in `publish-meta-response-conformance.test.ts` keeps these harnesses
self-contained so two gates can fail independently, and an objectql-local
helper could not serve the ten remaining files in other packages anyway.

Part of #7620 — this is the `packages/objectql` lane only; the
plugin-sharing, plugin-security and runtime files are routed separately.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Da8i4RxJBSv73tgr92D9KB
@vercel

vercel Bot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 11, 2026 8:33pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

No hand-written docs reference the 0 changed package(s). ✅

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 11, 2026
@huangyiirene
huangyiirene marked this pull request as ready for review August 11, 2026 21:20
@huangyiirene
huangyiirene added this pull request to the merge queue Aug 11, 2026
Merged via the queue into main with commit 255f2d7 Aug 11, 2026
26 checks passed
@huangyiirene
huangyiirene deleted the claude/issue-7620-objectql-or-matcher-conjoin branch August 11, 2026 21:32
huangyiirene pushed a commit that referenced this pull request Aug 11, 2026
… tier 1, part of #7620)

CI red on PR #7857: `packages/objectql/src/protocol-commit-history.test.ts` had
two org-scoped revert cases fail with COMMIT_NOT_FOUND. Measured rather than
assumed: its `matchesWhere` was pure flat equality, so the widened lookup
`{ id, $or: [{organization_id: <org>}, {organization_id: null}] }` compared
`row['$or']` against the array and matched nothing.

The double is the blind party, not the fix. Both failing rows carry the
CALLER'S OWN org (`organization_id: 'org_a'`, request org `'org_a'`), so they
match the FIRST `$or` branch outright — the same row the strict equality already
accepted. No real behaviour changed, and neither case's subject (#6602's
registry org-asymmetry) involves the commit lookup at all; it is merely the door
they enter through.

Conjoined with the sibling keys in the entries loop, matching the corrected form
#7846 landed across six doubles in this package an hour earlier. Not the
early-returning `if ($or) return …some(…)` shape those six carried before it:
that discards sibling keys, so `{ id, $or: [...] }` would stop constraining `id`
and could return some other commit whose org matched. This file was not among
#7846's six because it had no operator handling to correct, so it is a new
member of the #7620 lane rather than a regression of it.

`undefined` normalises to `null` on comparison, same as the six, because a
column a row never set reads as NULL out of a real driver.

@objectstack/objectql: 185 files / 3274 tests passing (was 184/3272 with the two
failures) — exactly the two cases restored, nothing else moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hxiiv8qFCUmDThHU1k7HV
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…ommit (objectstack-ai#7819 tier 1) (objectstack-ai#7857)

* fix(metadata-protocol): let an org-scoped caller revert an env-wide commit (objectstack-ai#7819 tier 1)

`revertCommit` and `rollbackToPackageCommit`'s target lookup each resolved their
target commit with a strict `organization_id` equality, which matches no row
whose column is NULL. An org-scoped caller therefore got COMMIT_NOT_FOUND (404)
for any commit recorded env-wide — a row that demonstrably exists and that the
same caller's `listCommits` hands back. Both lookups now accept org-scoped or
env-wide rows, the same `$or` `deletePackage` (objectstack-ai#7705) and `listCommits` (objectstack-ai#7779)
already carry.

The `$or` was chosen over the two alternatives rather than copied. `where` is
keyed on `id`, so the predicate reads like an authorization filter on a unique
key; measured against the only door it is not one. Authorization is
`requireManageMetadata`, checked before the call, and the `organizationId` that
arrives is the session's active org selection from `resolveActiveOrganizationId`
— a resolver whose body is entirely catch-wrapped and whose `undefined` omits
the predicate, i.e. the widest reading. A boundary that fails open is not a
boundary, which rules out "keep the check but distinguish 'not yours' from 'no
such commit'". Dropping the predicate outright would newly let an org caller
revert another organization's commit by id, a widening this card never asked
for. The body already agreed with the `$or`: objectstack-ai#7559 made each item resolve its
scope from the row, and since objectstack-ai#7814 `rollbackToPackageCommit` plans from
`listCommits` (org + env-wide) and fed each id back into a lookup that refused
half of them.

The no-org branch is deliberately left un-narrowed, exactly as objectstack-ai#7705 and objectstack-ai#7779
left theirs.

Pinned by a new real-engine/real-driver suite in packages/runtime (eight cases:
the premise out of SQLite, the positive per site, both negative directions, and
the no-org door per site; refusals asserted on code AND status per ADR-0112).
The objectstack-ai#7814 handoff assertion that pinned this as known-incomplete now asserts the
rollback succeeds. Reverse verification, direction predicted first: 3 failed |
11 passed, exactly the three positive cases.

Tier 1 only — `duplicatePackage` and `reassignOrphanedMetadata` are untouched
and objectstack-ai#7819 stays open to carry them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hxiiv8qFCUmDThHU1k7HV

* test(objectql): teach the commit-history double `$or`, conjoined (objectstack-ai#7819 tier 1, part of objectstack-ai#7620)

CI red on PR objectstack-ai#7857: `packages/objectql/src/protocol-commit-history.test.ts` had
two org-scoped revert cases fail with COMMIT_NOT_FOUND. Measured rather than
assumed: its `matchesWhere` was pure flat equality, so the widened lookup
`{ id, $or: [{organization_id: <org>}, {organization_id: null}] }` compared
`row['$or']` against the array and matched nothing.

The double is the blind party, not the fix. Both failing rows carry the
CALLER'S OWN org (`organization_id: 'org_a'`, request org `'org_a'`), so they
match the FIRST `$or` branch outright — the same row the strict equality already
accepted. No real behaviour changed, and neither case's subject (objectstack-ai#6602's
registry org-asymmetry) involves the commit lookup at all; it is merely the door
they enter through.

Conjoined with the sibling keys in the entries loop, matching the corrected form
objectstack-ai#7846 landed across six doubles in this package an hour earlier. Not the
early-returning `if ($or) return …some(…)` shape those six carried before it:
that discards sibling keys, so `{ id, $or: [...] }` would stop constraining `id`
and could return some other commit whose org matched. This file was not among
objectstack-ai#7846's six because it had no operator handling to correct, so it is a new
member of the objectstack-ai#7620 lane rather than a regression of it.

`undefined` normalises to `null` on comparison, same as the six, because a
column a row never set reads as NULL out of a real driver.

@objectstack/objectql: 185 files / 3274 tests passing (was 184/3272 with the two
failures) — exactly the two cases restored, nothing else moved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019hxiiv8qFCUmDThHU1k7HV

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…(part of objectstack-ai#7620) (objectstack-ai#8493)

Twelve in-memory WHERE matchers across packages/plugins/plugin-sharing,
packages/plugins/plugin-security and packages/runtime returned early on
$or/$and, discarding every sibling equality key in the same filter object —
a real driver ANDs them. Corrected to the same conjoin-with-siblings shape
already used by packages/objectql's six (objectstack-ai#7846) and by several
already-fixed siblings in these two packages.

Measured live-vs-dormant per file via an fs.appendFileSync probe (with a
positive control proving it would catch a live case): all twelve are
dormant today, for two different reasons. plugin-sharing's six never
receive $or/$and at all. plugin-security's five and the one runtime file
do receive them, but always as the sole key in their filter object (no
sibling ever present alongside), so early-return and conjoin produce
identical results in every observed call. No test outcome changes.

Re-grepped the issue's file enumeration at this branch's base ref rather
than trusting it: plugin-sharing/src/sharing-rule.test.ts was already
fixed, and three files this commit touches were never named in the issue
(plugin-sharing/src/sharing-service.test.ts,
plugin-security/src/check-only-write-scope.test.ts,
plugin-security/src/select-only-write-visibility.test.ts).


Claude-Session: https://claude.ai/code/session_01RDTnVvsgA6cUZ4xFVtPZRy

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants