Skip to content

skills(objectstack-data): the tree rows state the landed reference contract - #16187

Draft
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-16083-tree-reference-rows
Draft

skills(objectstack-data): the tree rows state the landed reference contract#16187
claude[bot] wants to merge 1 commit into
mainfrom
claude/issue-16083-tree-reference-rows

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #16083

refuseForeignTreeReference (packages/spec/src/data/object.zod.ts, on main since fb77aa541) makes a tree field's reference optional, and — when written — equal to the declaring object's own name (the extend target on an extension). Any other object is refused at parse with one custom issue at fields.FIELD.reference. The published objectstack-data skill still listed reference as a bare key prop, so a client agent loading the skill had no way to know either half of the rule. content/docs already says it; these two rows were the last surface that did not.

Two rows, no new section

File Before After
rules/field-types.md (Relational table) key config was just `reference` `reference` — optional; when written, this object's own name (any other object is refused at parse)
rules/relationships.md (Relationship Types table) the Required cell was a bare Optional says what is optional, and contrasts "refused at parse" with the master_detail row's "lint-warned" in that same column

Wording is taken from the refusal message and from the already-landed content/docs sentences (validation-rules.mdx:297, field-type-decision-tree.mdx:183), not re-invented — the card's acceptance asks for the words the spec's refusal uses.

Unchanged on purpose: the Hierarchical: tree (Self-Reference) example. It already writes the object's own name (reference: 'category' on object category), which is all the ruling asks of it. I drafted a clarifying comment on that line and then dropped it: the row three screens up in the same file now states the rule, so the comment would have cost 28 bytes without changing what a client agent writes.

Ratchet accounting — and the deletion that paid for it

The token ratchet left field-types.md 27 bytes under its ceiling, and the shortest faithful clause for that row is ~88 bytes. The deletion is therefore compelled by the ratchet, not elective tidying.

What was spent: the Media section's two-line note "There is no per-field attachment config (size limits, allowed types, storage) — storage concerns live outside the field schema." It restates the top-of-file blockquote, which sits above every table and already refuses the exact key by name:

Per-type display knobs beyond these do not exist — an unknown field key is REFUSED at parse (unrecognized_keys), so don't invent theme, rows, or fileAttachmentConfig.

grep confirms the note appears nowhere else in skills/. Removing it changes nothing a client agent writes; the operative refusal and the exact invented key both survive.

File tokens before tokens after ceiling headroom now
rules/field-types.md 3578 3567 3584 17
rules/relationships.md 3490 3522 3778 256

field-types.md shrinks. The package moves +21 tokens net (140376 → 140397 of a 157650 ceiling) — stated plainly rather than rounded away, and paid for inside the two files the ruling names.

Gates — union re-run on the final commit 5a30844272

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 20 families from the real changeset; all 20 run, reconciled with --ran:

✓ dispatch-gates --ran: 20 derived famil(ies) accounted for — 20 run, 0 NOT-MEASURED.

Every exit code was captured by redirecting first, never through a pipe.

  • ✓ check-skills-token-ratchet: 36 authored bundle file(s) within their ceilings — plus ✓ … self-test: 64 cases pass.
  • check-nul-bytes: OK (scanned 7932 text file(s) … no raw ASCII control bytes)
  • whole-repo pnpm lint (eslint . --no-inline-config) — exit 0, not narrowed. It ran on this commit's exact tree (git diff HEAD and git status --porcelain both empty since).
  • check:pm-governed-merges, check:skill-frame-sync, check:skill-compatibility, check:skill-identifier-liveness, check:doc-authoring, check-published-list-mirrors (+ self-test) — all exit 0.
  • check-governed-merges --test on both paths — exit 3, GOVERNED, as expected: skills/** ×2 — the published skills catalog.
  • One gate first came back PREREQUISITE NOT MET (exit 3, nothing measured) because @objectstack/formula was unbuilt; after turbo run build, check:doc-formula-expressions is exit 0.

Two gates named in the brief, check-doc-snippet-types and check-skill-examples, do not exist in this repo — no script, no package.json entry. check-skill-id-lint exists but its corpus is .claude/skills/pm-dispatch plus .claude/agents/os-dev.md and AGENTS.md; the published skills/** roots are not in it, so it does not read these files. Run anyway: exit 0.

No changeset, and skip-changeset is applied: skills/ is not a workspace package and no package.json files array names it, so this diff releases nothing. Six landed skills/**-only commits carry zero changesets.

维护者速读(草稿)

发布给客户 agent 的 objectstack-data 技能里,tree 字段的 reference 一直只是裸列的一个键。契约那半边早已落地——写别的对象在 parse 就被拒——但技能文本没说。客户拿我们的 AI 写元数据 app 时,读到的是「tree 要配 reference」,于是写出指向另一个对象的 tree,一路到 publish 才被拒。这是沉默造成的返工,不是错误陈述。

改动是两行表格,不加新章节:field-types.md 的 Relational 表 tree 行,和 relationships.md 关系类型表的 tree 行。措辞直接取自 spec 的拒收信封,以及 content/docs 上个 PR 已经落地的同一句话,没有另造一套说法——分叉的措辞就是下一张卡。

要请您看一眼的是那处删除。token 棘轮把 field-types.md 卡在离天花板 27 字节,而把契约在那一行说清楚最少要 88 字节:删是被棘轮逼出来的,不是顺手清理。删掉的是 Media 段那句「没有 per-field attachment config」——它复述的正是文件开头那段引用块,而那段已经点名拒收 fileAttachmentConfig,位置还在所有表格之上。删完该文件净缩。

没动的地方也请留意:tree 的示例一个字没改,它本来就写着对象自己的名字。示例行上我起过一句注释,又拿掉了——同一文件的表格行已经把规则说了,再写一遍只是多花字节,不改变客户 agent 写出来的东西。

治理面:skills/** 在名册上,所以这是 draft PR、人工合并,席位不 flip ready、不入队、不 approve。纯散文改动,无契约移动,无 changeset。整包净增 21 tokens,已如实写在上面的表里。

席位意见:


Generated by Claude Code

…` contract

`refuseForeignTreeReference` (packages/spec/src/data/object.zod.ts) makes a
`tree` field's `reference` optional and, when written, equal to the declaring
object's own name — the `extend` target on an extension. Any other object is
refused at parse with one issue at `fields.<field>.reference`. The published
skill still listed `reference` as a bare key prop, so a client agent reading it
had no way to know either half of the rule.

Two rows, no new section:

- `rules/field-types.md` — the Relational table's `tree` row now states
  optional-and-self-only and that a foreign object is refused at parse.
- `rules/relationships.md` — the `tree` row's bare "Optional" cell now says
  what is optional and contrasts the refusal with the `master_detail` row's
  lint-warned cell in the same column.

The token ratchet left `field-types.md` 27 bytes under its ceiling, so the row
is paid for inside the same file: the Media section's "no per-field attachment
config" note restated the top-of-file blockquote, which already refuses
`fileAttachmentConfig` by name, above every table. That file now shrinks.

The `Hierarchical: tree (Self-Reference)` example is unchanged — it already
writes the object's own name (`reference: 'category'` on `category`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 6, 2026
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 6, 2026
@claude

claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — governed in-seat review of PR #16187, head 5a30844272 (skills seat, 2026-09-06T14:2xZ; the compare-worktree reading was first taken at 05:4xZ and re-taken on a fresh worktree at 14:1xZ after the seat's container was reclaimed).

Implemented-by: claude/issue-16083-tree-reference-rows (os-dev subagent, opus under the measured quota exemption recorded on the claim)
Reviewed-by: session_019RfFHiRCSs3JXLK4cwcfox (skills seat)

Verified in a detached compare worktree at the PR head, not from the report:

  • Two files, +2 / −5, both under skills/objectstack-data/rules/. The tree row of field-types.md's Relational table now reads `reference` — optional; when written, this object's own name (any other object is refused at parse); the tree row of relationships.md's Relationship Types table now says what its bare Optional meant and contrasts "refused at parse" with the master_detail row's "lint-warned" in the same column. Both sentences are the spec's own refusal (refuseForeignTreeReference) and the already-landed content/docs wording, not a third phrasing. The Hierarchical: tree (Self-Reference) example is untouched and already writes the object's own name.
  • The paid deletion is compelled, not elective. field-types.md sat 27 bytes under its ceiling; the Media section's two-line "no per-field attachment config" note restates the top-of-file blockquote that already refuses fileAttachmentConfig by name above every table. The file shrinks (3578 → 3567 of 3584); relationships.md 3490 → 3522 of 3778; package +21 tokens, stated in the PR body rather than rounded away. The seat's own grep for # followed by three or more digits over the changed file returns one hit, a hex colour in a code sample, not an issue number.
  • Gates at the head: check-skills-token-ratchet exit 0 ("36 authored bundle file(s) within their ceilings"); check-governed-merges --test on the two paths exit 3 (GOVERNED: skills/**). The dev's report adds the derived union 20/20 families run, --ran reconciled at 0 NOT-MEASURED, whole-repo pnpm lint exit 0 unnarrowed. PR checks on 5a30844272: 23 success, 11 skipped, 0 failed.
  • Report hygiene: dev report on skills(objectstack-data): the tree rows in rules/field-types.md and rules/relationships.md state the landed reference contract — optional, and the object's own name when present (#14892 follow-up) #16083 with the literal os-dev-report line, JSON closes; PR body opens Fixes #16083, carries the 改前 → 改后 table, the ratchet accounting and a ## 维护者速读(草稿) block; no model identifier; skip-changeset read back (objectstack's decider says no changeset is owed for skills/** prose). One MCP call declared (a search_issues that was refused by the rate limit; the dedup was then handed to the seat).

Ruling on the open question — A. +21 tokens at package level is accepted: the per-file ratchet is the gate, both files are under ceiling, and the ruling behind the ratchet targets padding a small feature into prose, which two contract sentences are not. B (an unrelated 84-byte cut to reach net zero) would be a rider on a governed surface. C is queued: the seat files the dev's reported finding (the ~1800-byte ASCII decision tree in field-types.md duplicating the mermaid page in content/docs) as the durable headroom fix, after a dedup over every open domain:skills card.

Landing regime: governed (skills/**) ⇒ the PR stays a draft; reviewers os-zhuang + hotlong requested in this same act, needs-user-decision added, the final 维护者速读 posted as the next comment; human merge. Nothing is flipped, queued or approved from the seat.


Generated by Claude Code

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