Repository navigation
Description
Filed while working #8582 (session session_01RDTnVvsgA6cUZ4xFVtPZRy), which swept the 78 test-double matches(row, where) fakes that pnpm check:where-matcher discovered and grandfathered in scripts/where-matcher-conformance.baseline.json.
The gap
scripts/check-where-matcher-conformance.mjs's discovery only considers a candidate matcher when it is a function with two or more identifier parameters — see discoverInSource's params.length >= 2 && params[0] && params[1] gate. That is deliberate (it is how the control probe stays name-independent), but it has a structural blind spot: a single-param .filter() callback that closes over where/filter from its enclosing scope, rather than taking it as its own second parameter, is invisible to discovery — even when its body is textually identical to a matcher the gate already knows how to grade.
Measured on main (as of this filing), this exact shape —
.filter((r) => Object.entries(where).every(([k, v]) => r[k] === v))
— or a same-file filter variant, appears 17 times across 16 files, none of them reachable by pnpm check:where-matcher and none in its ledger:
packages/plugins/plugin-email/src/attachment-reclaim.test.ts:58packages/plugins/plugin-email/src/template-locale-resolution.test.ts:67packages/plugins/plugin-security/src/bootstrap-declared-positions.test.ts:29packages/plugins/plugin-security/src/bootstrap-declared-permissions.test.ts:18packages/plugins/plugin-security/src/bootstrap-platform-admin.test.ts:30packages/plugins/plugin-auth/src/admin-import-users.test.ts:36packages/core/src/security/api-key.test.ts:20packages/metadata-protocol/src/migrations/recorded-by-sentinel.test.ts:76packages/core/src/utils/migration-journal.test.ts:84packages/rest/src/import-runner-selfref.test.ts:47packages/rest/src/import-runner-idempotency.test.ts:56packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts:61and:88(two instances)packages/services/service-storage/src/file-reference-lifecycle.test.ts:237(a second, undiscovered instance in a file finding: 78 test-double WHERE matchers have no combinator branch at all — they read$oras a field name and silently drop the row #8582 already fixed a different, discovered matcher in)packages/runtime/src/migration-recovery-plugin.test.ts:48packages/runtime/src/http-dispatcher.keys.test.ts:28packages/runtime/src/http-dispatcher.test.ts:3947
Each treats where/filter as an ordinary field bag via Object.entries(...).every(([k,v]) => r[k] === v) with no $or/$and branch — the identical failure shape (b) #8494/#8582 exist to catch. Whether each is live (a suite actually hands it a $or/$and) is unmeasured — same as #8582's worklist was before it was swept — but the shape itself is not in question; it is byte-for-byte the pattern the gate already grades everywhere it CAN see it.
Why this is worth a decision, not a silent tolerance
check-where-matcher-conformance's own header states discovery is "self-verifying: membership is decided by behaviour, not by a name" — true for the matchers it finds, but the two-param requirement means a whole authoring STYLE (inline .filter() capturing an outer where) sits entirely outside its authority. A gate that cannot see a class of instance cannot grade it green OR red; "not in the ledger" reads as "conforming" to anyone skimming check:where-matcher's output, which is exactly backwards for these 17.
Options (not investigated further — this is the finding, not the fix)
- Extend discovery to also match a single-param
.filter/.some/.everycallback whose body readsObject.entries(<name>)where<name>resolves (via the samevisibleDeclarationswalk already built for extraction) to an enclosing parameter or destructured option — i.e., loosen the "must be its own 2nd parameter" requirement to "the where-shaped identifier must resolve to something in scope," while keeping the behavioural control probe as the real filter against false positives. - Manually audit and fix the 17 sites the same way finding: 78 test-double WHERE matchers have no combinator branch at all — they read
$oras a field name and silently drop the row #8582 did its 78 — cheaper per-instance, but does not close the gate's blind spot for the next one someone writes in this style.
Either way the two are not mutually exclusive; 2 without 1 leaves the hole open for new code.
Refs: #8582 (the sweep that surfaced this), #8494 (the gate this extends), #7620 (the original 16-file $or short-circuit finding).
Generated by Claude Code
Activity
hotlong commentedon Aug 14, 2026
Blocked-by: #8582
Triage: lands in
scripts/check-where-matcher-conformance.mjs(discovery gate verified at line 248 on currentorigin/main:params.length >= 2) plus the 17 listed test sites ⇒domain:devx(gate script underscripts/), type Task, statepm:blockedon #8582..filter/.some/.everycallbacks whose where-shaped identifier resolves in an enclosing scope, keeping the behavioural control probe as the false-positive filter; and (2) bring the 17 sites under the gate (fix them the way finding: 78 test-double WHERE matchers have no combinator branch at all — they read$oras a field name and silently drop the row #8582's sweep did, or grandfather into the ledger where the gate's own design says so). (2) without (1) leaves the hole open for the next matcher written in this style. This strengthens a gate — the gate-weakening human floor does not apply.$oras a field name and silently drop the row #8582 (in flight) rewritesscripts/where-matcher-conformance.baseline.jsonand touches at least one of the same test files (file-reference-lifecycle.test.ts); the 17-site measurement was taken against finding: 78 test-double WHERE matchers have no combinator branch at all — they read$oras a field name and silently drop the row #8582's branch state. Same-surface hard serial; the unlock scan returns this card when finding: 78 test-double WHERE matchers have no combinator branch at all — they read$oras a field name and silently drop the row #8582 closes.mode:subagent, opus (the AST-heuristic extension needs judgment; the 17-site half is mechanical).Generated by Claude Code
claude commentedon Aug 14, 2026
Unlock sweep: blocker cleared.
pm:blocked→pm:queue.Declared blocker: objectstack#8582 — verified closed against the GitHub API on 2026-08-14. No other blocker is declared on this card.
Blocked-by:line lives in a comment, not the body. The unlock sweep greps the body, so this card was not discoverable by the automated sweep and was found only by a manual read of everypm:blockedcard. Several seats recorded that they placed the line in a comment deliberately, to avoid the MCP body round-trip re-encoding the card's punctuation as HTML entities. That trade-off is sound per card and structurally lossy in aggregate: 26 of the 40 openpm:blockedcards across the three repos carry no body-levelBlocked-by:. The durable fix belongs in the sweep (grep comments too), not in asking seats to risk body rewrites.Authorization: maintainer directive (this session) —「先做 23 张 blocked 解锁 10 张 on-hold 按各自自述处理」· PM session
01JaVVMrSxt7Tgi1uwEuDtH7Generated by Claude Code
test(where-matcher): discover single-param filter callbacks that capt…