Skip to content

finding: check-where-matcher-conformance cannot grade an INVERTED survivor filter — the control probe drops it, so a delete double's combinator blindness stays ungraded #8662

Description

@qq9340100

Filed while working #8615 (PR #8661), which extended check-where-matcher-conformance's
discovery to single-parameter .filter() callbacks that capture their where from an
enclosing scope.

The gap

That extension proposed 64 new structural candidates. The behavioural control probe seated
62 and dropped 2. The 2 it dropped share one shape — an inverted survivor filter inside
a delete double:

const survivors = rowsOf(table).filter(
(r) => !Object.entries(o?.where ?? {}).every(([k, v]) => r[k] === v),
);

The control probe admits a candidate only when f(ROW, MATCH) === true and
f(ROW, MISS) === false. An inverted predicate answers false/true, so it is dropped as
OUT_OF_SCOPE — correctly, by the gate's own definition: it is not a row-selecting
predicate.

But it carries the identical failure shape (b) the gate exists to catch, one negation away.
A $or handed to one of these is read as an ordinary field name, row.$or is undefined,
.every() returns false, the negation makes it true — and the row survives a delete
that should have removed it
. The suite then asserts on a table that was never modified,
with nothing erroring. Same silence, opposite sign.

Why this was not "fixed" in #8661

Teaching the control probe to recognise a negated predicate means inferring intent from
syntax — deciding that a leading ! means "invert my expectations". That is precisely the
naming-and-shape reasoning the gate's header rules out ("membership is decided by behaviour,
not by a name"), and a probe that guesses wrong in this direction grades a non-matcher as a
matcher, which is worse than not grading it.

The two known instances were fixed on their merits in #8661 (they now refuse an
unrecognised $-key), and the limit is pinned there as a self-test fixture
(FIXTURE_CAPTURED_NEGATED) so it stays a known limit rather than drifting into an
accidental one. What remains open is that the gate still cannot grade this spelling: a
new inverted survivor filter written tomorrow is invisible to it, exactly as the single-param
capture was before #8615.

Measured state

  • instances found on main at filing: 2, both fixed in test(where-matcher): discover single-param filter callbacks that capture their where #8661
    • packages/plugins/plugin-email/src/attachment-reclaim.test.ts
    • packages/plugins/plugin-webhooks/src/auto-enqueuer.test.ts
  • both are DORMANT — no suite currently hands either one a $or/$and (verified: the
    refusals changed no test outcome across 11,257 tests)
  • structural candidates the probe currently drops for this reason: 2 of 64

Options, not investigated further

  1. Give the battery a second admission mode: a candidate that answers the control probe
    exactly inverted (false/true) is seated as an INVERTED matcher and graded against a
    negated battery. Keeps the criterion behavioural — inversion is measured, not read off a
    ! token.
  2. Leave it ungraded and rely on the pinned fixture plus review. Cheapest, and honest, but
    it is the same "a gate that cannot see a class of instance" argument check-where-matcher-conformance's discovery heuristic misses single-param inline filter closures — 16 files carry the same shape-(b) blind spot, undetected #8615 was filed on.

Option 1 looks structurally right and is the same move #8615 made one level down, but the
false-positive load of a second admission mode is unmeasured and that measurement is the
work here, not the coding.

Refs: #8615 (the discovery extension that surfaced this), #8661 (the PR), #8494 (the gate),
#8582 / #7620 (the original sweeps).

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions