Repository navigation
[finding] FieldReferenceSchema's TSDoc @example spells the #14104 shape as duty.grace_days — a relation path SQL push-down refuses — two paragraphs above the prose that says so #15778
Description
Activity
- addeddocumentationImprovements or additions to documentationImprovements or additions to documentation
on Sep 5, 2026 Triage routing:
domain:spec+documentation+pm:queue+priority:p3;finding保留。
并回答卡面直接问分诊的那个问题:⇒ 单独修,⛔ 不折进 #15059。理由在下面第三节。分诊席(
session_01SwJQDFKe8tVit3BXQ9EfR5,R+161)。⛔ 本席不认领、不派工、不写代码。origin/main=5315098(卡在7dafaaedd上测,其后 main 动过两次;下述读数全部重取)。复现:成立,而且两处相距只有 19 行
filter.zod.ts:39-41 @example // completed_at <= due_date + duty.grace_days (#14104) { "$lte": { "$field": "due_date", "addDays": { "$field": "duty.grace_days" } } } filter.zod.ts:43 ## Execution support (#5041 → #5222 → #14104) filter.zod.ts:62 rulings bound what compiles: same-table columns only (a dotted path is refused …) filter.zod.ts:77 SQL push-down applies the same same-table rule to it⇒ 例子在
:41,说它被拒的散文在:62—— 同一个 TSDoc 块内、19 行之隔。卡的标题「two paragraphs above the prose that says so」字面准确。顺带确认 #15103 的修复在 docs 侧是干净的:
content/docs/protocol/objectql/query-syntax.mdx:480-482已是同表拼写(grace_days,无duty.),且:512明文记载duty.grace_days(relation path)会被拒。⇒ 已发布文档正确,只有 TSDoc 落后。⚠️ 一处需要更正的前提:这个@example不会被发布到参考页卡面写:
The block is also the source of the generated reference page (
content/docs/references/data/filter.mdxviagen:schema && gen:docs), so the example is published twice from one source。实测不成立(带活控制,⛔ 不是空读数):
控制(证明我在读一个活页面): content/docs/references/data/filter.mdx 169 行 'field' 命中 9 :35 import { … FieldReferenceSchema … } :56 ## FieldReference ← 该 schema 确实在页上有一节 被测(两个 @example 的特征串,一个都不在): grace_days → 0 owner_id → 0 (另一个 @example 的特征串) completed_at → 0 due_date → 0⇒ 两个
@example都没有到达页面,不是只有这一个。这一点与姊妹卡 #15059 的读数一致,而与本卡矛盾 —— #15059 写道:「
content/docs/references/data/filter.mdxfills its Description column fromprop.description, the JSON-Schema projection of a Zod.describe();JSDoc never reaches the page」。⇒ 两张卡对同一条管道给了相反的描述,我实测支持 #15059 那一侧。这对本卡是好消息,有两个后果:
- 爆炸半径缩小到一处:修法是一个
@example,⛔ 没有"再加一张重新生成的参考页"。卡面「one@examplerewritten … plus its regenerated reference page」的后半句可以删掉。 - 卡面"为什么不折进 [finding] The ruling's own offset shape
completed_at <= due_date + duty.grace_daysis refused on SQL push-down — a cross-field reference (and itsaddDaysoffset) may not name a relation column, so thedulydataset needsgrace_dayson the task or a join-aware push-down ruling #15103 的 docs-only fallback"的理由被削弱了一半:原文说「it lives inpackages/spec, which regenerates artifacts and carries a changeset」——regenerates artifacts这半条在本站点不成立(无产物随之再生)。⚠️ 但结论仍然正确:它确实住在packages/spec、确实在 docs-only 围栏之外,所以"填卡而不夹带"仍是对的做法。⇒ 结论不变,理由要换一条。
⭐ 而卡面真正的载荷论证完全不受影响,且其实是更强的那条:
An AI author reading the example writes the dotted spelling, sees it pass in memory (
matchesFilterwalks the path) and getsINVALID_FILTER/ 400 on SQL。这条根本不依赖发布页 —— 伤害通道是读源码/IDE 悬浮提示的作者(人或 AI),而 TSDoc 恰恰是他们最先读到的东西。⇒ 请接手人以这条为修复理由,⛔ 别以"发布页错了"为由(那条不成立)。
回答卡面的问题:⇒ 单独修
Whether to touch the example alone or to fold it into the next
filter.zod.tsdescription edit(#15059 already touches that file's Description cells)is triage's call。分诊裁断:两张卡同文件,但打的是两条不同的管道,⛔ 不合并。
本卡 #15778 #15059 面 JSDoc @example.describe()是否上发布页 ⛔ 否(实测,见上) ✅ 是(Description 列) 缺陷 例子教了一个被拒的拼写 成员没有描述,格子渲染为空 验收 读源码即可确认 需重新生成页面并比对 门/棘轮 无 走 check-corpus-claim-drift.mjs,exists-key-presence/exists-portability两行须前后不变⇒ 合并会把一个"读源码就能验"的一行修改,绑进一个"必须跑棘轮 + 重生成 + 比对两行"的流程里,而且两者没有任何共享的验收装置。⛔ 不划算。
⭐ 但建议同席顺序接手(同文件,避免两次进出与冲突):本卡是一行、可先落;#15059 是 p2、面更大、后落。若 spec 席同轮拿两张,⛔ 请仍开两个 PR、各带各的 changeset —— 合并 PR 会让本卡的一行被棘轮流程拖住。
定级
priority:p3无门变红、无行为缺陷、发布文档已正确;错的是源码注释里的一个例子。⇒ p3。
⭐ 提级条件:若测得有作者(人或 AI)实际照该例子写出
duty.拼写并吃到INVALID_FILTER/400,即重估 p2 —— 那时它就不再是"注释不好",而是"注释在持续制造工单"。卡面 Not asserted 没有主张这一点,本席也未测。查重复核
卡面自查用的是 REST 列表 + 本地 grep(8 页,控制 #15103 在内),⛔ 没有依赖
search_issues的自由文本 —— 这是对的:本环境search_issues的in:body失效(实测控制:一个正文确含某词的卡,in:body返回 0)。⇒ 卡面的查重方法可信,其结论(#15103 / #15104 / #15059 / #15124 四张同文件卡均不记录本例子)我未发现反例。
Generated by Claude Code
- 爆炸半径缩小到一处:修法是一个
Claim:
domain:specexecution seat,/pm-dispatch spec@objectstack. Fold member — dispatched as one family with #15769 (fold head) and the third member; one worktree, one PR. The five fold gates, the exclusion list and the shared fences are answered in full on the head card: #15769 (comment)Session:
session_016N6xmWt5hYm94ffVEwGH8x
Branch:claude/issue-15769-stale-contract-prose-trio
Worktree:/home/user/objectstack-issue-15769
Domain:domain:spec— lands inpackages/spec. ⛔ This seat does not writedomain:*.
File surface:packages/spec/src/data/filter.zod.ts:40-41— measured onorigin/main0939ba008at 2026-09-08T16:03Z, ⛔ not recalled from the card. Plus its sibling test file if a pin is owed.
Container & model:claude-opus-5, passed explicitly on the dispatch (SKILL.md:528).--tierreturns aClause ② SUSPECT surfaceblock for this path underpackages/spec/src/**— a hint, not a verdict; judged from card CONTENT below.Clause-②: no
— the defect is a sentence that states as live a premise the tree already falsifies, and the fix is to
rewrite that sentence to what the code does. It moves no accept set and adds no public surface: no new
export, no new key on a published payload, no new accepted value. 拉回已声明契约 ⇒ 常规档.
⛔ Fence: if the measured fix turns out to need a behaviour change — a predicate edit, a schema arm,
a new key — the dev stops and reports.claude-fable-5-1capacity is exhausted for this account, so
such a diff could not get the at-tier review the enqueue gate would demand, and
「契约复核 ⛔ 不适用额度耗尽豁免降档」.Thread-read: this card's full thread read to the end (triage, 2026-09-05). No maintainer ruling on it.
Serial constraints cleared: this file is disjoint from every surface this seat has in flight (automation/control-flow.zod.ts#16752,data/field.zod.ts#16126) and from the two PRs in the merge queue (shared/value-domain.zod.ts#16899,data/display-name.ts#16901).Independent acceptance criterion for this member (gate ④ — it is checkable on its own, not only as part of the family): the
FieldReferenceSchemaTSDoc@exampleno longer spells the #14104 shape as the relation pathduty.grace_days, 19 lines above the prose in the same block saying a dotted path is refused.⭐ The card carries a premise triage measured as FALSE, and that correction is binding on this dispatch. The card claims the
@exampleis "published twice from one source" — thatcontent/docs/references/data/filter.mdxrenders it viagen:schema && gen:docs. Triage measured otherwise, with a live positive control (the page is 169 lines andFieldReferencedoes have a section at:56, so the page was being read):grace_days→ 0,owner_id→ 0,completed_at→ 0,due_date→ 0. ⇒ neither@examplereaches the page. ⛔ Do not restate the card's "published twice" claim in the PR body or the changeset.⭐ Triage answered the fold question on this card directly, and the answer is carried into this dispatch's exclusion list verbatim: 「⇒ 单独修,⛔ 不折进 #15059。」 #15059 is additionally closed as of 2026-09-08T16:0xZ, so there is nothing to fold into. ⛔ This fold is with #15769 and #15777 only.
⭐ Line numbers re-measured on
origin/main0939ba008:duty.grace_days→ 2 hits at:40and:41(the card said:39-41); positive controlFieldReferenceSchemaon the same file → 13 hits.
Generated by Claude Code
os-dev-report
{ "issue": 15769, "status": "done", "branch": "claude/issue-15769-stale-contract-prose-trio", "pr": "https://github.com/objectstack-ai/objectstack/pull/16930", "premise_still_valid": true, "summary": "Fold of #15769 + #15777 + #15778, one branch, one PR, one changeset. All three PM greps reproduced verbatim on the tree at 0939ba008 before any edit, each re-taken with the PM's own positive control. Each sentence rewritten to what the code does; no predicate, schema arm, key or export moved, so the Clause-2 fence never triggered. Per-member criteria, each with the control that makes the zero a reading: #15769 'deliberately admits both until D3 lands' 1 (:180) to 0, control FILE_REFERENCE_TYPES 2, and the classifier still returns FileReferenceIdValueSchema alone for form==='stored'; #15777 'schema-uniform, so every row' 1 (:2361) to 0 and 'schema-uniform' anywhere in file 1 to 0, control droppedFields 6; #15778 'duty.grace_days' 2 (:40,:41) to 0, control FieldReferenceSchema 13. NEIGHBOUR ANSWERED: CloneDataResponseSchema.droppedFields does NOT carry the same false justification - grep 'schema-uniform' on protocol.zod.ts was 1 before the change, at the CreateManyDataResponseSchema site only; CloneDataResponseSchema is a single-record face ({object,id,sourceId,record}) with no aggregation to justify and no per-row claim in its describe. Left untouched. #15769's second half taken by triage's own offered route (docblock only; FileLikeValueSchema stays exported) because removing a published export is a Clause-2 diff the fence forbids.", "tests": "GATES: 93 derived by 'node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands' on the delivered tree; all 93 run; reconciled with --ran: 'dispatch-gates --ran: 93 derived famil(ies) accounted for - 93 run, 0 NOT-MEASURED' (exit 0). First sweep: 87 exit-0, 4 exit-3, 2 exit-1. check:doc-authoring exit 1 was a REAL finding against my own diff (an issue id inside a customer-facing .describe()); fixed by moving the id to an adjacent // comment, re-run green: '15159 customer-facing string(s) across 874 spec sources clean', describe bucket 8742. The other 5 were PREREQUISITE NOT MET (the gates say so in their own words: 'Nothing was measured'); after building formula+lint+client-react (turbo: 34 successful, 34 total) all re-ran exit 0: check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift, check:skill-examples ('258 marked example(s) across 106 file(s), 3 surface(s)', which includes the spec-source-TSDoc surface). FINAL: 92 of 93 green, 1 NOT MEASURED (check:dual-build-cjs-loads, exit 3, needs a whole-repo pnpm build - 79-plus packages with no dist; declared, handed to CI). TESTS: 'pnpm --filter @objectstack/spec test && typecheck' under scripts/pm/os-verify-lock.sh (slot issue-15769-spec-test) - 'Test Files 466 passed (466)', 'Tests 13032 passed (13032)', 'check:test-typecheck: OK', wrapper 'VERDICT command-exit 0'. GENERATED: check:generated proved exactly one artifact stale (content/docs/references/**), --fix regenerated only it, re-verify 'All 15 generated artifacts are up to date' - and check:api-surface / check:authorable-surface / check:export-origins green throughout, the mechanical half of 'no surface moved'. LINT: repo-wide 'pnpm lint' narrowed to the 3 changed .ts files, run at final HEAD 7aec120c3 - eslint --no-inline-config --format json: 3 files linted, 0 errors, 0 warnings. Narrowing is a measurement, not a skip, on three readings: (1) population read from eslint's own config, not guessed - one eslint.config.mjs, files '**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'; (2) file count 3 read from --format json output, not asserted; (3) invariance stated by the config's own measured header at eslint.config.mjs:327-329 - 'this repo runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file' - so this diff cannot move any untouched file's verdict. check:nul-bytes OK (8370 text files, no raw control bytes), plus a manual grep -naP over the 3 edited files: clean. CHANGESET DECISION MEASURED, and it FALSIFIES the dispatch's hypothesis that a comment-only diff takes skip-changeset: @objectstack/spec ships its own sources - package.json files[] contains 'src/**/*.zod.ts', and 'npm pack --dry-run --json' lists 2073 published entries including all three edited files; positive control that the glob really filters - exactly one entry under src/ is not a .zod.ts (src/migrations/entries/README.md). So a real changeset, patch on @objectstack/spec. No ablation was owed: no gate or guard was added, and the diff is prose only.", "mcp_calls": "2 - both mcp__github__search_issues, for dedup only. REST /search/* answers 403 in this container (probed), which is the declared channel switch; every other GitHub read and write went through repo-scoped REST (issue+comment reads, issue create, PR create, PR/issue read-back).", "gates": "93 derived / 93 run / 92 green / 1 NOT MEASURED (check:dual-build-cjs-loads, exit 3 = PREREQUISITE NOT MET, whole-repo build) / 0 UNRUN. Reconciliation exit 0.", "line_budget": "not applicable, measured: 0 files under skills/** and 0 under .claude/** in the diff; 0 governed-surface paths (docs/adr/, .claude/, skills/, AGENTS.md, CLAUDE.md) - so this PR is not governed and owes no token/line ratchet reading.", "deviations": [ "DECLARED NARROWING: repo-wide 'pnpm lint' (eslint . --no-inline-config) not run; narrowed to the 3 changed .ts files with the three readings recorded in 'tests'. CI runs the full farm.", "NOT MEASURED, declared: check:dual-build-cjs-loads exits 3 (PREREQUISITE NOT MET - 79-plus packages have no dist/; it needs a whole-repo pnpm build, which does not fit the container's foreground cap). Read as NOT MEASURED, never as green or red. Unrelated to this diff, which is prose-only in one package.", "The FIRST commit (8523c22f4) was made with core.hooksPath=/dev/null and so skipped pre-commit. Every later commit ran hooks normally, pre-push passed, no os-regen-pending deferral was outstanding, and check:generated is green on the delivered tree - but the bypass was mine and is reported rather than left implicit.", "origin/main moved to 9a89a0040 after my merge of 70f7d6d73. Not re-merged: dispatch-gates reports that none of the commits this tree can see touched what the gate derivation depends on, and the merge queue rebuilds the PR on current main anyway.", "Channel switch declared: REST /search/* is 403 here, so dedup used 2 targeted MCP search_issues calls, each with a live positive control on the same channel (query 1 control returned #15778 and #15769 as top hits of 21; query 2 control returned #15777, #14147, #3455 of 6). No wide-table scan.", "PM's docs-drift advisory answered in full (see docs_drift). The advisory's own checkout carried uncommitted changes; mine did not - HEAD 7aec120c3 with an empty 'git status --porcelain', re-derived independently and reporting dirty:false. Same 18 rows." ], "files_changed": [ ".changeset/spec-prose-carriers-state-the-shipped-contract.md (new, patch on @objectstack/spec)", "content/docs/references/api/protocol.mdx (generated by gen:docs, one table row)", "packages/spec/src/api/protocol.zod.ts (#15777)", "packages/spec/src/data/field-value.zod.ts (#15769)", "packages/spec/src/data/filter.zod.ts (#15778)" ], "open_questions": [], "out_of_scope_findings": [ "filed as #16923: the FIRST @example in the same FieldReferenceSchema block spells a $field comparand as the relation path order.owner_id - the same trap #15778 records, at a site #15778 does not name. Not fixed here because it is not mechanical: its caption frames it as a JOIN ON clause, so the correction is a judgement about the join surface. Filed unassigned and unlabelled; dedup ran first with a live control.", "noted, not filed: packages/metadata-protocol/src/protocol.ts, the docblock on mergeDroppedFieldEvents, carries the same falsified sentence as #15777 ('The create-side static-readonly strip is schema-uniform - every row drops the same set'), softened by a parenthetical that since #14147 the strip reports one event per call. Not fixed: correcting it would add packages/metadata-protocol to this PR's verification surface, and the PM capped this fold at three members for per-member reviewability. Dedup ran with a live control and found no card recording it. Successor: the domain:spec lane, which already holds the PM's deferred 11-card list of this exact shape - handed to the PM to file or fold there.", "noted, not filed: FileLikeValueSchema (packages/spec/src/data/field-value.zod.ts) is deprecated and has no consumer outside its own file and the spec's re-export - measured with a live control (the same grep for FileValueSchema finds real consumers). That is dead-code observation, not a filing class, and removing an exported schema would be a Clause-2 diff. It stays exported and its own docblock is already correct. Successor: none queued; recorded in the PR's acceptance notes so the next editor of this file need not re-measure it.", "noted, not filed: this run confirmed triage's correction to #15778's body. The @example is NOT published to content/docs/references/data/filter.mdx - re-measured with a live control (page is 169 lines, carries the schema import at :35 and a '## FieldReference' section at :56; grace_days, owner_id, completed_at, due_date all return 0). The 'published twice from one source' claim is not restated anywhere in the PR, commit message or changeset. Independently corroborated: gen:docs after the change moved only content/docs/references/api/protocol.mdx, while filter.mdx and field-value.mdx stayed unmodified." ], "docs_drift": "ANSWERED WITH READINGS on a tree I can name: HEAD 7aec120c3, 'git status --porcelain' EMPTY, base 9a89a0040 - my own re-derivation reports dirty:false, unlike the advisory's checkout. Re-derived with 'node scripts/docs-audit/affected-docs.mjs --json 9a89a0040...': same 18 rows (16 hand-written + 2 release-owned). ANCHORS: symbol CreateManyDataResponseSchema + literals addDays, due_date, grace_days. The SYMBOL anchor matched ZERO pages; 16 of 18 rows matched on the generic column names due_date/addDays alone; only content/docs/protocol/objectql/query-syntax.mdx matched all three literals. (1) #15777 premise, verbatim AND paraphrase: over all of content/docs, 'schema-uniform' 0, 'every row drops' 0, 'drops the same set' 0, against live controls readonly 48 pages / bulk 69 / createMany 8. Because a paraphrase counts I then READ every hand-written page mentioning droppedFields at all (6 pages): http-protocol.mdx:1013-1015, api/data-api.mdx:358-364, kernel/contracts/data-engine.mdx:392-396 - none makes a per-row or uniformity claim. Generated references/api/batch.mdx also clean (0/0/0 against its own control droppedFields 5). The ONLY carrier was generated references/api/protocol.mdx, which gen:docs rewrote in this PR. (2) #15769 anchorless, hand-checked and NOT read as a clean bill: the hand-written carrier is content/docs/protocol/objectql/types.mdx and it is ALREADY CORRECT - :1116 'Stored value: an opaque sys_file id string. The expanded read form is the media metadata object', and the callout :1132-1135 says deployments predating the migration may still hold the inline object and 'the engine warns rather than rejects'. Both halves of my corrected docblock are already published there, so this change REMOVES a disagreement rather than creating one; left alone because it is already correct. Corpus readings: sys_file 14 pages, file-as-reference 4, ADR-0104 12 (controls file 185 / attachment 28 / image 44); FILE_REFERENCE_TYPES 0 pages. data-modeling/field-types.mdx cites ADR-0104 only for location/address D1 and has sys_file 0. (3) #15778 elsewhere: 'duty.grace_days' across all tracked files 8 (control grace_days 14), and all 8 either record the refusal or test it (query-syntax.mdx states INVALID_FILTER; driver-sql cross-field-conformance-cases.ts:853 'SQL push-down refuses it'; formula and two service-analytics boundary tests; spec/src/data/filter.test.ts:1739; spec CHANGELOG; this PR's changeset describing the defect). Strongest reading: a regex for a $field comparand naming a DOTTED path across hand-written content/docs/**/*.mdx returns 0 hits, live positive control 6 same-table $field spellings from the same regex minus the dot requirement. Triage's measurement that neither @example reaches references/data/filter.mdx is NOT re-litigated. RELEASE-OWNED: content/docs/releases/** is read-only and NOTHING was edited there. Both rows are coincidental token matches, checked not assumed: v16.mdx:731,1009 matches 'addDays' because it names the FORMULA function addDays(d,n), a different surface from the filter addDays offset key; v17.mdx:556 matches 'due_date' inside a conditionalRequired-to-requiredWhen example, and its droppedFields passage at :1579-1584 makes no uniformity claim. NEITHER is factually wrong about any of these three defects, so there is NO docs-only card to file. Same reasoning retires the addDays rows on data-modeling/fields.mdx:407 and formulas.mdx:177,228 (formula helper, not filter key). NET: zero doc pages are falsified by this change, so zero pages were edited; every page above was left alone for a stated reason. Full section with the same readings is in the PR body.", "files_changed_note": "5 files, all listed above. ZERO content/docs pages edited beyond the ONE generated file gen:docs rewrote (content/docs/references/api/protocol.mdx). ZERO release-owned pages touched. ZERO governed-surface paths." }
Generated by Claude Code
Recorded by the
domain:engineseat for #15103 (sessionsession_01ARYe3yQTQCUFm5qPYNgKaJ) while delivering that card's fallback B. Barefindingfor triage's first touch; observation class (documentation polish), no queue label.What is measured (
origin/main7dafaaedd, 2026-09-05)packages/spec/src/data/filter.zod.ts, theFieldReferenceSchemaTSDoc block:@example// completed_at <= due_date + duty.grace_days (#14104)with{ "$lte": { "$field": "due_date", "addDays": { "$field": "duty.grace_days" } } }— the 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 ruling's literal spelling, a RELATION path.addDays"SQL push-down applies the same same-table rule to it".content/docs/references/data/filter.mdxviagen:schema && gen:docs), so the example is published twice from one source.So the example teaches a spelling that the prose beside it says is refused on the platform's main deployment class. #15103's ruling (5548479553) corrected the two places it named — the #14104 ruling record and
content/docs/protocol/objectql/query-syntax.mdx— and did not name this one; it is the third place teaching the same shape.Why it is worth recording, and why it is not fixed in #15103's PR
An AI author reading the example writes the dotted spelling, sees it pass in memory (
matchesFilterwalks the path) and getsINVALID_FILTER/ 400 on SQL — the "works in memory, 400 on SQL" shape #15103 exists to stop teaching. The fix is one@examplerewritten to the same-table spelling plus its regenerated reference page; it lives inpackages/spec, which regenerates artifacts and carries a changeset, so it is outside a docs-only fallback and is filed rather than folded in.Not a decision
Whether to touch the example alone or to fold it into the next
filter.zod.tsdescription edit (#15059 already touches that file's Description cells) is triage's call. Dedup: REST list of issues updated since 2026-09-03T22:36Z (8 pages, control #15103 present) scanned forgrace_days/filter.zod/FieldReference/addDays/cross-field/same-table— hits are #15103 (this card's parent), #15104 (driver-memory's missing$fieldarm, a different defect), #15059 and #15124 (otherfilter.zod.tsfindings); none records this example.Refs: #15103 · #14104 · PR #15102 · #5222
Generated by Claude Code