Skip to content

check:where-matcher is derivable by dispatch-gates ONLY from its own baseline — never from the packages/** test tree it scans, the exact inverse of what it guards #13163

Description

@os-zhuang

Found by the L2 dev seat while getting PR #13146 green, and re-measured independently by the domain:engine PM seat (#6367) before filing. Live specimen this round.

The specimen

PR #13146 ran the gate family derived from its real 15-path change set (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack) plus the ones its diff implicated: 30 of 30 exit 0, whole-repo pnpm lint included. It then went RED in CI on Lint & Repo Gates:

✗ where-matcher conformance (1 problem(s)):
  • packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts:
    NEW silently-wrong WHERE matcher (1 silent, 0 unjudged).
      line 49 `matches`: no combinator branch (combinator read as a field name)
Discovered 315 matcher(s): 314 conforming (196 by refusing), 1 silently wrong, 0 unjudged.

The offending file is one the PR adds. So the gate works; what failed is that nothing told the dev to run it.

The measurement

check:where-matcher declares its population twice, and dispatch-gates.mjs can derive from neither:

  1. In prose, scripts/check-where-matcher-conformance.mjs:84 — a // comment reading "a function in packages/**/*.test.ts whose body…". The extractor masks comment ranges by construction, so a population documented in a comment is invisible to it.
  2. In code, scripts/check-where-matcher-conformance.mjs:233 — const SCAN_ROOT = 'packages';. A single-segment literal, which looksPathy reads as no hint at all. The one narrow exception, moduleRelativeDirectoryHint, resolves a single-segment literal against the SCRIPT'S OWN directory, so 'packages' resolves to scripts/packages, which is not tracked ⇒ null.

Run the tool's own extractor over the gate:

extractWatchHints(readFileSync('scripts/check-where-matcher-conformance.mjs'), …)
  ⇒ ["scripts/where-matcher-conformance.baseline.json"]

One hint, and it is the gate's own baseline JSON.

⇒ ⭐ The gate is derivable only for a change set that edits its own baseline — i.e. only for changes to the set of files ALREADY KNOWN to be wrong, and never for a NEW double anywhere under packages/**. That is the exact inverse of what it guards.

There is no second derivation route — checked, because it looked like there was one

PR #13122 ran check:where-matcher this same round on a driver-mongodb diff that touches no baseline, which looks like a counter-example. It is not: that PR's own report says verbatim "the dispatch named none" — its 15 gates were picked by hand. So dispatch-gates named nothing for that change set either, and the measurement above stands unqualified.

⚠️ The class is N=1 — swept, and the sweep is what finds the fix

The obvious follow-on worry is that every gate spelling its scan root as a single-segment constant is equally blind. Measured: it is not. Six such declarations exist across 115 gate scripts; the extractor was run over all of them:

gate verdict
check-where-matcher-conformance.mjs ✗ BASELINE-ONLY — the defect
check-objectql-double-limit.mjs ✅ reaches the tree
check-dispatcher-error-vocabulary.mjs ✅ reaches the tree
check-error-status-conformance.mjs ✅ reaches the tree
check-examples-live-imports.mjs ✅ reaches the tree
check-doc-authoring.mjs ✅ reaches the tree

⇒ A single-segment scan root is not sufficient to cause this. The defect needs a single-segment root AND no other tree-reaching hint. Exactly one gate is in that state.

⭐ The fix is a one-line copy from a sibling that scans the SAME population

check-objectql-double-limit.mjs has the identical const SCAN_ROOT = 'packages'; at line 231 and the identical packages/**/*.test.ts population — but it also declares:

hints: ["scripts/objectql-double-limit.baseline.json", "packages/**/*.test.ts", "n/a"]

That second hint is what makes it derivable. So this is not a design question: the correct shape already ships, in the nearest sibling, guarding the same tree.

Suggested (not prescribing): declare packages/**/*.test.ts in check-where-matcher-conformance.mjs where the extractor can see it, matching its sibling.

Two alternatives, recorded so they are not re-derived:

  • Widen moduleRelativeDirectoryHint to admit a single-segment literal that resolves against the REPO ROOT. ⛔ Its own docblock records that admitting on the resolved form alone was measured and refused — this would re-open a recorded refusal and needs the same measurement the refusal was built on. Given N=1, it buys nothing here.
  • Leave derivation alone, treat the derived list as advisory. It is the status quo, it is what actually caught this, and it concedes that a dev's local gate list can silently omit the one gate their diff trips.

Why p2 rather than p1

CI does catch it — no defect reaches main through this. The cost is a wasted push cycle per occurrence plus a false sense of coverage: "the derived list was green" reads as "the gates are green", and this round it was not. Recurring tax, not a correctness hole.

Scope note

⛔ Nothing here was fixed in PR #13146: that PR fixed its own matcher (by refusing the combinator, which is what 196 of the 314 conforming matchers already do — now 197) and did not touch dispatch-gates.mjs or the gate. Filed separately because the derivation gap outlives the one matcher that exposed it.

Activity

  1. claude commented on Aug 31, 2026

    @claude
    Contributor

    Label hygiene — domain:devx execution PM seat (#6023), session session_01Pk26oZ12t5N1hwGW1m1MgC.

    Stripped pm:dispatched from this closed card. 关闭即摘 pm:* — a pm state label on a closed issue asserts something false (pm:dispatched says a dev is working on it; pm:awaiting-maintainer says a manual action is still outstanding), and it makes the card visible to state sweeps that should no longer see it.

    ⛔ Nothing about the card's outcome is changed or re-judged here — domain:*, type and priority are left alone, since routing and grading are not state. This is the round's standing sweep for "closed but still carrying pm:*", not a verdict on this issue.

    ⚠️ Cause, for the record: GitHub's Fixes #n auto-close does not strip labels — measured 8/8 with no exception on this lane today. Cards closed that way need the strip done by hand, and these predate the sweep.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions