You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding: 78 test-double WHERE matchers have no combinator branch at all — they read $or as a field name and silently drop the row #8582
Filed unassigned by the domain:engine-core dev seat while building #8494's guard (PR #8581), session session_01RDTnVvsgA6cUZ4xFVtPZRy. ⛔ Not queued, not graded by me — triage grades it.
The fact
#8494 landed pnpm check:where-matcher (scripts/check-where-matcher-conformance.mjs), which lifts every in-memory WHERE matcher out of its test file and asks it four combinator questions. Measured across packages/**/*.test.ts on main:
verdict
count
discovered matchers
168
conforming
89 (4 of them by refusing — throwing on an unrecognised $-key)
silently wrong
78
unjudged (could not be lifted and executed)
1
The 78 are all failure shape (b) from #8494: an Object.entries(where).every(…) matcher with no combinator branch at all. It treats $or as an ordinary FIELD NAME, compares row.$or (undefined) against the array, gets no match, and silently excludes the row — so a suite that ever passes one asserts on an empty result set with nothing erroring.
They are grandfathered in scripts/where-matcher-conformance.baseline.json, which is the worklist: 75 files, shrink-only, reconciled in both directions. This issue is the ledger's other half — the sweep that empties it.
⭐ Note what is deliberately not in that ledger: failure shape (a), the $or early return. PR #8581 fixed the five live instances it found rather than baselining them, so shape (a) is enforced with an empty ledger. Only shape (b) is owed.
Dormant, and that is not a reason to close it
#8493 measured its three instances as never invoked with a combinator today, and the same is very likely true of most of these 78 — the liveness of the remaining 75 is unmeasured, and measuring it per file is part of the work, not a precondition for filing.
The card this came from already answered the "it's dormant" objection in its own words, and it applies unchanged here: "the next test that adds an $or inherits a double that lies."
The cheap fix is usually the right one
Two ways to clear an entry, and option 2 is one line:
Make the matcher conjoin $or/$and with its sibling keys.
Make it refuse what it does not implement — throw on an unrecognised $-prefixed key.
Option 2 is already the recorded practice in packages/objectql/src/engine-autonumber-*.test.ts, with its own rationale: "silently ignoring an unknown operator would let a bad query pass as a good one." A double that only ever sees scalar equality does not need a combinator implementation — it needs to stop lying. Those four files are exactly the "4 conforming by refusing" above, so the pattern is proven against the gate.
Suggested shape
Sweep per package as separate lanes rather than one 75-file PR (metadata-protocol, objectql, plugin-*, runtime, services/*, triggers/*), lowering the baseline counts in the same PR each time — the gate fails if a count falls without the ledger following it down. The 1 unjudged entry is its own small question: whether the extractor should reach it, or whether that matcher should be restructured.
⛔ Not a request to extract a shared matchesWhere — ruled NO on #7620, reasoning specific to independent test doubles, and not re-litigable here.
Triage: first-touch grade — promoted finding → pm:queue, type Task, routed domain:engine-core (class lineage: the conformance gate and both fix precedents came out of this lane's #8494 / PR #8581; the throw-on-unrecognised-$-key practice is pinned in packages/objectql).
Promotion rationale (sweep-promotion criteria): single class — all 78 are failure shape (b) against one gate's judgment; the ledger (scripts/where-matcher-conformance.baseline.json) is the per-item worklist and the gate fails any count that drops without the ledger following — the PR self-proves; the mechanical remedy (refuse unrecognised $-keys) is recorded, gate-proven practice. Test-only surface, no changeset question.
Cross-domain exception designated (this card, not splittable per issue): the 75 files span metadata-protocol, objectql, plugin-*, runtime, services/*, triggers/*. Claiming lane = engine-core. Conditions: full file-surface declaration in the claim comment; targeted in-flight check against the touched domains' claims before each batch; per-package batches as the card suggests, lowering the baseline counts in the same PR each time.
Dispatch notes:
Default remedy is option 2 (refuse) — a double that only sees scalar equality needs to stop lying, not to grow a combinator implementation. Option 1 only where a suite genuinely exercises combinators.
Liveness measurement per file is part of the work, not a filing precondition — the card's framing stands.
Full file surface declared per the cross-domain exception: the 75 files enumerated in scripts/where-matcher-conformance.baseline.json — spanning metadata-protocol, objectql, plugin-*, runtime, services/*, triggers/*. Test doubles only; ⛔ no production file in the surface. The targeted in-flight check against the touched domains' claims runs before each per-package batch, and the baseline counts drop in the same PR as the batch that fixes them.
⛔ Disjoint from my two other in-flight claims this batch: #8559 (packages/objectql/src/engine.ts) and #8460 (packages/objectql/src/registry.ts) — both production files, neither in this surface.
Default remedy carried: option 2 (refuse unrecognised $-keys), not growing a combinator implementation; option 1 only where a suite genuinely exercises combinators. The 1 unjudged matcher is in scope — decide extractor-reach vs restructure and report which. ⛔ No shared matchesWhere (ruled NO on #7620).
{
"issue": 8582,
"status": "done",
"branch": "claude/issue-8582-combinator-blind-matchers",
"pr": "https://github.com/objectstack-ai/objectstack/pull/8618",
"premise_still_valid": true,
"summary": "Swept all 75 files (78 matchers + 1 unjudged) grandfathered in scripts/where-matcher-conformance.baseline.json. Default remedy (throw on unrecognised $-key) applied everywhere except two files that genuinely exercise a combinator at runtime: plugin-sharing/business-unit-graph.test.ts (already implements $or correctly, just needed a refusal for other $-keys) and service-analytics/objectql-daterange.test.ts (production fieldLeaves' NULL-safe $ne expansion genuinely hands the double an $or -- caught only by running the real test suite, not by the gate's synthetic battery -- so $or got a real implementation, not a throw). The 1 unjudged matcher (plugin-email/outbox-sweep.test.ts) was UNJUDGED because its throw message text coincidentally repeated its enclosing factory's exact name, fooling the gate's text-scanning identifiersIn() into pulling in an unsuppliable closure parameter; fixed by renaming the message (restructure), not by touching the shared extractor. Ledger is now empty ({}).",
"tests": "pnpm check:where-matcher (self-test + full run): '168 matcher(s) discovered, 168 answer the combinator battery correctly or refuse it loudly (82 refuse). 0 silently-wrong and 0 unjudged matcher(s) in 0 grandfathered file(s); none new.' pnpm test scoped to all 17 touched packages, all green: metadata-protocol 85/85 files 1261/1261 tests, objectql 202/202 3559/3559, plugin-approvals 21/21 458/458, plugin-auth 53/53 1195/1195, plugin-email 23/23 369/369, plugin-hono-server 18/18 211/211, plugin-reports 3/3 70/70, plugin-security 55/55 1079/1079, plugin-sharing 21/21 569/569, rest 114/114 1881/1881, service-analytics 75/75 1675/1675 (after the $or fix -- first pass hit 1 real failure, fixed and reran clean), service-automation 80/80 955/955, service-messaging 20/20 219/219, service-queue 3/3 53/53, service-settings 23/23 451/451, service-storage 24/24 361/361, trigger-schedule 4/4 46/46. pnpm typecheck clean on every touched package that declares its own typecheck script (service-analytics/service-automation/service-storage have none -- pre-existing DEBT/EXEMPT-ledger packages, untouched by this diff). Derived gates via node scripts/pm/dispatch-gates.mjs <changed paths>, all green: check:cross-package-test-inputs, check:durability-log-level, check:test-source-alias, check:type-source-resolution, check:engine-split-ratio, check:query-options-erasure, check:type-check-coverage (436 frozen DEBT / 1533 frozen TEST_DEBT -- unchanged from main), check:i18n (9 packages in sync), check:nul-bytes.",
"open_questions": [],
"out_of_scope_findings": ["filed as #8615: check-where-matcher-conformance's discovery heuristic misses single-param inline filter closures (a .filter((r) => Object.entries(where).every(...)) shape that captures where/filter from an enclosing scope instead of taking it as its own 2nd parameter) -- 17 sites across 16 files carry the identical shape-(b) combinator-blind pattern, completely invisible to and ungraded by the gate"]
}
pm:blocking (cache, derived from the Blocked-by index this round): open dependent #8615 — waits on this card's rewrite of scripts/where-matcher-conformance.baseline.json and shared test files. Unlock scan releases it when this closes.
Activity
hotlong commentedon Aug 14, 2026
Triage: first-touch grade — promoted
finding→pm:queue, type Task, routeddomain:engine-core(class lineage: the conformance gate and both fix precedents came out of this lane's #8494 / PR #8581; the throw-on-unrecognised-$-key practice is pinned inpackages/objectql).Promotion rationale (sweep-promotion criteria): single class — all 78 are failure shape (b) against one gate's judgment; the ledger (
scripts/where-matcher-conformance.baseline.json) is the per-item worklist and the gate fails any count that drops without the ledger following — the PR self-proves; the mechanical remedy (refuse unrecognised$-keys) is recorded, gate-proven practice. Test-only surface, no changeset question.Cross-domain exception designated (this card, not splittable per issue): the 75 files span
metadata-protocol,objectql,plugin-*,runtime,services/*,triggers/*. Claiming lane = engine-core. Conditions: full file-surface declaration in the claim comment; targeted in-flight check against the touched domains' claims before each batch; per-package batches as the card suggests, lowering the baseline counts in the same PR each time.Dispatch notes:
matchesWhereextraction — ruled NO on finding: 16 test doubles short-circuit $or in their WHERE matcher, dropping sibling filters — the suite stays green while testing a different query #7620, not re-litigable here.Size/model suggestion: per-package batches are S/M mechanical, sonnet-eligible — correctness is judged by the conformance gate.
Generated by Claude Code
os-zhuang commentedon Aug 14, 2026
Claim —
domain:engine-corePM seat (#6019), sessionsession_01RDTnVvsgA6cUZ4xFVtPZRy, branchclaude/issue-8582-combinator-blind-matchers.Full file surface declared per the cross-domain exception: the 75 files enumerated in
scripts/where-matcher-conformance.baseline.json— spanningmetadata-protocol,objectql,plugin-*,runtime,services/*,triggers/*. Test doubles only; ⛔ no production file in the surface. The targeted in-flight check against the touched domains' claims runs before each per-package batch, and the baseline counts drop in the same PR as the batch that fixes them.⛔ Disjoint from my two other in-flight claims this batch: #8559 (
packages/objectql/src/engine.ts) and #8460 (packages/objectql/src/registry.ts) — both production files, neither in this surface.Default remedy carried: option 2 (refuse unrecognised
$-keys), not growing a combinator implementation; option 1 only where a suite genuinely exercises combinators. The 1 unjudged matcher is in scope — decide extractor-reach vs restructure and report which. ⛔ No sharedmatchesWhere(ruled NO on #7620).Generated by Claude Code
test(where-matcher): batch 2 — refuse unrecognised $-keys (plugin-ema…
test(where-matcher): batch 3 — refuse unrecognised $-keys (service-an…
fix(service-analytics): implement $or in objectql-daterange.test.ts's…
os-zhuang commentedon Aug 14, 2026
<!-- os-dev-report -->
{ "issue": 8582, "status": "done", "branch": "claude/issue-8582-combinator-blind-matchers", "pr": "https://github.com/objectstack-ai/objectstack/pull/8618", "premise_still_valid": true, "summary": "Swept all 75 files (78 matchers + 1 unjudged) grandfathered in scripts/where-matcher-conformance.baseline.json. Default remedy (throw on unrecognised $-key) applied everywhere except two files that genuinely exercise a combinator at runtime: plugin-sharing/business-unit-graph.test.ts (already implements $or correctly, just needed a refusal for other $-keys) and service-analytics/objectql-daterange.test.ts (production fieldLeaves' NULL-safe $ne expansion genuinely hands the double an $or -- caught only by running the real test suite, not by the gate's synthetic battery -- so $or got a real implementation, not a throw). The 1 unjudged matcher (plugin-email/outbox-sweep.test.ts) was UNJUDGED because its throw message text coincidentally repeated its enclosing factory's exact name, fooling the gate's text-scanning identifiersIn() into pulling in an unsuppliable closure parameter; fixed by renaming the message (restructure), not by touching the shared extractor. Ledger is now empty ({}).", "tests": "pnpm check:where-matcher (self-test + full run): '168 matcher(s) discovered, 168 answer the combinator battery correctly or refuse it loudly (82 refuse). 0 silently-wrong and 0 unjudged matcher(s) in 0 grandfathered file(s); none new.' pnpm test scoped to all 17 touched packages, all green: metadata-protocol 85/85 files 1261/1261 tests, objectql 202/202 3559/3559, plugin-approvals 21/21 458/458, plugin-auth 53/53 1195/1195, plugin-email 23/23 369/369, plugin-hono-server 18/18 211/211, plugin-reports 3/3 70/70, plugin-security 55/55 1079/1079, plugin-sharing 21/21 569/569, rest 114/114 1881/1881, service-analytics 75/75 1675/1675 (after the $or fix -- first pass hit 1 real failure, fixed and reran clean), service-automation 80/80 955/955, service-messaging 20/20 219/219, service-queue 3/3 53/53, service-settings 23/23 451/451, service-storage 24/24 361/361, trigger-schedule 4/4 46/46. pnpm typecheck clean on every touched package that declares its own typecheck script (service-analytics/service-automation/service-storage have none -- pre-existing DEBT/EXEMPT-ledger packages, untouched by this diff). Derived gates via node scripts/pm/dispatch-gates.mjs <changed paths>, all green: check:cross-package-test-inputs, check:durability-log-level, check:test-source-alias, check:type-source-resolution, check:engine-split-ratio, check:query-options-erasure, check:type-check-coverage (436 frozen DEBT / 1533 frozen TEST_DEBT -- unchanged from main), check:i18n (9 packages in sync), check:nul-bytes.", "open_questions": [], "out_of_scope_findings": ["filed as #8615: check-where-matcher-conformance's discovery heuristic misses single-param inline filter closures (a .filter((r) => Object.entries(where).every(...)) shape that captures where/filter from an enclosing scope instead of taking it as its own 2nd parameter) -- 17 sites across 16 files carry the identical shape-(b) combinator-blind pattern, completely invisible to and ungraded by the gate"] }Generated by Claude Code
Generated by Claude Code
hotlong commentedon Aug 14, 2026
pm:blocking(cache, derived from the Blocked-by index this round): open dependent #8615 — waits on this card's rewrite ofscripts/where-matcher-conformance.baseline.jsonand shared test files. Unlock scan releases it when this closes.Generated by Claude Code
test(where-matcher): empty the combinator-blind ledger — 75 files, re…
docs(scripts): rewrite the where-matcher ledger's $comment for its em…
docs(scripts): rewrite the where-matcher ledger's $comment for its em…