Repository navigation
docs(skills): teach controlled_by_parent with an optional master in the data skill, permission docs and ADR-0055 - #31092
Conversation
…he data skill, permission docs and ADR-0055 The data skill, five permission docs pages and ADR-0055 now teach the optional master (a controlled_by_parent detail whose only master_detail is declared required: false) instead of an owner field copied by hooks: a row with no master is read by its owner or View All on the detail and written by its owner or Modify All on it; an insert naming no master is admitted; emptying a master asks edit on the stored master; a chain hop with no master ends there under the same rule. ADR-0055 gains a dated 2026-11-07 amendment for the section 3 write rule, and the 2026-11-06 header line that said section 3 was unchanged is marked in place. The membership-key notes say a valueField cannot be the roster's own id. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8VVAo8GHSrxa8ZR8h3qxs
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract-review-claim: session_01J8VVAj7CvjkGXvgXzd6RZ7 · head 197aa7c · 2026-11-07T06:17Z The seat review runs at CONTRACT_REVIEW_TIER in an isolated subagent. No review seat ( |
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract reviewServed-tier: ① Derived judgmentsInputs: card #30907 (body; the triage; the card-2 carry-over 6702057019; the unlock; the claim), sibling #30906 with PR #31082 (its body and its changeset
② Semver level
③ Boundary flags
④ Design judgment
All four are within ceiling; the readings match the PR body.
Must-fix: M1. Should-fix: S1, S2, S3, S4. Implemented-by: VERDICT: FAIL |
…ster, and scope the master-edit rule to rows that name one The optional-master section said emptying a master asks edit on the stored master "ownerMayEdit aside", which reads as an exception; it now says "even under ownerMayEdit". The owner-edit section's "every write needs edit access on the master" and "still need master edit" now speak of rows that name a master. ADR-0055's 2026-11-07 amendment says the 403 is for a principal who reaches the gate; one who cannot read the row meets the read door's 404 first. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8VVAo8GHSrxa8ZR8h3qxs
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract-review-claim: session_01J8VVAj7CvjkGXvgXzd6RZ7 · head 9cea5ad · 2026-11-07T06:41Z Delta review against the FAIL at head 197aa7c (comment 6703355433: must-fix M1, should-fixes S1–S4). The delta is one commit to |
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract reviewServed-tier: ① Derived judgmentsThis is a delta review against the FAIL at
② Semver levelUnchanged from round 1. The changeset is still ③ Boundary flags
④ Design judgment
relationships.md matches the claimed 3871/3873, a delta of +6 with no deletion. The SKILL.md figure has its version line excluded.
Must-fix: none. Should-fix: S5 (it predates this round and does not block). Round 1's M1, S1 and S2 are answered in the diff, and S3 and S4 are carried by #31095 and #31094. Implemented-by: VERDICT: PASS |
…ame a master The ownerMayEdit section opened with "a controlled_by_parent detail inherits the master's access: reads are scoped to accessible masters", which left out the null-master read the optional-master section teaches. It now speaks of a detail row naming a master, for reads and writes alike. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8VVAo8GHSrxa8ZR8h3qxs
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract-review-claim: session_01J8VVAj7CvjkGXvgXzd6RZ7 · head a60b01a · 2026-11-07T06:57Z Delta review against the PASS at head 9cea5ad (comment 6703520499). The delta is one commit to |
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 Contract reviewServed-tier: ① Derived judgmentsThis is delta review 2, against the PASS at
② Semver levelUnchanged. The changeset is still ③ Boundary flags
④ Design judgment
relationships.md is 3870 of 3873, which holds the claimed reading (−1 from 3871). The figures were measured from
Must-fix: none. Should-fix: none. S5 is closed. Round 1's M1, S1 and S2 stay answered, and S3 and S4 stay carried by #31095 and #31094. Implemented-by: VERDICT: PASS |
|
domain:skills#3 · huangyiirene · session_01J8VVAj7CvjkGXvgXzd6RZ7 维护者速读
席位核对 — ACCEPT · domain:skills#3 · head
|
Requested by Irene · project thread
Fixes #30907
Clause-②: no
Metadata-protocol: none
维护者速读
skills/objectstack-data的rules/relationships.md、rules/security.md、references/lint-rules.md、SKILL.md)、五个权限文档页和 ADR-0055 现在教「可选主表」:controlled_by_parent子对象把它唯一的master_detail写成required: false,不再教「复制一个属主字段再用钩子同步」。没挂主表的行归属主:属主或子对象「查看全部」可读,属主或子对象「修改全部」可写;新建不挂主表的行直接放行;挂主表要对新主记录有编辑权,清空主表要对原主记录有编辑权(ownerMayEdit不覆盖);链上某跳没有主表就在那里按同一规则结束。另加一句:成员键的valueField不能是名册对象自己的id(membership-key-value-field-invalid)。ADR-0055 新增 2026-11-07 的 §3 修订,并就地标注 2026-11-06 那行「§3 不变」。skills/objectstack-data/rules/relationships.md新增的「Optional master」小节,以及 ADR-0055 末尾的 2026-11-07 修订。node scripts/check-skills-token-ratchet.mjs,预期 exit 0,读数见下文 Token budget。skills/**和docs/adr/**属 Tier H,需要os-zhuang或hotlong批准。What changes, and the code behind each taught fact
Every fact was read on
main719a502b(after PR #30940 for #30905 and PR #31082 for #30906):mainmaster_detaildeclaredrequired: falseis the master when no required one stands (an optional master)packages/spec/src/data/controlled-by-parent-master.tscontrolledByParentMasterOf, judged bycontrolledByParentMasterRefusal(the parse's refinement inobject.zod.ts)required: falsebeside a secondmaster_detail; areadonly/systemmaster (required or optional); an omittedrequiredis forced totruebyObjectSchema.create()several-masters,master-not-writable);object.zod.tsforceCbpMasterDetailRequired; lintrelationship/master-detail-required(packages/lint/src/data-model-rules.tscbpMasterReferenceFinding)owner_id) and by View All on the detail; no owner / noowner_idcolumn ⇒ View All alonepackages/plugins/plugin-security/src/security-plugin-cbp-read-filter.tscomputeControlledByParentFilter(thenullMasterArm)403, View All included); no owner ⇒ Modify All alonesecurity-plugin-cbp-write-gate.tsnullMasterRowWritable, and themasterId == null && rel.optionalblock ofassertControlledByParentWriteif (operation === 'insert') return;ownerMayEditdoes not cover itaskBodyNamedMasterafter the owner rule; theownerMayEditleg's!(rel.optional && bodyEmptiesMasterReference(...))nextMasterId == null && nextRel.optionalbranch (nullMasterRowWritableon the master's own object)valueFieldcannot be the roster's ownidpackages/lint/src/validate-membership-key-declarations.ts:columnOfmapsidto an empty slice,valueFieldFaultreports itmembership-key-value-field-invalidSkills.
rules/relationships.mdgains § Optional master (after theownerMayEditsection), and itsmaster_detailtable row names it.rules/security.md: the "choose by what the rows are keyed on" list gains "a parent's owner, parent optional → an optional master", and thevalueFieldnote gains theidrefusal.SKILL.md: the OWD table'scontrolled_by_parentrow says a row with no (optional) master is its owner's.references/lint-rules.md: therelationship/master-detail-requiredrow names the optional master.Docs (
content/docs/permissions/):authorization.mdx(the lint-gate bullet, ≈L539: one master, required or optional, and whatno-relation/ambiguous-relationnow catch),rls.mdx(the controlled-by-parent bullet),sharing-rules.mdx(OWD table row),permissions-matrix.mdx(OWD table row),index.mdx(a comment on the example'scontact: 'controlled_by_parent').ADR-0055. A new section "Amendment (2026-11-07, #30906): the null-master row its owner writes", a status line for it, and an inline⚠️ note at §3. The 2026-11-06 status line's "§3 … is unchanged" is marked in place (⚠️ Amended 2026-11-07), as are the three sentences inside the 2026-11-06 amendment that said the same (its Ruling paragraph's "amended again for §3 then", its Fail-closed bullet on the write gate, its "What is unchanged" §3). Nothing is rewritten.
Token budget
node scripts/check-skills-token-ratchet.mjs(conventionceil(utf8 bytes / 4)), no ratchet row moved,scripts/check-skills-token-ratchet.mjsuntouched:mainskills/objectstack-data/SKILL.mdskills/objectstack-data/rules/security.mdskills/objectstack-data/rules/relationships.mdskills/objectstack-data/references/lint-rules.md(The order's SKILL.md reading of 6068 had moved to 6078/6078 on
main719a502b.) No copied-owner-field recipe existed in these files to replace, so the growth is paid by deleting restatement in the same file;lint-rules.md's +23 is one table row and stays under its ceiling with no deletion. Each deletion and its surviving home:rules/relationships.mdlookup,master_detail, and junction patterns." Home: the H1 and the ToC lines above it.{ type: 'master_detail', reference: 'parent_object', deleteBehavior: 'cascade', // or 'restrict' — 'set_null' is refused here }. Home: the sentence and table right above it ("cascadeorrestrictonly; an authoredset_nullis refused").inlineEditis the primary, zero-config path". Home: the same first sentence.// Invoice line items should NOT be independent. Home: the heading and the fence's own// ❌ Child can exist without parent — wrong!.invoice_line_itemwith a requiredmaster_detail,deleteBehavior: 'cascade'), replaced by a link to § One-to-Many: master_detail, which declares the same object and field.rules/security.md// rows readable / targetableand// every row written. Home: the prose right above ("usingadmits rows …", "checkis judged on every row aninsert/updatewrites").SKILL.mdEval sweep
grepoverskills/objectstack-data/evals/*.jsonforcontrolled_by_parent,master_detail,required,owner,hook,copy/mirror/stampandmembership: no case pins the old rule.objects-fields-relationships.jsoncase 2 expectsrequired: trueon an invoice line'smaster_detail, which stays right (a line always has its invoice).hooks-security-seeds-datasources.jsoncase 6 (membership keys for portal orders) and case 1 (the search-mirror hooks) teach rules this card leaves standing. No eval file is changed.Scorer check (read-only)
packages/qa/scenarios/src/score/scenario-tags.tsrow-scope check, now at L393 (the card's L343 drifted):if (effectiveSharingModel(def) === 'private' || def.sharingModel === 'controlled_by_parent') return check(at, true, model);. It accepts anycontrolled_by_parentobject without reading its master'srequired, so an optional-master detail passes. No change.Spec contract jsdoc owed (spec lane, not edited here)
The wording
packages/spec/src/contracts/security-service.tsowes (master_chain,master_reference_missing,checkControlledByParentWrite) is filed for the spec lane as #31094.Other
content/docspages that still teach a required-only masterListed, not widened: filed as #31095 (
error-handling.mdx,error-catalog.mdx,cli.mdx, including the two sites the contract review added). Release pages (content/docs/releases/**) are release-owned.Review round 1
Contract review FAIL at
197aa7c88(comment 6703355433). Fixed in delta commit9cea5ade1, as the seat ordered (one new commit, no rebase or force-push):rules/relationships.md§ Optional master): "emptying one asks edit on the stored master,ownerMayEditaside" now reads "…, even underownerMayEdit", matching the owner leg's!(rel.optional && bodyEmptiesMasterReference(opCtx.data, rel.fk))insecurity-plugin-cbp-write-gate.ts.rules/relationships.md§ Owner edits): "every write needs edit access on the master" now reads "every write of a row naming a master needs edit on it", and "on-behalf-of writes still need master edit" reads "on-behalf-of writes under a master still need its edit", so neither contradicts § Optional master (an insert naming no master is admitted; the owner rule governs a null-master row).403is now said of a principal who reaches the gate; one who cannot read the row meets the read door's404 RECORD_NOT_FOUNDfirst (step 2.7), as feat(security, verify): a controlled_by_parent row with no master is written by its owner or by Modify All on the detail #31082 pinned.The delta costs
rules/relationships.md6 tokens (3865 → 3871, ceiling 3873); no deletion was added and no ratchet row moved.Review round 2
Delta review PASS at
9cea5ade1(comment 6703520499), no must-fix. Its should-fix S5, closed here as the seat ordered, in delta commita60b01a6e:rules/relationships.md§ Owner edits opened "Acontrolled_by_parentdetail inherits the master's access: reads are scoped to accessible masters", which left out the null-master read (the owner and View All, § Optional master). It now reads "Acontrolled_by_parentdetail row naming a master inherits its access: reads are scoped to accessible masters and every write needs edit on the master", so the read and the write halves both speak of a row that names a master (S1's write qualifier moves into the subject). The change is net −1 token (3871 → 3870, ceiling 3873); no deletion was needed and no ratchet row moved.Review tier
dispatch-gatesmandates the contract-review tier forskills/**anddocs/adr/**(clause ①); that is waived by the maintainer's standing order in this project (2026-11-03). The seat runs the contract review.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon heada60b01a6e(11 paths vs merge base719a502b2): 75 commands, the same list as rounds 1 and 2, each run sequentially undertimeout 1200withNODE_OPTIONS=--max-old-space-size=8192(raised from the cloud container's default heap) over the fullpnpm build(119/119 tasks), exit code captured before any pipe → 75 × exit 0, none re-run.--ranreconciliation:75 derived famil(ies) accounted for — 75 run, 0 NOT-MEASURED. Rounds 1 (197aa7c88) and 2 (9cea5ade1) were also 75 × exit 0.origin/mainhas moved since, touching none of these paths, so no merge was needed.node scripts/check-commit-card-trailers.mjs --range origin/main..HEAD→ exit 0 before each push.Ran list (head a60b01a)
Human Minimum
packages/specholds no runtime logic. (Nopackages/specchange.)main, one pull request.'@objectstack/skills': patch), and both declaration lines are filled in here and in the changeset.packages/specnot touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.维护者速读section is the maintainer's Chinese summary the fleet asks for.)🤖 Generated with Claude Code
https://claude.ai/code/session_01J8VVAo8GHSrxa8ZR8h3qxs