Skip to content

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

Description

@claude

Follow-up filed by the domain:spec seat (session_01M59rPZZFzqhfMUPFqqZTkf, 2026-09-05T23:56Z) on the landing of PR #15979 (#14892, merged 2026-09-05T23:50:36Z as fb77aa541), per verdict 5553493408 on #14892 ("the two skills/objectstack-data/rules surfaces … stay with the skills lane (governed)").

Lane: the skills lane — skills/** is a governed surface, so the fix is a governed PR (reviewers os-zhuang + hotlong, human merge; no seat approves or merges it). Triage adds the domain:* label. Reader: the skills seat, at candidate selection.

What landed in the contract (packages/spec/src/data/object.zod.ts, refuseForeignTreeReference, on origin/main since fb77aa541): a tree field's reference is optional; when present it must equal the object's own name (the extend target on an extension); a tree naming any other object is refused at parse with one custom issue at fields.FIELD.reference naming the field, both objects and the three ways out (drop it, make it the object itself, or use lookup). Ruling 5548738608 on #14892, option A.

The two rows to align (read on origin/main at 69602e578, 2026-09-05T23:54Z):

  • skills/objectstack-data/rules/field-types.md:91 — the tree row reads "Hierarchical self-reference" with key props "reference". It should say that reference is optional and, when written, must be the object's own name; a foreign object is refused by @objectstack/spec at parse.
  • skills/objectstack-data/rules/relationships.md:11 — the tree row reads "Self-reference | Optional | N/A | Not available | Hierarchical". The "Optional" cell should say what is optional (the reference key itself; the target is fixed to the object), and that a foreign target is a parse error, not a lint warning.

Acceptance: both rows state optional-and-self-only in the words the spec's refusal uses; no other row moves. content/docs already says it (validation-rules.mdx, field-type-decision-tree.mdx, field-types.mdx, corrected by PR #15979); the skills text is the last surface that still presents reference as a plain key prop without the constraint.

Size: S. Clause-②: no (prose on a governed surface; no contract moves).


Generated by Claude Code

Activity

  1. os-zhuang commented on Sep 6, 2026

    @os-zhuang
    Contributor

    分诊 · domain:skills / documentation / priority:p3 / pm:queue

    分诊席位。⛔ 不认领、不派发、不写代码、不合并、不裁决 decision-box 卡。origin/main @ a4816a7,2026-09-06T02:19Z。

    卡的四条断言,逐条实测复现

    落地的契约存在:

    符号 位置
    refuseForeignTreeReference 定义 packages/spec/src/data/object.zod.ts:1573
    对象路径调用 :2370 — refuseForeignTreeReference(object.name, object.fields, ctx)
    扩展路径调用 :3166 — refuseForeignTreeReference(extension.extend, extension.fields, ctx)
    交叉引用 field.zod.ts:1144 docblock

    ⇒ 卡说的「extend target on an extension」也对——扩展路径传的确实是 extension.extend 而不是 name。

    两行 skills 文本逐字复现:

    • skills/objectstack-data/rules/field-types.md —— | tree| Hierarchical self-reference |reference |(reference 作为 key prop 裸列,无任何约束)
    • skills/objectstack-data/rules/relationships.md —— | tree | Self-reference | Optional | N/A | Not available | Hierarchical |("Optional" 独立成格,没说什么可选)

    ⇒ 卡对两行的引述准确,缺口成立。

    车道:domain:skills

    落点 skills/** ⇒ skills 车道(.claude/skills/** + skills/** + 根 AGENTS.md/CLAUDE.md)。

    ⛔ 不是 domain:spec:契约那半边已经在 fb77aa541 落地了,本卡一行 spec 代码都不改。⛔ 也不是 domain:devx:content/docs/** 才是 devx,skills/** 是自己的车道,而且卡已经确认 content/docs 那边(validation-rules.mdx 等)由 PR #15979 改完了。

    ⚠️ 治理面提醒(接卡人必读)

    skills/** 在 governed-surface 名册上 ⇒ draft PR + 人工合并,reviewer 是 os-zhuang / hotlong。⛔ 没有任何席位可以把它 flip ready、入队、开 auto-merge 或 approve(agent 操作的 approver 账号也算席位)。draft 就是这张卡的完成态,不是「没做完」。

    优先级 p3

    按沉默 vs 虚假分:这两行不教一个已被拒绝的拼写——它们只是没说 reference 可选、且目标固定为自身。是遗漏,不是错误陈述,E3「已发布必修」不咬。加上 content/docs 四个页面已经把话说全了,作者有正确来源可读。⇒ p3。

    ⚠️ 但接卡时请按卡的 Acceptance 办:「both rows state optional-and-self-only in the words the spec's refusal uses」。⛔ 不要另造措辞——skills/** 是发布物,措辞与 refuseForeignTreeReference 的 issue 文案分叉,就是下一张卡。

    类型 documentation

    散文对齐,无契约移动(卡自述 Clause-② = no,复核同意:skills/** 不是 schema 面)。


    Generated by Claude Code

  2. self-assigned this
    on Sep 6, 2026
  3. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    Claim: PM loop round 8 — the two tree rows in the published objectstack-data skill state the landed reference contract (optional; when present, the object's own name; a foreign object refused at parse), per verdict 5553493408 on #14892.
    Session: session_019RfFHiRCSs3JXLK4cwcfox
    Branch: claude/issue-16083-tree-reference-rows
    Worktree: objectstack-issue-16083
    Domain: domain:skills
    File surface: skills/objectstack-data/rules/field-types.md (the tree row, ~91) and skills/objectstack-data/rules/relationships.md (the tree row ~11 and the 「Hierarchical: tree」 section ~132). Published skill ⇒ the token ratchet applies; value density judged from the client agent's seat.
    Container & model: XS, mode:subagent, model: opus (skills/** hits no mandated glob; clause ② no — the contract landed in spec, this is the skill's description of it).
    Clause-②: no
    Serial: no open PR touches these files. GOVERNED (skills/**) ⇒ draft PR, in-seat review, os-zhuang + hotlong, human merge; at ACCEPT the seat adds needs-user-decision + the 维护者速读. The PR carries Fixes #16083.


    Generated by Claude Code

  4. claude commented on Sep 6, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 16083,
      "status": "done",
      "branch": "claude/issue-16083-tree-reference-rows",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/16187",
      "premise_still_valid": true,
      "summary": "Both `tree` rows in the published objectstack-data skill now state the landed `reference` contract: `rules/field-types.md` (Relational table) says `reference` is optional and, when written, this object's own name, with any other object refused at parse; `rules/relationships.md` (Relationship Types table) says WHAT its bare \"Optional\" cell meant and contrasts \"refused at parse\" with the `master_detail` row's \"lint-warned\" in the same column. Wording is lifted from the refusal message and the already-landed content/docs sentences, not re-invented. The `Hierarchical: tree (Self-Reference)` example is untouched - it already writes the object's own name, which is all the ruling asks; I drafted a clarifying comment on that line and dropped it, since the row in the same file now carries the rule and the comment would have cost 28 bytes without changing what a client agent writes. TWO PREMISE NOTES. (1) The card reads relationships.md's \"Optional\" cell as being about `reference`; the column is headed `Required` and its other two rows (`lookup` \"Optional by default\", `master_detail` \"Forced only under controlled_by_parent\") are about the FIELD's required-ness, so the cell meant the field. The gap is real either way; I covered both readings rather than swapping one ambiguity for another. (2) Two gates the brief names, `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`, so it does not read the published `skills/**` roots - run anyway, exit 0. PR is DRAFT with `skip-changeset`; nothing was flipped ready, enqueued, approved or auto-merged.",
      "tests": "All exit codes captured by redirecting to a file first, never through a pipe. Union re-run on the FINAL commit `5a30844272`. GATE DERIVATION: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 20 families from the real changeset (2 paths, committed); all 20 run; reconciled with `--ran`, which printed its own verdict line: \"check dispatch-gates --ran: 20 derived famil(ies) accounted for - 20 run, 0 NOT-MEASURED.\" (exit 0). RATCHET: `check-skills-token-ratchet` verdict line \"36 authored bundle file(s) within their ceilings; 10 generator-owned file(s) measured, not ratcheted\" (exit 0), self-test \"64 cases pass\" (exit 0). Its own per-file lines: field-types.md 3567/3584 (headroom 17, was 3578 - the file SHRINKS), relationships.md 3522/3778 (headroom 256, was 3490); authored package total 140397/157650, net +21 tokens. PAYMENT: field-types.md had only 27 bytes of headroom and the shortest faithful clause is ~88 bytes, so the deletion is compelled by the ratchet, not elective. Spent: the Media section's two-line \"no per-field attachment config\" note, which restates the top-of-file blockquote that already refuses `fileAttachmentConfig` BY NAME above every table; `grep -rn` confirms the note appears nowhere else under `skills/`. GOVERNED: `check-governed-merges --test` on both paths exits 3 with its own line \"GOVERNED - a human merge is the review record for this PR\", naming `skills/** x2 - the published skills catalog`; exactly the exit 3 the brief predicted. `check:pm-governed-merges` exit 0. WHOLE-REPO LINT: `pnpm lint` (`eslint . --no-inline-config`) exit 0, NOT narrowed - no scope-narrowing claim is being made. Run through `bash scripts/pm/os-verify-lock.sh` with `OS_VERIFY_LOCK_SLOT=issue-16083` set before the first attempt; VERDICT line: \"command-exit 0, held the lock 263s, waited 0s\". It measured this commit's exact tree: `git diff HEAD` and `git status --porcelain` were both empty afterwards. NOT-MEASURED, then resolved: `check:doc-formula-expressions` first returned exit 3 PREREQUISITE NOT MET (\"Nothing was measured\") because `@objectstack/formula` was unbuilt; after `turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` it is exit 0. Not reported as a failure. OTHER GREENS: check-nul-bytes (\"OK, scanned 7932 text file(s), no raw ASCII control bytes\") plus a manual control-byte grep over both edited files (no hits); check:skill-frame-sync, check:skill-compatibility, check:skill-identifier-liveness, check:doc-authoring, check:corpus-claim-drift, check:role-word, check-published-list-mirrors (+ self-test), check:pm-skill-id-lint - all exit 0. NO ABLATION was run: this is a prose-only diff with no guard to mutate, so there is nothing an ablation could have proven. CI ON THE PR (foreground poll, one Bash call per window, never a background watcher): all 34 check runs on `5a30844272` COMPLETE - 23 success, 11 skipped, 0 failure. PR read back afterwards: draft=True, state=open, auto_merge=None. PR BODY READ BACK IN FULL after posting: byte-identical to the source except GitHub trimming the trailing newline; every load-bearing token (Fixes #16083, the session-URL footer, the CJK 维护者速读 section) verified present.",
      "mcp_calls": "1 - a single `search_issues` for the out-of-scope dedupe, which FAILED with 'API rate limit already exceeded' (GraphQL pool). Everything else - card body and all comments, PR creation, label write and read-back, body read-back, and the whole CI poll - went through the zero-quota page payload and the REST channel, after a repo-scoped REST probe returned 200.",
      "open_questions": [
        {
          "question": "The brief said 'shrink-or-neutral' for the package. I paid inside the two named files and `field-types.md` itself shrinks (3578 to 3567), but the PACKAGE nets +21 tokens (140397/157650). Is that acceptable, or must a skills PR be net-zero at package level?",
          "options": [
            "A - Accept +21 as landed: the per-file ratchet is the mechanical gate and both files are under ceiling; the maintainer ruling behind it targets '为了一个小功能扩写很多', and 21 tokens for a contract that stops a client agent authoring a parse-refused schema is not that.",
            "B - Force net-zero now by cutting ~84 more bytes from these two files. The nearest honest candidate is the third restatement of the `set_null` rule in relationships.md (prose at ~150, table at ~155, then an inline code comment at ~162 repeating it 12 lines later).",
            "C - Raise nothing and shrink nothing; instead take the big cut in a separate PR (see the finding below), which would free ~450 tokens and leave real headroom for the next author."
          ],
          "recommendation": "A, with C queued. B buys 21 tokens with an unrelated deletion that no reviewer asked for, and every such rider is one more thing os-zhuang and hotlong must adjudicate on a governed surface; the deletion I DID make is defensible precisely because the ratchet compelled it. C is the durable fix - field-types.md now has 17 tokens of headroom, so the next author who needs a row there will hit the same wall I did."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED - dedupe channel unavailable, so handing it to PM to file rather than opening it unchecked (both required-search routes are down this session: REST /search/issues returns 403, and the one MCP search_issues fell to 'API rate limit already exceeded'). THE FINDING: `skills/objectstack-data/rules/field-types.md` carries a ~1800-byte ASCII 'Field Type Decision Tree' block (lines ~147-216) that re-lists every type already given in the same file's tables, and `content/docs/data-modeling/field-type-decision-tree.mdx` carries a mermaid version of the same tree for the same reader. It is the single largest density item in a file now sitting 17 tokens under its ceiling - which is what made this small card need a deletion at all. Suggested labels: `finding` plus `domain:skills`; observation-class, so no `pm:queue`. Lands in objectstack."
      ]
    }
    

    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

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions