Skip to content

Commit 4c31321

Browse files
os-zhuangclaude
andauthored
docs(plugin-security): restate the upsertEnvPermissionSet JSDoc for ADR-0094 D5-R (#7082) (#7346)
PR #6962 retired the 2026-07-14 env-overlay direction and fixed this file's header; the function-level JSDoc of `upsertEnvPermissionSet` kept the same two claims D5-R retired -- "an env overlay is the platform's standard customization of a packaged definition" and "deleting the overlay resets the row to the shipped declaration". #6483 / PR #6608 rolled `permission` back to `allowOrgOverride: false` (the first is 403 NOT_OVERRIDABLE) and #6960 measures the delete path refusing to lift even a legacy overlay (the second). Prose only, no behaviour change. Three neighbouring copies in the same package are corrected with it so the package no longer states the direction in two voices. Claude-Session: https://claude.ai/code/session_01BM1tNf5U3nEbHKR4fo5qVQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 58ccd9d commit 4c31321

5 files changed

Lines changed: 106 additions & 29 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/plugin-security": patch
3+
---
4+
5+
Docs: bring the second ADR-0094 copy in `permission-set-projection.ts` up to D5-R.
6+
7+
PR #6962 retired the 2026-07-14 "customize packaged permission sets through an ADR-0005
8+
env overlay" direction and corrected this file's **header**. A second copy survived in
9+
the function-level JSDoc of `upsertEnvPermissionSet` — an exported symbol, so the stale
10+
text ships in the published `.d.ts` and reads as fact to the next author. It stated both
11+
halves D5-R retired: that an env-scope overlay is the platform's standard customization
12+
of a packaged definition, and that deleting the overlay resets the row to the shipped
13+
declaration.
14+
15+
Both are now stated as current: `#6483` (PR #6608) rolled `permission` back to
16+
`allowOrgOverride: false`, so a metadata write against a code-declared (artifact-backed)
17+
set is refused by the producer with 403 `NOT_OVERRIDABLE` and the supported channel is
18+
ADR-0086's (edit the package, re-publish); and `#6960` measures the ordinary delete path
19+
refusing to lift even a legacy pre-rollback overlay, leaving `OS_METADATA_WRITABLE` as
20+
the only documented removal — so "delete = reset" is recorded as retired rather than
21+
restated. The retirement itself is kept in the text, not deleted, so a reader arriving
22+
at this function does not have to reconstruct the history.
23+
24+
Prose only — no behaviour change. The same retired direction was also corrected in three
25+
neighbouring comments in this package (the `readDeclaredBody` JSDoc, and the two
26+
`security-plugin.ts` package-managed write-gate comments) plus their two test rationales,
27+
so the package no longer states the direction in two voices.

‎packages/plugins/plugin-security/src/permission-set-projection.test.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -333,9 +333,12 @@ describe('upsertEnvPermissionSet (ADR-0094 — record is a pure projection)', ()
333333
});
334334

335335
it('projects onto a PACKAGE-OWNED row (overlay customization) while preserving its provenance', async () => {
336-
// Direction confirmed 2026-07-14: an env overlay of a packaged set is the
337-
// platform's standard ADR-0005 customization — the record follows the
338-
// effective body; the package still owns the row.
336+
// Unit-level shape, unchanged by ADR-0094 D5-R: handed a body for a
337+
// package-owned row, this function projects the facets and preserves the
338+
// provenance. What D5-R retired is the CLAIM about where that body comes
339+
// from — an env overlay of a packaged set is no longer a supported
340+
// customization channel (#6483 / PR #6608); see the D5-R lifecycle block
341+
// below for the refusal this projector now sits behind.
339342
const ql = makeQl();
340343
ql.permRows.push({ id: 'ps_pkg', name: 'organization_admin', managed_by: 'package', package_id: 'com.example.crm', system_permissions: '["pkg"]' });
341344
const r = await upsertEnvPermissionSet(ql, envBody());

‎packages/plugins/plugin-security/src/permission-set-projection.ts‎

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,11 @@ const isProjectionEcho = (v: any): boolean =>
287287
* Read the DECLARED (artifact) body for a permission set from the engine's
288288
* SchemaRegistry — the same source `bootstrapDeclaredPermissions` seeds from,
289289
* and the one store the env projection never writes, so it can't be poisoned
290-
* by our own registry sync. Used as the reset target when an env overlay is
291-
* deleted off a declared set.
290+
* by our own registry sync. It is the body for a declared set with no overlay
291+
* at all, and the reset target when an overlay IS lifted off one — which since
292+
* ADR-0094 D5-R means a LEGACY (pre-#6483) row removed through the operator
293+
* hatch, not a data-door delete: #6960 measures that delete refusing with 403
294+
* `NOT_OVERRIDABLE`.
292295
*
293296
* Items tagged `_packageId: 'sys_metadata'` are RUNTIME SHADOWS — hydrated
294297
* into the registry from overlay rows (loadMetaFromDb / getMetaItems), not
@@ -328,13 +331,49 @@ function hasSchemaRegistry(ql: any): boolean {
328331
* (`managed_by:'admin'` — A4 #2920 unified vocab, formerly 'user'; a
329332
* Studio-authored set appears in Setup, where the
330333
* #2867 band-aid declined to create). A PACKAGE-OWNED row is also projected —
331-
* an env-scope overlay is the platform's standard customization of a packaged
332-
* definition (ADR-0005; direction confirmed 2026-07-14, reversing the earlier
333-
* refuse-the-env-door rule): the facets update to the EFFECTIVE (overlay-wins)
334-
* body while the `managed_by:'package'` + `package_id` provenance is
335-
* PRESERVED — the row still belongs to the package; the overlay is a
336-
* customization of it, and deleting the overlay resets the row to the shipped
337-
* declaration (the layered read reveals the baseline again).
334+
* its facets follow the body this pass is handed, while the
335+
* `managed_by:'package'` + `package_id` provenance is PRESERVED: the row
336+
* still belongs to the package.
337+
*
338+
* WHICH body that is, is the part this comment used to get wrong. The
339+
* 2026-07-14 direction confirmation that used to sit here — "an env-scope
340+
* overlay is the platform's standard ADR-0005 customization of a packaged
341+
* definition, and deleting the overlay resets the row to the shipped
342+
* declaration" — is **RETIRED**, in BOTH halves (ADR-0094 D5-R, 2026-08-09;
343+
* #6609 ruling A executed by #6858; the file header above records the same
344+
* retirement). Since #6483 / PR #6608 rolled `permission` back to
345+
* `allowOrgOverride: false`:
346+
*
347+
* - **no new overlay of a packaged set can be minted.** A metadata write
348+
* against a CODE-DECLARED (artifact-backed) set — `*.permission.ts`, a
349+
* stack's `permissionSets` — is refused by the producer with 403
350+
* `NOT_OVERRIDABLE`, so for those names the body reaching here is the
351+
* DECLARED one and the projected facets ARE the shipped declaration. The
352+
* supported way to change them is the one ADR-0086 always named: edit the
353+
* package and re-publish. What survives is the neighbouring
354+
* `allowRuntimeCreate` tier (still `true`) — a set whose definition lives
355+
* only in `sys_metadata`, created through the data door or authored and
356+
* published through the METADATA door (ADR-0070). That tier edits the
357+
* single stored definition IN PLACE: no code-vs-overlay layering, and
358+
* nothing to reset to (ADR-0094 D5-R calls it the surviving neighbour, not
359+
* D5's successor);
360+
* - **`customized` therefore badges LEGACY state, not a supported channel.**
361+
* `supportsOverlay` is unchanged, so an overlay row authored BEFORE the
362+
* rollback still merges overlay-wins at read time
363+
* ({@link projectPermissionMutation} hands us `overlay ?? declared`) and
364+
* this pass still stamps the flag for it. The in-repo corpus had zero such
365+
* rows when PR #6608 measured it;
366+
* - **"delete = reset" must NOT be read back into that.** #6960 measures the
367+
* ordinary delete path refusing to lift exactly such a legacy overlay: on
368+
* an environment-scoped kernel `deleteMetaItem` throws `NOT_OVERRIDABLE` /
369+
* 403 for an artifact-backed target of a non-overridable type BEFORE it
370+
* probes for the row, and a kernel with no `environmentId` refuses the
371+
* same write as `override-artifact` intent — leaving the operator hatch
372+
* (`OS_METADATA_WRITABLE=permission`) as the only documented removal. With
373+
* no overlay to lift — the normal case — the delete is a no-op success and
374+
* the row keeps projecting the declaration. {@link readDeclaredBody} stays
375+
* the correct reset target IF an overlay is ever lifted; it is no longer a
376+
* path the data door can walk.
338377
*/
339378
export async function upsertEnvPermissionSet(
340379
ql: any,

‎packages/plugins/plugin-security/src/security-plugin.test.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,11 +1986,14 @@ describe('SecurityPlugin', () => {
19861986
return harness.run(opCtx);
19871987
};
19881988

1989-
it('PASSES an admin update of a package-managed set (ADR-0094: the write-through turns it into an env overlay)', async () => {
1990-
// Direction confirmed 2026-07-14: update/delete on a package row are no
1991-
// longer refused at this gate — the ADR-0094 write-through downstream
1992-
// translates them into env-scope overlay operations (customize / reset).
1993-
// The single-store refusal lives in the write-through itself, covered in
1989+
it('PASSES an admin update of a package-managed set at THIS gate (ADR-0094: the refusal is the write-through producer\'s, not this gate\'s)', async () => {
1990+
// update/delete on a package row are not refused at this gate — the
1991+
// ADR-0094 write-through downstream translates them into a metadata
1992+
// write, and that producer decides. Since ADR-0094 D5-R (#6483 /
1993+
// PR #6608) the answer for a CODE-DECLARED set is 403 NOT_OVERRIDABLE,
1994+
// so "the write-through turns it into an env overlay" is no longer why
1995+
// this passes; it passes because the gate's job is forging provenance,
1996+
// not overridability. The refusal is covered in
19941997
// permission-set-projection.test.ts.
19951998
const opCtx: any = {
19961999
object: 'sys_permission_set', operation: 'update',

‎packages/plugins/plugin-security/src/security-plugin.ts‎

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -887,10 +887,12 @@ export class SecurityPlugin implements Plugin {
887887
// update), and the lifecycle ops with no overlay translation
888888
// (transfer/restore/purge) stay refused on package rows. Ordinary
889889
// `update`/`delete` on a package row are handled downstream by the
890-
// ADR-0094 write-through, which translates them into env-scope OVERLAY
891-
// operations (customize / reset via the standard ADR-0005 layering) —
892-
// the boot re-seed can no longer revert an admin's change, because the
893-
// change lives in the overlay and the record projects overlay-wins.
890+
// ADR-0094 write-through, which TRANSLATES them into a metadata write.
891+
// Whether that write is ACCEPTED is ADR-0005's call, not this gate's,
892+
// and since ADR-0094 D5-R (#6483 / PR #6608 rolled `permission` back to
893+
// `allowOrgOverride: false`) a CODE-DECLARED set is refused there with
894+
// 403 `NOT_OVERRIDABLE` — the 2026-07-14 "customize / reset via an env
895+
// overlay" direction this comment used to state is RETIRED.
894896
// Placed BEFORE the empty-principal fall-open and the CRUD check so the
895897
// forging boundary holds even for a principal-less context and a
896898
// superuser with modifyAllRecords. System/boot writes carry `isSystem`
@@ -3189,14 +3191,17 @@ export class SecurityPlugin implements Plugin {
31893191
}
31903192
if (op === 'insert') return; // no existing row to protect
31913193

3192-
// [ADR-0094, direction confirmed 2026-07-14] `update`/`delete` on a
3193-
// package-managed row are no longer refused here: the write-through
3194-
// middleware (which runs after this gate + the delegated-admin gate +
3195-
// the CRUD checks) translates them into env-scope OVERLAY operations —
3196-
// customize / reset via the standard ADR-0005 layering — and itself
3197-
// re-asserts the legacy refusal when the kernel has no metadata overlay
3198-
// layer to carry the customization. The lifecycle ops below have no
3199-
// overlay translation, so the package-row protection stays for them.
3194+
// [ADR-0094 D5-R] `update`/`delete` on a package-managed row are not
3195+
// refused HERE: the write-through middleware (which runs after this gate
3196+
// + the delegated-admin gate + the CRUD checks) translates them into a
3197+
// metadata write, and the refusal is LEFT TO THAT PRODUCER. Since #6483 /
3198+
// PR #6608 rolled `permission` back to `allowOrgOverride: false`, a
3199+
// CODE-DECLARED (artifact-backed) set is refused there with 403
3200+
// `NOT_OVERRIDABLE`; a `sys_metadata`-backed set rides
3201+
// `allowRuntimeCreate` and still lands. The 2026-07-14 "customize / reset
3202+
// via the standard ADR-0005 layering" direction this comment used to cite
3203+
// is RETIRED. The lifecycle ops below have no metadata translation, so the
3204+
// package-row protection stays for them.
32003205
if (op === 'update' || op === 'delete') return;
32013206

32023207
if (!this.ql) return;

0 commit comments

Comments
 (0)