Skip to content

docs(protocol): $exists means HAS A VALUE, not key presence - #13581

Draft
os-project-manager wants to merge 1 commit into
mainfrom
claude/issue-13539-exists-key-presence-teaching
Draft

docs(protocol): $exists means HAS A VALUE, not key presence#13581
os-project-manager wants to merge 1 commit into
mainfrom
claude/issue-13539-exists-key-presence-teaching

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

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.ts JSDoc site belongs to the domain:spec seat. Merging this must not close the card while those remain.

Session, for durable attribution: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC

What was false

Three hand-written protocol docs described $exists as 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 check

That 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

$exists asks whether the field has a value (!= null). It is the exact inverse of $null, it is portable rather than NoSQL-specific, and a stored null counts as no value on every backend:

Executed rather than merely read: 14/14 green on packages/drivers/driver-mongodb/src/mongodb-exists-has-value-translation.test.ts, which pins translateFilter({name: {$exists: true}}) to {name: {$ne: null}} and asserts that document is exactly what $null emits.

The change

Both table/list rows now read "Field has a value — the inverse of $null" with the IS NOT NULL / IS NULL compilation 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.mdx already 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.mdx is generated from the Zod JSDoc and was not hand-edited; check:docs confirms 230 generated files still in sync.

Gates — run locally at head b81efc88f

The 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.

EXIT=0 :: pnpm check:doc-authoring
EXIT=0 :: pnpm check:doc-anchors
EXIT=0 :: pnpm check:docs-single-h1
EXIT=0 :: pnpm check:docs-redirects
EXIT=0 :: pnpm check:docs-audit-scope
EXIT=0 :: pnpm check:role-word
EXIT=0 :: node scripts/check-docs-section-name.mjs
EXIT=0 :: node scripts/check-doc-frontmatter.mjs
EXIT=0 :: pnpm --filter @objectstack/spec run check:docs
        (230 generated files in sync with packages/spec)
EXIT=0 :: pnpm --filter @objectstack/spec run check:skill-examples
        (260 prose examples type-check across 3 surfaces)
EXIT=0 :: pnpm --filter @objectstack/spec run check:liveness / check:empty-state /
          check:strictness-ledger / check:variant-docs / check:yaml-examples
EXIT=0 :: pnpm --filter @objectstack/lint run check:doc-formula-expressions / check:doc-security-posture
EXIT=0 :: 6 further derived families

check:skill-examples matters here specifically: the edited Null Checks block carries an os:check marker, 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.mjs exits 3 = PREREQUISITE NOT MET by its own text (it grades a saved turbo run test log and none was named): recorded as NOT MEASURED, not as a red.

ESLint was not run repo-wide: its population, read from eslint.config.mjs itself, is **/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs} and this diff is .mdx only, 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 on main carry zero changesets. skip-changeset is applied.

Clause ② carrier

needs:contract-review is 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 zero packages/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:check type-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 over content/docs/** + skills/** (the roots check:role-word already 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

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
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 needs:contract-review skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant