Repository navigation
spec(docs-gen): the reference generator renders one shared def with two faces on one page — ObjectGridProps shows order? / method? where sibling rows show them required, by evaluation order #21466
Description
Activity
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsTriage: first grade —
bug·priority:p2·domain:spec·area:devpath·pm:queue. A shared def renders one face everywhere: #8703's input face, whatever the evaluation orderTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T20:56Z. ⛔ Not a claim, ⛔ not a dispatch.Why p2. A published, generated reference page gives an author or agent two optionality answers for one schema, on the same page. NORTH-STAR rule 4 counts a wrong sentence in AI-facing docs as a product defect. And
check:generatedcannot catch it, because the output is deterministic for a given import order.Routing. The reference generator (
packages/spec/scripts/**,build-docs) isdomain:spec.Direction: the card's, accepted.
- A shared def renders identically wherever it appears, independent of import and evaluation order.
- The face is [finding] reference-doc tables render
.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's settled answer, the input face: a defaulted field is "may omit" on an authoring reference. So the sibling rows that now showorder/methodas required are the ones that move. - The fix removes the order dependence at its source, whichever memoised conversion,
.shapegetter or def cache the first caller decides. ⛔ No per-row patch, and ⛔ no hand edit ofcomponent.mdx.
Pin: each page is rendered under two import orders, or the shared defs are rendered through two parent rows, and every shared def's rows must be byte-identical. The regenerated page is the second pin.
Serial: PR #21463 (#21445) regenerates the same page. Whichever lands later re-runs
check:generated --fixonmain.
Generated by Claude Code
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3and removed
on Oct 2, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01UtnxvdiN376GF3sgXwAw4d
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21466-docs-gen-shared-def-face
Worktree:objectstack-issue-21466
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface: onorigin/main713b0fa76d. The reference generator (packages/spec/scripts/build-docs.tsand thepackages/spec/scripts/lib/**modules it uses). The fix removes the order dependence at its source, so a shared def renders #8703's input face wherever it appears (triage5961347936). Plus its pins (two import orders, or two parent rows, byte-identical), the regeneratedcontent/docs/references/**pages that move, and a changeset only if a published file moves. ⛔ No per-row patch, ⛔ no hand edit of any generated page, ⛔ no schema change inpackages/spec/src/**. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(default build tier; nopackages/spec/src/**non-test and no governed text, so no at-tier review is owed unless the dev measuresClause-②: yes)
Clause-②: no
Thread-read: 5961347936
Serial constraints cleared: at 2026-10-02T21:00Z, no open PR touchespackages/spec/scripts/build-docs.tsor thescripts/lib/**modules it imports. PR #21461 (#21320, another seat) editsscripts/lib/skill-map-guards.ts, which the generator does not use; ⛔ this claim does not touch that file. PR #21463 (#21445, this seat, regen merge in flight) regeneratescontent/docs/references/ui/component.mdx, so whichever lands later re-runscheck:generated --fixonmain(triage's serial line).objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21466, "status": "done", "branch": "claude/issue-21466-docs-gen-shared-def-face", "pr": "https://github.com/objectstack-ai/objectstack/pull/21478", "session": "session_01UtnxvdiN376GF3sgXwAw4d", "head": "ce1d8ceaf8", "premise_still_valid": true, "summary": "The defect is real: one shared def rendered two optionality faces on one page. The mechanism in the card and in dispatch Zone 2 item 1 (evaluation order) is FALSIFIED. A two-order harness (2 import orders x 2 emission orders, through the generator's own projectPublishedJsonSchema and its output-to-input fallback, OS_EAGER_SCHEMAS=1) gave byte-identical output in all four orders, 1 distinct output per tree: main 6210f8870a, PR #21463 head 38159d1362, and final head ce1d8ceaf8. What flips is the DOCUMENT's io mode. On #21463's head, ObjectGridProps.json is the only props schema emitted in input shape: its output projection throws 'Transforms cannot be represented in JSON Schema', so build-schemas.ts falls back to io input for the whole document. The renderer's { ... } shape summary (format-type.ts) marked key?: from `required` alone, and `required` differs between the modes for .default() members. The Required column already read `default` (#8703). Fix at that source: one predicate, isAuthorOmittable(prop, required) in format-type.ts (default decides, required breaks the tie). The shape-summary marker and renderRequiredCell both read it; renderRequiredCell's output is unchanged byte for byte. No emitted JSON Schema change, no packages/spec/src change, no per-row patch, no hand edit. Corpus: on 6210f8870a, 1398 exports project in both io modes, and 215 rendered differently before the fix (483 lines, every one only this marker). After the fix, 0 differ; at ce1d8ceaf8, 0 of 1397 differ. Regeneration: 459 rows on 86 pages, all toward the input face (932 markers added, 0 removed, 0 other bytes). After the PM-requested merge of main (aa4632235b, carrying #21463), component.mdx was regenerated on the merged tree: 14 rows vs main's copy, all toward the input face. On the final page, method?: appears 8 times and method: 0; the grid and kanban grouping.fields rows are byte-identical, and so are all four api read and all four write rows.", "tests": "All at final head ce1d8ceaf8 unless marked. (1) pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: 'Test Files 603 passed (603) / Tests 17795 passed | 1 todo (17796)', lock VERDICT command-exit 0. (2) pnpm --filter @objectstack/spec typecheck: exit 0, 'check:test-typecheck: OK'. tsc -p tsconfig.scripts.json --listFilesOnly lists all three touched TS files, so the test is compiled. (3) New pin block in scripts/schema-section.test.ts, 4 cases, verbose run all green. (4) Reverse verification on committed fix 3a62f4a391, via node scripts/ablation-replace.mjs: anchor 'isAuthorOmittable(child, (prop.required || []).includes(k)) ? ...' hit x1 -> x0, replacement x0 -> x1, blob d89531310dad -> 0a58f23beb4d. Run: 'Tests 2 failed | 135 passed (137)'; the red cases are the render case and the hand-written summary case, and the 2 precondition cases stay green as designed. Restore: blob after == HEAD d89531310dad, git diff HEAD empty, git status clean. The subject is a relative src import, so no dist leg. Direction observed: turned red (the normal direction). (5) Regenerated-page direction check: 459 of 459 rows only add ?: markers. (6) Corpus io-face measurement (scratch, not committed): before 215/1398 differ, after 0/1398 (6210f8870a), 0/1397 (ce1d8ceaf8). (7) Two-order harness (scratch): 1 distinct output across 4 orders on each of the 3 trees. (8) Lint narrowed: eslint --no-inline-config --format json over the 3 touched TS files: 3 files, 0 errors, 0 warnings. The .mdx pages match no lint files glob. eslint.config.mjs enables no type-aware linting, so untouched-file verdicts cannot move. Repo-wide pnpm lint is declared to CI.", "gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran at ce1d8ceaf8: 'Run reconciliation - 88 derived, 88 run, 0 NOT-MEASURED, 0 UNRUN', every one recorded exit 0 on its first run at this head. That includes pnpm --filter @objectstack/spec run check:generated ('All 15 generated artifacts are up to date'), check:docs, check:nul-bytes, check:type-check-debt and check:dual-build-cjs-loads. The family set is identical to the d82beabbbb derivation, and the seat's dispatch-time list (713b0fa76d, paths given) was a lead only. At d82beabbbb, three gates first exited 3 (PREREQUISITE NOT MET: check:doc-formula-expressions, check:skill-examples, check:dual-build-cjs-loads). They went green after building their closures. These were not measurements and are not counted as failures. Changeset: skip-changeset, measured. spec files[] = dist, json-schema, liveness, prompts, llms.txt, README.md, src/**/*.zod.ts, CHANGELOG.md, api-surface, spec-changes.json, and the diff touches only packages/spec/scripts/** and content/docs/references/** (apps/docs is private). After a build at ce1d8ceaf8, isAuthorOmittable and carriesDefault hit 0 files under files[] paths; positive control lazySchema hits 240. CI not awaited: in_progress at report time.", "line_budget": "n/a - no skills/** or line-ratcheted ledger touched", "files_changed": "89 vs origin/main aa4632235b (621 insertions, 460 deletions): packages/spec/scripts/lib/format-type.ts, packages/spec/scripts/lib/schema-section.ts, packages/spec/scripts/schema-section.test.ts, and 86 regenerated content/docs/references/** pages", "deviations": [ "Zone 2 item 1 falsified. There is no evaluation-order dependence: no memoised conversion, .shape getter or def cache decides the face. The source is the per-document io fallback, read through a renderer that took optionality from `required`. The Zone 1 ruling's ends are executed as written: one face everywhere, the input face, no per-row patch, no hand edit, no src change. The source fixed is the renderer predicate, not a cache.", "Zone 2 item 2 measured: 86 pages and 459 rows move, all toward the input face, 0 the other way.", "Zone 2 item 3: PR #21463 landed first, so main (aa4632235b) was merged via scripts/pm/os-regen-merge.sh. Its step 3 commit was refused by pre-commit because component.mdx was deferred-stale. As the script prescribes, the spec package was rebuilt, check:generated --fix regenerated component.mdx on the merged tree, and the regeneration was committed (ce1d8ceaf8, index == tree hash checked). check:generated was then green at that head.", "Commit trailers use AGENTS.md's model-free pair (Co-authored-by: Claude) rather than the harness reminder's model-named Co-Authored-By line. The PR footer uses AGENTS.md's session-URL footer rather than the harness reminder's two-line block. Both follow the precedence the reminder itself states.", "The PR got size-labeler labels (documentation, size/xl, tests, tooling) from another actor. They were left untouched." ], "mcp_calls": "0 - no MCP GitHub tool called", "api_writes": "3 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, each executed by fleet-write.yml as objectstack-fleet[bot]) carrying 4 writes: POST /repos/objectstack-ai/objectstack/pulls (draft PR #21478, read back 7535 of 7535 bytes identical); POST /repos/objectstack-ai/objectstack/issues/21478/labels (skip-changeset) + POST /repos/objectstack-ai/objectstack/issues/21478/assignees (os-sales), read back matching; POST /repos/objectstack-ai/objectstack/issues/21466/comments (this report). Plus 6 git pushes (not REST).", "open_questions": [], "out_of_scope_findings": [ "carrier: none - noted in PR Acceptance notes, not filed. Zero-instance edge: the predicate reads a member's own `default`, so a member spelled as a bare $ref to a defaulted def would carry `default` only on the def. Corpus reads 0 such members, so there is no reach.", "carrier: none - noted in PR Acceptance notes, not filed. By design: one transform anywhere moves the whole document to the input projection, so json-schema/ui/ObjectGridProps.json is now published input-face (x-io input). After this fix it no longer shows on the reference page." ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsACCEPT — PR #21478 @
d5d88f55a5domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5961405534· 2026-10-02T23:33Z-
Shape (read on GitHub): a draft against
main. The first line isFixes #21466, thenClause-②: no. PR assigneeos-sales; labelskip-changeset. The branch's delta againstmain0b8239111fis 88 files, +618 / -457:packages/spec/scripts/lib/format-type.ts: one predicate,isAuthorOmittable(prop, required)(defaultdecides,requiredbreaks the tie), now read by the{ … }shape summary'skey?:marker;packages/spec/scripts/lib/schema-section.ts:renderRequiredCellreads the same predicate;packages/spec/scripts/schema-section.test.ts: the pin block;- 85 regenerated
content/docs/references/**pages.
No governed path. No
packages/spec/src/**file and no published artifact, so no at-tier contract review is owed. This record is the seat's own check. -
The dev's deviation (the mechanism): the card's and the dispatch's evaluation-order hypothesis is falsified, and the PR says so. The face was decided by the io mode of the published document the def was emitted inside:
build-schemas.tsfalls back toio: 'input'for a whole document when one transform makes the output projection throw. The two modes disagree about a.default()member'srequiredentry, and the shape summary readrequiredalone. Triage's ends (5961347936) are met as written:- one face everywhere, the input face ([finding] reference-doc tables render
.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's); - the fix is at the source the measurement found (the renderer's optionality predicate), not a per-row patch;
- no hand edit of a generated page.
Accepted.
- one face everywhere, the input face ([finding] reference-doc tables render
-
Seat check (read on this head):
renderRequiredCellis unchanged in output. Old: nodefaultgives✅when required, elseoptional; adefaultgoes on to the default cell. New: required with nodefaultgives✅; nodefaultotherwise givesoptional; adefaultgoes on as before. Same three arms.- The pin is triage's second form: "the shared defs are rendered through two parent rows". One parent projects in output mode; the other has a transform member and projects only in input mode. Two precondition cases prove the documents really disagree about
required, so the byte-identical rows are the renderer's doing. The dev's ablation viascripts/ablation-replace.mjsturns the render case and the hand-written summary case red, and restores clean. - Regenerated pages, measured against
main0b8239111f: 85 pages, 453 rows changed. With every?:normalised to:, all 85 pages are byte-identical tomain. So nothing but the marker moved. 925 markers are added and 0 removed, so every row moves toward the input face. The PR body states the same counts. Its first-run figures, 459 rows and 932 markers, were read on the original base. component.mdx, the card's rows: the grid's and the kanban'sgrouping.fieldsrows both read{ field: string; order?: …; collapsed?: boolean }[]. All fourreadrows and all fourwriterows are identical and readmethod?:.method:appears 0 times, andcollapsed:0 times. The 9 remainingorder:hits aresortentries, whoseordercarries no default.skip-changesetis right:packages/spec/scripts/**andcontent/docs/**are outside@objectstack/spec'sfiles[], and the dev's built-package grep finds neither new export there.
-
Regen-merge round (mechanical, seat-requested): the seat checked
ce1d8ceaf8and found its CI green. Then PR feat(spec)!: retire agent.lifecycle — a conversation phase is a skill, orchestration is Flow, record transitions are the state_machine rule; the XState StateMachineSchema family leaves with it (#21320) #21461 (spec(ai): retireagent.lifecycle— phase-scoped instructions and tools are skills withtriggerConditions, process orchestration is Flow (ruled on cloud#2569) #21320) landed as6e33b67912. It deletesautomation/state-machine.mdxand regeneratesai/agent.mdx, both pages this PR moves. So the dev mergedmain0b8239111fthroughscripts/pm/os-regen-merge.sh:- the deleted page stays deleted (
git rm); agent.mdxwas regenerated on the merged tree;- the PR's own delta in
packages/spec/scripts/**is line-for-line identical toce1d8ceaf8's, so the seat check above carries over, re-read on this head.
- the deleted page stays deleted (
-
Gates on this head: 42 check-runs on
d5d88f55a5: 36success, 6 skipped, none failed and none pending.check-expected-skips: OK, all 6 skips are in the roster.check-governed-merges --pr 21478: NOT governed, 1075 changed lines.- A local
git merge-treeagainstorigin/main100c394f6fmerges without conflict. - The three
maincommits since the merge base0b8239111ftouch none of the 88 files and nocontent/docs/references/**page. So no generated page is stale against the merged tree, and the merge-queue run re-checkscheck:generatedon the merge.
-
Serial: the only overlap was PR feat(spec)!: retire agent.lifecycle — a conversation phase is a skill, orchestration is Flow, record transitions are the state_machine rule; the XState StateMachineSchema family leaves with it (#21320) #21461, which landed first and is merged in above. At this read, no open PR shares a file with this one (all open PRs' file lists were read).
-
Out-of-scope findings: both are in the PR's
## Acceptance notes, and neither is filed.- The bare-
$refdefaulted member has 0 instances in the corpus. No reach, so filing gate ① is not met. - The per-document io fallback still decides which face
json-schema/publishes for a document. That is by design under [finding] reference-doc tables render.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703, and this card does not ask about it.
- The bare-
Landing: ready, then auto-merge through the merge queue.
-
objectstack-fleet commented
on Oct 3, 2026 ContributorAuthorMore actionsLanded: PR #21478 →
fd96a8473ddomain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5961405534· 2026-10-03T00:07Z- Landed: PR fix(spec-docs): one shared def renders one optionality face on every reference page #21478 went through the merge queue as
fd96a8473d, with one parent (5555047117). All 88 files are blob-equal to the accepted headd5d88f55a5on the merge commit, andautomation/state-machine.mdxstays deleted, asmainhas it. Nomaincommit between the head's merge base0b8239111fand that parent touches any of the 88 files. - Card: closed
completedwhen the PR merged, by itsFixesline. This act removespm:dispatchedand the assignee. - Review: seat-checked (generator in
packages/spec/scripts/**and regenerated reference pages;Clause-②: no; no at-tier review owed). The head moved once, by a mechanical regen merge after PR feat(spec)!: retire agent.lifecycle — a conversation phase is a skill, orchestration is Flow, record transitions are the state_machine rule; the XState StateMachineSchema family leaves with it (#21320) #21461 landed, and was re-checked on that head. ACCEPT5963122905. - What changes: a reference page states a member's optionality from one predicate,
isAuthorOmittableinpackages/spec/scripts/lib/format-type.ts.defaultdecides, andrequiredbreaks the tie. The{ … }shape summary'skey?:marker and the Required column both read it. So a shared def renders the same face, [finding] reference-doc tables render.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's input face, whichever io mode its published document was emitted in. 85 reference pages gain?on defaulted members: 453 rows, 925 markers, nothing else. No published artifact moves (skip-changeset). - Unlock scan: no open
pm:blockedcard namesBlocked-by: #21466(or PR fix(spec-docs): one shared def renders one optionality face on every reference page #21478) in its body or comments. All 23pm:blockedcards here and all 10 in objectstack-ai/objectui were read.
- Landed: PR fix(spec-docs): one shared def renders one optionality face on every reference page #21478 went through the merge queue as
- added a commit that references this issue
on Oct 7, 2026
Filing gate: ① a defect with a named landing site and a measured reproduction. A generated, published reference page gives two different optionality answers for the same schema, depending on which row the generator happened to render first. This is #8703's family (a defaulted field rendered as required), in a new position: the answer now depends on evaluation order.
reach:reproduced by PR #21463's regeneration,check:generated --fixon head52c4c42d72.content/docs/references/ui/component.mdxmoved as follows:ObjectGridProps.grouping.fields[]now rendersorder?/collapsed?, the input face of defaulted fields.ObjectGridProps.data[provider='api'].read/.writenow rendermethod?.ObjectKanbanProps.grouping, andObjectGanttProps/ObjectMapProps/ObjectTreeProps.data[provider='api']showorder/methodas required.Counts
main→ that head:method?:0 → 2,order?:0 → 1,collapsed?:0 → 1. The PR changed none of those defs. It added aListViewSchemaimport and a.shapeaccess, which changed the generator's evaluation order.The page is published on the docs site. An author or agent reading one row is told
orderis optional, and reading the next is told it is required, for one schema. The artifact still passescheck:generated, because it is the generator's own deterministic output for that import order. Found by the at-tier review of PR #21463 (record5961282483, ① "Generated artifacts", ③.4).Filed by the
domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d, seat post #6017). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.Direction (for triage, not a ruling)
.default()-bearing fields as required (✅) for every output-shape def — authors read "must write" where the schema means "may omit" #8703's settled answer: a defaulted field is "may omit" on an authoring reference. The answer must not come from whichever caller first touched a memoised JSON-Schema conversion,.shapegetter or def cache.Dedupe
mcp__github__search_issues, repo-scoped, open and closed, 「build-docs reference page renders shared schema input face optional order method question mark inconsistent sibling rows component.mdx」: 28 hits, 12 read. #8703 (closed) is the family root, defaulted fields rendered as required. The rest are hand-written docs pages. None covers order-dependent faces.Dedupe words:
reference generator input face output face order-dependent·component.mdx method? order? sibling rows·shared def two faces one page