Skip to content

Commit f4cf2c5

Browse files
committed
chore: correct three pending changeset clauses for API-disabled objects
One clause each, every other line byte-identical: an object with enable.apiEnabled false is annotated [] rather than left without apiOperations, so the 18931, 18990 and 20134 notes now name that exception. Claude-Session: https://claude.ai/code/session_01Bvd69VPa6puiNzzPUroDBx Co-authored-by: Claude <noreply@anthropic.com>
1 parent f2904cd commit f4cf2c5

3 files changed

Lines changed: 3 additions & 3 deletions

File tree

‎.changeset/18931-me-permissions-unrestricted-export-annotation.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The endpoint builds its per-object map in four passes — seed, fold, clamp, ann
1010

1111
For that principal an unrestricted object got no entry, so annotate never saw it and the response said nothing about it at all. The client reads `apiOperations: undefined`, takes the default-allow path #3391 gave it, renders **Export**, and the click is refused. A sibling object declaring `apiMethods` got an entry, an `apiOperations` without `export`, and no button — the same principal, the same session, two answers.
1212

13-
- **The seed and annotate cannot diverge again.** Since #20134 the seed places an entry for every registered object a super-user wildcard reaches that the merge left without one, and `annotateEffectiveApiOperations` alone decides which entries carry `apiOperations`: an object that is unrestricted **and** keeps `export` gets none.
13+
- **The seed and annotate cannot diverge again.** Since #20134 the seed places an entry for every registered object a super-user wildcard reaches that the merge left without one, and `annotateEffectiveApiOperations` alone decides which entries carry `apiOperations`: an object that is unrestricted **and** keeps `export` gets none — unless its `enable.apiEnabled` is `false`, which is annotated `[]` since #20135 because the REST door answers 404 for every verb on it.
1414
- **The export axis is the only axis this reaches.** Measured across the `enable` shapes an unrestricted object can carry: withholding `export` subtracts `export` and nothing else, and `mode` stays `unrestricted` either way — which is why the old `mode`-only guard could not tell the two cases apart. The CRUD axis needed no annotation and still gets none.
1515
- **What the response gains**: for such a principal, one entry per unrestricted object, each the full closure minus `export`. Its CRUD bits are folded `true` — the same answer the client already computed by falling back to `'*'`, now stated explicitly rather than inherited.
1616
- **Denial is unchanged.** `enforceExportPermission` → `security.canExport` still answers `403 EXPORT_NOT_PERMITTED`, and no request that was refused is now accepted. This is the affordance half: the channel that is supposed to tell the client now does.

‎.changeset/18990-viewall-only-permissions-seed.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
- **One predicate admits both classes.** The seed now asks the wildcard READ bypass — `viewAllRecords || modifyAllRecords` — which is the same question `foldWildcardSuperUser` already asks to decide whose `allowRead` it pulls true, and the same one `PermissionEvaluator` applies server-side. It is now a single module-local reading both call sites share, so the seed can never materialise an entry for a principal the fold leaves entirely false.
1010
- **A plain wildcard grant carrying neither bypass bit is still not seeded.** That is what makes the admission the read bypass rather than "any wildcard": the fold pulls nothing true for it, so a seeded entry would be an all-false claim with no server behaviour behind it.
1111
- **The seeded entry is the truth, not an overreach.** It starts `{allow*: false}`, the fold pulls `allowRead` true, and the write bits stay false. The seed only ever touches objects with **no explicit entry**, and on those a viewAll-only principal really can only read — so "explicit false" for edit is what is true about it, where the silence it replaces was not.
12-
- **`apiOperations` is attached through the predicate already shared with the modify-all class** — an unrestricted object whose export stays allowed still gets no `apiOperations` (the entry itself is seeded since #20134), because for it the client's default-allow path is already right.
12+
- **`apiOperations` is attached through the predicate already shared with the modify-all class** — an unrestricted object whose export stays allowed still gets no `apiOperations` (the entry itself is seeded since #20134), because for it the client's default-allow path is already right — except an object with `enable.apiEnabled: false`, annotated `[]` since #20135 because the REST door refuses every verb on it.
1313

1414
⚠️ **This is a deliberate behaviour change on an existing published channel, ruled rather than inferred.** For a viewAll-only principal a client that reads "no entry" as default-allow now reads an explicit `allowEdit: false` instead. Two pins asserting the old silence (`toBeUndefined` for the viewAll-only principal, one of them added by the framework#18931 PR that pinned this boundary while saying the pin was not a ruling that the silence was correct) are inverted on purpose under that ruling. Payload growth is the same one-entry-per-object framework#18931 accepted, now also for viewAll principals.

‎.changeset/20134-super-user-entries-every-bit.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ fix(core): an effective-map entry reached through a super-user `'*'` carries eve
1212

1313
**What changes.** The seed now places an entry for every registered object the merged map does not already carry. A new step after the fold then applies each set's super-user `'*'` to every entry that set does not name, and sets every bit the spec's `objectPermissionGrants` says that wildcard grants: `transfer` through `modifyAllRecords`, the wildcard's own plain bits, and its `allowExport`. This is the per-set reading `checkObjectPermission` applies. A set that names an object keeps its explicit entry as its whole answer for that object, and a private object is covered, as on the server. Only `true` bits are set. The step runs before the managed-write clamp, which still narrows create, edit and delete on a guarded managed object. No exported name or type changes.
1414

15-
**What a reader of `/auth/me/permissions` sees.** For a subject holding a super-user wildcard, entries gain `true` bits (`allowTransfer`, and the wildcard's own plain and export bits). Where that subject's wildcards also grant `allowExport`, the map gains an entry for each registered unrestricted object that had none. That entry carries no `apiOperations`, exactly as the operation channel said nothing about the object before, so a client's default-allow path for the operation set is unchanged. Nothing is removed and no `true` bit turns `false`. The response is byte-identical for a subject holding no super-user wildcard: `member_default` alone, and `viewer_readonly` or `organization_admin_no_bypass` beside it. The response shape, its keys and the route are unchanged. On the write path, a `can(object, 'transfer')`-gated option or default is now admitted for these subjects wherever the server grants `transfer`.
15+
**What a reader of `/auth/me/permissions` sees.** For a subject holding a super-user wildcard, entries gain `true` bits (`allowTransfer`, and the wildcard's own plain and export bits). Where that subject's wildcards also grant `allowExport`, the map gains an entry for each registered unrestricted object that had none. That entry carries no `apiOperations` — unless the object declares `enable.apiEnabled: false`, which is annotated `[]` since #20135 — so for every other such object the operation channel says what it said before and a client's default-allow path is unchanged. Nothing is removed and no `true` bit turns `false`. The response is byte-identical for a subject holding no super-user wildcard: `member_default` alone, and `viewer_readonly` or `organization_admin_no_bypass` beside it. The response shape, its keys and the route are unchanged. On the write path, a `can(object, 'transfer')`-gated option or default is now admitted for these subjects wherever the server grants `transfer`.
1616

1717
**Still broader than the server, unchanged here.** The fold still folds the merged super-user bits into an entry the super-user set itself names narrower. It also still pulls `allowCreate` on `modifyAllRecords` alone, which the server does not grant. Both over-grants are left exactly as they were.

0 commit comments

Comments
 (0)