Skip to content

Commit e9b612a

Browse files
committed
docs(sharing): patch round 2 — deferred-revoke precision, bu-tree docblock, operator cost sentence
Documentation-only follow-up on the contract review's non-blocking §5 notes 2, 3 and 4. No code path changes: the only .ts edit is a docblock body. - system-context.mdx row 30: "Lose: nothing" glossed the one shape where the cascade's delivery is the queued orphan sweep rather than a synchronous revoke. The cell now says the revoke is delivered but deferred on the unbounded shape. - bu-tree-recompute.ts: the docblock still described bindRuleHooks' materialisation skip as current. It now says what bindRuleHooks does after this change and why this file's own hooks never skipped system writes. - changeset: one sentence on the operator-visible cost of seed- and import-time system writes on rule-covered objects, and "unexported" becomes "not exported from the package entry point". Level stays patch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AUF1NoViznQK32gqpK8wS8
1 parent 25d0307 commit e9b612a

3 files changed

Lines changed: 17 additions & 9 deletions

File tree

.changeset/system-write-sharing-materialization.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ set as *unbounded*, which would have turned every single-row system update into
2929
an object-wide revoke plus an asynchronous re-grant). The INFO line that
3030
announced the skip — `[sharing-rule] sharing materialisation skipped for isSystem
3131
writes; re-evaluate rules or restart to backfill` — is retired with it, along
32-
with the unexported `SYSTEM_WRITE_SKIP_NOTICE` constant.
32+
with the `SYSTEM_WRITE_SKIP_NOTICE` constant, which was not exported from the
33+
package entry point. Operationally this means seed- and import-time system writes
34+
on rule-covered objects now pay per-record sharing evaluation at write time — the
35+
cost a user write of the same shape has always paid, with the
36+
`kernel:bootstrapped` backfill still reconciling behind it.
3337

3438
**What does not change.** `afterDelete` still skips system writes, on separate
3539
grounds: what it skips is revocation, and `record-share-cascade.ts` delivers that

content/docs/permissions/system-context.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ The largest single consumer — **17 of the 106 sites**.
128128

129129
| # | Behaviour when `isSystem` | What you get / what you lose | Anchor |
130130
|:--|:---|:---|:---|
131-
| 30 | **Sharing-rule REVOCATION is skipped on the record-`afterDelete` hook** — and on that hook only | Lose: nothing. The payload is delivered by another subscriber: `record-share-cascade.ts` binds on every sharing-capable object and stashes for system writes on its own account (#5103), with the boot orphan sweep behind it. This is one subscriber declining work another owns, not elevation silencing a consequence. ⚠️ **Grant MATERIALISATION no longer asks** — the `afterInsert` / `afterUpdate` skips, and the `before*` stash skip that fed them, were removed by the 2026-08-31 ruling on #13533; a system write materialises exactly as a user write does | `rule-hooks.ts:292` |
131+
| 30 | **Sharing-rule REVOCATION is skipped on the record-`afterDelete` hook** — and on that hook only | Lose: nothing permanently — the revoke is **delivered, but deferred on the unbounded shape**. The payload belongs to another subscriber: `record-share-cascade.ts` binds on every sharing-capable object and stashes for system writes on its own account (#5103). When the deleted ids are enumerable it revokes inline; when they are not — a predicate delete whose row set the stash could not resolve — it hands the reclaim to a queued background orphan sweep instead, so the share rows outlive the deleted records until that sweep runs, with the boot orphan sweep behind it. No surviving record loses access either way, and a restart re-runs the same sweep. This is one subscriber declining work another owns, not elevation silencing a consequence. ⚠️ **Grant MATERIALISATION no longer asks** — the `afterInsert` / `afterUpdate` skips, and the `before*` stash skip that fed them, were removed by the 2026-08-31 ruling on #13533; a system write materialises exactly as a user write does | `rule-hooks.ts:292` |
132132
| 31 | Sharing write verdict short-circuits to `allow` | Get: writes pass the sharing gate unconditionally | `plugin-sharing/src/sharing-service.ts:654` |
133133
| 32 | Record visibility / manage-shares checks return true | Get: no ownership or Modify-All requirement | `plugin-sharing/src/sharing-service.ts:920`, `:1007`, `:1597` |
134134
| 33 | `grant()` skips the enforcement + manage-shares assertions | Get: the rule evaluator can materialise through the public API. Note it is **not** a bare skip: the system branch asserts the grant is not *inert* instead (a grant on an object no verdict can consult is refused) | `plugin-sharing/src/sharing-service.ts:1208` |

packages/plugins/plugin-sharing/src/bu-tree-recompute.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,13 +205,17 @@ export function writeCanChangeExpansion(objectName: string, event: string, hookC
205205
* nothing here needs the pre-write state. What the revoke needs is the tree as
206206
* it is NOW, and the write is what makes that true.
207207
*
208-
* **System writes are NOT skipped**, which is the opposite of what
209-
* `bindRuleHooks` does and is deliberate. Its skip is about grant
210-
* MATERIALISATION, which the boot backfill re-does anyway. Here the payload is
211-
* REVOCATION, and the realistic production trigger for a re-parent is an HRIS
212-
* or directory sync — a system write. Skipping those would leave the hole open
213-
* on the very path most likely to open it. `primary-bu-projection.ts` reached
214-
* the same conclusion for the same table.
208+
* **System writes are NOT skipped**, and this file's hooks never carried an
209+
* `isSystem` branch to skip them with. `bindRuleHooks` no longer skips them
210+
* either: its `afterInsert` / `afterUpdate` materialisation skips, and the
211+
* `before*` stash skip that fed them, were removed by the 2026-08-31 ruling on
212+
* #13533, so a system write there materialises grants exactly as a user write
213+
* does — the one skip it keeps is `afterDelete` revocation, which
214+
* `record-share-cascade.ts` delivers instead. Here the payload is REVOCATION,
215+
* and the realistic production trigger for a re-parent is an HRIS or directory
216+
* sync — a system write. Skipping those would leave the hole open on the very
217+
* path most likely to open it. `primary-bu-projection.ts` reached the same
218+
* conclusion for the same table.
215219
*/
216220
export function bindBusinessUnitTreeRecompute(
217221
engine: MinimalEngine,

0 commit comments

Comments
 (0)