Skip to content

docs(spec): describe() for SpecialOperator $null and $exists - #15054

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-14048-special-operator-describe
Sep 3, 2026
Merged

docs(spec): describe() for SpecialOperator $null and $exists#15054
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-14048-special-operator-describe

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes #14048

Clause ②: no — path limb fires (packages/spec/src/data/filter.zod.ts describe text); content limb no: no accept-set or shape change.

What was wrong

SpecialOperatorSchema declared both members as bare z.boolean().optional() with a JSDoc comment and no .describe(). content/docs/references/data/filter.mdx fills its Description column from prop.description — the JSON-Schema projection of a Zod .describe(), read at packages/spec/scripts/lib/schema-section.ts:437 — so both cells rendered empty. The published reference page therefore said nothing at all about the operator pair whose meaning was the subject of a six-site correction campaign (#13539, #13709), and $exists is the operator where getting it wrong changes which rows an RLS predicate admits.

What changed

Two .describe() calls on the existing members, and the page regenerated from them. The JSDoc stays; the describe restates it.

  • packages/spec/src/data/filter.zod.ts$null and $exists in SpecialOperatorSchema only.
  • content/docs/references/data/filter.mdx — regenerated, never hand-edited. pnpm --filter @objectstack/spec check:generated --fix reported content/docs/references/** as the single stale artifact of 15 and regenerated only that one; the resulting diff is exactly rows 138-139 and nothing else.
  • .changeset/spec-special-operator-describe.md@objectstack/spec patch.

The mechanism hypothesis was verified rather than assumed: .optional().describe(...) — describe on the optional wrapper, the shape already used by $in, $nin, $icontains, $like, $ilike in this file — does reach prop.description and does render. That is what the regenerated rows prove.

Ratchet: run before the wording was written, and again after regeneration

scripts/check-corpus-claim-drift.mjs rule exists-key-presence is shrink-only and watches content/docs/**, which is where this page lands, so it was run first and the prose was written against it.

  • Before any wording existed, on the pristine worktree: check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling. / Rules: 4 row(s) — exists-key-presence 4, exists-portability 0, regex-retired 0, section-visiblewhen-unbound 0.
  • After the page was regenerated, at the final commit: the identical two lines, exists-key-presence 4.

The new prose adds zero claim sites. It says the field HAS A VALUE and never touches the key-presence family of phrasings; the $exists cell also stays clear of the exists-portability row (it says portable, and never names a backend restriction).

Measured and reported, deliberately not acted on

Triage 5488438905 scoped this card to the one pair and asked for the file-wide number to be measured and reported so the next card has a basis. Both readings are of packages/spec/src/data/filter.zod.ts at this branch's merge base:

  • .describe( call sites: 11 before this PR, 13 after. The card said 15 and triage counted 14; both are drift, the current number is 11. Command: grep -c '\.describe(' packages/spec/src/data/filter.zod.ts.
  • Zod object members carrying a JSDoc block and no .describe(): 12 before this PR, 10 after. They are $eq, $ne (EqualityOperator), $gt, $gte, $lt, $lte (ComparisonOperator), $contains, $notContains, $startsWith, $endsWith (StringOperator), and the two this PR converts. Of 43 Zod object members in the file, 9 carried a .describe() before this PR (11 after), 12 were JSDoc-only, and 22 carry neither.

Not acted on, per the triage fence. One observation for whoever picks up that card: FieldOperatorsSchema restates $null and $exists around line 1097 with no JSDoc and no describe, so the FieldOperator table on this same page still renders those two Description cells empty. That is inside the file-wide sweep this card explicitly deferred, so it is left alone here.

Verification

Verified at 79007b38 (git rev-parse --short HEAD after the final commit; every gate below ran on that tree).

  • pnpm --filter '@objectstack/spec...' buildos-verify-lock: VERDICT command-exit 0, check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.
  • pnpm --filter @objectstack/spec testVERDICT command-exit 0; Test Files 463 passed (463), Tests 12374 passed (12374).
  • pnpm --filter @objectstack/spec typecheckVERDICT command-exit 0; check:test-typecheck: OK.
  • Gate family re-derived from the actual diff after the final commit: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed; it took the change set from the merge base itself) named 71 families. All 71 were run. 67 green, 4 NOT MEASURED, none red.

Named verdict lines from the ones this diff is about:

  • check:generatedAll 15 generated artifacts are up to date.
  • check:api-surface@objectstack/spec public API surface + factory signatures unchanged
  • check:export-originsexport-origins/ is current: 5239 exports across 17 entry points resolve exactly as recorded. (no new export, as expected)
  • check:authorable-surface, check:docs, check:llms-txt, check:skill-refs, check:strictness-ledger, check:variant-docs, check:yaml-examples, check:quick-reference-counts, check:doc-anchors, check:doc-authoring, check:docs-audit-scope, check:docs-redirects, check:docs-single-h1, check:merge-driver, check:published-files, check:changeset-gate-self-tests, check:pm-half-states, check:spec-parsed-alias, check:closing-keyword-parity — all green.
  • check:nul-bytesOK (scanned 8174 text file(s) -- 8174 tracked, 0 untracked-not-ignored; skipped 7 binary; no raw ASCII control bytes). Plus a hand scan of the three touched files for ASCII control bytes: no matches.

The 4 NOT MEASURED, each with the gate's own reason — none is a red, and CI runs all of them:

  • scripts/check-dev-prereqs.mjsThe workspace is not built — 1 unmet precondition, not a list of problems. Only @objectstack/spec plus its dependency closure, formula, lint and client-react were built here; a whole-workspace build is the repo-wide run CI owns.
  • scripts/check-test-completeness.mjs — exit 3, and its own text says so: the gate needs a saved turbo run test log, and the derived family names it with no argument, so the local reading for this gate is NOT MEASURED.
  • scripts/pm/check-half-states.mjs (bare invocation) — a live GitHub patrol; it exceeded the 300s per-command cap used here. Its runnable family form pnpm check:pm-half-states is green (check-half-states self-test: 2062 cases pass.).
  • pnpm check:dual-build-cjs-loads — exit 3, PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Same whole-workspace build as above.

Three gates were prerequisite-blocked on the first pass and are counted green only after their package was built and they were re-run: check:doc-formula-expressions (needed @objectstack/formula), check:doc-security-posture (needed @objectstack/lint), check:skill-examples (needed @objectstack/client-react).

One reading worth flagging for the reviewer: dispatch-gates.mjs printed a STALE TREE warning — main moved 7 commits ahead during this run, and 4 files the derivation reads changed in that range (.github/workflows/stale.yml, scripts/pm/check-clause2-carriers.mjs, scripts/pm/check-skill-line-ratchet.mjs, scripts/pm/ensure-pm-labels.sh). The family count was 71 on both derivations, and none of those four is a gate this diff touches. The branch is left at its merge base rather than merged forward.

Generated by Claude Code


Generated by Claude Code

SpecialOperatorSchema declared both members as bare z.boolean().optional()
with a JSDoc comment and no .describe(), so the reference page's Description
column -- filled from prop.description, the JSON-Schema projection of a Zod
.describe() -- rendered both cells empty. The published page therefore said
nothing about the operator pair whose meaning was the subject of a six-site
correction campaign.

Each member gains a .describe(); the JSDoc stays. Regenerating with
check:generated --fix touched exactly two rows of
content/docs/references/data/filter.mdx and nothing else. Prose only: no
accept/reject or shape change, the JSON-Schema delta is a description string
on two properties.

Wording written against scripts/check-corpus-claim-drift.mjs rather than into
it: its exists-key-presence row reads 4 sites before this diff and 4 after the
page was regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:data tooling labels Sep 3, 2026
@github-actions

github-actions Bot commented Sep 3, 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 778c59f75dabe2788ea3bc539b98738fd3fe5b91packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6bbc255d1d21fc7ecd4dfbb1cbc5de728e03742d — the merge of head 79007b38f4f09813e3d61a493fd24410064ec944 into base 778c59f75dabe2788ea3bc539b98738fd3fe5b91, 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 6bbc255d1d21fc7ecd4dfbb1cbc5de728e03742d && git checkout 6bbc255d1d21fc7ecd4dfbb1cbc5de728e03742d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 778c59f75dabe2788ea3bc539b98738fd3fe5b91 79007b38f4f09813e3d61a493fd24410064ec944 && git checkout -B drift-repro 778c59f75dabe2788ea3bc539b98738fd3fe5b91 && git merge --no-ff 79007b38f4f09813e3d61a493fd24410064ec944

node scripts/docs-audit/affected-docs.mjs --json 778c59f75dabe2788ea3bc539b98738fd3fe5b91

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

Copy link
Copy Markdown
Collaborator Author

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T19:37Z) — flipping to ready and enabling auto-merge (squash).


Generated by Claude Code

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/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] SpecialOperator ships an empty Description column on the published reference page — $null and $exists carry no describe() at all

2 participants