Skip to content

docs(spec): $exists asks whether a field has a value, not whether a key is present - #14050

Merged
os-support-ai merged 2 commits into
mainfrom
claude/issue-13709-exists-jsdoc-has-value
Sep 1, 2026
Merged

docs(spec): $exists asks whether a field has a value, not whether a key is present#14050
os-support-ai merged 2 commits into
mainfrom
claude/issue-13709-exists-jsdoc-has-value

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes #13709

Clause-②: no. Prose only, zero accept/reject change — $exists is z.boolean().optional() before and after, and the edited text is a JSDoc comment, not a .describe(). The path limb (packages/spec/src/**/*.zod.ts) still binds the enqueue gate, so needs:contract-review is hung on this PR and on the card.

The one line

SpecialOperatorSchema.$exists carried:

/** Field exists check (primarily for NoSQL) - MongoDB: $exists */

That describes key-presence. $exists stopped meaning key-presence when the has-value alignment landed at 9dac1ae017 (PR #13529), so the line was false rather than merely stale — the last of the six sites carrying the old claim, the other five having been corrected by PR #13581 and PR #13577.

Anchored by the string primarily for NoSQL, never a line number: the card's own title said :954, its comments said :997, and it sat at :997 on my base. The symptom grep returns exactly one hit repo-wide, and zero after this diff.

Replaced with the wording recorded on #13539, adapted to this file's JSDoc idiom — the sibling $null line annotates which boolean maps to which lowering, so this one does too:

/**
 * Field HAS A VALUE (`!= null`) — the inverse of `$null`, never key presence.
 * Lowered to `IS NOT NULL` (true) / `IS NULL` (false) on SQL and to
 * `{$ne: null}` / `{$eq: null}` on MongoDB.
 */

Direction verified against the implementations before rewriting, not copied on faith: formula's matchesFilterCondition reads v === true ? actual != null : actual == null; objectql's having face lists $exists among the operators for which "no value" is a real answer; and filter-logic-conformance.ts records the 2026-08-10 ruling with $exists enrolled in both directions.

A sub-premise of the card is falsified — measured, not argued

The card and the dispatch both state that content/docs/references/data/filter.mdx inherits its Description column from this JSDoc, and asked for that page to be regenerated in the same commit. I ran the regeneration (gen:schema then gen:docs, 230 pages). It produced a zero-byte diff.

The reason: packages/spec/scripts/lib/schema-section.ts fills that column from prop.description, the projection of a Zod .describe() — and $exists has none, so its cell is empty before and after. The two surfaces were therefore never in the disagreement the card describes; the generated page is silent about $exists, not wrong about it.

Recorded as #14048 (unassigned, for triage) rather than fixed here: adding .describe() writes new prose onto a published page under a root watched by check:corpus-claim-drift, whose single rule is exists-key-presence — the exact shape that ratchet inspects. That earns its own diff and its own gate run instead of riding along on a fenced one-line card.

Where the corrected line actually surfaces

  • Published source — yes. packages/spec's files array ships src/**/*.zod.ts, so filter.zod.ts is in the npm tarball and the comment reaches consumers verbatim. This is the surface the patch changeset is for.
  • Emitted .d.ts — no. Zero of the emitted .d.ts/.d.mts carry the text (the property's type is inferred from Zod, so no declaration comment is written); it appears in dist only inside .js.map sourcemaps.
  • Generated reference page — no, per the section above.

An earlier draft of the changeset claimed the .d.ts hover surface. That was assumed, not measured; the follow-up commit corrects it, because a PR that exists to remove a false claim must not ship one.

Verification

All of the following on the final commit a12460d5, clean tree, after pnpm --filter @objectstack/spec build:

check verdict line
@objectstack/spec suite Test Files 446 passed | 1 skipped (447) · Tests 11989 passed | 1 skipped (11990)
@objectstack/spec typecheck exit 0 — tsc --noEmit, check:scripts-typecheck, and check:test-typecheck (test layer really compiled, via tsconfig.test.json)
check:generated ✓ All 15 generated artifacts are up to date.
spec check:docs ✅ 230 generated files in sync with packages/spec
check:authorable-surface exit 0 — 1237 default(s) unchanged, 1608 schemas
check:corpus-claim-drift OK, no new claim sites beside a pinned spelling. — 226 files, rule exists-key-presence
check:doc-authoring 14301 customer-facing string(s) across 693 spec sources clean
check:doc-formula-expressions 22 record-scoped formula example(s) across 427 files / 1451 TS blocks judged clean
check:comment-mask-adoption OK — 14 private comment-stripper(s) ... all 14 recorded
check:keyed-text-bounds 148 keyed text-family columns judged, 148 bounded
check:empty-changeset ✓ No empty-frontmatter changeset introduced by this diff (1 declaring changeset(s) added).
check:nul-bytes OK (scanned 7671 text file(s) ... no raw ASCII control bytes)
check:changeset-gate-self-tests exit 0 — all three self-tests
pnpm lint (repo-wide) 5601 files, 0 errors, 0 warnings — full run, not narrowed

Gates re-derived from the actual diff with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack. Because filter.mdx does not change, the content/docs/** families named at dispatch (check:doc-anchors, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:doc-security-posture) do not match this diff; the packages/spec/** families above do.

Two gates first reported a prerequisite, not a finding, and were re-run after the missing builds: check:generated (exit 1 — dist held no .d.ts) and check:doc-formula-expressions (exit 3 twice — @objectstack/formula, then @objectstack/lint, unbuilt). Both green above. Every exit code was captured before any pipe.

Not touched, on purpose

content/docs/data-modeling/queries.mdx — its wording was already correct before any of this and is the target wording, not another site to change. No blanket sweep: the diff is one comment and one changeset.


Generated by Claude Code

The JSDoc on `SpecialOperatorSchema.$exists` read "Field exists check
(primarily for NoSQL)", which describes key-presence. `$exists` stopped
meaning key-presence when the has-value alignment landed (PR #13529 /
9dac1ae); the line was false, not merely stale, and it was the last of
six sites still carrying the old claim.

Replaced with the wording recorded on #13539 and already shipped on the
other five sites, adapted to this file's JSDoc idiom (the sibling `$null`
line annotates which boolean maps to which lowering, so this one does too).
Direction verified against all three evaluators before rewriting.

Prose only: `$exists` is `z.boolean().optional()` before and after, and the
comment is not a `.describe()`, so no generated artifact moves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1
…3709)

The first draft claimed the corrected JSDoc reaches consumers as `.d.ts`
hover text. Measured on a freshly built dist: zero `.d.ts`/`.d.mts` carry
it (the property's type is inferred from Zod, so no declaration comment is
emitted) — it appears in `dist` only inside `.js.map` sourcemaps.

The surface that does carry it is published SOURCE: this package's `files`
array ships `src/**/*.zod.ts`, so `filter.zod.ts` itself is in the tarball.
Recorded alongside the two negative results, since a PR correcting a false
claim must not ship one.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mciyv38maJ6HYVMiaM26T1
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 836a29c27aa9dbac27cc643106e5b2bf68cc96c1packageMentionDocs.

Which tree this was computed on

This run read content/docs from 69f83a50c99ba6cb684dd319e96cd442b05ae7c8 — the merge of head a12460d53fbd912c5f28b8955f285566442bec39 into base 836a29c27aa9dbac27cc643106e5b2bf68cc96c1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 69f83a50c99ba6cb684dd319e96cd442b05ae7c8 && git checkout 69f83a50c99ba6cb684dd319e96cd442b05ae7c8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 836a29c27aa9dbac27cc643106e5b2bf68cc96c1 a12460d53fbd912c5f28b8955f285566442bec39 && git checkout -B drift-repro 836a29c27aa9dbac27cc643106e5b2bf68cc96c1 && git merge --no-ff a12460d53fbd912c5f28b8955f285566442bec39

node scripts/docs-audit/affected-docs.mjs --json 836a29c27aa9dbac27cc643106e5b2bf68cc96c1

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants