Repository navigation
Description
Filed unassigned by the domain:spec execution seat while doing #13709. ⛔ Ungraded, ⛔ unclaimed — an execution seat does not produce routing labels.
What was measured
content/docs/references/data/filter.mdx renders the SpecialOperator properties table with both Description cells empty:
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **$null** | `boolean` | optional | |
| **$exists** | `boolean` | optional | |
The reason is mechanical, not a rendering bug. packages/spec/scripts/lib/schema-section.ts fills that column from prop.description:
const desc = escapeMdxDescription((prop.description || '').replace(/\n/g, ' '))prop.description is the JSON-Schema projection of a Zod .describe(). In packages/spec/src/data/filter.zod.ts, SpecialOperatorSchema declares both members as bare z.boolean().optional() with a JSDoc comment and no .describe() — so nothing reaches the projection and the cell is empty. filter.zod.ts does use .describe() elsewhere (15 call sites), so this is an omission on these two members, not a file-wide convention.
Why it is worth recording
#13709 corrected the $exists JSDoc, which had described key-presence semantics that stopped being true at 9dac1ae017 (PR #13529). That correction lands in the source file — which does ship, since this package's files array includes src/**/*.zod.ts — but it provably does not reach the generated page: regenerating all 230 reference pages after the fix produced a zero-byte diff on filter.mdx.
So the published reference page says nothing whatsoever about the two operators whose meaning was the subject of a six-site correction campaign. A reader of the reference page cannot learn from it that $exists asks whether a field has a value rather than whether a key is present — which is precisely the misreading that campaign existed to stop, and the operator pair where getting it wrong changes which rows an RLS predicate admits.
build-docs.ts dropping describe() text that exists on nested item shapes. Here no describe() exists to drop.
Deliberately not fixed in the #13709 PR
The #13709 triage fence scoped that card to one JSDoc line with an explicit ⛔ on sweeping. Adding .describe() calls is a different change with a different blast radius: it writes new prose onto a published docs page, and that page sits under a root watched by scripts/check-corpus-claim-drift.mjs (content/docs/** + skills/**), a shrink-only ratchet whose one rule is exists-key-presence. New $exists prose in the corpus is exactly the shape that ratchet inspects, so this needs its own diff and its own gate run rather than riding along.
Open question for triage, not decided here
Whether the remedy is .describe() on this pair only, or a broader question about how many operator members across filter.zod.ts document themselves in JSDoc that no generated surface reads. That second reading was not measured and should not be assumed from this card.
Related: #13709 (the JSDoc correction that surfaced this), #13539 (the has-value alignment), #11601 (adjacent, closed, different mechanism).
Activity
zhuangjianguo commentedon Sep 3, 2026
Claim: PM loop,
domain:specseat (seat post #6017), R3 of this shiftSession:
session_0174WZTU6XcFcS7g2kykC53i(GitHubzhuangjianguo)Branch:
claude/issue-14048-special-operator-describeWorktree:
objectstack-issue-14048Domain:
domain:specFile surface:
packages/spec/src/data/filter.zod.ts:1026-1035—SpecialOperatorSchema's$nulland$existsgain a.describe()each, this pair only (triage 5488438905 adopts the card's own restraint: the file-wide sweep is another card; the dev MEASURES and REPORTS the file-wide count of JSDoc-only members, ⛔ does not act on it) · the regeneratedcontent/docs/references/data/filter.mdx(⛔ never hand-edited) plus whatever elsecheck:generatedproves stale · apatchchangeset for@objectstack/spec. Ratchet fence:scripts/check-corpus-claim-drift.mjsruleexists-key-presencewatchescontent/docs/**— the gate runs BEFORE the wording is written; the$existsprose says the field HAS A VALUE (!= null), never key presence, matching the corrected JSDoc at:1031. Stop on breach; explain in the report.Container & model: S,
mode:subagent,model: opus.node scripts/pm/dispatch-gates.mjs --tieron the surface ata4e4d2d7(18:05Z): "no path-derived mandate" + "Clause ② SUSPECT surface" (filter.zod.ts); tier judged from content.Clause-②: no —
.describe()on two existing keys; the JSON Schema projection gainsdescriptiononly, no accept/reject or shape change.Serial constraints cleared: open-PR file scan at 18:04Z (31 open PRs, 233 changed-file rows) — none touches
filter.zod.tsorreferences/data/filter.mdx; same-day churn: none (filter.zod.tslastd16df7412026-09-02; the page 2026-08-31); lane siblings in flight (#14847, #14791, #14825, #14865) and this batch's siblings (#13916field.zod.ts; #13988driver.zod.ts+references/data/driver*.mdx+ three hand-written pages) file-disjoint — both this card and #13988 regenerate underreferences/data/, different pages; H17 on-hold trigger-file index (#9857 body, 13:47Z): no row names these files.Decision re-read (1 comment): triage 5488438905 — this pair only, report the count, run the ratchet first, ⛔ hand-editing the
.mdx. Premise re-read onorigin/maina4e4d2d7(18:05Z):filter.zod.ts:1026-1034declares both members with JSDoc only, no.describe();filter.mdx:138-139renders both Description cells empty;.describe(occurs on 11 lines of the file today (the card counted 15, triage 14 — drift, the dev reports the current number). Premise holds.Generated by Claude Code
zhuangjianguo commentedon Sep 3, 2026
Dispatch (R3 of this shift, 2026-09-03T18:13Z) —
domain:specseat,session_0174WZTU6XcFcR7g2kykC53i, seat post #6017.mode:subagent,model: opus, size S, Clause ② no (path limb fires, declared in the PR body). The dev leaves its ownClaim:comment below; the assignee stays the PM's.Dispatch text handed to the dev, in brief (triage 5488438905's "this pair only", "run the ratchet first", and "never hand-edit the
.mdx" are quoted verbatim and marked non-renegotiable):.describe()on$nulland$existsinSpecialOperatorSchema(filter.zod.ts),$existsworded as has-a-value (the corrected JSDoc is the source of truth), ⛔ no key-presence wording;pnpm check:corpus-claim-driftbefore and after; regeneratereferences/data/filter.mdxwith--fixonly..describe(count (card 15 / triage 14 / seat 11 lines — drift to settle by measurement).prop.descriptionwhere the generator reads it; no test pins the empty cells.Fixes #14048,@objectstack/specpatch changeset, two-limb Clause ② declaration, ⛔ noneeds:contract-review, ⛔ no ready flip; report asos-dev-reporton this card.Generated by Claude Code
zhuangjianguo commentedon Sep 3, 2026
Correction to the dispatch comment above (5530117850): the session id there is mistyped; the seat's session is
session_0174WZTU6XcFcS7g2kykC53i, as in the claim comment 5530074295. Nothing else in that comment changes.Generated by Claude Code
docs(spec): describe() for SpecialOperator $null and $exists (#14048)
zhuangjianguo commentedon Sep 3, 2026
os-dev-report
{
"issue": 14048,
"status": "done",
"branch": "claude/issue-14048-special-operator-describe",
"pr": "#15054",
"premise_still_valid": true,
"summary": "Premise re-verified on origin/main before editing: SpecialOperatorSchema declared $null and $exists as bare z.boolean().optional() with JSDoc only, and filter.mdx:138-139 rendered both Description cells empty. Each member gained a .describe() (this pair only, per triage 5488438905); content/docs/references/data/filter.mdx was regenerated with check:generated --fix, never hand-edited, and the regenerated diff is exactly those two rows. Mechanism hypothesis H1 confirmed empirically rather than assumed: .optional().describe(...) reaches prop.description and renders, the same shape $in/$nin/$icontains/$like/$ilike already use. H3 confirmed: no test pinned the empty cells or the SpecialOperator JSON-schema description (positive control: the same grep instrument found 5 test files naming icontains). A patch changeset for @objectstack/spec is included. PM claim comment 5530074295 was present and the assignee was set by the PM as expected.",
"tests": "All readings taken at commit 79007b3, quoted after the final commit. BUILD: pnpm --filter '@objectstack/spec...' build -> os-verify-lock VERDICT command-exit 0; check-dts-emitted: 34/34 declared declaration file(s) present. TEST: pnpm --filter @objectstack/spec test -> VERDICT command-exit 0; Test Files 463 passed (463), Tests 12374 passed (12374). TYPECHECK: pnpm --filter @objectstack/spec typecheck -> VERDICT command-exit 0; check:test-typecheck OK. RATCHET, run BEFORE any wording was written and again after regeneration, both at their own tree: identical verdict lines 'check-corpus-claim-drift: OK, no new claim sites beside a pinned spelling.' and 'Rules: 4 row(s) - exists-key-presence 4, exists-portability 0, regex-retired 0, section-visiblewhen-unbound 0.' - the new prose adds zero claim sites. GATE FAMILY re-derived on the actual diff after the final commit with 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack' (no paths passed) -> 71 families; all 71 run: 67 green, 4 NOT MEASURED, none red. Named verdicts: check:generated 'All 15 generated artifacts are up to date.'; check:api-surface 'public API surface + factory signatures unchanged'; check:export-origins 'export-origins/ is current: 5239 exports across 17 entry points resolve exactly as recorded' (no new export); check:nul-bytes 'OK (scanned 8174 text file(s) ... no raw ASCII control bytes)' plus a hand grep of the three touched files for ASCII control bytes, no matches. NOT MEASURED, with each gate's own reason, never read as green or red: check-dev-prereqs.mjs (workspace not fully built - only spec plus its dependency closure, formula, lint and client-react were built here); check-test-completeness.mjs (exit 3; the gate itself says the local reading is NOT MEASURED without a saved turbo test log); scripts/pm/check-half-states.mjs bare invocation (live GitHub patrol, exceeded the 300s per-command cap; its family form pnpm check:pm-half-states is green with 2062 self-test cases); check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET, reads built output of every package). Three gates were prerequisite-blocked on the first pass and count as green only after their package was built and they were re-run: check:doc-formula-expressions (formula), check:doc-security-posture (lint), check:skill-examples (client-react). No ablation was performed - this is a prose-only diff with no guard to ablate. NARROWING DECLARED: the whole-workspace build that would clear the two prerequisite gates was not run locally; CI owns it. STALE TREE note: dispatch-gates warned main moved 7 commits ahead during the run and 4 files it reads changed (stale.yml, check-clause2-carriers.mjs, check-skill-line-ratchet.mjs, ensure-pm-labels.sh); the family count was 71 on both derivations and none of the four is a gate this diff touches; the branch is left at its merge base f28eb1d.",
"mcp_calls": "2 - create_pull_request and add_issue_comment. Card body and all comments were read through the zero-quota public-repo page payload, and the PR body readback was done from the rendered PR page (every marker from the first line through the session-URL footer present), so neither cost a call.",
"open_questions": [],
"out_of_scope_findings": [
"NOT filed, deliberately, and reported here instead: FieldOperatorsSchema restates $null and $exists around filter.zod.ts:1097 with neither JSDoc nor .describe(), so the FieldOperator table on the same generated page still renders those two Description cells empty. This sits inside the file-wide sweep that #14048's own body raises as its open question for triage and that ruling 5488438905 deferred to another card, so filing it separately would duplicate that card. Reported in the PR body as an observation for whoever picks that card up.",
"Measurement delivered as instructed, not acted on: filter.zod.ts has 11 .describe( call sites before this PR and 13 after (the card said 15, triage counted 14 - both are drift). Of 43 Zod object members, 12 carried a JSDoc block and no .describe() before this PR and 10 after: $eq, $ne, $gt, $gte, $lt, $lte, $contains, $notContains, $startsWith, $endsWith, plus the two converted here; 22 members carry neither."
]
}
Generated by Claude Code
zhuangjianguo commentedon Sep 3, 2026
Contract review — not applicable (Clause ②
noon both carriers) · ACCEPT — PR #15054 (head79007b38,Fixes #14048)Reviewer of record: PM seat
domain:spec(session_0174WZTU6XcFcS7g2kykC53i). Readings taken against GitHub,origin/main82291fbaand the PR branch, not the report's prose; UTC times are read times.Clause ② gate (19:14Z). Declaration
Clause-②: noon the claim and the two-limb line at the top of the PR body;node scripts/pm/check-clause2-carriers.mjs --pair 15054exit 0. Content:.describe()on two existingz.boolean().optional()members — the JSON-Schema projection gains twodescriptionstrings and nothing else; no tier review is owed.PR form (19:14Z). Draft, base
main, first lineFixes #14048— the only closing keyword (#13539 and #13709 sit next to no verb). Trial merge againstorigin/mainclean;check-governed-merges.mjs --teston the 3 changed paths: NOT governed (0 of 3) — queue landing applies. The branch sits at its merge basef28eb1df; the queue rebuilds on currentmain.Scope (19:14Z). 3 files:
packages/spec/src/data/filter.zod.ts(SpecialOperatorSchema$null/$existsonly — triage 5488438905's fence held), the regeneratedcontent/docs/references/data/filter.mdx(exactly the two Description cells, rows 138-139), one@objectstack/specpatch changeset. Nocontent/docs/releases/change.Derived judgments.
$existssays the field HAS A VALUE (!= null), the exact inverse of$null; a grep of the added lines for the key-presence family (key presence,key is present,exists as a key,has the key,key exists) returns 0. The SQL and MongoDB lowerings restate the corrected JSDoc at:1031-1033(IS NOT NULL/IS NULL;{ $ne: null }/{ $eq: null }). Correct.check:corpus-claim-driftrun before any wording and again after regeneration, both readingexists-key-presence 4— zero new claim sites; CI's Lint & Repo Gates re-runs it on the head..optional().describe(...)reachesprop.descriptionand renders — the same shape$in/$nin/$icontains/$like/$ilikealready use in the file.check:api-surfaceandcheck:export-originsreport no new export..describe(11 → 13; JSDoc-only members 12 → 10; 22 members with neither;FieldOperatorsSchema's own$null/$existsaround:1097still render empty on the same page. Correct restraint.Evidence. Pinned to the current head: report and PR body quote
79007b38, PRhead.shais79007b38f(19:14Z). Spec package 463 files / 12374 tests, typecheck clean; gate family re-derived on the actual diff (71 families): 67 green, 4 NOT MEASURED on prerequisite exits (CI owns them). H3 confirmed with a positive control. No ablation — right call for a prose-only diff. MCP calls: 2 (both writes).Out-of-scope group (sweep criterion: describe coverage of the rest of
filter.zod.ts, the sweep triage deferred): filed by this seat as #15059 from the dev's measurement — 10 JSDoc-only members, 22 with neither, and theFieldOperatorsSchemapair on the same page. Bare, for triage. The dev correctly did not file it (it would have duplicated the deferred card, which did not yet exist).Landing. CI on
79007b38: 16 in progress, 0 red (19:14Z); flip timer armed for 19:42Z. At all-checks-green: provenance comment on the PR, flip to ready, auto-merge (squash), enqueue reading. On MERGED: card closes viaFixes, strippm:dispatched, probe the two describes onorigin/main, landing note. Dev session: Agent task a4c26a2567e2c4ec1 (subagent, finished).Generated by Claude Code
zhuangjianguo commentedon Sep 3, 2026
Landing note (PM seat
domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T20:06Z) — PR #15054 merged through the queue at 20:05:27Z asfc58a99e(enqueued 19:38:47Z); this card closed viaFixesat 20:05:29Z.origin/main(fetched 20:06Z, tipfc58a99e):packages/spec/src/data/filter.zod.ts:1029carries the$nulldescribe ("Is-null check …") and:1040the$existsdescribe ("Has-a-value check — the exact inverse of$null…");content/docs/references/data/filter.mdx:138-139render both Description cells. Landed as reviewed (ACCEPT 5530901437).pm:dispatchedremoved with a compared readback (documentation, domain:spec, priority:p3remain, MATCH at 20:06Z).$orof$containsin six places — and one of them tells the reader the case-insensitivity question is "still open" when #7641 closed it #13988, merged in the same window).filter.zod.ts: 10 operator members are documented by JSDoc only and 22 by neither, so their Description cells on the publishedfilter.mdxrender empty — the file-wide sweep #14048 deferred, now measured #15059 (file-wide describe coverage offilter.zod.ts, incl. theFieldOperatorsSchemapair) — bare, for triage; it is the next writer offilter.zod.tsafter An ADR-0021 dataset measure cannot express a deadline that is another column plus an offset held in a third column — the grace-aware on-time rate has no spelling #14104'sFieldReferenceSchemachange, serial on that file.Generated by Claude Code
docs(spec): describe() for SpecialOperator $null and $exists (objects…