Skip to content

docs(spec,formula,driver-memory): record the ruled EXCLUDE direction for negative operators over no-value rows (#5299) - #7391

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5299-negative-operators-value-semantics
Aug 10, 2026
Merged

docs(spec,formula,driver-memory): record the ruled EXCLUDE direction for negative operators over no-value rows (#5299)#7391
os-zhuang merged 1 commit into
mainfrom
claude/issue-5299-negative-operators-value-semantics

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

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/formula change.

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.

Negative operators never match no-value rows; the only ways to select "no value" are $exists: false / $null: true.

Concretely: $notContains on a value-less field does not match; $exists means "has a value" (!= null), never key-presence; $nin on 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 in filter-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 enrolled d-column cases get a pointer saying they now state the superseded direction and why they stay as they are.
  • packages/formula — the $not of $notContains pin is annotated as the superseded direction, and a new block asserts the non-negated $notContains / $nin row sets (['2','3','4']) that were previously unpinned, alongside $ne answering 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 $not before it. That is not an inference from the comment history — it is what the tree answers.

Measured on 60f0dd8 by adding the candidate rows to FILTER_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:

Surface $notContains $nin $exists: true on a null value
formula matchesFilterCondition MATCH MATCH no — already ruled-correct
driver-memory reference matcher no — already ruled-correct MATCH on null, no on missing key no — already ruled-correct
driver-memory live mingo path MATCH MATCH MATCH — key-presence
driver-memory analytics face MATCH MATCH MATCH — key-presence
driver-sql / driver-sqlite-wasm / driver-turso local MATCH MATCH no — IS NOT NULL
driver-turso REMOTE MATCH MATCH no — IS NOT NULL
driver-mongodb translateFilter MATCH MATCH MATCH — mongo $exists is key-presence
service-analytics read-scope-sql MATCH MATCH no — IS NOT NULL
service-analytics filter-normalizer MATCH MATCH no — IS NOT NULL

Every MATCH is deliberate, not drift: #5298 put it there via nullSafeNegative (col IS NULL OR col NOT LIKE ?) and four independent copies of nullValueSatisfiesOperator ($nin → true, $notContains → true) in sql-driver.ts, read-scope-sql.ts, filter-normalizer.ts and remote-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:

  1. RLS coupling. PR fix(drivers,analytics,formula): $ne / $nin / $notContains 在 $not 之外也 NULL-safe (#5298) #5962 converged formula (write-side check) and read-scope-sql (read-side lowering) in one change because they are security-coupled. Flipping formula alone re-opens that split sign-reversed: an RLS check would deny a write on a null field that the read scope still returns.
  2. $nin is the list form of $ne, and $ne is already enrolled in FILTER_LOGIC_CASES asserting ['2','3','4'] — three lines from where the new rows would go. Under native three-valued SQL that row, and the $not row beside it, both become ['2']. Enrolling family 4 means re-ruling those two, not adding rows next to them.
  3. Enrolment is blocked by mechanism, not by wording. The DEBT ledger in scripts/check-driver-conformance.mjs is 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-conformance before and after is identical:

check-driver-conformance: OK — 36 covered cell(s), 4 in the DEBT ledger, 0 exempt.

packages/spec is 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, and typecheck + full vitest for packages/spec (9480), packages/formula (623), packages/drivers/driver-memory (560).

One correction to the record

driver-memory's pin said formula reads $exists as "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 $exists cell is converged between the two evaluators and needs no work on either.

Refs #5299 #5298 #5962 #5499 #5240 #5146


Generated by Claude Code

…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
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 9:05am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31376075232 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 11 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 5f7669e Aug 10, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5299-negative-operators-value-semantics branch August 10, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants