Skip to content

Commit 83c661d

Browse files
docs(plugin-sharing): record the write-scope provenance omission as ruled, not oversight (#9114)
The bulk-write merge in `buildSharingMiddleware` carries no `markFilterSubtreeProvenance` and no `vouchCallerWhereBeforeRewrite`, while the read path above marks all three of its injected scopes and vouches the caller's `where`. Maintainer ruling 2026-08-15 took the "no" branch on #8792: #8220's mark stays read-scope, and write-scope refusal semantics stay unspecified rather than inherited. Comments only at the two write-side merge sites, so the next reader does not "complete" the pattern by reflex — which would widen what a bulk-write refusal is permitted to name on a boundary whose current behaviour is the #7929 redaction. Includes the ruling's named re-open trigger. Claude-Session: https://claude.ai/code/session_01Y26DJEHSBhhAQ6wwfsHNza Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0961065 commit 83c661d

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

packages/plugins/plugin-sharing/src/sharing-plugin.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,41 @@ export function buildSharingMiddleware(
10091009
// may write, exactly as the read path scopes finds. The verb is threaded
10101010
// through so a bulk DELETE scopes to owned rows alone (no share widening),
10111011
// while a bulk UPDATE keeps the edit-share widening (ADR-0111 D3).
1012+
//
1013+
// [#8792, maintainer ruling 2026-08-15] ⛔ This merge carries NO
1014+
// `markFilterSubtreeProvenance` and NO `vouchCallerWhereBeforeRewrite`,
1015+
// and that is RULED, not an oversight. #8220 declares the mark for
1016+
// READ-scope merge boundaries; write-scope refusal semantics stay
1017+
// deliberately unspecified rather than inherited from it. So the
1018+
// asymmetry with the read path above — which marks each injected scope
1019+
// `'policy'` and vouches the caller's `where` `'author'` — is the ruled
1020+
// boundary itself. ⛔ Do not "complete" the pattern here. Why:
1021+
//
1022+
// - nothing is disclosed by the omission. Unmarked ⇒ WITHHELD, so a
1023+
// cross-field refusal raised anywhere in this composed tree already
1024+
// keeps the #7929 redaction. Unmarked is the SAFE fail direction, and
1025+
// it is what happens today;
1026+
// - marking the injected filter `'policy'` would therefore change no
1027+
// behaviour at all, and the only thing the `'author'` vouch would ADD
1028+
// is the author's own diagnostic on the author's own predicate — an
1029+
// author-experience gain, never a disclosure fix;
1030+
// - ⚠️ and that gain is not free, which is the real reason. Vouching the
1031+
// caller's `where` on a WRITE boundary WIDENS what a bulk
1032+
// update/delete refusal is permitted to name, on a boundary whose
1033+
// current behaviour is exactly that redaction. Widening a disclosure
1034+
// surface is a product decision wearing a lint's clothing;
1035+
// - and there is no inherited answer to widen it BY. A bulk write that
1036+
// refuses has already passed the per-verb gate (ADR-0111 D3) and the
1037+
// `probeAuthoredRowWrite` deferral above, so "what may a refusal name
1038+
// here" is a question read scope never had to answer.
1039+
//
1040+
// Re-open trigger, named by the ruling so this is not a permanent "no":
1041+
// a real report of an author unable to diagnose a bulk-write refusal on
1042+
// their own predicate. That makes the extension a pulled feature with a
1043+
// consumer attached, and it returns as a decision — with write-scope
1044+
// refusal semantics to specify and pin at a real driver, and #8836's
1045+
// request-scoped invariant to carry (no filter object that can be
1046+
// vouched `'author'` may outlive the request that vouched it).
10121047
let writeFilter = await service.buildWriteFilter(ctx.object, exec ?? {}, verb);
10131048
// [ADR-0090 D10] Intersect the delegator's writable set for on-behalf-of.
10141049
if (exec?.onBehalfOf?.userId) {
@@ -1018,6 +1053,10 @@ export function buildSharingMiddleware(
10181053
onBehalfOf: undefined,
10191054
__writeScope: exec.__delegatorWriteScope,
10201055
}, verb);
1056+
// [#8792] Unmarked deliberately too — same ruling, same reasons as
1057+
// above, not repeated here. Its read-path twin (the delegator's
1058+
// `buildReadFilter`) IS marked `'policy'`; that difference is the ruled
1059+
// read/write boundary, not drift between two copies of one pattern.
10211060
writeFilter = composeAnd(writeFilter, delFilter);
10221061
}
10231062
if (writeFilter) {

0 commit comments

Comments
 (0)