Skip to content

skills: the published lint-rules table says relationship/master-detail-required is a warning, which PR #22109 makes false on a controlled_by_parent object #22111

Description

@objectstack-fleet

This card carries the published-skill half of #9139: one table cell in skills/** that PR #22109 makes false. The parent #9139 keeps the lint change itself (PR #22109), which is not a governed surface and lands through the queue on its own.

Filing gate: ① a product defect with a named landing site: published skill text that the parent's change falsifies. skills/** ships to customers through npx skills add, so the text is published. Filed by the domain:spec seat 3 (seat post #18883, session_01RPo7FUd6bSnAfkWMAKi848) as a derived sub-issue of the in-flight #9139, from the dev report 6042817677 (open_questions[0]). ⛔ Not a claim.

What becomes false

Landing

Reader who acts

The domain:spec seat 3 dispatches it once PR #22109 merges (the claiming seat follows its derived work to the end), with a cross-lane note to the domain:skills seat.

Dedupe

MCP search_issues, repo-scoped, closed included: 「skills lint-rules.md relationship/master-detail-required severity warning controlled_by_parent error」 → 37 hits; none is this. #9139 (the parent) is the first hit, as the positive control; #13699 (closed) is the earlier severity finding that #9139 absorbed.

Dedupe words: lint-rules.md master-detail-required severity · skills objectstack-data lint table stale · controlled_by_parent master_detail error skill

Activity

  1. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Blocked-by: #9139

    domain:spec seat 3 (#18883) · session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-07T17:10Z. The cell this card fixes is still true until PR #22109 merges; Fixes #9139 closes the parent at that moment, which releases this card to the seat that filed it.

  2. objectstack-fleet commented on Oct 7, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlocked: pm:blocked → pm:queue. Its one blocker, #9139, closed when PR #22109 merged as ace0a53852

    Blocked-by: none

    domain:spec seat 3 (#18883) · session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-07T18:26Z. ⛔ Not a claim.

    • Check ①: the latest pm:blocked transition is this seat's 6042894195; its one condition is Blocked-by: #9139, and lint: promote relationship/master-detail-required from warning to error, scoped to controlled_by_parent — ruled for the v18 boundary (Direction 1 of #8772) #9139 closed completed at 2026-10-07T18:24Z (landing record 6044183998).
    • Check ②: no merged PR has referenced this card since that comment.
    • Now true: skills/objectstack-data/references/lint-rules.md:13 still says warning on origin/main while os lint refuses the shape at error on a controlled_by_parent object — the premise of this card holds from this moment.
    • Landing reminder for the claimant: skills/** is Tier H. The PR stays draft with the 维护者速读 draft section, requests review from both authorized approvers, and lands only on an authorized approval or the maintainer's hand; its skills hunk is reviewed at CONTRACT_REVIEW_TIER. Net line budget 0.
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-08T22:49Z
    Session: session_01RPo7FUd6bSnAfkWMAKi848
    Account: os-justin (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22111-lint-rules-master-detail-severity
    Worktree: objectstack-issue-22111
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main 3f80f1716; stop on breach and explain in the report):

    Route: a governed draft PR (Fixes #22111) with ## 维护者速读(草稿). It lands on an authorized approval, after the seat's ACCEPT and a contract-review-tier review of the skills hunk.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22111,
      "status": "done",
      "branch": "claude/issue-22111-lint-rules-master-detail-severity",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22370",
      "session": "session_01RPo7FUd6bSnAfkWMAKi848",
      "premise_still_valid": true,
      "summary": "Rewrote the one `relationship/master-detail-required` row in skills/objectstack-data/references/lint-rules.md so both cells are true in both cases: severity `warning; error under controlled_by_parent`, description `a master_detail that isn't required — or, under controlled_by_parent, is readonly/system`. Verified against packages/lint/src/data-model-rules.ts on main first (facts below). The file sat at its token ceiling (970/970, headroom 0), so the longer row is paid for by deleting the intro clause that duplicated the table heading's own `in addition to naming/label/i18n` framing, re-wrapped to keep the line count: net 0 lines, 3880 -> 3856 bytes, 970 -> 964 tokens. Draft PR #22370 (Tier H, stays draft) with the 维护者速读(草稿) section; skip-changeset applied because skills/** publishes nothing from any released package.",
      "tests": "Gates derived at 6dc260aae (the PR head) with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (24 families), each exit captured before any pipe, reconciled with --ran: `Run reconciliation — 24 derived, 24 run, 0 NOT-MEASURED, 0 UNRUN`. First pass 23/24 exit 0; `pnpm --filter @objectstack/lint run check:doc-formula-expressions` exit 3 (PREREQUISITE NOT MET: formula/lint not built) -> ran the gate's own fix `pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint` under os-verify-lock (`VERDICT command-exit 0 · held the lock 161s · waited 0s`), re-run exit 0: `✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 471 files / 1384 TS blocks judged clean`. Token ratchet: `✓ check-skills-token-ratchet: skills/objectstack-data/references/lint-rules.md is 964 tokens (ceiling 970; headroom 6)`, self-test exit 0. check:skill-refs exit 0 (generates references/_index.md from the Zod map, not from this file). check:nul-bytes exit 0; control-byte grep over the edited file: none. Roster block (52 families, run as instructed): 47 exit 0; 3 PR-context guards refused without PR context (exit 2, NOT WIRED) and were then measured for real: `✓ check:partof-closing-keyword` (PR_BODY = this body) exit 0, `✓ check:closing-target-claim: PR #22370 closes #22111, and each carries a Claim: whose Branch: line names claude/issue-22111-lint-rules-master-detail-severity` exit 0, `✓ check:single-claim-paths: PR #22370 modifies none of the 1 declared at-most-one-writer path(s)` exit 0; 2 NOT MEASURED locally: check:dts-closure and check:published-readme-exports (exit 3, PREREQUISITE NOT MET, whole-tree dist) — a skills-only diff cannot move either roster; CI runs both. No ablation: text-only change, no runtime behaviour. pnpm lint not run locally (one .md in the diff); CI owns it. CI status at report time: in_progress.",
      "mcp_calls": "0 — no MCP GitHub tool called; reads were gh api GET (REST) and the scripts/pm tools' own reads",
      "api_writes": "4 REST writes, all as objectstack-fleet[bot] through the fleet-write relay in 3 dispatches: POST /repos/objectstack-ai/objectstack/pulls (pr_create, run 37858441121 -> PR #22370, 6595 bytes sent, 6595 stored identical); POST /repos/objectstack-ai/objectstack/issues/22370/labels (skip-changeset) + POST /repos/objectstack-ai/objectstack/issues/22370/assignees (os-justin), one dispatch, run 37858532586, read-back MATCHES target; POST /repos/objectstack-ai/objectstack/issues/22111/comments (this report, via post-stamped). Plus 2 git pushes (empty-branch probe, then 6dc260aae), not REST.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed: the card's landing text assumed a line ratchet on skills/**; none exists (scripts/pm/check-skill-line-ratchet.mjs excludes the published root by design), so the binding gate was the token ratchet at 0 headroom — one cell cannot grow without a same-file content deletion",
        "carrier: none · noted, not filed: skills/objectstack-data/rules/relationships.md:16 `Forced only under controlled_by_parent; else lint-warned` is true as written (ObjectSchema.create forces required under controlled_by_parent; the lint warning is the other case) — left alone"
      ],
      "row_before": "| `relationship/master-detail-required` | warning | a `master_detail` that isn't `required` (a detail can't exist without its master) |",
      "row_after": "| `relationship/master-detail-required` | warning; error under `controlled_by_parent` | a `master_detail` that isn't `required` — or, under `controlled_by_parent`, is `readonly`/`system` |",
      "code_facts": [
        "packages/lint/src/data-model-rules.ts:256 — one rule id relationship/master-detail-required on both tiers",
        "packages/lint/src/data-model-rules.ts:863 — tier chosen by obj.sharingModel === 'controlled_by_parent'",
        "packages/lint/src/data-model-rules.ts:324-325 and :344 — under controlled_by_parent the finding is severity 'error' when required !== true, or readonly === true, or system === true (so required:true + readonly:true and required:true + system:true are refused there)",
        "packages/lint/src/data-model-rules.ts:866-868 — on every other object only required !== true fires, at severity 'warning', unchanged",
        "packages/lint/src/data-model-rules.master-detail-required.test.ts:52-57 pins the three unsafe shapes at error; :111-131 pins private / public_read / public_read_write / unset at warning and readonly/system drawing nothing there"
      ],
      "readings": {
        "file": "skills/objectstack-data/references/lint-rules.md",
        "lines": "53 -> 53 (net 0)",
        "bytes": "3880 -> 3856",
        "tokens_ceil_bytes_over_4": "970 -> 964 (ceiling 970; headroom 0 -> 6)",
        "row_bytes": "136 -> 191",
        "payment": "deleted the intro clause `not just naming/labels but the relationship/master-detail/roll-up patterns` (duplicated the table heading) and the row's parenthetical rationale; intro re-wrapped to keep 4 lines",
        "line_ratchet": "none on skills/** — scripts/pm/check-skill-line-ratchet.mjs excludes the published root by design",
        "token_ratchet": "node scripts/check-skills-token-ratchet.mjs exit 0 before (970/970) and after (964/970); ceiling row untouched"
      },
      "gates": {
        "derived_24": [
          "node scripts/check-ci-filter-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs :: exit 0",
          "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
          "node scripts/check-comment-mask-corpus.mjs :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
          "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
          "node scripts/check-skills-token-ratchet.mjs :: exit 0",
          "node scripts/check-skills-token-ratchet.mjs --self-test :: exit 0",
          "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 3 first (PREREQUISITE NOT MET), exit 0 after building formula+lint under the lock",
          "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
          "pnpm check:agent-test-spelling :: exit 0",
          "pnpm check:corpus-claim-drift :: exit 0",
          "pnpm check:cross-package-test-inputs :: exit 0",
          "pnpm check:doc-authoring :: exit 0",
          "pnpm check:driver-memory-census :: exit 0",
          "pnpm check:gitlink-declared :: exit 0",
          "pnpm check:nul-bytes :: exit 0",
          "pnpm check:pm-governed-merges :: exit 0",
          "pnpm check:refd-timer-probe :: exit 0",
          "pnpm check:role-word :: exit 0",
          "pnpm check:skill-compatibility :: exit 0",
          "pnpm check:skill-frame-sync :: exit 0",
          "pnpm check:skill-identifier-liveness :: exit 0",
          "pnpm check:watch-hint-literal :: exit 0"
        ],
        "ran_verdict": "✓ dispatch-gates --ran: 24 derived famil(ies) accounted for — 24 run, 0 NOT-MEASURED",
        "roster_52": "47 exit 0; check-closing-target-claim / check-partof-closing-keyword / check-single-claim-paths exit 2 NOT WIRED without PR context, then exit 0 each with PR_NUMBER=22370 / PR_BODY; check:dts-closure and check:published-readme-exports exit 3 PREREQUISITE NOT MET (whole-tree dist) — NOT MEASURED locally, CI runs both"
      },
      "files_changed": ["skills/objectstack-data/references/lint-rules.md"],
      "line_counts": "1 file, +5 / -5 (git diff --stat); 10 changed lines; PR label size/s",
      "changeset": "none — skills/** is in no released package's files[] (no package.json under packages/ names it; the catalog ships through npx skills add from this repo); skip-changeset applied via label-write, read-back MATCHES",
      "labels_on_pr": "documentation (path labeler, not this write), size/s (size labeler), skip-changeset (this write); assignee os-justin (this write, mirrors the card)",
      "deviations": [
        "The card's `one table cell` is one row (both cells) plus a same-file intro-clause deletion: the token ratchet had 0 headroom, and the only legal currency is deleted content. Net 0 lines, -24 bytes.",
        "Worktree cut from fresh origin/main 43fc50051 (the claim named 3f80f1716); the diff applies identically.",
        "Roster block: 2 of 52 NOT MEASURED locally (check:dts-closure, check:published-readme-exports — whole-tree dist prerequisite); not built for a roster the derivation scores silent for this diff.",
        "pnpm lint not run locally; the diff is one .md file. CI owns it.",
        "The first gate-runner launch was refused by the harness's removal safety check on a bash -c string (it contained no removal; nothing ran); relaunched from a script file.",
        "The harness attribution reminder names a model in Co-Authored-By; AGENTS.md's model-free trailer pair was used instead per the reminder's own precedence clause (pre-push check:commit-card-trailers green).",
        "PR label `documentation` was added by the path labeler (another actor), not by this write; left as is.",
        "Worktree and its node_modules removed after the PR opened (git worktree remove exit 0, status clean)."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22370 at 6dc260aae. Governed (Tier H): parked for the contract review, then the maintainer's approval

    domain:spec seat 3 (#18883) · os-justin · session session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-08T23:22Z · holder of claim 6070608363. Report read: 6070988947.

    Checked on GitHub and against origin/main, not from the report:

    • Shape:
      • 1 file, +5/−5. The body's first line is Fixes #22111, with Clause-②: no and skip-changeset. skills/** is in no released package's files, so no changeset is owed.
      • The body carries ## 维护者速读(草稿) with the seat's opinion left empty. No model identifier appears in the body or the commit.
    • The row is true against the rule on main (packages/lint/src/data-model-rules.ts):
      • On a controlled_by_parent object, the finding is error when the field is not required, or carries readonly: true or system: true (:324–:325, severity at :344).
      • On every other object, only a missing required fires, at warning.
      • The new cells read "warning; error under controlled_by_parent" and "a master_detail that isn't required — or, under controlled_by_parent, is readonly/system".
    • The payment:
      • The file sat at its token ceiling (970/970), so the longer row is paid for by deleting the intro clause "not just naming/labels but the relationship/master-detail/roll-up patterns". The table's own heading, "in addition to naming/label/i18n", still says that.
      • The row's parenthetical rationale is also dropped. Net 0 lines, 964/970 tokens.
      • No fact an author acts on is lost.
    • Governed: check-governed-merges.mjs --pr 22370 puts 1 of 1 paths on the register (skills/**). Landing tier H.

    Dispositions:

    • The card's "net 0 lines (the skill line ratchet)" named the wrong guard: check-skill-line-ratchet.mjs excludes the published root by design, and the binding guard is the token ratchet. This is a correction to the card's own wording, not a defect.
    • skills/objectstack-data/rules/relationships.md:16 ("Forced only under controlled_by_parent; else lint-warned") is true as written and stays.

    Next:

    1. A contract review at CONTRACT_REVIEW_TIER of the skills hunk (this seat reviews below that tier). The marker needs:contract-review goes on the PR with this ACCEPT.
    2. After a PASS and settled checks: needs-user-decision on the PR, the final 维护者速读, and review requests to os-zhuang and hotlong.
    3. On an authorized approval (or the maintainer's own merge), the seat lands it, and the card closes on the merge.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Contract review

    Served-tier: CONTRACT_REVIEW_TIER
    Head-sha: 6dc260aaec43058e3226f1a18ea6c3fc637aa5a0
    Local-runs: none

    Rendered 2026-10-08T23:30Z on PR #22370 (card #22111) by an isolated subagent of the seat's session, from these inputs only: the card's body and its 5 comments, the PR's body, file list and net diff against main (1 file, +5/−5, skills/objectstack-data/references/lint-rules.md), the check-runs on the head, and packages/lint/src/data-model-rules.ts plus data-model-rules.master-detail-required.test.ts on main. No worktree, no build, no test or gate run.

    ① Derived judgments

    The severity cell — "warning; error under controlled_by_parent" — is true. data-model-rules.ts:863 branches on obj.sharingModel === 'controlled_by_parent'; that branch's finding is severity: 'error' (:344), the else branch's is severity: 'warning' (:866-868), and both report under the one id relationship/master-detail-required (:256). Pinned at error for all three unsafe shapes (test :52-57) and at warning for private, public_read, public_read_write and unset (test :111-123).

    The description cell — "a master_detail that isn't required — or, under controlled_by_parent, is readonly/system" — is true clause by clause. Default tier: the only predicate is def.required !== true (:866), which is "isn't required" (absent or false). controlled_by_parent tier: cbpMasterReferenceFinding fires when required !== true, or readonly === true, or system === true (:324-326), so shape 1 is the row's first clause and shapes 2 and 3 are its second. An explicit readonly: false / system: false is clean (test :65-69), which is what "is readonly/system" reads as. Outside controlled_by_parent the two flags draw nothing (test :125-130), so scoping them to that sharing model is true too. A lookup is not the rule's subject (test :104-108); the row says master_detail. The legend's "error (structural, fails the command)" matches the rule's reach — it moves os lint's exit code and the generation rubric, not a publish verdict (:306-310) — and the row claims no publish refusal.

    The deleted intro clause loses no fact an author acts on. "not just naming/labels but the relationship/master-detail/roll-up patterns" is carried by the heading that stays ("Data-model rules (in addition to naming/label/i18n)") and by the relationship/* and rollup/* rows themselves. The re-wrap inserts no blank line, so the intro is still one Markdown paragraph; rendered output is unchanged.

    The dropped parenthetical "(a detail can't exist without its master)" is rationale, not an instruction. The action an author takes — required: true — is still named in the row and in the file's closing paragraph (:51-52); the default-tier lint message still carries the sentence at runtime (:870); and rules/relationships.md:27-31 keeps it in the skill. Read as text third parties install verbatim through npx skills add: an AI author modelling a controlled_by_parent detail now learns that os lint refuses the three shapes, and nothing in the new cells overstates the rule.

    Concur with the seat on rules/relationships.md:16 ("Forced only under controlled_by_parent; else lint-warned"): true as written — the builder forces required there (:292-297; test :133-137, :156-159) and the lint warning is the other case. Outside this card's one-row scope; stays.

    ② Semver level

    • skip-changeset is the right form for what the diff publishes. The one changed path is in no packages/*/package.json files[]; the catalog ships through npx skills add from this repo, not through a versioned npm release, so no released package's surface moves and no .changeset/*.md is owed. The head's Check Changeset check-run is success.
    • Clause-②: no is correct against the current definition (scripts/pm/clause2-line.mjs; execution-duties: 「本卡放宽接受集或扩大公开面吗」, where 公开面 is what a built package's entry declarations reach). A row rewrite in published skill text relaxes no accept set and widens no public surface. The line stands at line start in the PR body and in claim 6070608363, in the fixed spelling, with the token first after the colon and no arm — and no arm is owed: this diff narrows nothing (the lint narrowing was PR feat(lint)!: relationship/master-detail-required refuses the three unsafe master-reference shapes at error on a controlled_by_parent object #22109's, already on main).

    ③ Boundary flags

    Report 6070988947 is the newest os-dev-report on the card; seat ACCEPT 6071013571 is the newest comment. open_questions: [] — nothing to answer there. Each dev flag, answered:

    • "One table cell" became one row plus an intro-clause deletion. scripts/check-skills-token-ratchet.mjs:579 pins this file at 970; ceil(3880/4) = 970 before, ceil(3856/4) = 964 after, so the ratchet was binding at zero headroom and same-file deletion is the only legal payment. The card's "skill line ratchet" named the wrong guard (check-skill-line-ratchet.mjs excludes the published root). Concur with the seat: a correction to the card's wording, not a defect. This also answers out_of_scope_findings[0].
    • Worktree cut from main at 43fc500 rather than the claim's 3f80f17. The head's parent is 43fc500; main...head is 1 ahead / 4 behind, mergeable: true, and the one file is untouched on main since. No action.
    • Two roster families NOT MEASURED locally (check:dts-closure, check:published-readme-exports) and pnpm lint not run locally. Owned by CI; Lint & Repo Gates is success on the head. Answered by the check-runs.
    • Trailer pair. The head commit carries AGENTS.md's model-free pair (the session-link trailer plus a plain Co-authored-by: line); no model identifier in the commit, the PR title or the PR body. Verified.
    • Harness refusal of the first gate-runner launch; documentation label added by the path labeler; worktree removed afterwards. Process notes; nothing landed from them. No action.
    • out_of_scope_findings[1] (relationships.md:16). Concur; see ①.

    One observation for the seat's 维护者速读, not a defect: the PR body's "Derived from the merge base at 6dc260a" names the head, not the merge base (43fc500); the report itself says "the PR head". Wording in the PR body only; nothing published turns on it. Not escalated.

    Check-runs on the head. Read at 2026-10-08T23:25:10Z: 38 runs — 22 success, 15 skipped (re-triggered pr-automation jobs whose condition did not match), 1 in_progress (Test Core (1/6)), 0 failures. Re-read at 2026-10-08T23:28:49Z: unchanged, Test Core (1/6) still in_progress. Every family a one-file .md diff can move is success (Lint & Repo Gates, Check Changeset, Governed Surface Queue Guard, the three claim guards, Check Documentation Links); the seat reads the last shard settled before landing, as the brief provides.

    Implemented-by: claude/issue-22111-lint-rules-master-detail-severity
    Reviewed-by: session_01RPo7FUd6bSnAfkWMAKi848

    VERDICT: PASS

  7. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22370 → 16096e8d7b (Fixes #22111). The card is closed

    domain:spec seat 3 (#18883) · os-justin · session session_01RPo7FUd6bSnAfkWMAKi848 · 2026-10-09T00:48Z · holder of claim 6070608363, closed by this act.

    This act removes pm:dispatched; the domain and type labels stay.


    Generated by Claude Code

  8. added a commit that references this issue on Oct 9, 2026
    16096e8
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions