You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(sharing): hierarchy managers may manage shares within their write DEPTH (ADR-0111 D1 DEPTH, framework) (#3997)
canManageShares gains a DEPTH branch: a hierarchy manager whose effective write scope (unit / unit_and_below / own_and_reports) covers the record's owner may manage its shares, via the new ISecurityService.resolveWriteScope probe + the enterprise hierarchy-scope-resolver. 'org' from the probe is ignored (doubles as the fail-open default; org management stays gated by explicit modifyAllRecords). Fails closed to owner + Modify-All without the resolver.
Copy file name to clipboardExpand all lines: docs/adr/0111-record-share-management-authority-and-verb-boundary.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# ADR-0111: Record-share management authority and the verb boundary — sharing needs "who may manage a share" and "which verbs a level grants"
2
2
3
-
**Status**: Accepted (2026-07-30) — **P0 + P1 + D8 (framework) implemented**. P0 (D1/D2/D4/D5/D6/D7/D9): `canManageShares` + `hasWriteBypass`, verified by the #3902 Mallory reproduction. P1 (D3, the verb boundary): `canDelete` + verb-split `buildWriteFilter` in `plugin-sharing/src/sharing-service.ts`, routed by the middleware and `/security/explain`, verified by the "edit share cannot delete" suite. D8 (share-link re-share): `ShareLinkService.revokeLink` now admits a record share-manager via the late-bound `canManageShares` probe, and the mint-authority ruling (publicSharing opt-in + visibility) is enforced as before — verified in `share-link-service.test.ts`. **Still open**: the cloud-side D8 wiring (`HttpDispatcher.handleShareLinks` + a `.objectstack-sha` bump, in `objectstack-ai/cloud`) and the DEPTH management extension (D1 D-future) — the follow-ups this ADR's rollout section names.
3
+
**Status**: Accepted (2026-07-30) — **P0 + P1 + D8 (framework) implemented**. P0 (D1/D2/D4/D5/D6/D7/D9): `canManageShares` + `hasWriteBypass`, verified by the #3902 Mallory reproduction. P1 (D3, the verb boundary): `canDelete` + verb-split `buildWriteFilter` in `plugin-sharing/src/sharing-service.ts`, routed by the middleware and `/security/explain`, verified by the "edit share cannot delete" suite. D8 (share-link re-share): `ShareLinkService.revokeLink` now admits a record share-manager via the late-bound `canManageShares` probe, and the mint-authority ruling (publicSharing opt-in + visibility) is enforced as before — verified in `share-link-service.test.ts`. D1 DEPTH extension: `canManageShares` now admits a hierarchy manager whose effective write scope covers the record's owner, via the new `ISecurityService.resolveWriteScope` probe + the enterprise `hierarchy-scope-resolver` (framework side, fails closed to owner+Modify-All without the resolver). **Still open**: the cloud-side wiring for both D8 and the DEPTH extension (`HttpDispatcher.handleShareLinks` + a `.objectstack-sha` bump + `security-enterprise` integration tests, in `objectstack-ai/cloud`) — batched into one framework-SHA bump per this ADR's rollout.
4
4
**Deciders**: ObjectStack Protocol Architects
5
5
**Builds on**: [ADR-0049](./0049-no-unenforced-security-properties.md) (enforce-or-remove — a security property that parses but enforces nothing is worse than absent), [ADR-0057](./0057-erp-authorization-core-business-units-and-scope-depth.md) (DEPTH scopes + the `sys_record_share` / `sys_sharing_rule` split), [ADR-0066](./0066-unified-authorization-model.md) (unified capability model; `modifyAllRecords` super-user bit), [ADR-0078](./0078-no-silently-inert-metadata.md) (no silently inert metadata — a persisted share level or recipient type that no gate consults is exactly this), [ADR-0090](./0090-permission-model-v2-concept-convergence.md) (D1 secure-default OWD, D4 retired aliases, D10 delegated identity intersection), [ADR-0091](./0091-grant-lifecycle-and-recertification.md) (time-boxed grants — the lifecycle axis this ADR deliberately does not re-open)
**Explicitly not**`getEffectiveScope(...) === 'org'`: that helper returns `'org'` for the "no permission set even mentions this object" case (a compatibility fail-*open* baked into the read path). Reusing it as a management gate would be a fresh hole. Management authority keys off the **explicit**`modifyAllRecords` bit only.
92
92
93
-
**DEPTH is a named direction, not in the MVP.** Salesforce (roles above the owner) and Dataverse (the `Share` privilege's BU depth) both let hierarchy managers share subordinates' records. ObjectStack's `__writeScope` (`unit` / `unit_and_below` / `own_and_reports`) is computed today only inside the engine middleware, not on the REST management path; plumbing it here is real work behind the enterprise `hierarchy-scope-resolver`. The MVP is owner + `modifyAllRecords`; D-future records the DEPTH extension so the model has a place for it.
93
+
**DEPTH — landed after the MVP.** Salesforce (roles above the owner) and Dataverse (the `Share` privilege's BU depth) both let hierarchy managers share subordinates' records. The MVP shipped as owner + `modifyAllRecords`; the DEPTH extension then landed on the framework side: rather than reuse the middleware-only `__writeScope` (absent on the REST management path), `canManageShares` resolves the caller's effective write scope through a new `ISecurityService.resolveWriteScope` probe and, for a hierarchy scope only, expands it via the enterprise `hierarchy-scope-resolver` (the same owner set the write filter uses). `org` from that probe is deliberately NOT honoured as manager authority — it doubles as the fail-open "no permission set matches" default — so org-wide management stays gated by the explicit `modifyAllRecords` bypass. Fails closed to owner + Modify-All when the resolver (open edition) or security service is absent.
94
94
95
95
**Grant may never exceed the granter's own level** (Dataverse's rule). A caller who may manage sharing but is not owner/super-user may only grant a level they themselves hold on the record. In the MVP this is trivially satisfied (only owner/super-user pass the gate, and both hold every level), but the constraint is stated now so the DEPTH extension inherits it rather than bolting it on later.
0 commit comments