docs(spec,formula,driver-memory): record the ruled EXCLUDE direction for negative operators over no-value rows (#5299) - #7391
Conversation
…for negative operators over no-value rows (#5299) The 2026-08-10 maintainer ruling on #5299 takes SQL's native three-valued logic as the common denominator: negative operators never match no-value rows; the only ways to select "no value" are `$exists: false` / `$null: true`. No behaviour moves here, and the diff explains why rather than leaving the next reader to re-derive it. The ruled direction REVERSES what #5298 shipped and #5146 ruled before it — which is what the four enrolled `d`-column conformance cases enforce today, and what every SQL compiler in the repo was deliberately bent to (`nullSafeNegative`, plus four independent copies of `nullValueSatisfiesOperator` answering `$nin` -> true and `$notContains` -> true). Flipping `formula` alone would re-open the write-side/read-side split PR #5962 closed, with the sign reversed. Measured on 60f0dd8 by adding the candidate rows to `FILTER_LOGIC_CASES` and running every suite that drives it. Eleven surfaces answer the include direction on `$notContains` and `$nin`; `$exists` is already ruled-correct on both JS evaluators (#5962) and still key-presence on `driver-memory`'s live mingo path and on `driver-mongodb`. The table is in the new family 4 note in the conformance header. Two measured blockers to enrolment, both recorded there: - the DEBT ledger in `scripts/check-driver-conformance.mjs` is per (driver x case-set), not per case, so there is no legal spelling for "this driver fails one row" — a row added ahead of a backend is simply a red gate; - two of the five scored drivers are inside the #5499 investment freeze, and both answer the include direction on their live query paths. Pins re-annotated rather than flipped, and made load-bearing: the formula-side file now asserts the non-negated `$notContains` / `$nin` row sets explicitly, so the cross-backend PR has to move them deliberately. `driver-memory`'s behaviour is untouched per #5499 and per the ruling's own "the early-exit guard stays as-is"; one stale claim in its pin is corrected — it said `formula` reads `$exists` as key-presence, which stopped being true in PR #5962. Refs #5299 #5298 #5962 #5499 #5240 #5146 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmjKBzRFKnUgkk74DVUqYd
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also reference the affected code. These are read-only:
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 31376075232 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Closes nothing yet — this lands the part of #5299 that is shippable today and states, with the measurement behind it, why the rest is a cross-backend programme rather than a
packages/formulachange.The ruling
Maintainer ruling on #5299, 2026-08-10 07:33Z: SQL's native three-valued logic is the common denominator, and both JS evaluators align to it.
Concretely:
$notContainson a value-less field does not match;$existsmeans "has a value" (!= null), never key-presence;$ninon a no-value field does not match.What this PR does
No behaviour moves. It records the ruled target where the next author reads it, and makes the pins load-bearing so the behaviour change cannot land quietly:
packages/spec— a new "family 4" note infilter-logic-conformance.ts's header, under the existing "Case families that are RULED but not yet enrolled" section: the ruling, the eleven-surface measurement, and the two blockers to enrolment. The four enrolledd-column cases get a pointer saying they now state the superseded direction and why they stay as they are.packages/formula— the$not of $notContainspin is annotated as the superseded direction, and a new block asserts the non-negated$notContains/$ninrow sets (['2','3','4']) that were previously unpinned, alongside$neanswering identically. Assertions, not deletions: the cross-backend PR has to move these lines deliberately.packages/drivers/driver-memory— re-annotation only, per [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 and per the ruling's own "the early-exit guard stays as-is". Both pin sections move from "a divergence nobody has ruled on" to "a ruled target, with this matcher's distance from it measured".Why the behaviour change is not in this PR
The ruled direction reverses what #5298 shipped (2026-08-06, five-PR programme) and what #5146 ruled for
$notbefore it. That is not an inference from the comment history — it is what the tree answers.Measured on
60f0dd8by adding the candidate rows toFILTER_LOGIC_CASES, rebuilding spec, and running every suite that drives the table.MATCH= a no-value row satisfies the operator, i.e. the include direction the ruling reverses:$notContains$nin$exists: trueon a null valueformulamatchesFilterConditiondriver-memoryreference matcherdriver-memorylive mingo pathdriver-memoryanalytics facedriver-sql/driver-sqlite-wasm/driver-tursolocalIS NOT NULLdriver-tursoREMOTEIS NOT NULLdriver-mongodbtranslateFilter$existsis key-presenceservice-analyticsread-scope-sqlIS NOT NULLservice-analyticsfilter-normalizerIS NOT NULLEvery
MATCHis deliberate, not drift: #5298 put it there vianullSafeNegative(col IS NULL OR col NOT LIKE ?) and four independent copies ofnullValueSatisfiesOperator($nin→ true,$notContains→ true) insql-driver.ts,read-scope-sql.ts,filter-normalizer.tsandremote-transport.ts. So the ruling's parenthetical "driver-memory and SQL already agree" does not hold on this tree: SQL was bent towards the JS answer, and the ruling asks for it to be bent back.Three consequences, each of which is why a
packages/formula-only flip is not shippable:formula(write-sidecheck) andread-scope-sql(read-side lowering) in one change because they are security-coupled. Flippingformulaalone re-opens that split sign-reversed: an RLScheckwould deny a write on a null field that the read scope still returns.$ninis the list form of$ne, and$neis already enrolled inFILTER_LOGIC_CASESasserting['2','3','4']— three lines from where the new rows would go. Under native three-valued SQL that row, and the$notrow beside it, both become['2']. Enrolling family 4 means re-ruling those two, not adding rows next to them.scripts/check-driver-conformance.mjsis per (driver × case-set), not per case — an entry means "this driver's suite does not import this marker at all". There is no spelling for "this driver fails one row while passing the other thirty-five", so a row added ahead of a backend is simply a red gate. And two of the five scored drivers (driver-memory,driver-mongodb) are inside the [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 investment freeze while answering the include direction on their live query paths.Gates
Run in the worktree on this branch.
check:driver-conformancebefore and after is identical:packages/specis touched, so all of its gates were run individually —check:generated,check:skill-docs,check:spec-changes,check:upgrade-guide,check:export-origins,check:authorable-surface,check:docs,check:skill-refs,check:react-blocks,check:api-surface,check:exported-any,check:dual-source-exports,check:skill-examples— all pass. Also green:check:driver-memory-census,check:empty-changeset,check:nul-bytes,check:changeset-gate-self-tests,pnpm lint, andtypecheck+ fullvitestforpackages/spec(9480),packages/formula(623),packages/drivers/driver-memory(560).One correction to the record
driver-memory's pin saidformulareads$existsas "the key is present". That stopped being true in PR #5962 (#5298 ③ / #5369) and the comment had gone stale in a way that mattered — it is the sentence a reader would use to decide which evaluator to change. Corrected here; the$existscell is converged between the two evaluators and needs no work on either.Refs #5299 #5298 #5962 #5499 #5240 #5146
Generated by Claude Code