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
* wip: fix sharing-rule withdrawal (#4433) and DELETE 500 (#4434)
An over-granting sharing rule had no withdrawal path on the API surface:
deactivating it never withdrew its materialized grants, and the DELETE
route answered 500 for both address forms. RC-exit blockers, since v17
advertises "switching a rule off actually withdraws access".
#4434 — deleteRule purged sys_record_share with a predicate-shaped
engine.delete carrying neither a scalar id nor multi:true, the one shape
the engine's dispatch refuses. It threw before ever reaching the rule
row, so every DELETE /sharing/rules/:idOrName 500'd. Routed through
purgeRuleGrants instead of adding multi:true, so a rule's grants retire
exactly one way — SharingService.revoke, as every other withdrawal path
already does (AGENTS.md PD #5).
#4433 — three independent gaps, one per path the issue walked:
- deactivation: the rule-write trigger skipped reconcile on every
isSystem write. defineRule (the only implementation behind
POST /sharing/rules) writes with SYSTEM_CTX unconditionally, so the
skip caught 100% of REST authoring. Now gated on boot phase, the
question the skip actually meant to ask.
- record touch: evaluateAllForRecord listed activeOnly rules, so a
deactivated rule was absent from the loop and its grants were never
examined. Lists every rule; inactive ones desire nothing and take
reconcileForRecord's existing revoke branch.
- boot: the backfill was handed activeOnly rules, making it
structurally incapable of revoking. Walks every rule, plus a new
sweepOrphanedRuleGrants for grants whose rule row is gone entirely
(unreachable by rule iteration).
Test fakes: makeEngine().delete now mirrors the real engine's dispatch
guard. The looser fake is why #4434 shipped green — the pre-existing
"deleteRule drops rule + all its grants" test passed against a delete
the running server always rejected.
Progress: service + plugin fixes done, regression tests added for both
issues. Remaining: rule-rebind.test.ts still pins the old isSystem skip
and needs updating; full gates + live-server verification pending.
Refs #4433, #4434
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
* test(sharing): pin the boot-phase reconcile contract (#4433)
rule-rebind.test.ts asserted the defect: 'skips system-context writes'
passed a mocked session the real REST path never sends, then asserted
the reconcile did NOT happen. Replaced with the corrected contract —
deferral is about WHEN a write happens (before the kernel:bootstrapped
backfill), not WHO made it — plus cases for both session kinds after
boot and the seeding deferral during it.
pnpm --filter @objectstack/plugin-sharing test: 243 passed (11 files).
Refs #4433
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
* docs(sharing): document the withdrawal lifecycle + add changeset
Live verification on a persistent SQLite datastore confirmed all three
withdrawal moments and both DELETE address forms, so the guarantee is now
worth stating: an over-granting sharing rule is always recoverable from
the API surface.
The CI docs-drift advisory flagged six hand-written pages against this
change. None asserted anything false — the withdrawal timing was simply
undocumented, which is how the behaviour drifted unnoticed in the first
place. Added the missing section to permissions/sharing-rules.mdx (the
three reconcile moments, delete-by-id-or-name, the boot sweep for grants
whose rule row is gone) and sharpened permissions-matrix.mdx, which said
rules re-evaluate on record insert/update and stopped there — true but
partial now that rule writes and boot also reconcile. The other four
flagged pages only list the plugin or its layer and needed no change.
content/docs/references/ is generated and was not touched.
Changeset: minor on @objectstack/plugin-sharing. Not patch — a
`source: 'rule'` grant whose rule is inactive or gone now disappears on
upgrade, and DELETE starts succeeding where it used to 500, so callers
that treated that 500 as "unsupported" will now really delete.
Refs #4433, #4434
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/permissions/permissions-matrix.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -161,7 +161,7 @@ Sharing rules extend access beyond ownership and the depth axis. The declarative
161
161
</Callout>
162
162
163
163
<Callouttype="info">
164
-
**Beyond declarative rules:** Two other sharing mechanisms exist but are **not**`SharingRule` types. **Manual sharing** is a runtime grant — `sys_record_share` rows created with `source: 'manual'` (see `packages/plugins/plugin-sharing/src/sharing-service.ts`). **Matrix / territory-shaped access** is served today by multi-position assignment anchored on business units, plus pre-resolved membership sets: a registered `rls-membership-resolver` (ADR-0105 D11) stages e.g. `territory_account_ids` into `ExecutionContext.rlsMembership`, and a policy references it as `account_id IN (current_user.territory_account_ids)`. The aspirational `TerritorySchema` was removed in ADR-0105 D11 — it had no runtime object, stack field or resolver; a generalized dimension-security module will arrive with its own ADR. Owner/criteria rules are re-evaluated on insert/update via internal sharing rule hooks.
164
+
**Beyond declarative rules:** Two other sharing mechanisms exist but are **not** `SharingRule` types. **Manual sharing** is a runtime grant — `sys_record_share` rows created with `source: 'manual'` (see `packages/plugins/plugin-sharing/src/sharing-service.ts`). **Matrix / territory-shaped access** is served today by multi-position assignment anchored on business units, plus pre-resolved membership sets: a registered `rls-membership-resolver` (ADR-0105 D11) stages e.g. `territory_account_ids` into `ExecutionContext.rlsMembership`, and a policy references it as `account_id IN (current_user.territory_account_ids)`. The aspirational `TerritorySchema` was removed in ADR-0105 D11 — it had no runtime object, stack field or resolver; a generalized dimension-security module will arrive with its own ADR. Criteria rules are re-evaluated on record insert/update via internal sharing rule hooks, on every write to the rule itself, and once per boot — so deactivating or deleting a rule withdraws the grants it materialized (see [Sharing Rules](/docs/permissions/sharing-rules#switching-a-rule-off-withdraws-the-access-it-granted)).
|**Thewritethatdeactivatesoreditstherule**|Thatrule's grants, immediately — deactivating with `active: false` (or `POST {basePath}/sharing/rules` with the same name) revokes them before the call returns |
174
+
|**Thenextinsert/updateofamatchingrecord**|Thatrecord's grants for every rule on the object — an inactive rule desires nothing, so its rows are revoked |
0 commit comments