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
{{ message }}
Repository navigation
Commit 4289f44
Browse filesBrowse the repository at this point in the historyBrowse files
fix(plugin-auth): the self-registration grant settles on the registrant's first membership, owned by that organization (ADR-0131 D3)
6
+
7
+
Clause-②: no
8
+
9
+
The declared self-registration permission set (`auth.audience.selfRegistrationPermissionSet`) is an assignment, and an assignment is an organization row (ADR-0131 D3). It is now written with the registrant's first membership and owned by that membership's organization, under every posture. It is never written without an organization.
10
+
11
+
-**`single`: unchanged.** The membership reconciler binds the Default Organization in the same `user.create.after` hook, so a sign-up still ends with one grant, owned by the Default Organization.
12
+
-**A wall (`group`, `isolated`).** A self-registrant belongs to no organization at sign-up, so the sign-up writes no `sys_user_permission_set` row. It used to write one with no organization. The grant is held as a pending claim in `sys_verification`, which is deployment-level and survives a restart. The first `sys_member` row written for the user lands exactly one grant, owned by that organization. That row can come from creating an organization (the creator becomes its owner), accepting an invitation, being added by an administrator, or any other membership writer. A later membership writes no second grant.
13
+
-**The claim waits 365 days.** A registrant who first joins an organization after that holds only the deployment's baseline. An engine with no middleware seam cannot settle a claim, so there a registrant with no organization is reported ungranted at `error` at sign-up instead.
14
+
- Grants already written without an organization by earlier walled sign-ups are not touched here.
feat(spec)!: retire a page action row's own `patch` — a row's `operation: 'update'` runs the declared action its `name` names, and the platform action route writes that action's `patch` (#24394)
6
+
7
+
**BREAKING** — `patch` on a page action row (`action:button`, `action:icon`, and each `action:group` / `action:menu` member of `properties.actions`) is now refused. A row's `operation: 'update'` posts to the platform action route under the row's `name`. The route resolves that declared action by name and writes the action's own `patch`, merged under the request's params. The row's `patch` was never sent: the reference console read it only for its Undo snapshot. Beside `operation: 'update'` a row now needs a `name`, since without one it has no declared action to run and the console refused it before any request. A row's static `params` are written only where that action declares them, and the route refuses any other with a 400 (ADR-0104 D2). `ActionSchema.patch`, the action's own declaration, is unchanged.
8
+
9
+
Clause-②: yes (narrowing)
10
+
11
+
Both directions, measured on the built rows. Narrowing: every row `patch` is refused, and a row with `operation: 'update'` and no `name` (an input list or static `params` included) is refused at `name`. Widening: a row with `operation: 'update'` and a `name` but no `patch` and no `params` now parses; before, the rows refused it at `patch` as having nothing to write, though its write is the named action's `patch`.
12
+
13
+
Measured before removal, at `71fa166e96`: no producer. No page action row in this repository's examples or apps authors `patch` (or any `operation`), and neither does any objectui page row at the pinned `0707b4ae37` or at objectui `main``4b7ce47`.
14
+
15
+
## FROM → TO
16
+
17
+
| you wrote (17.7 and earlier) | write instead |
18
+
| --- | --- |
19
+
|`operation: 'update'` with a `patch` on a page action row | the values in the `patch` of the declared action the row's `name` names (`defineAction({ name, operation: 'update', patch })`), the row keeping only `operation: 'update'` and that `name`|
20
+
|`patch` on a page action row with no `operation`| nothing — delete the key; it was already refused |
21
+
|`operation: 'update'` on a page action row with no `name`| a `name` naming the declared `operation: 'update'` action |
22
+
23
+
**The one-line fix:** move each row's `patch` values onto the declared action its `name` names, then delete the row's `patch`.
24
+
25
+
**What an author who still writes it sees.**`tsc` fails at a typed authoring site (the rows type the key as retired), and `os validate`, `os build` and `os lint` refuse it as an `error` (`component-props-invalid`) at its own path (`properties.patch`, `properties.actions.N.patch`) with the prescription:
26
+
27
+
> An `action:button`'s `patch` was removed in @objectstack/spec 18 (ADR-0110 D2) — a page action row's `operation: 'update'` runs the declared action its `name` names, and the platform action route resolves that action by name and writes ITS `patch`, so the row's own `patch` was never sent and never written. Move the values into the `patch` of the action the row's `name` names, then delete the key. Run `os migrate meta --from 17` to list the mechanical edits for existing sources; `--write` applies the ones it can prove, and you apply the rest by hand.
28
+
29
+
A page's own parse keeps its component `properties` as an open bag, so an existing page carrying the key is never refused at load.
30
+
31
+
## The retirement kit
32
+
33
+
-**A `retiredKey()` tombstone** at `patch` on `ActionButtonProps`, `ActionIconProps` and the `action:group` / `action:menu` member. `RETIRED_KEYS_BY_MAJOR[18]`: `ui/ActionButtonProps:patch`, `ui/ActionIconProps:patch` (the member's keys have no surface row of their own). The authorable-surface rows read `[RETIRED]`.
34
+
-**The rows' half of the declarative-update judge** reads the row's own surface: a row has no "`patch` without `operation`" rule of its own (its tombstone refuses every `patch`), and its "nothing to write" rule asks for the `name`. `ActionSchema`'s half of every rule, and every text it prints, is unchanged.
35
+
-**The D2 conversion `page-action-row-patch-removed`** (protocol 18, retired from the load path) deletes the key from every node and member — region-level, nested in a container's `children`, and in a slotted page's named slot — one notice per row. It is scoped by component `type`: the same key name on another component is left alone.
36
+
-**The D3 entry `ui-page-action-row-patch-retired`** records the judgement the strip cannot make: when the named action's own `patch` lacks a value the row carried, that value was never written, and adding it to the action changes what the action writes.
37
+
-**No deprecation window**, per the project's startup-stage posture.
38
+
39
+
**Not part of it:** objectui's renderers still forward the row's `patch`, its runner still reads it for the Undo snapshot, and its component registry still lists a `patch` input on `action:button` / `action:icon` (so the tracked `sdui.manifest.json` advertises it to the html-tier page gate until the pin moves). Those reads leave on the objectui side.
40
+
41
+
⚠️ **The out-of-repo consumer population is NOT MEASURED.**`@objectstack/spec` is published, so this is breaking for consumers no telemetry was consulted for.
fix(plugin-sharing,runtime)!: a declared sharing rule is read-only and clonable — no rule, new or renamed, can take its name, and it is switched off through the activation ledger, which revokes its grants at once
7
+
8
+
Clause-②: no (narrowing)
9
+
10
+
<!-- adr-0087: not-required (no-migration-prescription) no spec key, export, option or stored metadata shape is added, removed or renamed, so `objectstack migrate meta` has nothing to rewrite; what changes is the accept set of two write doors, whose remedy is another name for the caller's rule plus one activation call -->
11
+
12
+
**BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes.
13
+
14
+
ADR-0131 D6 charts declared sharing rules under regime C: a rule a package declares is read-only, it is customised by a clone under another name, and it is switched off through the deployment-level activation ledger (`sys_metadata_activation`), like flows, actions, positions and permission sets. Same-name shadowing is retired.
15
+
16
+
What is refused now:
17
+
18
+
-`POST /api/v1/sharing/rules` (and `SharingRuleService.defineRule`) creating a rule under a declared rule's name — FROM `201`, the new row replacing the declared rule in its scope, TO `409 RESOURCE_CONFLICT` naming the declared rule. Fix: create your rule under another name, and switch the declared one off with `POST /api/v1/security/_activation/sharing_rule/<name>` and `{ "enabled": false }`.
19
+
- A `/data` insert of a `sys_sharing_rule` row under a declared rule's name, or an update that renames a row onto one — FROM `201` / `200`, TO `409 RESOURCE_CONFLICT`. Fix: the same.
20
+
21
+
What is added:
22
+
23
+
-**The switch.**`POST /api/v1/security/_activation/sharing_rule/:name` writes the declared rule's activation row, behind the same authority as the other activation doors: `manage_metadata`, and the platform operator under a wall, because the row covers every organization. A declared rule switched off is served nowhere. The write revokes every grant that carries the rule's name in the same request, and switching it back on re-derives them. The name must be a declared rule (`404` otherwise; a rule your organization authored keeps its own `active` field), and with no sharing-rule service bound the door answers `503`.
24
+
-**A declaration over a stored row is reported, never made an overlay.** When a package declares a rule whose name a `sys_sharing_rule` row already holds in a scope (a package upgrade, or a copy customised before this release), the row keeps answering as its own rule. The declaration is not served beside it, and that is logged at `error` once per name and scope per process, with the remedy.
25
+
26
+
Unchanged: rules whose name no declaration holds are created, renamed and switched as before. A stored row that already holds a declared name stays editable as itself. `DELETE /api/v1/sharing/rules/:idOrName` on a declared rule is still `403 PERMISSION_DENIED`, and is now answered the same way when the rule is switched off.
Copy file name to clipboardExpand all lines: content/docs/permissions/system-context.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
@@ -159,7 +159,7 @@ One of the largest consumers of the flag;
159
159
| 37 |`sys_record_share` reads are **not** self-scoped | Get: tenant-wide share listing without `manage_sharing`|`packages/plugins/plugin-sharing/src/sharing-plugin.ts#buildSharingMiddleware`|
160
160
| 38 | Share-link policy `enabled` check bypassed; system callers re-enter under a system context | Get: link **creation** while the policy is off — resolution is **not** bypassed since #14033 (`publicSharing.enabled` is a standing policy held at every redemption): a link minted this way does not resolve until the block is enabled |`packages/plugins/plugin-sharing/src/share-link-service.ts#createLink`, `#revokeLink`, `#listLinks`|
161
161
| 39 | Sharing-rule provenance stamp skipped | Lose: the row is not marked as an admin customization — seeder / `defineRule` / boot reconcilers are "the package door" |`packages/plugins/plugin-sharing/src/sharing-rule-provenance.ts#bindRuleProvenanceStamp`|
162
-
| 40 | Sharing-rule service write + delete paths return early | Lose: the manage-rules gate on the service surface, and the platform-global-rule delete guard|`packages/plugins/plugin-sharing/src/sharing-rule-service.ts#assertCanManageRules`, `#assertCanDeletePlatformGlobalRule`|
162
+
| 40 | Sharing-rule service write + delete paths return early | Lose: the manage-rules gate on the service surface, the platform-global-rule delete guard, and the declared-name guard on `sys_sharing_rule` writes (a new row or a rename taking a declared rule's name, 409) — `defineRule` makes that check itself before its own system write, and the other system writers are the platform's machinery (the ownership ceremony, migrations) |`packages/plugins/plugin-sharing/src/sharing-rule-service.ts#assertCanManageRules`, `#assertCanDeletePlatformGlobalRule`, `#bindDeclaredRuleNameGuard`|
Copy file name to clipboardExpand all lines: content/docs/permissions/tenant-audit-census.mdx
+25-25Lines changed: 25 additions & 25 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -83,7 +83,7 @@ what moved this page's population from 225 to 227; nothing about the two sites
83
83
changed, only whether this instrument could see them.
84
84
85
85
**The expensive failure direction is a keyword.** Sites whose receiver the author
86
-
typed `any` have no type to read, and there are 38 of them — just under a fifth
86
+
typed `any` have no type to read, and there are 40 of them — just under a fifth
87
87
of the population, concentrated in exactly the seed and bootstrap paths this
88
88
control exists for. Scoring an unreadable receiver as "not an engine" would have
89
89
dropped every one of them silently, with a clean exit and a smaller number that
@@ -122,7 +122,7 @@ are reported as `undecidable` rather than assumed either way.
122
122
123
123
The same holds twice over for the context. An options argument spelled as a
124
124
literal can be read; one spelled `options`, `{ ...opts }`, or handed through a
125
-
forwarding shim cannot, and **53 of the 229 sites are spelled that way**. A
125
+
forwarding shim cannot, and **52 of the 231 sites are spelled that way**. A
126
126
context resolved from an inline literal or a local `const` can be tested for
127
127
`isSystem`; one arriving from a helper call cannot.
128
128
@@ -150,8 +150,8 @@ now **0**: nothing on this surface threads a context that provably lacks the fla
150
150
151
151
**"No tenant context" counted sites it had not read.** An options argument the
152
152
walker could not parse was folded into the same bucket as one it had read and
153
-
found empty. That published **61 sites "carrying no tenant context at all"**
154
-
when 8 said so and 53 were simply unread — an over-claim in the *alarming*
153
+
found empty. That published **60 sites "carrying no tenant context at all"**
154
+
when 8 said so and 52 were simply unread — an over-claim in the *alarming*
155
155
direction, on the very figure this page tells other cards to cite. `carries` is
156
156
now three-valued, and an unreadable argument can never contribute to the
157
157
provable count.
@@ -187,10 +187,10 @@ reproduce them. Where it disagrees, it disagrees on the page:
187
187
188
188
| carried figure | where it survives | this census |
189
189
| :--- | :--- | ---: |
190
-
| 175 write call sites | quoted in the merged changeset |**229**|
191
-
| 24 carrying no tenant context | quoted in the merged changeset |**2** provable and tenancy-enabled; **32** more whose options argument is unreadable |
192
-
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card |**158 of 229** decidable, **71** undecidable |
193
-
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration**|**not reproduced**: 125 decidably elevated, 0 decidably not, 96 undecidable |
190
+
| 175 write call sites | quoted in the merged changeset |**231**|
191
+
| 24 carrying no tenant context | quoted in the merged changeset |**2** provable and tenancy-enabled; **31** more whose options argument is unreadable |
192
+
| 127 of 175 statically decidable, 48 runtime-parameter-name sites | restated on the `isSystem`-scoping card |**160 of 231** decidable, **71** undecidable |
193
+
| 135 (77%) silenced by the `isSystem` guard before the posture gate | the lost issue body — **no surviving corroboration**|**not reproduced**: 128 decidably elevated, 0 decidably not, 95 undecidable |
194
194
| 141 and 132, two independent re-derivations | the card that filed this work | — |
195
195
196
196
**The differences are not reconciled, and deliberately so.** The old census's
@@ -200,21 +200,21 @@ be stated is what this instrument counts, which is written above and re-runnable
200
200
at any commit.
201
201
202
202
Two structural facts do plausibly widen this reading against any hand or regex
203
-
one, and both are counted in the generated tables below: the 38 sites reached
203
+
one, and both are counted in the generated tables below: the 40 sites reached
204
204
through an erased (`any`) receiver, and the 52 that name their object through a
205
205
`const` rather than inline. An instrument that read either the way a person does
206
206
would report a smaller number and would not say so.
207
207
208
208
The fourth row is the one worth flagging to anyone citing it. **The 135 / 77%
209
209
figure has no surviving corroboration anywhere in the tree.** This census reads
210
-
125 of 229 (55%) as decidably elevated, with 96 more whose elevation is a
210
+
128 of 231 (55%) as decidably elevated, with 95 more whose elevation is a
211
211
run-time fact — so the claim is neither confirmed nor refuted, and the honest
212
212
answer is that a static reading cannot settle it.
213
213
214
-
⇒ **Cite `2 / 229`, and say what it is**: the sites whose options argument was
214
+
⇒ **Cite `2 / 231`, and say what it is**: the sites whose options argument was
215
215
READ and holds no tenant context, against a decidably tenancy-enabled object.
216
216
That is the control's provable yield surface. ⛔ Do not cite it as "the sites
217
-
without tenant context" — **32 further sites** have an options argument this
217
+
without tenant context" — **31 further sites** have an options argument this
218
218
cannot read, and they are neither in nor out.
219
219
220
220
{/* BEGIN GENERATED: tenant-audit-census (scripts/tenant-audit-census.mjs) — DO NOT EDIT */}
@@ -223,28 +223,28 @@ cannot read, and they are neither in nor out.
223
223
224
224
| what | count |
225
225
| :--- | ---: |
226
-
| write call sites on the application surface |**229**|
227
-
| …whose object name is statically decidable |158|
226
+
| write call sites on the application surface |**231**|
227
+
| …whose object name is statically decidable |160|
228
228
| …whose object name is chosen at run time | 71 |
229
-
| …against an object with tenancy ENABLED |157|
229
+
| …against an object with tenancy ENABLED |159|
230
230
| …against an object that declares tenancy off | 1 |
231
-
| threading a tenant context |168|
231
+
| threading a tenant context |171|
232
232
| PROVABLY carrying none (options read, no context key) |**8**|
233
233
| …of those, against a decidably tenancy-enabled object |**2**|
234
-
| options argument UNREADABLE — may or may not carry one |53|
235
-
| …of those, against a decidably tenancy-enabled object |32|
236
-
| threading a decidably ELEVATED (`isSystem`) context |125|
234
+
| options argument UNREADABLE — may or may not carry one |52|
235
+
| …of those, against a decidably tenancy-enabled object |31|
236
+
| threading a decidably ELEVATED (`isSystem`) context |128|
237
237
| threading a context that is decidably NOT elevated | 0 |
238
-
| threading a context whose elevation is a run-time fact |96|
238
+
| threading a context whose elevation is a run-time fact |95|
239
239
240
240
| how the instrument reached the site | count |
241
241
| :--- | ---: |
242
242
| receiver carried a readable engine type | 191 |
243
-
| receiver erased, placed by the object NAME |28|
243
+
| receiver erased, placed by the object NAME |30|
244
244
| receiver erased, placed by an `object: string` PARAMETER | 5 |
245
245
| receiver erased, placed by an `UNTYPED_RECEIVERS` row | 5 |
246
246
247
-
| object name spelled inline |106|
247
+
| object name spelled inline |108|
248
248
| object name spelled through a `const`| 52 |
249
249
| object name is an `object: string` parameter | 7 |
250
250
| object name is some other run-time expression | 64 |
@@ -297,13 +297,13 @@ holds still. They are required to be HERE and to say WHEN they were true;
297
297
their values are not compared. The reasoning, and the measurement behind it,
0 commit comments