Found while implementing #8119 (the assertSharingEnforced half). Filed unassigned, deliberately not fixed there: #8119's scope ruling names assertSharingEnforced only, and this is about who the guard runs for, not what it checks.
The shape
SharingService.grant runs the ADR-0111 D7 posture guard and the D1 management gate only for non-system callers (sharing-service.ts):
if(!context?.isSystem){this.assertSharingEnforced(input.object);awaitthis.assertCanManageShares(input.object,input.recordId,context);}The comment above it states the intent — "System callers bypass: the rule evaluator materialises through here under its own validation." The management half (D1) is clearly right to skip: a rule is not a principal and has no ownership to prove.
The posture half is a different question. assertSharingEnforced is not an authorization check — it is an inertness check (its own docblock says so: "an inertness guard, not the authority gate"). It answers "would any gate ever read a row on this object?", and that answer does not depend on who is asking. A rule-materialised row on a public_read_write object, a controlled_by_parent detail, an owner-less object, or (after #8119) a federated phantom-anchor object is exactly as inert as a manually granted one — the ADR-0078 silently-inert trap, arriving through the door the guard does not watch.
Why this is observation-class, not a live defect
I did not find a shipped app that declares a sharing rule on any of those object classes, so as far as I measured the path is dormant. What I did measure (booted showcase stack, #8119's evidence) is only that the guard is skipped for system context by construction, and that the non-system path now refuses all four classes.
The "own validation" the comment refers to is the rule evaluator's — worth checking whether it independently rejects these object classes before materialising. If it does, this is a documentation gap rather than a hole; if it does not, an authored rule pointed at one of those objects mints rows that look granted and enforce nothing. That check is the first step of triaging this, and I did not do it.
Suggested direction (not a decision)
Split the two halves rather than moving the whole block: keep the D1 management gate system-skipped, and let the D7 inertness guard run for every caller. It refuses nothing that could have worked, so it is the fail-closed direction — the same argument that authorised #8119's Phase 2. Worth confirming it does not break the boot-time rule backfill (SharingServicePlugin: boot rule backfill done), which reconciles declared rules through this path on every start.
Related
Generated by Claude Code
Found while implementing #8119 (the
assertSharingEnforcedhalf). Filed unassigned, deliberately not fixed there: #8119's scope ruling namesassertSharingEnforcedonly, and this is about who the guard runs for, not what it checks.The shape
SharingService.grantruns the ADR-0111 D7 posture guard and the D1 management gate only for non-system callers (sharing-service.ts):The comment above it states the intent — "System callers bypass: the rule evaluator materialises through here under its own validation." The management half (D1) is clearly right to skip: a rule is not a principal and has no ownership to prove.
The posture half is a different question.
assertSharingEnforcedis not an authorization check — it is an inertness check (its own docblock says so: "an inertness guard, not the authority gate"). It answers "would any gate ever read a row on this object?", and that answer does not depend on who is asking. A rule-materialised row on apublic_read_writeobject, acontrolled_by_parentdetail, an owner-less object, or (after #8119) a federated phantom-anchor object is exactly as inert as a manually granted one — the ADR-0078 silently-inert trap, arriving through the door the guard does not watch.Why this is observation-class, not a live defect
I did not find a shipped app that declares a sharing rule on any of those object classes, so as far as I measured the path is dormant. What I did measure (booted showcase stack, #8119's evidence) is only that the guard is skipped for system context by construction, and that the non-system path now refuses all four classes.
The "own validation" the comment refers to is the rule evaluator's — worth checking whether it independently rejects these object classes before materialising. If it does, this is a documentation gap rather than a hole; if it does not, an authored rule pointed at one of those objects mints rows that look granted and enforce nothing. That check is the first step of triaging this, and I did not do it.
Suggested direction (not a decision)
Split the two halves rather than moving the whole block: keep the D1 management gate system-skipped, and let the D7 inertness guard run for every caller. It refuses nothing that could have worked, so it is the fail-closed direction — the same argument that authorised #8119's Phase 2. Worth confirming it does not break the boot-time rule backfill (
SharingServicePlugin: boot rule backfill done), which reconciles declared rules through this path on every start.Related
owner_idon federated objects (the threehasOwnerFieldconsumers #7858 did not touch) #8119 — the federated phantom-anchor class this guard now refuses on the non-system path.Generated by Claude Code