Skip to content

docs(content): a tree field does not require reference — two pages said it did - #14890

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-13928-troubleshooting-tree-reference-line
Sep 3, 2026
Merged

os-sam merged 1 commit into
mainfrom
claude/issue-13928-troubleshooting-tree-reference-line

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #13928

Two docs cells said a tree field requires a reference. Nothing enforces that, and the spec's own tests construct a reference-less tree and assert defined behaviour for it. Both cells now say what the other surfaces already said.

Before / after

content/docs/deployment/troubleshooting.mdx:78 — the card's line:

-**Symptom:** A `lookup`, `master_detail`, or `tree` field fails validation.
+**Symptom:** A `lookup` or `master_detail` field fails validation.

:80 ("Relational fields require a reference property pointing to the target object.") is untouched — it stays true for the two remaining types.

content/docs/data-modeling/validation-rules.mdx:542 — the second page, see "Second page" below:

-| `tree` | `reference` | Self-referencing; no automatic cycle check |
+| `tree` | — | Self-referencing; no automatic cycle check |

That table's second column header is Required Props (:517), so the old cell was the same claim in table form. The em dash matches the user row directly above it (:540) — likewise a reference-carrying type with no required prop — and is the same U+2014 byte the rest of the column uses.

Why — the triage census, quoted

Triage comment 5486903425 ran the card's census and tabulated four surfaces, three agreeing and one outlier. Quoted verbatim, untranslated:

现有四个面里,三个已经一致,文档是唯一的异类:

面 对「tree 需要 reference」的态度
field.zod.ts:1064-1065 (reference 字段自己的 docblock) 「required for these types」只点名 lookup / master_detail
field.zod.ts:1073 (describe) 「Target object name … for lookup/master_detail fields」
lint RELATIONSHIP_TYPES {lookup, master_detail}
troubleshooting.mdx:78 lookup, master_detail, or tree ← 唯一异类

⇒ 修复 = 把 :78 的 tree 去掉。

All three agreeing surfaces re-read on origin/main 2263ca4d6 before editing:

  • packages/spec/src/data/field.zod.ts:1064-1065 — "Used by lookup and master_detail field types … The reference property is required for these types … the superRefine below enforces it".
  • packages/spec/src/data/field.zod.ts:1073 — .describe('Target object name (snake_case) for lookup/master_detail fields. …').
  • packages/lint/src/data-model-rules.ts:103 — const RELATIONSHIP_TYPES = new Set(['lookup', 'master_detail']);, consumed at :499 and :577 by relationship/missing-reference. tree is not in the set.

And the two readings that make "required" and "default self-reference" untenable rather than merely unenforced:

  • packages/spec/src/data/field-value.test.ts:69 and packages/spec/src/data/filter-dotted-head.test.ts:31 construct a tree with no reference and assert defined behaviour, so a gate demanding one would red the spec's own test surface.
  • examples/app-showcase/src/data/objects/field-zoo.object.ts:108 — f_tree: { type: 'tree', reference: 'showcase_category' } points at another object, so "defaults to self" is wrong for a shipped example.

Second page: validation-rules.mdx:542

The dispatch's H2 anticipated a second page in the same sentence family and allowed fixing it here only if it is the same one-line claim. It is: the Required Props cell for tree, stating the identical requirement in a table instead of a sentence. The PM's H2 grep missed it because the row carries no "require" wording of its own — the requirement lives in the column header.

Four conditions for taking it in place, measured rather than assumed:

  1. Same defect class — byte-for-byte the same claim, falsified by the same census.
  2. Mechanical, pinned by landed evidence — the correct cell is fixed by the sibling user row and by the three agreeing surfaces above. No judgement call was left open.
  3. No other claim holds the file — every one of the 60 open PRs was scanned by file list (REST GET /pulls/{n}/files, 60/60 readable, 0 hits on either path). Nearest neighbour is PR perf(datasource): size the primary SQL pool from OS_DATABASE_POOL_MAX #14776, which touches a different content/docs/deployment/ page. Neither page has churned on main since b1581b044.
  4. Same gate family, no new verification surface — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands re-derived at HEAD returns the same 29 commands for the two-path change set that the PM derived for the one-path change set. The second page added no family.

H2 control grep

The zero was verified with a control before it was believed. Control: master_detail across content/docs/** + skills/** returns 40 files, including skills/objectstack-data/SKILL.md — so the grep surface really covers both trees and a zero there means something.

Sweeping every table row keyed on `tree` across both trees returns six rows. Only validation-rules.mdx:542 asserts the requirement. The two skills/** rows are left alone and nothing governed is touched:

row column it sits under verdict
content/docs/data-modeling/validation-rules.mdx:542 Required Props the same claim — fixed here
skills/objectstack-data/rules/field-types.md:91 Key Config true as written — reference is the relevant knob, not a requirement
skills/objectstack-data/rules/relationships.md:11 Required already reads Optional — agrees with the status quo
content/docs/data-modeling/field-type-decision-tree.mdx:176 When to Use no requirement claim
content/docs/ui/views.mdx:92 view kinds, not field types unrelated
skills/objectstack-ui/SKILL.md:48 view kinds, not field types unrelated

A "require / must have / mandatory / missing" sweep near `tree` across both trees returns nothing relevant either.

Gates

Re-derived on the actual changed paths at the final commit — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, which takes its own change set from the merge base (2263ca4d6, three-dot) and confirmed --repo against this checkout's origin. 29 commands, all run at 5df44f134, exit code captured before any pipe:

  • 28 green. Among their own verdict lines: check-doc-frontmatter — "2 content root(s) verified … content/docs 404, content/blog 3"; check-docs-single-h1 — "404 page(s) under content/docs/ carry no body-level # heading"; check-doc-anchors — "295 internal #fragment link(s) across 409 source file(s) all resolve to a real heading"; check:docs — "229 generated files in sync with packages/spec"; check:doc-security-posture — "27 ObjectSchema.create example(s) in 226 marked block(s) across 236 prose file(s) in 2 root(s) carry an os validate-clean security posture"; check:skill-examples — "256 prose examples type-check across 3 surface(s)"; check:doc-authoring — "393 files clean".
  • 1 NOT MEASURED, not a red: node scripts/check-test-completeness.mjs exits 3 with "PREREQUISITE NOT MET — this gate grades a saved turbo run test log, and no log was named", and its own text prescribes the reading: "running the family locally, record this gate as NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix." CI tees the log and passes the path, so CI is unaffected.

Four gates first came back prerequisite-blocked on unbuilt packages (check:doc-formula-expressions, check:doc-security-posture, check:docs, check:skill-examples) rather than being reported as NOT MEASURED: @objectstack/lint, @objectstack/formula, @objectstack/spec and @objectstack/client-react were built under the shared verify lock (scripts/pm/os-verify-lock.sh, VERDICT command-exit 0 on both runs) and all four then measured green.

Changeset

None, deliberately — skip-changeset is on this PR. The rule is "publishes nothing from any released package", and this diff is two content/docs/** cells:

  • apps/docs, the only workspace that consumes content/docs, is private: true.
  • No released package ships content/docs: the sole package.json in packages/ that mentions the path is plugin-webhooks, and the mention is prose inside its description; its files is ["dist","README.md","CHANGELOG.md"].
  • Repo precedent agrees — the seven most recent content/docs-only commits on main (89a156af8, db7f3e492, 365a740a8, a69dfde80, 4485f7dad, 63fb473a9, 167bcb2e1) each carry zero .changeset/ files.

The Check Changeset job's own exemption text names the label as "the author's explicit opt-out", meaning "this PR declares no release of its own" — which is what this is.

Deliberately not in this PR

  • ⛔ packages/lint (RELATIONSHIP_TYPES) — unchanged; it is one of the three surfaces this PR converges the docs onto.
  • ⛔ packages/spec/src/data/field.zod.ts — unchanged; two other cards are writing that file this round.
  • ⛔ Triage's fenced residual — whether a reference-less tree being judged relation and materialising deleteBehavior makes sense — is not answered here. This docs fix holds whichever way that lands. Method precedent for the census: [spec] FieldSchema accepts a lookup/master_detail with no reference target, though its own TSDoc calls the key required #13632 / PR fix(spec): require a non-empty reference on lookup/master_detail fields #13927.
  • The Self-referencing descriptor in the same validation-rules.mdx row is a different claim from the requirement one and is left as-is; it is reported to the PM as an out-of-scope finding, since field-zoo.object.ts:108 makes "self-referencing" over-narrow rather than false, and the wording is spread over five surfaces including two governed skills/** files.

🤖 Generated with Claude Code

https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE


Generated by Claude Code

…s said it did

`reference` is required for `lookup` and `master_detail` only. Three surfaces
already agree on that and two docs pages disagreed with all three:

- `packages/spec/src/data/field.zod.ts:1064-1065` (the `reference` docblock)
  names `lookup` and `master_detail` as the types the superRefine enforces it for.
- `packages/spec/src/data/field.zod.ts:1073` (`.describe()`) says "Target object
  name (snake_case) for lookup/master_detail fields".
- `packages/lint/src/data-model-rules.ts:103` —
  `RELATIONSHIP_TYPES = new Set(['lookup', 'master_detail'])`, consumed at :499
  and :577 by `relationship/missing-reference`. `tree` is not in the set.

A reference-less `tree` is a shape the spec's own tests construct and assert
defined behaviour for — `field-value.test.ts:69` (`referenceTargetOf` returns
undefined) and `filter-dotted-head.test.ts:31` (still classified `relation`) —
so no gate rejects one and none should: the docs were asserting a requirement
nothing enforces. A default-self-reference reading is falsified too, by
`examples/app-showcase/src/data/objects/field-zoo.object.ts:108`, where a `tree`
field points at `showcase_category` rather than at its own object.

Two cells, both stating the same claim:

- `content/docs/deployment/troubleshooting.mdx:78` drops `tree` from the symptom
  line. The Cause line at :80 stays true for the two remaining types.
- `content/docs/data-modeling/validation-rules.mdx:542` — the "Quick Validation
  Summary" table's second column is `Required Props` (:517), and the `tree` row
  listed `reference` there. It becomes an em dash, matching the `user` row beside
  it, which is likewise a reference-carrying type with no required prop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RbbUMnxkUnWhE4j94v8FE
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
@github-actions github-actions Bot added size/xs documentation Improvements or additions to documentation labels Sep 3, 2026
Merged via the queue into main with commit 3ad5680 Sep 3, 2026
37 checks passed
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 size/xs skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant