Repository navigation
docs(spec,plugin-sharing): the master_chain refusals can name the record's own master; canEdit no longer lists the attachment and comment parent gates - #22535
Conversation
…ord's own master; canEdit no longer lists the attachment and comment parent gates The ControlledByParentWriteDenialLeg docblock said the master_chain arm's three resolution refusals name a master above the record's own master. The walk resolves the record's own master first, so when that master is itself controlled_by_parent the refusals can name it too. The two canEdit docblocks (ISharingService and SharingService) listed the sys_attachment and sys_comment parent gates among canEdit's callers; those gates read checkEdit and the master-detail write check now. Docblock sentences only: no type change, no code change. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…tences Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 915dc10c1774831192b2f4313a070b4c12bd3454 && git checkout 915dc10c1774831192b2f4313a070b4c12bd3454
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ce78ff7bcd850f440b2bacc25a35cedbcc847c56 4a6d71cfcda08d9bf78ecc959292bc516f43ee61 && git checkout -B drift-repro ce78ff7bcd850f440b2bacc25a35cedbcc847c56 && git merge --no-ff 4a6d71cfcda08d9bf78ecc959292bc516f43ee61
node scripts/docs-audit/affected-docs.mjs --json ce78ff7bcd850f440b2bacc25a35cedbcc847c56
|
…the master_chain leg, on the first hop or above it The record_not_found bullet of ControlledByParentWriteUnresolvedReason said the legs judge a missing master row on the first hop and only above it is it the master_chain leg. The walk reads a controlled_by_parent master's row after the legs on every iteration, the first included, so an absent row there answers master_chain. The changeset names the sentence. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #22497 (body and all seven comments), PR #22535 (body, its one comment, file list), the net diff of ① Derived judgmentsEvery hunk in the diff is inside a
② Semver levelThe diff publishes in exactly two packages, and each has its own entry in
No third package is touched. ③ Boundary flags
Dev deviations, each answered:
The red check on this head. 46 check-runs; every required context is green except Governance and size. None of the four paths is a governed surface ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…ster-chain-docblock
|
Regen-provenance: 6089801286 ·
The head moved after the contract review PASS
The record carries to Generated by Claude Code |
Fixes #22497
Clause-②: no
Four docblock sentences. No type change, no code change, no test change.
@objectstack/specand@objectstack/plugin-sharingpatch: the built.d.tsof both carries the text (checked below, with a positive control).What changed
ControlledByParentWriteDenialLeg, themaster_chainbullet (packages/spec/src/contracts/security-service.ts).controlled_by_parent(the record's own master, or any master above it) has no relation to derive its own master from, is not present, or has an empty master reference".ISharingService.canEdit(packages/spec/src/contracts/sharing-service.ts). The write-depth paragraph said thesys_commentandsys_attachmentparent gates "reach this service through this method"; it now says through{@link checkEdit}. The two-state-projection paragraph's caller list drops thesys_attachmentparent gate.SharingService.canEdit(packages/plugins/plugin-sharing/src/sharing-service.ts). The same caller list drops thesys_attachmentparent gate.ControlledByParentWriteUnresolvedReason, therecord_not_foundbullet (packages/spec/src/contracts/security-service.ts). Added by thedomain:specseat's order on the card, after the missing-row probe below.master_chainleg."controlled_by_parentthe walk reads that row to derive the next master, so an absent row after the legs is themaster_chainleg, on the first hop or above it."The changeset's first bullet names this fourth sentence.
The
allow-arm clause is not in this PR: it belongs to #22455's serving decision, per the triage comment on the card.Measured before editing
The first hop. At this PR's base
ee8751d41e,SecurityPlugin.assertControlledByParentWriterunsassertMasterRowEditablefor the record's own master on the first loop iteration (security-plugin.ts:9473). It then setsmasterObject = hopRel.master(:9477), which is that same master. WhendeclaresControlledByParent(masterObject)holds (:9478) the walk resolves that master's relation (:9496-:9503), reads its row (:9507-:9515) and its master reference (:9516-:9523). Every one of those refusals namesmasterObject. The card's:9320-:9353anchors have moved; the walk is now:9468-:9526.Observed through the real engine middleware and the registered
checkControlledByParentWritemember, on the fixture ofcontrolled-by-parent-write-member.test.tsplus two rows. The probe was a throwaway copy of that test file; it was deleted and is not in this PR.In all three the named master is the record's own master, not one above it. The suite's existing
master_chaincase is the first row of this probe. The second row is the missing-row case behind the fourth sentence: the record's own master is itselfcontrolled_by_parent, its row is absent, and the answer ismaster_chainon the first hop.The canEdit callers. #22513 is merged (
ce3d0ad419) and is in this base. The attachment gate callscheckEdit(attachment-access-hooks.ts:402) andcheckControlledByParentWrite(:428). The comment gate does the same (comment-access-hooks.ts:422,:448). Neither callscanEdit, and both type their sharing dependency as a pick ofcheckEditalone. They still hand the service a context with the operation-private keys stripped, so the write-depth paragraph's behaviour (ownership matched atown) is unchanged. Both lists were stale. The "Write DEPTH is an input the CALLER supplies" section of the same file says the gates "ask this service" without namingcanEdit, so it was correct and is untouched.Verification
Head
9e21e91be7, baseee8751d41e. The commits up to99f06a1073carry the other three docblocks; the last commit adds the fourth sentence and one changeset clause.At
9e21e91be7:pnpm --filter @objectstack/spec build, thencheck:generated: all 15 generated artifacts up to date,--fixnot needed. The builtdist/contracts/index.d.tscarries the newrecord_not_foundsentence. No generated artifact renders these docblocks:check:generatedfinds nothing stale.dispatch-gates --commandswith no paths re-derived the same 83 families over the same four paths (48 changed lines, +32/-16). 82 run, all exit 0, one NOT MEASURED. The changeset gates are among the 82.pnpm check:i18nand thelintdoc-formula gate passed after their prerequisite builds.Carried over from
99f06a1073, because the delta since is one comment sentence and one changeset clause on an unchanged path set:@objectstack/speccontracts tests: 45 files, 441 tests passed. Not re-run at the new head.@objectstack/plugin-sharingbuild andtypecheck: green. Publish check with a positive control: its builtdist/index.d.tscarries the editedSharingService.canEditsentence and the uneditedTri-state UPDATE verdictdocblock beside it; the old list text has 0 hits;files[]isdist,README.md,CHANGELOG.md.NOT MEASURED:
pnpm check:dual-build-cjs-loads(no whole-workspace build, per the dispatch).pnpm check:published-readme-exportsexits 3,PREREQUISITE NOT METfor six unbuilt packages; the diff changes no README and no export. CI owns both. Not run, declared to CI: full suites,pnpm lint, the declared wide-population gates, the 5 path-scheduled CI jobs.Deviations
record_not_foundbullet above. The changeset headline reads "four contract docblocks" for that reason.origin/mainmoved 6 commits after the base. None touches the four paths of this PR (checked atfaf6348508), so no merge commit was added.Acceptance notes (noted, not edited)
controlled-by-parent-write-member.test.ts:306is titled "a master above the record's own master with no master reference"; its fixture is the first-hop case. The assertion (deny,master_chain) is right, so the title is the only thing out of step.security/explainis also acanEditcaller (security-plugin.ts:5526, throughcanEditRecord) and neither list names it. That omission predates fix(service-storage,plugin-audit,plugin-security)!: the attachment and comment parent gates judge a controlled_by_parent parent through its master #22513.sys_attachmentparent gate as acanEditconsumer:permission-evaluator.ts:369-:370andexplain-engine.ts:1950-:1951, as does a dogfood fixture comment,packages/qa/dogfood/test/fixtures/attachments-fixture.ts:15. All three are comments outside the docblocks this card names, with no runtime reader.Generated by Claude Code