Repository navigation
Commit 11d119a
docs(permissions): describe the packaged permission-set lock in "One authoritative store" (#22391)
Fixes #22379
Clause-②: no
## What changed
Two docs pages: four passages that still said a set shipped in code can
be overlaid in the environment. +25 / -16 lines against `main`.
### `content/docs/permissions/permission-sets.mdx`, "One authoritative
store — the record is a projection (ADR-0094)"
- **First bullet.** It said a Setup edit of any declared set, packaged
sets included, "becomes an environment overlay" that "genuinely takes
effect", and that the Studio layered view diffs it and a reset removes
it. Since the packaged lock, that edit answers `403 NOT_OVERRIDABLE` and
stores nothing. The bullet now says:
- which sets are locked: a set shipped in code, meaning a set an
installed package declares (`*.permission.ts`, a stack's
`permissionSets`) or a platform default such as `member_default`;
- what the edit answers (`403 NOT_OVERRIDABLE`, nothing stored);
- the remedy the refusal names: clone to a new name, with the **Clone**
action or `POST /api/v1/data/sys_permission_set`;
- which sets are still edited in place: one created in this environment,
a clone, or one saved into a writable runtime package.
- **Closing sentence.** "use an overlay where a packaged set must be
narrowed" becomes: clone it, narrow the clone, and bind the clone in its
place (restriction is done by not granting). Narrowing the packaged set
itself is the package author's change (ADR-0086 two-doors). The
position-binding half of the sentence did not change.
- **Third bullet** ("Deleting through the data door … resets it"). It
holds against `main`, so it is **not edited**. Evidence is under premise
check 5.
### `content/docs/permissions/permission-sets.mdx`, "Provenance —
package vs environment sets (ADR-0086)", `:311-312`
- Before: "(two-doors separation, evolved by ADR-0094 — ordinary edits
of a packaged set become environment overlays, see below)".
- After: "(two-doors separation, evolved by ADR-0094 — an edit of a set
shipped in code is refused; clone it instead, see below)".
- Why: its "see below" pointed at the corrected bullet, which now says
the opposite.
- Backed by the same pins as the bullet:
`permission-set-write-through-package-binding.dogfood.test.ts:167-173`
(a data-door edit of `showcase_contributor` answers `403
NOT_OVERRIDABLE`, no row minted), and
`permission-set-lock-row-provenance.dogfood.test.ts` (the shipped set is
refused at both doors, while a clone takes an edit). The clone remedy is
the `clone_permission_set` action in
`packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts:115-160`.
### `content/docs/permissions/administrator-guide.mdx`, Step 5
(`:162-164`) and the FAQ (`:194-196`)
- **Step 5.**
- Before: "Editing a set that shipped with an app creates an
*environment overlay* — your change wins, survives upgrades, and can be
reset back to the vendor baseline; the API name is immutable after
creation."
- After: "A set shipped in code — by an app you installed, or as a
platform default such as `member_default` — can't be edited in the
environment (a save answers `403 NOT_OVERRIDABLE`): clone it and adjust
the clone, which is your own set while upgrades keep reaching the
original. Sets created here, clones, and sets saved into a writable
runtime package are edited in place. The API name is immutable after
creation."
- The page's order of preference is unchanged: bind an existing set to a
position, then clone and adjust, then author a new set.
- **FAQ, "Can I delete the built-in permission sets?"**
- Before: "Shipped sets can be overlaid (Step 5) or simply left
unassigned."
- After: "Shipped sets can't be edited in place either, but they can be
cloned and adjusted (Step 5) or simply left unassigned."
- Backed by:
- The installed-app half:
`permission-set-write-through-package-binding.dogfood.test.ts:167-173`
and `two-doors-permission.dogfood.test.ts` 块2 (`showcase_contributor`
answers 403, and no overlay is minted).
- The platform-default half: `two-doors-permission.dogfood.test.ts`
(last 块2 case) and `showcase-permission-projection.dogfood.test.ts` §2
pin `403` on a `member_default` edit with no overlay minted. The status
is pinned. The code is read from the producer
(`refusePackagedBaseOverride`, `NOT_OVERRIDABLE`) and is not pinned.
- The editable populations:
`permission-set-lock-row-provenance.dogfood.test.ts` shapes 1-3
(runtime-package set, org set, clone).
- The upgrade sentence: the lock's own `userMessage` in
`packaged-permission-set-lock.ts` ("The clone is your organization's own
permission set, and package upgrades keep reaching the original.").
Not touched: the "Declared ≠ enforced — diagnosing a frozen package set"
section. PR #22365 (card #22307) adds a clause there in a hunk starting
at `:368`, and the decision card #22371 may rewrite that section.
`content/docs/releases/` is not touched either.
## Premise checks against `main` (base `117d34de3f`, now merged up to
`191543456f`)
1. **The two passages were still pre-lock text.** At `117d34de3f`,
`permission-sets.mdx:332-339` ("it genuinely takes effect") and
`:350-352` ("use an overlay where a packaged set must be narrowed") read
as the card quotes them. Holds.
2. **The Setup edit of a set a code package ships answers 403 and stores
nothing.** Holds:
- Pin:
`packages/qa/dogfood/test/permission-set-write-through-package-binding.dogfood.test.ts:167-173`.
`PATCH /data/sys_permission_set/:id` on `showcase_contributor` answers
`{ status: 403, code: 'NOT_OVERRIDABLE' }`, and the active
`sys_metadata` rows are unchanged.
- Producer: `PackagedPermissionSetLockedError` in
`packages/plugins/plugin-security/src/packaged-permission-set-lock.ts`.
The data door's insert and update legs throw it
(`permission-set-projection.ts`, `createPermissionSetWriteThrough`), and
so does the metadata door (`packaged-permission-set-lock-gate.ts`).
3. **Scope: which package-held sets does the lock cover?** The caution
was right. "A packaged set cannot be edited" over-claims, so the text is
written to a narrower group: sets shipped in code.
- How the lock decides: `classifyPackagedPermissionSet` reads the engine
SchemaRegistry. `declaredPackageIdOf` skips projection echoes,
tenant-authored stored rows (`isTenantAuthored`, `_provenance: 'org'`),
and `_packageId: 'sys_metadata'` shadows.
- Editable shapes, pinned: a set saved into a writable runtime package
(`permission-set-write-through-package-binding.dogfood.test.ts:152-160`,
and `permission-set-lock-row-provenance.dogfood.test.ts` shape 1), an
org-created set (shape 2), and a clone (shape 3).
4. **Both remedies exist and work on `main`.** Holds.
- **Clone:** the `clone_permission_set` action
(`sys-permission-set.object.ts:115-160`) POSTs to
`/api/v1/data/sys_permission_set`. It carries every permission facet but
deliberately not `admin_scope`. The lock's `userMessage` and the
producer's regime sentence (`packaged-base-regime.ts:162-167`) both name
it.
- **Bind to a position:** `sys_position_permission_set` rows ("Assigning
permission sets" on this page; `positions.mdx:7-10`).
`showcase-permission-zoo.dogfood.test.ts:133-143` pins a tenant admin
binding the package-shipped `showcase_contributor` to a position.
- Binding only adds (union) and cannot narrow anything, so narrowing
goes through clone-and-bind-in-its-place.
5. **Third bullet: holds.** Measured per kind of set:
- *A set created in this environment is removed:*
`showcase-permission-projection.dogfood.test.ts` ("deleting a
runtime-only set retires both the definition and the record") and
`permission-set-projection.test.ts:785`.
- *A set shipped in code is not removed, and the row remains:*
`two-doors-permission.dogfood.test.ts` 块2 delete case and
`showcase-permission-projection.dogfood.test.ts` §3 both answer 2xx with
`success: false`, and the record stays.
- *"(the customization overlay is dropped)":* a pre-lock overlay is
removed by `deleteMetaItem` (the 2026-08-10 maintainer ruling, the
`mergesOverlayAtRead` carve-out in `refusePackagedBaseRemoval`). Pinned
by `protocol.legacy-overlay-delete.test.ts` and
`permission-set-projection.test.ts:926`. With no overlay present, the
delete changes nothing.
- NOT MEASURED: deleting a set saved into a writable runtime package. No
pin covers it. See Acceptance notes.
- CI's `Dogfood Regression Gate` concluded `success` on `117d34de3f`.
Confirming that each cited file appears in the job logs is NOT MEASURED:
the log host answered `Forbidden` from this container.
## Gates
Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on head `fc3fdb1b69`: 43 commands, the same
list as round 1. All 43 ran on `fc3fdb1b69` and exited 0. Exit codes
were captured before any pipe.
- Prerequisite builds: the spec, lint and client/client-react closures
were built under the verify lock (`VERDICT command-exit 0`) before the
run, so the dist-reading gates measured this tree.
- Reconciliation (`--ran` with per-command exit codes): "43 derived
famil(ies) accounted for — 43 run, 0 NOT-MEASURED (a DERIVED zero …)".
- Sample verdict lines:
- `check:skill-examples`: "262 prose examples type-check across 3
surface(s)".
- `check:doc-anchors`: "468 internal #fragment link(s) across 417 source
file(s) all resolve to a real heading".
- `check:nul-bytes`: "OK (scanned 10373 text file(s) … no raw ASCII
control bytes)".
- `check:doc-authoring`: clean.
- `check:docs`: "225 generated files in sync with packages/spec".
- `origin/main` moved to `0ef9029da4` after this round's merge. That
commit only retitles `packages/spec` test files and touches no docs, so
it was not merged again; CI's merge ref covers it.
- Outside the derived list, for CI: the path-scheduled `Build Docs` and
`Test Core` jobs, and the type-check lanes.
## Changeset
None. The diff is docs-only under `content/docs/`, which no package
publishes, so the PR carries the `skip-changeset` label.
## Acceptance notes
Accepted by the seat as notes, not filed:
- **Stale code comments, not docs.** These are comment drift with no
runtime effect.
- In
`packages/plugins/plugin-security/src/permission-set-projection.ts:559-568`,
the comment says "#6960 measures the ordinary delete path refusing to
lift" a legacy overlay. The 2026-08-10 ruling since lets that delete go
through (`protocol.legacy-overlay-delete.test.ts`).
- The `createPermissionSetWriteThrough` doc comment at `:1055-1058`
still says a package-owned row's update becomes an env-scope overlay.
- **Unmeasured: deleting a runtime-package set.** This is a code reading
only, not reproduced.
- The data door's delete leg calls `deleteMetaItem` with no package. The
repository delete matches any package (`sys-metadata-repository.ts`,
`whereFor`).
- `readDeclaredBody` (`permission-set-projection.ts:494`) skips
`_packageId: 'sys_metadata'` shadows and projection echoes, but not the
tenant-authored (`_provenance: 'org'`) rows the lock's classifier
learned to skip.
- A delete after a list read might therefore re-project instead of
retiring. A booted-stack probe would settle it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 00bee27 commit 11d119a
2 files changed
Lines changed: 25 additions & 16 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | | - | |
163 | | - | |
164 | | - | |
165 | | - | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
166 | 169 | | |
167 | 170 | | |
168 | 171 | | |
| |||
193 | 196 | | |
194 | 197 | | |
195 | 198 | | |
196 | | - | |
| 199 | + | |
| 200 | + | |
197 | 201 | | |
198 | 202 | | |
199 | 203 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
312 | | - | |
| 312 | + | |
313 | 313 | | |
314 | 314 | | |
315 | 315 | | |
| |||
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
332 | | - | |
333 | | - | |
334 | | - | |
335 | | - | |
336 | | - | |
337 | | - | |
338 | | - | |
339 | | - | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
340 | 342 | | |
341 | 343 | | |
342 | 344 | | |
| |||
348 | 350 | | |
349 | 351 | | |
350 | 352 | | |
351 | | - | |
352 | | - | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
353 | 358 | | |
354 | 359 | | |
355 | 360 | | |
| |||
0 commit comments