Repository navigation
docs(skills): teach sharing.controlledByParent.writeRequiresMasterRead in the controlled_by_parent rules - #33427
Conversation
…d in the controlled_by_parent rules The relationship rules said every detail write needs edit on the master, with ownerMayEdit as the only opt-in. They now teach the read-on-master opt-in: what it admits, its parse refusals, the unchanged default, the ownership floor that still binds predicate and on-behalf-of writes, and the lint arm that stays silent. One eval prompt covers it. The lookup and junction examples drop boilerplate parents to stay inside the token ceiling. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pfprxUTKxctsoaYzF
|
domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb Contract reviewServed-tier: Inputs read: card #33374 body and all three comments (N1/N3 addendum 6882020972, claim 6883494291, dev report 6883705207); PR #33427 body, its 3-file list and the net diff against Check-runs on the head, polled until none was pending: 48 total, 30 success, 18 skipped, 0 failed, 0 cancelled. Green among them: Check Changeset, Governed Surface Queue Guard, PR-body guards, Lint and Repo Gates and every Lint shard (ESLint, PM dispatch-gates, docs and code guards, repo and tooling gates, CI release and engine gates, the scoped shards), Type Check (source gates, debt ledger, workspace, consumer gates) and TypeScript Type Check, Build Core, Test Core with its shards 1/8 and 2/8, Test UI, Dogfood Regression Gate, Live E2E UI. The commit-status API carries no statuses. ① Derived judgments
② Semver levelChangeset Clause-②: no, with ③ Boundary flags
④ Design judgment
Findings:
No must-fix, no should-fix. Implemented-by: VERDICT: PASS Adopted by the seat 2026-11-14T20:43Z · isolated sub-agent at CONTRACT_REVIEW_TIER · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z |
|
domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb 维护者速读
席位核对Verdict: ACCEPT, held for the maintainer · head
|
|
domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb Correction: contract-review tierThe contract-review record(s) 6883930925 on this PR say This PR has already merged. The seat re-runs the review at |
|
domain:skills#1 · os-project-manager · session_01FEkQ5pjSPT7ui2EUcunaVb Contract reviewServed-tier: At-tier re-review of a merged head (merge commit 3968a57). The earlier record 6883930925 was served off-tier and is not used as evidence; ③ says which of its findings this review confirms. Inputs read: card #33374 body and all four comments (N1/N3 addendum 6882020972, claim 6883494291, dev report 6883705207, ACCEPT pointer 6883962683); PR #33427 body, its 3-file list, the net diff of the head against its merge base b462444; the check-runs on the head. Code outside the diff was read on Check-runs on the head: 55 total, 33 success, 22 skipped, none failed or cancelled. Green among them: Check Changeset, Governed Surface Queue Guard, PR-body guards, Lint and Repo Gates with every Lint shard, the Type Check shards and TypeScript Type Check, Build Core, Test Core and its shards, Test UI, Dogfood Regression Gate, Live E2E UI. State of ① Derived judgments
② Semver level
Clause-②: no, with ③ Boundary flags
④ Design judgment
Findings:
No must-fix, no should-fix. The head is merged, so each nit is a follow-up card at most, not a fix on this PR. Implemented-by: VERDICT: PASS Adopted by the seat 2026-11-15T00:50Z · isolated sub-agent at CONTRACT_REVIEW_TIER (served model verified by the seat) · Ruling-ref: Leehom card cmsg_01TenzsRopZj4jMbvjX8uz1XFhuiZmTH8VFgMmHRmpiGAb, option A, tapped 2026-11-14T15:37Z |
Requested by Leehom · project thread
Fixes #33374
domain:skills#1 · os-project-manager · session_01FEkQ5pfprxUTKxctsoaYzF
Clause-②: no
Metadata-protocol: none
维护者速读
objectstack-data技能的关系规则(rules/relationships.md)原来只教「明细的每次写入都要主记录的编辑权,唯一开关是ownerMayEdit」,现在补上第二个开关writeRequiresMasterRead:打开后,能读主记录的人按明细对象自己的权限、行级与字段级安全新增、修改、删除;默认不变。同时写明三处拒绝(与ownerMayEdit: true同时声明、非controlled_by_parent对象、写在上一层)、所有权底线仍约束批量写与代他人写、相关 lint 在该明细上不报。eval 新增第 17 例。main上线这个键,技能文字却仍说「每次写入都要主记录编辑权」,AI 作者照着写会错过这个开关,或把它写到非明细对象上被拒。relationships.mdtoken 4127 到 4127(上限 4127,零余量),新增文字由删去 lookup 与多对多示例里的两段样板父对象付账;回滚即还原本 PR。skills/objectstack-data/rules/relationships.md的「Writes undercontrolled_by_parent— two opt-ins」一节;再看 eval 文件末尾第 17 例。node scripts/check-skills-token-ratchet.mjs,预期 exit 0、relationships.md is 4127 tokens (ceiling 4127; headroom 0)。skills/**):请批准或合并。What changes
skills/objectstack-data/rules/relationships.md, the rule's home. The section "Owner edits undercontrolled_by_parent—ownerMayEdit" becomes "Writes undercontrolled_by_parent— two opt-ins". By default every write needs edit on the master.ownerMayEdit: truekeeps its old meaning (the row owner's by-id update only).writeRequiresMasterRead: trueis new: whoever reads the master inserts, updates and deletes the detail under the detail's own object permissions, RLS and FLS, re-points and each opted-in chain hop too; on behalf of another user, both users must read the master. The ownership floor (rows the caller created) still binds predicate and on-behalf-of updates and deletes (N3). Refused besideownerMayEdit: true, off acontrolled_by_parentobject, and one level up (sharing.KEY). The lintsecurity-controlled-by-parent-owner-policy-without-opt-inis silent on such a detail. The code example shows the new key with "or ownerMayEdit" so the refused pair is never copied as one literal.ownerMayEdit" to "read-on-master opt-ins (below)"), and the optional-master paragraph (setting or emptying a master asks read underwriteRequiresMasterRead, edit even underownerMayEdit).accountobject and the junction example's two side objects (project,employee), each a name-onlyObjectSchema.create, are replaced by one comment naming them. They taught nothing the surrounding prose and the child objects do not.skills/objectstack-data/evals/objects-fields-relationships.jsoneval 17. Order lines anyone who can see the order may add, change or remove:controlled_by_parentwithwriteRequiresMasterRead: true, notownerMayEdit, not one level up.must_contain:controlled_by_parent,master_detail,controlledByParent,writeRequiresMasterRead: true;must_not_contain:sharing: { writeRequiresMasterRead. The set already covered the master-edit rule (eval 2,ownerMayEdit), so the order's condition for adding one holds..changeset/33374-skills-teach-write-requires-master-read.md:'@objectstack/skills': patch, both declaration lines.origin/main:skills/objectstack-data/SKILL.md(itscontrolled_by_parentrow says "inherited from the master record", still true),references/lint-rules.md(does not list the owner-policy lint),skills/objectstack-ui/rules/record-page.md(anos:checkexample, no write rule stated). No restatement outside the rule's home.Why
Read on
origin/mainb462444 (PR #33369 merged), not from the card's summary:packages/spec/src/data/object.zod.ts:3162-3195(refuseControlledByParentWriteRequiresMasterReadMisplaced: refused offcontrolled_by_parent, refused besideownerMayEdit: truewith "Delete ownerMayEdit; writeRequiresMasterRead already admits the owner"),:4244-4246(strictsharingblock's guidance for the key one level up),:4210-4216(read on the master is the master question for every write; absent orfalse, master edit)..changeset/33250-controlled-by-parent-write-requires-master-read.md(insert single and batch, by-id and predicate update and delete, re-point and emptying, each hop whose detail opted in; on-behalf-of asks each context). The body-named master goes through the same chain walk (security-plugin-cbp-write-gate.ts:540-553,askBodyNamedMastercallsassertControlledByParentChainEditable).security-plugin-authz-middleware.ts:740-746,delegatorOwnWriteFloorOptions). Confirmed identical with the ADR-0055: record the 2026-11-14 amendment, writeRequiresMasterRead (read on the master is enough to write, by opt-in) #33373 ADR-0055 amendment thread (branchclaude/project-thread-mllmvl).@objectstack/lintparagraph (no new rule; the owner-policy arm stands down on a declaring detail).Ruling: Leehom 2026-11-14, A+ (record 6880588685 on #33250): 「开关打开后,能读主记录的人按子对象自己的授权新增、修改、删除,默认不变。」
Acceptance notes
master_detailparent-currency path owes two follow-ups (ui dashboards aggregate rule, data eval case) #32392, awaiting merge), which also appends eval 17 to the same file. Whichever lands second mergesorigin/mainand renumbers to 18; the order expected this.#owner-edits-under-controlled_by_parent--ownermayeditis gone.git grepover the whole tree finds no link to it.403 PERMISSION_DENIED, by-id update and delete404 RECORD_NOT_FOUND) and the lifecycle verbs. They live in the ADR amendment (ADR-0055: record the 2026-11-14 amendment, writeRequiresMasterRead (read on the master is enough to write, by opt-in) #33373) and the permissions docs; the file has no headroom for them.Verification
Head
4629a84a, baseorigin/mainb462444.Line and token budget (skills/**)
skills/objectstack-data/rules/relationships.md: 419 to 399 lines, 16505 to 16508 bytes; token ratchet 4127 to 4127 (ceiling 4127, headroom 0). Paid by deleting three boilerplate parent objects; no ceiling moved, no re-wrap counted as payment. First wording measured +14 tokens over and was shortened.skills/objectstack-data/evals/objects-fields-relationships.json: 296 to 313 lines, 17850 to 19199 bytes (eval fixture, measured, not ratcheted).skills/*/SKILL.md: 4602 to 4602 lines (no SKILL.md touched).node scripts/pm/check-skill-line-ratchet.mjs: exit 0.node scripts/check-skills-token-ratchet.mjs: exit 0.Derived gates: 44 derived, 42 run green, 2 NOT MEASURED
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 44 families for the three paths. Each was run on head4629a84awith its exit code captured before any pipe, after building@objectstack/formulaand@objectstack/lintunder the verify lock (VERDICT command-exit 0, 104s):check-skills-token-ratchet(scan and--self-test),check-empty-changeset(scan and--self-test),check-changeset-no-major,check-adr-0087-registration,@objectstack/lint check:doc-formula-expressions,check:doc-authoring,check:nul-bytes,check:pm-governed-merges,check:published-files,check:skill-compatibility,check:skill-frame-sync,check:skill-identifier-liveness,check:skill-links,check:dts-closure,check:sourcemap-no-sources-content.dist/):check:dual-build-cjs-loads,check:lean-entry-closure. No package source is touched; CI builds and runs them.--ranreconciliation: "44 derived famil(ies) accounted for — 42 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)".check:skill-examplesis not derived and not run: noos:checkblock is touched.Tier and governed readings
dispatch-gates --tier: MANDATORY contract-review tier, bothskills/**paths under clause ① (the published catalog); no one-line exit for this surface. This thread ran at the claim's default model; the seat's in-seat contract review is the owed record.node scripts/pm/check-governed-merges.mjs --branch claude/project-thread-jqd53n: exit 3, GOVERNED, landing tier H (人合). 2 of 3 paths hit the register (skills/**x2); the changeset is not on it. Size +47 / -40, under the 3000 threshold.🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5pfprxUTKxctsoaYzF