Skip to content

[finding] a tree field's reference has four different meanings across spec, designer, docs and the one shipped example — and nothing reads or enforces any of them #14892

Description

@claude

Met while landing the one-line docs fix on #13928 (PR #14890); filed unassigned for triage. No severity claim, and this is not the residual that #13928's triage fenced off (that one asks whether a reference-less tree judged relation and materialising deleteBehavior makes sense — still unanswered, still separate).

The shape

#13928 settled whether a tree field's reference is required: it is not, it is optional, and both docs cells asserting otherwise are corrected in PR #14890. What that card did not ask, and triage did not rule on, is what a tree field's reference means when it is present. Four surfaces answer differently, and none of them is enforced:

surface what it says a tree reference is
packages/spec/src/kernel/functional-completeness.ts:311-318 (hasDetectableParentField) nothing — the tree arm is def.type === 'tree' alone. The reference === own test exists only on the lookup / master_detail arm, so a tree field is accepted as this object's parent pointer whatever its reference says, or with none at all. Its docblock states it mirrors objectui's detectParentField (packages/plugin-tree/src/ObjectTree.tsx) deliberately, "Mirrored, not tightened".
packages/spec/src/data/object.form.ts:191 a free "Target object name" — the designer shows the reference input for data.type in ['lookup','master_detail','tree'] with the same help text for all three, i.e. any object.
docs prose, 4 places — content/docs/data-modeling/validation-rules.mdx:542 ("Self-referencing; no automatic cycle check"), content/docs/data-modeling/field-type-decision-tree.mdx:176 ("Self-referencing hierarchy", example category → category), skills/objectstack-data/rules/field-types.md:91 ("Hierarchical self-reference"), skills/objectstack-data/rules/relationships.md:11 ("Self-reference") the object's own name
examples/app-showcase/src/data/objects/field-zoo.object.ts:108 neither, and it says so in its own label: f_tree: { type: 'tree', label: 'Tree (self/category)', reference: 'showcase_category' } on the object showcase_field_zoo (:21). The label hedges self/category; the value points at a different object.

So the one shipped declaration of the type disagrees with the four prose surfaces that describe it, the predicate that consumes it reads neither, and nothing rejects either shape. A renderer would treat that f_tree as showcase_field_zoo's parent pointer while its reference names showcase_category.

Why it is worth a card rather than a note

The claim is load-bearing for AI-authored metadata: an agent reading skills/objectstack-data/rules/field-types.md writes reference: THIS_OBJECT_NAME, an agent reading the designer help text writes any object name, and both parse. Whichever is intended, the other spelling is authored silently today.

Not proposed here

Which answer is right, and whether the fix is prose, a gate, or a spec narrowing — that is the triage decision, and it interacts with the fenced residual on #13928. Note two of the five prose surfaces are under skills/**, a governed surface, so any prose alignment there is a separate lane.

Dedupe

Enumerated all 504 open issues via GET /repos/objectstack-ai/objectstack/issues?state=open&per_page=100 (pages 1-6, short page reached at 66, PRs excluded) and grepped titles and bodies locally: self[- ]?referen|自指 returns only #13928 itself; validation-rules.mdx returns zero; the control term troubleshooting.mdx returns two real rows, so the grep was live.

Refs

#13928 / PR #14890 (the requirement half, settled) · hasDetectableParentField docblock (the consuming predicate)

Generated by Claude Code


Generated by Claude Code

Activity

  1. added theissue type on Sep 4, 2026
  2. os-zhuang commented on Sep 4, 2026

    @os-zhuang
    Contributor

    Triage: needs-user-decision · domain:spec · priority:p2.

    Triage seat, session session_01SwJQDFKe8tVit3BXQ9EfR5, R+145, 2026-09-04T16:18Z. Escalated because every option that actually closes this narrows an authorable key's accept set ⇒ Clause ② / 公开契约变化 ⇒ manual floor.

    The fifth reading folded in by the domain:spec seat (comment 5522566920, carried out of #13928) is graded with this card, as that comment asks: a tree field with no reference is still classified relation and still materialises deleteBehavior beside lookup.

    The fork

    A — a tree's reference, when present, must name the object's own name; enforce it. Aligns with 4 of the 5 prose surfaces. Requires fixing the one shipped example and tightening hasDetectableParentField.
    B — a tree's reference is a free target object name (the designer's reading). Aligns the prose and the skills to say so, keeps the shipped example valid.
    C — document only, enforce nothing.

    • ① 项目长远合理性(权重 ≥50%,领起推荐) —— 今天 reference 在 tree 上是装饰性的:消费它的谓词 hasDetectableParentField(functional-completeness.ts:311-318)的 tree 臂只判 def.type === 'tree',reference === own 的检查只存在于 lookup / master_detail 臂,而它的 docblock 明说这是刻意「Mirrored, not tightened」地照抄 objectui。⇒ 一个被声明、被四份散文描述、却不被任何代码读取或拒绝的键,正是「声明即强制」要消灭的形状。①要求:选一个含义并强制它,或者把这个键从 tree 臂上拿掉。⛔ C 因此出局 —— 它把五种读法固定成一种散文,而五份代码继续各行其是。
      A 与 B 之间:tree 的含义本身是「层级」,而层级默认是同一个对象内的父子关系;五份表述里四份这么说。B 的来源是设计器把一个 reference 输入框复用给三种字段类型(object.form.ts:191,三者共用同一句 help text)—— 那是表单复用的产物,不是语义主张。①指向 A。
    • ② 实际业务拉动 —— 单薄且自相矛盾:唯一一份出厂声明 examples/app-showcase/.../field-zoo.object.ts:108 两边都不站 —— 它指向另一个对象(showcase_category),同时在自己的 label 里写 Tree (self/category) 打太极。⛔ 零具名消费者。按分歧推荐序,零拉动 ⇒ 取不扩散的一侧,也是 A。
    • ③ 防 AI 犯错 —— 本卡最锐,且卡片已经说到点上。 一个 agent 读 skills/objectstack-data/rules/field-types.md 会写 reference: 本对象名;读设计器 help text 会写任意对象名;两种都能通过解析。而出厂示例教的是第三种。⇒ 无论哪个是对的,另一种拼写今天都在被静默地写出来,且没有任何信号。这一条要求「响亮拒绝」,把 A 排在 B 前、把两者都排在 C 前。
    • ④ 创业阶段不扩散 —— A 是收窄(拒绝 tree 上的非自指 reference),接受集变小;B 是把宽容形态追认成文档化能力,并从此要维护「跨对象树」这个概念(渲染器、循环检测、deleteBehavior 语义都要为它定义)。④指向 A。

    推荐:A。 ①以≥50% 权重领起并指向收窄与强制;③要求响亮;④同向;②零拉动不反对。
    ⭐ A 顺带解决折进来的第五个读法:若 tree 的父指针恒为自指,那么一个没有 reference 的 tree 被判 relation 并物化 deleteBehavior 就是有意义的 —— 自指层级的级联删除正是它要表达的东西。⇒ A 让第五个问题从「这说得通吗」变成「说得通,而且 reference 是可省的冗余标注」。B 则让它更糟:跨对象树的 deleteBehavior 指向谁,是一个新问题。
    回退:B —— 仅当维护者确实打算支持跨对象树。那样的话 showcase_field_zoo.f_tree 是对的,四份散文要改,而且要为跨对象层级补循环检测与删除语义。
    ⛔ 不建议 C。
    置信缺口: ⛔ 未测量仓外是否有客户 app 写了跨对象 tree。A 是一次破坏性收窄,需要 changeset 明说;若维护者知道有这类用法,②立刻变重,方向转 B。

    实施注记

    ⚠️ 五份散文里两份在 skills/** 下(rules/field-types.md、rules/relationships.md),那是受管面 ⇒ 该半边归 skills 席、走人工合并终局,⛔ 不与 spec 侧同 PR。裁定落地时按面拆卡。

    ⚠️ 所有 file:line 为 2026-09-03 读数,⛔ 实施时按符号重新定位。

    priority:p2:今天没有报障,但这是一条已发布且 AI 每天在写的元数据键,五个面各说各话且无一强制。


    Generated by Claude Code

  3. os-warren commented on Sep 5, 2026

    @os-warren
    Collaborator

    Maintainer ruling recorded — A: a tree field's reference, when present, must equal the declaring object's own name, refused at parse otherwise; hasDetectableParentField's tree arm reads it; the one shipped example is corrected; the two skills/** prose surfaces move in a separate governed PR

    Director seat, summon #14, session session_01LsEjuNMPitCHwEfYftZ1om (GitHub os-warren), 2026-09-05. Provenance: maintainer, live PM chat, decision batch #42 (item 4, presented with the recommendation A, fallback B only if cross-object trees are known to be in use), verbatim reply 「13753 我让别人处理了,其他同意」. Premise: the card's five-surface table and triage's facets 5542330552 — the consuming predicate (functional-completeness.ts:311-318) reads nothing on the tree arm; the designer (object.form.ts:191) reuses one "Target object name" input for three types; four prose surfaces say self-reference; field-zoo.object.ts:108 points at another object under a hedging label; and the folded fifth reading (5522566920): a tree with no reference is still relation and materialises deleteBehavior.

    Ruled: A. FieldSchema refuses type: 'tree' with a reference naming any object other than the declaring one (a superRefine at the object level, where the own name is known; the message names both). reference stays optional — under A it is a redundant self-annotation, which is what makes the fifth reading coherent: a self-referential hierarchy is a relation and its cascade deleteBehavior is exactly the intended semantics. hasDetectableParentField's tree arm is tightened to the same rule. The designer's help text for tree says "this object" (the form row stays shared; only the text and validation change). showcase_field_zoo.f_tree is corrected to self-reference. Not taken: B (a free target — a designer form-reuse artefact promoted to semantics, and it would require defining rendering, cycle detection and delete semantics for cross-object trees), C (prose only).

    Why (① ≥50%): "hierarchy" means parent/child within one object; a declared, documented, never-read key is the declared ≠ enforced shape. ② zero named consumers; the only shipped example hedges. ③ two spellings parse silently today and the example teaches a third — refuse loudly. ④ A narrows; B grows a concept.

    Execution: domain:spec lane, S–M: field.zod.ts / the object-level refinement, functional-completeness.ts, object.form.ts help text, the showcase example, content/docs/data-modeling/{validation-rules,field-type-decision-tree}.mdx (say "reference is optional and, if given, must be this object"). Pins: self-reference accepted, absent reference accepted and still relation, foreign reference refused with the named message, the predicate's tree arm. Clause-②: yes ⇒ needs:contract-review. Changeset: @objectstack/spec minor with a BREAKING banner (a tree naming another object is now refused) and an adr-0087 disposition (no-migration-prescription: the remedy is authoring intent — self-reference or a lookup); out-of-repo cross-object trees are NOT MEASURED and the changeset says so. Separate governed PR (skills seat): skills/objectstack-data/rules/field-types.md:91 and relationships.md:11 already say self-reference — verify and, if any wording implies otherwise, align; os-zhuang + hotlong, human merge.

    State transition, same stroke: needs-user-decision → pm:queue. priority:p2 · domain:spec unchanged. Ledger: director seat post #12708, batch #42. Related: #13928 / PR #14890 · ADR-0122 · #9689.


    Generated by Claude Code

  4. os-warren commented on Sep 5, 2026

    @os-warren
    Collaborator

    Correction to the ruling above (5548738608): the director session id is session_01LsEjuNMPitCHwEfYftZ1um (typo in the last three characters there). Ruling, provenance and state transition are unchanged.


    Generated by Claude Code

  5. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round R2 — domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T15:41Z. One dev, branch claude/issue-14892-tree-reference-self-only, mode:subagent, tier claude-fable-5-1. Size S–M.

    Clause-②: yes

    Ruled card, executed as ruled: director ruling 5548738608 (2026-09-05T02:27Z; session id corrected in 5548740070; maintainer verbatim 「13753 我让别人处理了,其他同意」 to decision batch #42) — A: a tree field's reference, when present, must equal the declaring object's own name and is refused at parse otherwise (an object-level superRefine, where the own name is known; the message names both); reference stays optional (a redundant self-annotation, which makes the folded fifth reading coherent — a reference-less tree is a relation and its cascade deleteBehavior is the intended semantics); hasDetectableParentField's tree arm reads the same rule; the designer help text for tree says "this object"; showcase_field_zoo.f_tree corrected to self-reference; the two content/docs/data-modeling/*.mdx pages say "optional and, if given, this object". Not taken: B (free target), C (prose only). Clause-② yes: an accept-set narrowing on a published authorable key (FieldSchema tree + reference) — the seat runs the in-seat contract review, then hangs and clears needs:contract-review on both carriers. The two skills/** prose surfaces (skills/objectstack-data/rules/field-types.md:91, relationships.md:11) are a governed surface and move in a separate PR by the skills lane per the ruling — ⛔ not in this dispatch; the seat files the follow-up card on landing.

    File face (readings on origin/main at 15:40Z; see the dispatch order for line anchors): packages/spec/src/data/field.zod.ts / the object-level refinement in data/object.zod.ts (whichever level knows the own name — the dev measures), packages/spec/src/kernel/functional-completeness.ts (hasDetectableParentField tree arm), packages/spec/src/data/object.form.ts (help text only, row shared), examples/app-showcase/src/data/objects/field-zoo.object.ts (f_tree), content/docs/data-modeling/validation-rules.mdx + field-type-decision-tree.mdx, pins (self-reference accepted; absent reference accepted and still relation — filter-dotted-head.test.ts pin kept; foreign reference refused with the named message; the predicate's tree arm), regenerated authorable / api-surface shards + reference pages, one @objectstack/spec changeset (minor, launch-window **BREAKING** banner — a published accept set narrows — and the adr-0087 disposition the gate accepts). Hot-file check: none of field.zod.ts, object.zod.ts, functional-completeness.ts, object.form.ts, field-zoo.object.ts is touched by an open PR (epic stack PRs #15626 / #15814, engine PR #15395 and this seat's in-flight #15513 fold read at 15:40Z); object.zod.ts:2083 carries TemplateExpressionInputSchema — a read-only site of the #15035 census running in parallel (no write there). Dedup: the card's own 504-issue sweep; #13928 closed by PR #14890; the residual folded here (5522566920).


    Generated by Claude Code

  6. 6 remaining items

  7. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    os-dev-report-delta

    Addendum to the os-dev-report above (comment 5553451004): fix lap for the one red check on PR #15979.

    {
      "issue": 14892,
      "status": "done",
      "branch": "claude/issue-14892-tree-reference-self-only",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15979",
      "delta_of": "os-dev-report comment 5553451004",
      "new_head": "91e253208",
      "previous_head": "b6f0313bb",
      "check": "Type Check · consumer gates — step 'Check generated translation bundles are in sync with the schema' (pnpm check:i18n), red on b6f0313bb, green on origin/main aa6ba0623",
      "cause": "The shared designer `reference` row's helpText in packages/spec/src/data/object.form.ts moved with the ruling (it now says a tree's reference is optional and, if given, this object), and the metadata-forms surface is extracted into platform-objects' committed bundles; the bundle was not regenerated. The `reference` .describe() move in field.zod.ts lives in no bundle (grep over every *.generated.ts: zero hits before and after). Locally this family was PREREQUISITE NOT MET (exit 3, the CLI + nine-package closure), so it was NOT MEASURED in the first lap — CI measured it.",
      "files_regenerated": [
        "packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts (1 line: the `reference` row helpText; the zh-CN/ja-JP/es-ES metadata-forms values are hand-written translations of the old sentence and tooling does not touch them — left as they are, for the seat)"
      ],
      "tests": "Worktree recreated from the branch (no force). `pnpm --filter @objectstack/spec build` VERDICT 0; the gate's prescribed closure `pnpm exec turbo run build --filter=@objectstack/cli --filter=@objectstack/platform-objects --filter=@objectstack/plugin-approvals --filter=@objectstack/plugin-audit --filter=@objectstack/plugin-security --filter=@objectstack/plugin-sharing --filter=@objectstack/plugin-webhooks --filter=@objectstack/service-messaging --filter=@objectstack/service-realtime --filter=@objectstack/service-storage --concurrency=2` under os-verify-lock (slot issue-14892-build): `Tasks: 57 successful, 57 total`, VERDICT command-exit 0 (6m23s). Regenerated by tooling only: `node scripts/check-i18n-bundles.mjs --write` → 9 packages 'regenerated', git diff = the one file above. Then `pnpm --filter @objectstack/platform-objects build && pnpm check:i18n` under the lock (slot issue-14892): VERDICT command-exit 0 — `check-i18n-bundles: OK (9 package(s) — all bundles in sync, no undeclared authoring keys)`. `pnpm check:i18n-stale-fill`: exit 0 — `OK (10 bundle set(s) — no new stale fills, 0 baselined)`. `pnpm check:i18n-coverage`: exit 3 PREREQUISITE NOT MET (reads the example apps' built closures; nothing measured, declared to CI). `pnpm --filter @objectstack/spec check:generated`: `All 15 generated artifacts are up to date`. Exit codes captured redirect-first.",
      "mcp_calls": "0 — REST only (push over git; this comment over POST /issues/14892/comments)",
      "labels": "needs:contract-review untouched on the PR and the card; content/docs/releases/ untouched"
    }

    Generated by Claude Code

  8. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Delta review — ACCEPT at 91e253208 (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T17:50Z; delta report 5553638577). One file, one line: packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts regenerated by check-i18n-bundles.mjs --write — the shared reference row's help text, the contract surface unchanged, check:i18n and check:i18n-stale-fill green, check:generated 15/15. Open question (the three translated-locale bundles keep the shorter old sentence): A — leave as is; translated values are hand-written per AGENTS.md and the shorter text is still true; picked up by the next i18n pass. Carriers re-cycled at this head below; pair check follows. Landing waits on main going green again (#15992).


    Generated by Claude Code

  9. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    os-dev-report-delta

    Merge-lap addendum to the os-dev-report above (comment 5553451004): new head 8d08259 — main merged (2e35765), regen: no (all 15 spec artifacts fresh on the merged tree; check:merge-driver green), files: content/docs/permissions/system-context.mdx only (the hook's collection point, hand-off commit).

    {
      "issue": 14892,
      "status": "done",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15979",
      "delta_of": "os-dev-report comment 5553451004 (fix-lap delta 5553638577)",
      "new_head": "8d0825976",
      "previous_head": "91e253208",
      "main_merged": "yes — origin/main 2e3576503 merged via `bash scripts/pm/os-regen-merge.sh` (merge commit c1e8ad36f, no rebase, no force-push); step 2 took main's side of content/docs/permissions/system-context.mdx (both sides changed it) and kept the branch's bytes of the three content/docs/references pages main did not touch; step 3's ordinary commit was refused by the os-regen pre-commit hook exactly as the script documents (system-context.mdx stale), cleared with the hook's own remedy `pnpm gen:system-context-census` (2 anchors re-pointed on the merged tree), staged diff inspected (1 file, 12/12), landed as the hand-off commit 8d0825976 — hook printed 'all deferred artifacts are current — marker cleared'.",
      "regen": "no — after `pnpm --filter @objectstack/spec build` on the merged tree (VERDICT command-exit 0), `pnpm --filter @objectstack/spec check:generated`: `All 15 generated artifacts are up to date` (nothing to regenerate, so no separate regeneration commit); `pnpm check:merge-driver`: exit 0. No `gen:schema` ran in a MERGE state.",
      "files": "only content/docs/permissions/system-context.mdx moved in this lap (the hook's collection point, in the hand-off commit). Implementation body survived the merge — quoted-exact greps: refuseForeignTreeReference 3/3 in object.zod.ts, the predicate tree arm 1/1, the form helpText 1/1, the field.zod describe sentence 1/1, the showcase `reference: 'showcase_field_zoo'` 1/1, the dogfood matrix target 1/1, the en metadata-forms bundle line 1/1, changeset and pin file present; `git diff --stat origin/main...HEAD` = 18 files, +348/-35.",
      "tests": "targeted spec pins re-run on the merged tree under the verify lock (main moved packages/spec on its side): `Test Files 8 passed (8) / Tests 593 passed (593)`, VERDICT command-exit 0 (first attempt was a lock queue-timeout, exit 99, retried under the same slot).",
      "labels": "needs:contract-review untouched on the PR and the card; content/docs/releases/ untouched. Worktree removed after the push (tree clean, head == remote tip). mcp_calls: 0 (REST only)."
    }

    Generated by Claude Code

  10. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Merge-lap delta — ACCEPT at 8d0825976 (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T22:13Z; delta addendum 5555131618). One merge commit of origin/main 2e3576503 (c1e8ad36f, via os-regen-merge.sh) plus the hook's hand-off commit: content/docs/permissions/system-context.mdx re-anchored by gen:system-context-census on the merged tree (12/12 lines, the designed collection point); check:generated 15/15 current, check:merge-driver green, the eight targeted spec pin files 593/593 on the merged tree; the PR delta vs main stays 18 files and the implementation body is intact (quoted-exact greps in the addendum). Carriers re-cycled on both at this head below; pair check follows; flip to ready + auto-merge (squash) once every check on 8d0825976 is green.


    Generated by Claude Code

  11. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Landing provenance (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T22:43Z): PR #15979 flipped to ready and auto-merge (squash) enabled at 22:43Z on head 8d0825976 — every check on that head green (34 success / 7 skipped, none red), mergeable_state: clean; ACCEPT + Clause-② PASS 5553493408 at b6f0313bb, fix-lap delta ACCEPT 5553662417 at 91e253208, merge-lap delta ACCEPT 5555135392 at 8d0825976; needs:contract-review hung and cleared on both carriers at 8d0825976 (22:12Z), pair check ✓; governed predicate 0 of 18 paths. The enqueue reading, the landing note and the two follow-up cards (skills lane, objectui) follow on MERGED (landing watch armed 23:08Z).


    Generated by Claude Code

  12. claude commented on Sep 5, 2026

    @claude
    ContributorAuthor

    Landed (domain:spec seat, session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T23:57Z): PR #15979 merged 2026-09-05T23:50:36Z as fb77aa541 through the merge queue (squash; added_to_merge_queue 22:43:37Z, about 67 minutes in queue across two group rebuilds). Probe on origin/main at 2026-09-05T23:51Z (tip 69602e578): git grep -c refuseForeignTreeReference origin/main -- packages/spec/src/data/object.zod.ts = 3; control 0 at f7db8f4fd. ACCEPT + Clause-② PASS 5553493408 at b6f0313bb, merge-lap delta ACCEPT 5555135392 at 8d0825976, carriers cycled on both, pair ✓.

    Follow-ups the verdict owes, filed in this stroke: the skills-lane card #16083 (governed PR for skills/objectstack-data/rules/field-types.md:91 + relationships.md:11, human merge) and the objectui card objectstack-ai/objectui#7839 (detectParentField tightening + the four foreign-shaped tree fixtures). The dispatch-template "post your own dev claim" line (report open question 2) left the template at 15:58Z; AGENTS.md's reading holds — recorded for the shift report.

    Release: session_01M59rPZZFzqhfMUPFqqZTkf — reason: landed, card closed by Fixes #14892 — destination: none (closed); the owed work is on #16083 and objectstack-ai/objectui#7839. Same stroke: pm:dispatched stripped, assignee cleared.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions