Skip to content

check-where-matcher-conformance's discovery heuristic misses single-param inline filter closures — 16 files carry the same shape-(b) blind spot, undetected #8615

Closed

Description

@os-zhuang

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:58
  • packages/plugins/plugin-email/src/template-locale-resolution.test.ts:67
  • packages/plugins/plugin-security/src/bootstrap-declared-positions.test.ts:29
  • packages/plugins/plugin-security/src/bootstrap-declared-permissions.test.ts:18
  • packages/plugins/plugin-security/src/bootstrap-platform-admin.test.ts:30
  • packages/plugins/plugin-auth/src/admin-import-users.test.ts:36
  • packages/core/src/security/api-key.test.ts:20
  • packages/metadata-protocol/src/migrations/recorded-by-sentinel.test.ts:76
  • packages/core/src/utils/migration-journal.test.ts:84
  • packages/rest/src/import-runner-selfref.test.ts:47
  • packages/rest/src/import-runner-idempotency.test.ts:56
  • packages/services/service-storage/src/lax-deviation-reclamation-gate.test.ts:61 and :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 $or as a field name and silently drop the row #8582 already fixed a different, discovered matcher in)
  • packages/runtime/src/migration-recovery-plugin.test.ts:48
  • packages/runtime/src/http-dispatcher.keys.test.ts:28
  • packages/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)

  1. Extend discovery to also match a single-param .filter/.some/.every callback whose body reads Object.entries(<name>) where <name> resolves (via the same visibleDeclarations walk 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.
  2. Manually audit and fix the 17 sites the same way 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 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

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 14, 2026
  2. added theissue type on Aug 14, 2026
  3. hotlong commented on Aug 14, 2026

    @hotlong
    Contributor

    Blocked-by: #8582

    Triage: lands in scripts/check-where-matcher-conformance.mjs (discovery gate verified at line 248 on current origin/main: params.length >= 2) plus the 17 listed test sites ⇒ domain:devx (gate script under scripts/), type Task, state pm:blocked on #8582.


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Aug 14, 2026
  5. claude commented on Aug 14, 2026

    @claude
    Contributor

    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.

    ⚠️ This card's 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 every pm:blocked card. 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 open pm:blocked cards across the three repos carry no body-level Blocked-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 01JaVVMrSxt7Tgi1uwEuDtH7


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions