docs(protocol): $exists means HAS A VALUE, not key presence - #13581
Draft
os-project-manager wants to merge 1 commit into
Draft
docs(protocol): $exists means HAS A VALUE, not key presence#13581os-project-manager wants to merge 1 commit into
$exists means HAS A VALUE, not key presence#13581os-project-manager wants to merge 1 commit into
Conversation
The two protocol pages still described `$exists` as a key-presence test — "Field exists (NoSQL)" and "Field existence check". That stopped being true when the has-value alignment landed at 9dac1ae (PR #13529): every backend now answers "the field has a value" (`!= null`), and driver-mongodb no longer emits MongoDB's own `$exists` at all. Established from code, not from another document: - packages/drivers/driver-mongodb/src/mongodb-filter.ts `case '$exists'` puts `$ne: null` / `$eq: null` — the spelling `$null` already emits; - packages/drivers/driver-sql/src/sql-driver.ts `case '$exists'` compiles `whereNotNull` / `whereNull`; - packages/objectql/src/having-filter.ts evaluates `value !== undefined && value !== null`; - packages/spec/src/data/filter-logic-conformance.ts states the settled semantic verbatim: "`$exists` means \"has a value\" (`!= null`), never key-presence". content/docs/data-modeling/queries.mdx already said "Field has a value" and is deliberately untouched — it is the target wording, not another site to sweep. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
This was referenced Aug 31, 2026
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #13539 — deliberately not a closing keyword. This PR carries 3 of that card's 6 sites. The two published skill sites ship separately as #13577 (governed surface, human merge), and the
packages/spec/src/data/filter.zod.tsJSDoc site belongs to thedomain:specseat. Merging this must not close the card while those remain.Session, for durable attribution:
https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgCWhat was false
Three hand-written protocol docs described
$existsas a key-presence test:content/docs/protocol/objectql/query-syntax.mdx, operator table —`$exists` | Field exists (NoSQL)content/docs/protocol/objectql/query-syntax.mdx, Null Checks example —// Field exists (NoSQL)content/docs/protocol/kernel/http-protocol.mdx, operator list —`$exists` - Field existence checkThat stopped being true when the has-value alignment landed at
9dac1ae017(PR #13529). These are not stale wording — they are false about shipped behaviour, and the corpus does not execute, so no gate could go red on them.What the operator actually does — established from code, not from another document
$existsasks whether the field has a value (!= null). It is the exact inverse of$null, it is portable rather than NoSQL-specific, and a storednullcounts as no value on every backend:packages/drivers/driver-mongodb/src/mongodb-filter.ts,case '$exists'→put(value === true ? '$ne' : '$eq', null). MongoDB's own$existsis never emitted; that spelling would have been key presence and would have kept anull-valued row.packages/drivers/driver-sql/src/sql-driver.ts,case '$exists'→whereNotNull/whereNull.packages/objectql/src/having-filter.ts,case '$exists'→value !== undefined && value !== null.packages/spec/src/data/filter-logic-conformance.tsstates the settled semantic verbatim: "$existsmeans "has a value" (!= null), never key-presence" — 非否定路径上的$ne/$nin/$notContains:driver-sql 排除 NULL 行,driver-memory / formula 返回它们(#5146 只裁定了$not) #5298 leg 3 /$exists的比较值不是布尔时,三个后端面给出三个答案,driver-memory 自己的两个面在'yes'上就已分叉 —— 实测,$null(#5347)的同族另一轴 #5369, landed in PR fix(drivers,analytics,formula): $ne / $nin / $notContains 在 $not 之外也 NULL-safe (#5298) #5962.Executed rather than merely read: 14/14 green on
packages/drivers/driver-mongodb/src/mongodb-exists-has-value-translation.test.ts, which pinstranslateFilter({name: {$exists: true}})to{name: {$ne: null}}and asserts that document is exactly what$nullemits.The change
Both table/list rows now read "Field has a value — the inverse of
$null" with theIS NOT NULL/IS NULLcompilation stated, the example comment says what the block actually selects, and a callout under the Null Checks example says plainly that this is not a key-presence test and that MongoDB never sees an$exists.content/docs/data-modeling/queries.mdxalready read "Field has a value" and is deliberately untouched — the card names it as the target wording, not as another site to sweep. A blanket find-and-replace would have broken the one line already telling the truth.content/docs/references/data/filter.mdxis generated from the Zod JSDoc and was not hand-edited;check:docsconfirms 230 generated files still in sync.Gates — run locally at head
b81efc88fThe family was derived from the real diff with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, not recalled: 26 families, 25 green, 1 not measurable locally.check:skill-examplesmatters here specifically: the editedNull Checksblock carries anos:checkmarker, so its TypeScript is type-checked against the live spec — the edit is inside that checked block and it still compiles.node scripts/check-test-completeness.mjsexits 3 = PREREQUISITE NOT MET by its own text (it grades a savedturbo run testlog and none was named): recorded as NOT MEASURED, not as a red.ESLint was not run repo-wide: its population, read from
eslint.config.mjsitself, is**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}and this diff is.mdxonly, so zero of these files are in that gate's population in either direction.No changeset: this PR releases nothing from any package, which is the repo's live convention for a pure
content/docs/**change — measured, the last 20 pure-docs commits onmaincarry zero changesets.skip-changesetis applied.Clause ② carrier
needs:contract-reviewis attached to this PR and to #13539, under the standing instruction that the card's clause-② determination is unchanged and the same write that creates a PR re-attaches both carriers. Measured honestly: this diff touches zeropackages/spec/**paths, so the path limb is not fired by these bytes — the carrier rides the card's determination, not this diff. ⛔ Not self-cleared here.Why no gate caught it, tested rather than assumed
The card's claim is that no gate could. That holds as stated:
os:checktype-checks the block's TypeScript but is blind to a comment inside it, and no gate reads prose for semantic agreement with the driver it describes. It does not hold as a limit — a lexical pin overcontent/docs/**+skills/**(the rootscheck:role-wordalready walks) would catch exactly this class. That is a new validation surface and belongs on its own card, not smuggled into a wording repair.Generated by Claude Code
Generated by Claude Code