Repository navigation
[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
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 4, 2026 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:specseat (comment5522566920, carried out of #13928) is graded with this card, as that comment asks: atreefield with noreferenceis still classifiedrelationand still materialisesdeleteBehaviorbesidelookup.The fork
A — a
tree'sreference, 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 tighteninghasDetectableParentField.
B — atree'sreferenceis 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
- ① 项目长远合理性(权重 ≥50%,领起推荐) —— 今天
Maintainer ruling recorded — A: a
treefield'sreference, when present, must equal the declaring object's own name, refused at parse otherwise;hasDetectableParentField'streearm reads it; the one shipped example is corrected; the twoskills/**prose surfaces move in a separate governed PRDirector seat, summon #14, session
session_01LsEjuNMPitCHwEfYftZ1om(GitHubos-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 thetreearm; 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:108points at another object under a hedging label; and the folded fifth reading (5522566920): atreewith noreferenceis stillrelationand materialisesdeleteBehavior.Ruled: A.
FieldSchemarefusestype: 'tree'with areferencenaming any object other than the declaring one (asuperRefineat the object level, where the own name is known; the message names both).referencestays 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 cascadedeleteBehavioris exactly the intended semantics.hasDetectableParentField'streearm is tightened to the same rule. The designer's help text fortreesays "this object" (the form row stays shared; only the text and validation change).showcase_field_zoo.f_treeis 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:speclane, S–M:field.zod.ts/ the object-level refinement,functional-completeness.ts,object.form.tshelp 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 stillrelation, foreign reference refused with the named message, the predicate'streearm.Clause-②: yes⇒needs:contract-review. Changeset:@objectstack/specminor with a BREAKING banner (atreenaming another object is now refused) and an adr-0087 disposition (no-migration-prescription: the remedy is authoring intent — self-reference or alookup); 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:91andrelationships.md:11already 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:specunchanged. Ledger: director seat post #12708, batch #42. Related: #13928 / PR #14890 · ADR-0122 · #9689.
Generated by Claude Code
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
Claim: PM loop round R2 —
domain:specseat,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T15:41Z. One dev, branchclaude/issue-14892-tree-reference-self-only,mode:subagent, tierclaude-fable-5-1. Size S–M.Clause-②: yes
Ruled card, executed as ruled: director ruling
5548738608(2026-09-05T02:27Z; session id corrected in5548740070; maintainer verbatim 「13753 我让别人处理了,其他同意」 to decision batch #42) — A: atreefield'sreference, when present, must equal the declaring object's own name and is refused at parse otherwise (an object-levelsuperRefine, where the own name is known; the message names both);referencestays optional (a redundant self-annotation, which makes the folded fifth reading coherent — a reference-lesstreeis a relation and its cascadedeleteBehavioris the intended semantics);hasDetectableParentField'streearm reads the same rule; the designer help text fortreesays "this object";showcase_field_zoo.f_treecorrected to self-reference; the twocontent/docs/data-modeling/*.mdxpages 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 (FieldSchematree+reference) — the seat runs the in-seat contract review, then hangs and clearsneeds:contract-reviewon both carriers. The twoskills/**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/mainat 15:40Z; see the dispatch order for line anchors):packages/spec/src/data/field.zod.ts/ the object-level refinement indata/object.zod.ts(whichever level knows the own name — the dev measures),packages/spec/src/kernel/functional-completeness.ts(hasDetectableParentFieldtreearm),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 stillrelation—filter-dotted-head.test.tspin kept; foreign reference refused with the named message; the predicate'streearm), regenerated authorable / api-surface shards + reference pages, one@objectstack/specchangeset (minor, launch-window**BREAKING**banner — a published accept set narrows — and the adr-0087 disposition the gate accepts). Hot-file check: none offield.zod.ts,object.zod.ts,functional-completeness.ts,object.form.ts,field-zoo.object.tsis 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:2083carriesTemplateExpressionInputSchema— 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 remaining items
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
Delta review — ACCEPT at
91e253208(domain:spec seat,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T17:50Z; delta report5553638577). One file, one line:packages/platform-objects/src/apps/translations/en.metadata-forms.generated.tsregenerated bycheck-i18n-bundles.mjs --write— the sharedreferencerow's help text, the contract surface unchanged,check:i18nandcheck:i18n-stale-fillgreen,check:generated15/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 onmaingoing green again (#15992).
Generated by Claude Code
- added a commit that references this issue
on Sep 5, 2026 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
Merge-lap delta — ACCEPT at
8d0825976(domain:spec seat,session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T22:13Z; delta addendum5555131618). One merge commit oforigin/main2e3576503(c1e8ad36f, viaos-regen-merge.sh) plus the hook's hand-off commit:content/docs/permissions/system-context.mdxre-anchored bygen:system-context-censuson the merged tree (12/12 lines, the designed collection point);check:generated15/15 current,check:merge-drivergreen, 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 on8d0825976is green.
Generated by Claude Code
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 head8d0825976— every check on that head green (34 success / 7 skipped, none red),mergeable_state: clean; ACCEPT + Clause-② PASS5553493408atb6f0313bb, fix-lap delta ACCEPT5553662417at91e253208, merge-lap delta ACCEPT5555135392at8d0825976;needs:contract-reviewhung and cleared on both carriers at8d0825976(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
Landed (domain:spec seat,
session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T23:57Z): PR #15979 merged 2026-09-05T23:50:36Z asfb77aa541through the merge queue (squash;added_to_merge_queue22:43:37Z, about 67 minutes in queue across two group rebuilds). Probe onorigin/mainat 2026-09-05T23:51Z (tip69602e578):git grep -c refuseForeignTreeReference origin/main -- packages/spec/src/data/object.zod.ts= 3; control 0 atf7db8f4fd. ACCEPT + Clause-② PASS5553493408atb6f0313bb, merge-lap delta ACCEPT5555135392at8d0825976, 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 (detectParentFieldtightening + the four foreign-shapedtreefixtures). 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 byFixes #14892— destination: none (closed); the owed work is on #16083 and objectstack-ai/objectui#7839. Same stroke:pm:dispatchedstripped, assignee cleared.
Generated by Claude Code
- added a commit that references this issue
on Sep 9, 2026
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
treejudgedrelationand materialisingdeleteBehaviormakes sense — still unanswered, still separate).The shape
#13928 settled whether a
treefield'sreferenceis 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 atreefield'sreferencemeans when it is present. Four surfaces answer differently, and none of them is enforced:referenceispackages/spec/src/kernel/functional-completeness.ts:311-318(hasDetectableParentField)treearm isdef.type === 'tree'alone. Thereference === owntest exists only on thelookup/master_detailarm, so atreefield is accepted as this object's parent pointer whatever itsreferencesays, or with none at all. Its docblock states it mirrors objectui'sdetectParentField(packages/plugin-tree/src/ObjectTree.tsx) deliberately, "Mirrored, not tightened".packages/spec/src/data/object.form.ts:191referenceinput fordata.type in ['lookup','master_detail','tree']with the same help text for all three, i.e. any object.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", examplecategory → category),skills/objectstack-data/rules/field-types.md:91("Hierarchical self-reference"),skills/objectstack-data/rules/relationships.md:11("Self-reference")examples/app-showcase/src/data/objects/field-zoo.object.ts:108f_tree: { type: 'tree', label: 'Tree (self/category)', reference: 'showcase_category' }on the objectshowcase_field_zoo(:21). The label hedgesself/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_treeasshowcase_field_zoo's parent pointer while itsreferencenamesshowcase_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.mdwritesreference: 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.mdxreturns zero; the control termtroubleshooting.mdxreturns two real rows, so the grep was live.Refs
#13928 / PR #14890 (the requirement half, settled) ·
hasDetectableParentFielddocblock (the consuming predicate)Generated by Claude Code
Generated by Claude Code