Repository navigation
Commit b3a3634
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)
Fixes #22497
Clause-②: no
Four docblock sentences. No type change, no code change, no test change.
`@objectstack/spec` and `@objectstack/plugin-sharing` `patch`: the built
`.d.ts` of both carries the text (checked below, with a positive
control).
## What changed
1. **`ControlledByParentWriteDenialLeg`, the `master_chain` bullet**
(`packages/spec/src/contracts/security-service.ts`).
- Before: "or a master ABOVE the record's own master has no relation to
derive from, is not present, or has an empty master reference".
- After: "or a master that is itself `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".
- The next sentence's frame changes from "Above the first hop" to "Past
the record itself" (see Deviations).
2. **`ISharingService.canEdit`**
(`packages/spec/src/contracts/sharing-service.ts`). The write-depth
paragraph said the `sys_comment` and `sys_attachment` parent gates
"reach this service through this method"; it now says through `{@link
checkEdit}`. The two-state-projection paragraph's caller list drops the
`sys_attachment` parent gate.
3. **`SharingService.canEdit`**
(`packages/plugins/plugin-sharing/src/sharing-service.ts`). The same
caller list drops the `sys_attachment` parent gate.
4. **`ControlledByParentWriteUnresolvedReason`, the `record_not_found`
bullet** (`packages/spec/src/contracts/security-service.ts`). Added by
the `domain:spec` seat's order on the card, after the missing-row probe
below.
- Before: "A missing master row is not one of these outcomes: on the
first hop the legs judge it as they judge any master row, and above the
first hop it is the `master_chain` leg."
- After: "A missing master row is not one of these outcomes: where the
master governs its own rows the legs judge it as they judge any master
row, and where it is itself `controlled_by_parent` the walk reads that
row to derive the next master, so an absent row after the legs is the
`master_chain` leg, 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.assertControlledByParentWrite` runs
`assertMasterRowEditable` for the record's own master on the first loop
iteration (`security-plugin.ts:9473`). It then sets `masterObject =
hopRel.master` (`:9477`), which is that same master. When
`declaresControlledByParent(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 names `masterObject`. The card's `:9320`-`:9353`
anchors have moved; the walk is now `:9468`-`:9526`.
Observed through the real engine middleware and the registered
`checkControlledByParentWrite` member, on the fixture of
`controlled-by-parent-write-member.test.ts` plus two rows. The probe was
a throwaway copy of that test file; it was deleted and is not in this
PR.
```
cbpm_line/line_above_dangling -> {"outcome":"deny","leg":"master_chain"} :: ... (master 'cbpm_quote' has no 'account' master reference to derive edit access from)
cbpm_line/line_missing_master -> {"outcome":"deny","leg":"master_chain"} :: ... (master 'cbpm_quote' record 'q_missing' is not present, so its own master access cannot be derived)
cbpm_orph_child/oc1 -> {"outcome":"deny","leg":"master_chain"} :: ... (master 'cbpm_orphan' declares controlled_by_parent with no relation to derive edit access from)
```
In all three the named master is the record's own master, not one above
it. The suite's existing `master_chain` case 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 itself `controlled_by_parent`, its
row is absent, and the answer is `master_chain` on the first hop.
**The canEdit callers.** #22513 is merged (`ce3d0ad419`) and is in this
base. The attachment gate calls `checkEdit`
(`attachment-access-hooks.ts:402`) and `checkControlledByParentWrite`
(`:428`). The comment gate does the same (`comment-access-hooks.ts:422`,
`:448`). Neither calls `canEdit`, and both type their sharing dependency
as a pick of `checkEdit` alone. They still hand the service a context
with the operation-private keys stripped, so the write-depth paragraph's
behaviour (ownership matched at `own`) 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 naming `canEdit`, so
it was correct and is untouched.
## Verification
Head `9e21e91be7`, base `ee8751d41e`. The commits up to `99f06a1073`
carry the other three docblocks; the last commit adds the fourth
sentence and one changeset clause.
At `9e21e91be7`:
- `pnpm --filter @objectstack/spec build`, then `check:generated`: all
15 generated artifacts up to date, `--fix` not needed. The built
`dist/contracts/index.d.ts` carries the new `record_not_found` sentence.
No generated artifact renders these docblocks: `check:generated` finds
nothing stale.
- `dispatch-gates --commands` with 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:i18n` and the `lint` doc-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/spec` contracts tests: 45 files, 441 tests passed. Not
re-run at the new head.
- `@objectstack/plugin-sharing` build and `typecheck`: green. Publish
check with a positive control: its built `dist/index.d.ts` carries the
edited `SharingService.canEdit` sentence and the unedited `Tri-state
UPDATE verdict` docblock beside it; the old list text has 0 hits;
`files[]` is `dist`, `README.md`, `CHANGELOG.md`.
- The artifact-roster block: 48 of 51 run, 47 exit 0. The 3 PR-context
guards, wired to this PR, exit 0.
NOT MEASURED: `pnpm check:dual-build-cjs-loads` (no whole-workspace
build, per the dispatch). `pnpm check:published-readme-exports` exits 3,
`PREREQUISITE NOT MET` for 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
- **One word-frame change in a second sentence.** The card asks for one
sentence. The sentence after it said "Above the first hop the check
answers these as refusals". Once the first sentence says the record's
own master can be named, that frame contradicts it, so it now reads
"Past the record itself". It is a four-word change and reverts alone.
The seat accepted it on the card.
- **The surface widened by one bullet** by the seat's order on the card:
the `record_not_found` bullet above. The changeset headline reads "four
contract docblocks" for that reason.
- **`origin/main` moved 6 commits** after the base. None touches the
four paths of this PR (checked at `faf6348508`), so no merge commit was
added.
## Acceptance notes (noted, not edited)
- **A test title repeats the old wording.**
`controlled-by-parent-write-member.test.ts:306` is 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/explain` is also a `canEdit` caller**
(`security-plugin.ts:5526`, through `canEditRecord`) and neither list
names it. That omission predates #22513.
- **Three more comments still list the `sys_attachment` parent gate as a
`canEdit` consumer:** `permission-evaluator.ts:369`-`:370` and
`explain-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](https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ebbb8f9 commit b3a3634
4 files changed
Lines changed: 32 additions & 16 deletions
File tree
- .changeset
- packages
- plugins/plugin-sharing/src
- spec/src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
816 | 816 | | |
817 | 817 | | |
818 | 818 | | |
819 | | - | |
820 | | - | |
821 | | - | |
822 | | - | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
823 | 823 | | |
824 | 824 | | |
825 | 825 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
312 | 312 | | |
313 | 313 | | |
314 | 314 | | |
315 | | - | |
316 | | - | |
317 | | - | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
318 | 319 | | |
319 | 320 | | |
320 | 321 | | |
| |||
339 | 340 | | |
340 | 341 | | |
341 | 342 | | |
342 | | - | |
343 | | - | |
344 | | - | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
345 | 348 | | |
346 | 349 | | |
347 | 350 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
291 | 291 | | |
292 | 292 | | |
293 | 293 | | |
294 | | - | |
295 | | - | |
296 | | - | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
297 | 298 | | |
298 | 299 | | |
299 | 300 | | |
300 | 301 | | |
301 | 302 | | |
302 | 303 | | |
303 | | - | |
304 | | - | |
305 | | - | |
| 304 | + | |
| 305 | + | |
306 | 306 | | |
307 | 307 | | |
308 | 308 | | |
| |||
0 commit comments