Repository navigation
Commit 964a237
Part of #24421'; check-partof-closing-keyword with this text as PR_BODY
exits 0, and check-closing-keyword-parity exits 0.
Part of #24421
Clause-②: no (narrowing)
This PR carries items 2 and 5 of ruling A on #24378 (comment 6258883445,
maintainer 「24378 同意 A」), which supersedes ruling B′. ADR-0131 D6 now
charts `sharing_rule` under regime C: a declared sharing rule is
read-only and clonable, and it is switched through the activation
ledger. The scope is the claim amendment 6258985416. The
ownership-ceremony step (each customised copy becomes a clone plus the
base switched off) is a later stage, which carries the card's closing
line.
## What changed
**The guard: every same-name row is refused with 409
`RESOURCE_CONFLICT`, naming the declared rule.** It applies at both
write doors.
- `SharingRuleService.defineRule` covers `POST /api/v1/sharing/rules`,
the SDK's `shares.rules.save` and any service caller. A rule created
under a declared name is refused whether or not that rule is switched
off. The first round's `id`-addressed projection edit is removed, so
there is no customised-copy door. A stored row that already holds a
declared name stays editable as itself: that is the upsert path, not a
new row.
- `bindDeclaredRuleNameGuard`, a `beforeInsert` / `beforeUpdate` hook on
`sys_sharing_rule`, covers the `/data` door. It refuses an insert of a
declared name, and an update that renames a row onto one (judged against
`ctx.previous`). System writes pass.
- `DeclaredSharingRuleNameConflictError` carries `code`
`RESOURCE_CONFLICT` and `status` 409, which is
`HttpStatusErrorCodeMap[409]`. It is catalogued, so nothing is minted.
Its message tells the caller to create their rule under another name and
to switch the declared rule off through the activation door.
**The ledger switch.** `sys_metadata_activation` now records
`metadata_type` `sharing_rule` (`SHARING_RULE_ACTIVATION_TYPE`).
- `declaredRules` reads the ledger once per call, as
`readPositionActive` does: no ledger object means no read, and a failed
read switches nothing off. A declared rule switched off is served in no
scope.
- `bindSharingRuleActivationReconcile` hooks `afterInsert` /
`afterUpdate` / `afterDelete` on the ledger and calls
`reconcileDeclaredRuleActivation(name)` in the same request. Switched
off, the rule's name-keyed grants are purged across every scope.
Switched on, each projection's one-rule pass re-derives them. When
scopes are unknown, nothing is reconciled. A failure is thrown to the
door, which answers 5xx. The ledger row is not rolled back: a
code-registered hook opens no abort unit, so the grants converge at the
next boot sweep or the next flip (contract review 2, ③).
- The door is the existing catalog activation door, `POST
/api/v1/security/_activation/:type/:name`
(`packages/runtime/src/domains/catalog-activation.ts`), with
`sharing_rule` admitted. It enforces the same two tiers:
`manage_metadata`, then the ADR-0126 §5 posture rule, which makes the
switch the platform operator's under a wall. The name is resolved
through the `sharingRules` service (`declaredRuleNamed`), and the row
records the registry item's `_packageId` (new
`DeclaredSharingRule.packageId`). An undeclared name answers 404. With
no service bound, or a failed read, the door answers 503.
**A declaration over a stored row.** When a package declares a rule
whose name a stored row already holds (a package upgrade, or a pre-C
customised copy), the row is never treated as an overlay. It answers as
its own rule. The declaration is withheld in that scope, and the
conflict is logged at `error` once per name and scope per process, with
the remedy (rename the row, and switch the declared rule off if it must
not grant).
**Dropped:** the first round's immediate reconcile on `sys_sharing_rule`
writes, and its delete path. `sharing-plugin.ts`'s rule trigger is back
to `main`'s shape, and the `id` pluck in `sharing-routes.ts` is
reverted.
## Tests
- `pnpm --filter @objectstack/plugin-sharing test`: 48 files / 1156
tests. `typecheck`: exit 0.
- `@objectstack/runtime`: the 7 activation-door test files, 145 tests.
`typecheck`: exit 0.
- `@objectstack/rest`: the sharing-rule test files, 19 tests.
`typecheck`: exit 0.
- `sharing-rule.test.ts`, block "regime C", 17 pins:
- A rule taking the declared name is refused 409 for an org admin and
for a system context, with nothing landing. A projection id is refused.
A switched-off rule still holds its name.
- Clone plus base-off revokes the base's grants everywhere in the same
write, keeps the clone's grants, and leaves nothing for the boot sweep.
Switching it back on re-derives the grants.
- Another type's ledger row does nothing. A failed ledger read revokes
nothing. A switched-off rule cannot be deleted (403).
- A declaration over a stored row is withheld and reported once, and the
row stays editable. Walled, it is reported per organization.
- The `/data` door refuses an insert and a rename onto the name.
- Walled, one ledger row switches the rule off in every organization and
back on. Unknown scopes reconcile nothing.
- `catalog-activation-door.test.ts`, 6 pins for `sharing_rule`: switched
under `single` with the package recorded; operator-only under `group` /
`isolated`; 404 for an undeclared name; 503 with no service; 503 on a
failed read; a row with no package.
- `sharing-rule-declared-name-conflict.test.ts`: `POST /sharing/rules`
answers 409 nested.
- Ablations (`scripts/ablation-replace.mjs`; each leg restored, `blob ==
HEAD`, `git diff HEAD` empty; the tests import `src/` relatively):
| Ablated | Red |
|---|---|
| `defineRule` throw | 5 |
| the ledger filter in `declaredRules` | 3 |
| the ledger hook's reconcile call | 3 |
| the declaration-over-row report | 2 |
| the `/data` hook throw | 2 |
| the door's `declaredRuleNamed` lookup | 3 |
Gates were run locally at `768fc0b2c`: 149 commands, made up of the 101
families `dispatch-gates --commands` derived plus the artifact-roster
block. 145 exited 0. `--ran` reports 101 derived, 101 run, 0
NOT-MEASURED. The other four need PR or event context:
`check-closing-target-claim`, `check-single-claim-paths`,
`check-partof-closing-keyword` (exit 0 with `PR_BODY` set) and
`check-governed-queue-guard`.
## Acceptance notes
- The ADR-0087 marker stays `not-required (no-migration-prescription)`,
as the contract review on the first round decided from ADR-0087 D8 and
ADR-0131 D13. `check:adr-0087-registration` accepts it.
- Files outside the plugin, all within the amended surface or
doc-comment only:
- `runtime/src/domains/{catalog-activation,activation-gate}.ts`: the
activation door.
- `runtime/src/domains/security.ts`, `dispatcher-plugin.ts`,
`route-ledger.ts`: notes now name `sharing_rule`.
- `content/docs/permissions/system-context.mdx` row 40: the guard's
`isSystem` anchor.
- `sys-metadata-activation.object.ts` is unchanged. Its `metadata_type`
description reads "('flow', 'permission', …)" and needed no edit.
- Not in this PR:
- the ceremony step, the stage that finishes the card;
- objectui#12982 (Disable and Clone);
- `content/docs/permissions/sharing-rules.mdx`, which does not describe
regime C yet.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01MaH9HpfmNnJGzgWQPqNmhP)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9524666 commit 964a237
13 files changed
Lines changed: 782 additions & 20 deletions
File tree
- .changeset
- content/docs/permissions
- packages
- plugins/plugin-sharing/src
- rest/src
- runtime/src
- domains
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | | - | |
| 162 | + | |
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
151 | 151 | | |
152 | 152 | | |
153 | 153 | | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
154 | 161 | | |
155 | 162 | | |
156 | 163 | | |
| |||
217 | 224 | | |
218 | 225 | | |
219 | 226 | | |
| 227 | + | |
220 | 228 | | |
221 | 229 | | |
222 | 230 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
| |||
849 | 849 | | |
850 | 850 | | |
851 | 851 | | |
| 852 | + | |
| 853 | + | |
| 854 | + | |
| 855 | + | |
| 856 | + | |
| 857 | + | |
| 858 | + | |
| 859 | + | |
852 | 860 | | |
853 | 861 | | |
854 | 862 | | |
| |||
0 commit comments