Skip to content

Commit 2d1a76b

Browse files
docs(spec): retire the "return with M2" texts for allowRestore / allowPurge (#31562)
<!-- claude-projects-attribution: {"github_login":"marchtian"} --> _Requested by **田騏** · [project thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSi8kq4zpmYQCfyfqbeo3ASPM)_ Fixes #31508 Clause-②: no Metadata-protocol: none ## 维护者速读 - **改了什么:** 只改文字,不改任何行为。旧权限键 `allowRestore` / `allowPurge` 的第 18 号转换条目 `summary`(`os migrate meta` 会打印给升级的作者)、语义迁移条目、step 18 的 rationale、四条 retired-key 注释、liveness 的两条 dead 记录和文件说明、lint 的 `retiredKeyRemedy` 注释、metadata 与 ui 的代码注释,不再说「随 M2 回归」,改为:这两个键不会回来,回收站(ADR-0148)已提供 restore / purge,授权键是 `allowRestoreOwnDeleted` 和 `manageRecycleBin`。 - **为什么改:** PR #31438 已把墓碑提示改成现状,但这些地方还在承诺旧键回归,升级旧元数据的作者会读到错误指引。 - **风险与回滚:** 无运行时风险:转换条目的 id、toMajor、retiredAfter、surface、变换与 fixture 逐字节不变;无 schema、accept-set、墓碑或 lint 逻辑变化。条目文字随 `@objectstack/spec` 发布,所以带 patch changeset。回滚即 revert。 - **先看:** `packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts` 的 `summary`,和 `packages/spec/src/migrations/entries/semantic/18.permission-restore-purge-bits-retired.ts` 的 `replacement`。 - **两分钟验证:** `grep -rn "M2" packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts packages/spec/src/migrations/entries/semantic/18.permission-restore-purge-bits-retired.ts packages/spec/src/migrations/registry.ts.template | grep -i restore`,预期无输出;`pnpm --filter @objectstack/spec exec vitest run src/migrations src/conversions` 全部通过。 - **需要你做的:** 无,座位做契约复核后落地。 ## What changes Card #31508: after PR #31438 (`6a708214`) the recycle bin's `restore` / `purge` read `allowRestoreOwnDeleted` / `manageRecycleBin` (ADR-0148 D5), and the tombstone prescriptions say so. These texts still said the retired keys "return with the M2 lifecycle initiative". Each now says the keys do not return and names the two grants, in the tombstones' wording; the history (the operations did not exist when the keys were retired, and a dispatched `restore`/`purge` then stayed denied by `DESTRUCTIVE_OPERATIONS`) is kept in past tense. The order's six sites: 1. **Conversion `permission-allow-restore-purge-removed`** (`conversions/entries/18/`): `summary` and docblock. Measured byte-identical outside those two hunks: `id`, `toMajor: 18`, `retiredFromLoadPath`, `retiredAfter: '17.2.0'`, `surface`, `apply` and `fixture` are unchanged (`git diff -U0` shows only the docblock and `summary` hunks). 2. **Semantic entry `permission-restore-purge-bits-retired`**: `replacement`, `reason`, `acceptanceCriteria`. The acceptance criteria no longer say `restore`/`purge` is denied for every principal; they say stripping the keys changes no access decision because nothing reads them. Registry regenerated with `gen:migration-registry` (git-ignored, not committed). 3. **`liveness/permission.json`**: the `allowRestore` / `allowPurge` `dead` notes (status and evidence unchanged), plus a dated sentence on the file `_note` (review should-fix). The `allowTransfer` row is left alone, as ordered. 4. **`lint` `retiredKeyRemedy` docblock**: neither prescription's "Delete the key" sentence runs past 200 characters now, so the parenthetical says so. 5. **`metadata` `plugin.ts`** comment on the artifact door's version key. 6. **`ui` `permission-slice.ts`** comment: "NO RETURN PATH", and its stale strip rationale (review should-fix). Ridden along under the bounded in-place exemption (same defect class, same spec gate family, no other claim on the files; the seat amends the claim's file surface): - **Step 18 `rationale` fragment** in `migrations/registry.ts.template`, which the protocol 17 → 18 upgrade guide prints. - **The four retired-key rows** `18.security__{ObjectPermission,EffectiveObjectPermission}__{allowRestore,allowPurge}.ts`, whose comments the generator carries into `registry.ts`. - **Comments** in `packages/spec/src/security/permission.test.ts` and `ui/.../previews/PermissionPreview.retiredLifecycleKeys.test.tsx`. ## Acceptance notes - **Seat ruling (claim 6722322190): a released entry's prose may be reworded.** Measured again: no test or generated, committed artifact pins the old wording (`docs/protocol-upgrade-guide.md` is a pointer; the guide is generated and git-ignored). - **Left for another card (stale "M2" prose about the same keys, not touched here):** - `content/docs/permissions/permission-metadata.mdx:99-104` and `content/docs/permissions/permission-sets.mdx:54` (user-facing docs: "the operations … do not exist yet … return with the M2 lifecycle batch"). - `packages/spec/liveness/README.md:991` (permission row: "keys return with M2 per the #1883 ruling"). - `packages/spec/liveness/permission.json:88` (`allowTransfer` row: "no transfer/restore/purge operations in packages/objectql"), left by order. - Test headers `ui/.../PermissionMatrixEditor.retiredLifecycleKeys.test.tsx:7-15`, `packages/metadata/src/plugin-artifact-forward-conversion.test.ts:24`, `packages/plugins/plugin-security/src/controlled-by-parent-detail-write-authority.test.ts:218`, `packages/plugins/plugin-security/src/security-plugin.test.ts:2797`. The first two were edited and then reverted: editing a test file brings it under `check:test-source-alias`'s edited-file half, which then asks for a module-top import, a test change outside this card. ## Reviews Fable full review (round 1): PASS with should-fix, no must-fix; 3 should-fixes and 4 nits applied, nit 6 (the transfer row) declined per the order. Delta review: PASS; its two tense nits applied. The verdict is posted on this PR. ## Verification After `git merge origin/main` (merge `9c50b2f254`), with the delta commits on top (head `b266470e70`): - `pnpm install --frozen-lockfile`, spec `gen:schema` and `gen:docs` :: exit 0, no tree change; `gen:migration-registry` regenerated the git-ignored registry. - `pnpm turbo run build` (all packages) :: exit 0. Spec `check:generated` :: exit 0 (14 artifacts current; the first pass called `test-typecheck-debt.json` stale from a heap limit, and `check:test-typecheck` with `--max-old-space-size=6144` :: exit 0). - `dispatch-gates --commands` derived 99 commands; all 99 ran. `check:test-source-alias` was red only on the two test headers this PR then reverted, and :: exit 0 after; `check:dual-build-cjs-loads` exit 3 before the full build and :: exit 0 after. `dispatch-gates --ran`: 99 run, 0 NOT-MEASURED, exit 0. - `eslint` on the touched spec, lint and metadata sources :: exit 0. - `vitest`: spec `src/migrations` + `src/conversions` + `src/security/permission.test.ts`: 29 files, 1116 tests passed; lint `validate-retired-permission-residue.test.ts`: 23 passed; ui `PermissionPreview.retiredLifecycleKeys.test.tsx` (from the ui root): 3 passed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_017ihL2c6HACa8QkBPQD6c7i --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 645ed3d commit 2d1a76b

16 files changed

Lines changed: 119 additions & 85 deletions
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the `allowRestore` / `allowPurge` migration texts stop promising the keys back with M2 and name the recycle bin's grants
6+
7+
Clause-②: no
8+
Metadata-protocol: none
9+
10+
- **Conversion `permission-allow-restore-purge-removed`.** Its `summary`, which `os migrate meta` prints, said the keys "return with the M2 lifecycle initiative". It now says the keys do not return: the `restore` / `purge` operations exist with the recycle bin (ADR-0148), and their grants are `allowRestoreOwnDeleted` and `manageRecycleBin`. Its docblock says the same. The conversion's `id`, `toMajor`, `retiredAfter`, `surface`, transform and fixture are byte-identical.
11+
- **Semantic entry `permission-restore-purge-bits-retired`.** Its `replacement` names the two grants instead of "the bits return together with the operations they gate". Its `reason` and `acceptanceCriteria` keep the history (the operations did not exist when the keys were retired) and no longer say a `restore` or `purge` request is denied for every principal: stripping the keys still changes no access decision, because nothing reads them.
12+
- **Step 18's rationale and the four retired-key rows** for `allowRestore` / `allowPurge` (authoring and effective faces) say the same, so the protocol 17 → 18 upgrade guide no longer names M2 as their return path.
13+
- **Liveness notes.** The two `dead` rows in `liveness/permission.json` and the file's note say the same; their status and evidence are unchanged.
14+
15+
Text only, matching the tombstone prescriptions PR #31438 rewrote: no schema, accepted set, conversion behaviour, tombstone or runtime answer changes.

‎content/docs/permissions/permission-metadata.mdx‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,12 @@ objects: {
9898

9999
> **Retired:** the former `allowRestore` / `allowPurge` keys were removed
100100
> (#12497, ADR-0049) — the `restore` / `purge` operations they claimed to gate
101-
> do not exist yet, so authoring them granted nothing. Authoring either key is
102-
> now a loud publish-time error carrying this prescription. The keys return
103-
> with the M2 lifecycle initiative (feature + RBAC in one batch, #1883);
104-
> until then a dispatched `restore` / `purge` is denied unconditionally.
101+
> did not exist then, so authoring them granted nothing. Authoring either key is
102+
> now a loud publish-time error carrying this prescription. The keys do not
103+
> return: the operations now exist with the recycle bin (ADR-0148), and their
104+
> grants are `allowRestoreOwnDeleted` (restore the entries you deleted) and
105+
> `manageRecycleBin` (restore any entry, purge an entry, and empty an
106+
> object's entries).
105107
>
106108
> **Exactly one value is still accepted, and it is not an author's to write:**
107109
> the literal `false` that the published 17.x toolchain materialized into every

‎content/docs/permissions/permission-sets.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export const SalesUser = definePermissionSet({
5151
|------------|-------------|
5252
| `allowCreate` / `allowRead` / `allowEdit` / `allowDelete` | CRUD on records the user can see |
5353
| `allowExport` | Bulk data egress — an opt-in grant on top of read, see below |
54-
| `allowTransfer` | Lifecycle class: change record ownership — enforced today via the `owner_id` guard (#3004). The former `allowRestore` / `allowPurge` keys were retired (#12497, ADR-0049 — the operations they claimed to gate do not exist yet) and return with the M2 lifecycle batch (#1883) |
54+
| `allowTransfer` | Lifecycle class: change record ownership — enforced today via the `owner_id` guard (#3004). The former `allowRestore` / `allowPurge` keys were retired (#12497, ADR-0049 — the operations they claimed to gate did not exist then) and do not return: the recycle bin's `restore` / `purge` are granted by `allowRestoreOwnDeleted` and `manageRecycleBin` (ADR-0148) |
5555
| `viewAllRecords` | Read ALL records regardless of ownership (super-user read) |
5656
| `modifyAllRecords` | Edit and delete ALL records regardless of ownership (super-user write); `allowDelete: false` beside it is refused |
5757

‎packages/lint/src/validate-retired-permission-residue.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,10 @@ export function retiredKeyPrescription(key: string): string | null {
113113
/**
114114
* [#22161] The `fix:` line, cut from the retirement's own prescription: its
115115
* "Delete the key" sentence up to its end, and up to its first ` — ` when the
116-
* whole sentence would run past 200 characters (both keys' do: the clause after
117-
* the dash, on what stays denied, is the explanation's). It cuts and never
116+
* whole sentence would run past 200 characters (neither key's does: each
117+
* prescription spells "Delete the key." as its own sentence, and the sentences
118+
* after it, naming the recycle bin's grants `allowRestoreOwnDeleted` /
119+
* `manageRecycleBin` (ADR-0148), are the explanation's). It cuts and never
118120
* rewrites, so the single-wording discipline above holds; a prescription with
119121
* no such sentence is returned whole rather than replaced by an invented line.
120122
*/

‎packages/metadata/src/plugin.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,9 +1207,13 @@ export class MetadataPlugin implements Plugin {
12071207
* releases); an artifact authored at the current (or a newer) surface
12081208
* converts nothing and answers to the strict parse, tombstones included.
12091209
* The version key is what keeps this a conversion
1210-
* rather than an amnesty — the retired keys return with the M2 lifecycle
1211-
* batch (#1883), and artifacts authored against that surface must never
1212-
* have them stripped by history.
1210+
* rather than an amnesty — an artifact authored against the current surface
1211+
* that still writes a retired key is an authoring error its tombstone must
1212+
* report (only the #12840 `false` residue is tolerated, at the schema), never
1213+
* a strip this door performs. (`allowRestore` /
1214+
* `allowPurge`, the keys this door was built for, stay retired: the recycle
1215+
* bin's grants are the new keys `allowRestoreOwnDeleted` /
1216+
* `manageRecycleBin`, ADR-0148 D5.)
12131217
*
12141218
* Notices surface the way the stored-row pass's do — operator-visible and
12151219
* deduped — as one summary line per conversion per artifact rather than

‎packages/spec/liveness/permission.json‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"type": "permission",
3-
"_note": "PermissionSetSchema (the `profile` alias and isProfile flag were removed by ADR-0090 D2). Seeded from docs/audits/2026-06-security-identity-property-liveness.md. CRUD/FLS/RLS enforced via plugin-security. allowTransfer/Restore/Purge are RBAC-mapped in the evaluator (#1883) — deny unless granted; the ObjectQL operations themselves are pending M2. 2026-07-30 security-subset re-verification (#3896 follow-up): every entry call-graph-closed by hand; TWO corrections — rowLevelSecurity.enabled was live-with-wrong-evidence and UNREAD anywhere (a disabled policy kept granting; enforced in rls-compiler the same day), rowLevelSecurity.priority is semantically void under OR-combination → dead+authorWarn. tabPermissions was UNDERSTATED (all four rank values read, not just 'hidden').",
3+
"_note": "PermissionSetSchema (the `profile` alias and isProfile flag were removed by ADR-0090 D2). Seeded from docs/audits/2026-06-security-identity-property-liveness.md. CRUD/FLS/RLS enforced via plugin-security. allowTransfer is RBAC-mapped in the evaluator (#1883) — deny unless granted; the transfer ObjectQL operation itself is pending M2 (see its row). 2026-07-30 security-subset re-verification (#3896 follow-up): every entry call-graph-closed by hand; TWO corrections — rowLevelSecurity.enabled was live-with-wrong-evidence and UNREAD anywhere (a disabled policy kept granting; enforced in rls-compiler the same day), rowLevelSecurity.priority is semantically void under OR-combination → dead+authorWarn. tabPermissions was UNDERSTATED (all four rank values read, not just 'hidden'). 2026-11-08 (#31508): `allowRestore`/`allowPurge` are retiredKey tombstones (#12497) and do not return; `restore`/`purge` exist with the recycle bin (ADR-0148) and the evaluator reads the new keys `allowRestoreOwnDeleted` / `manageRecycleBin` (#30237).",
44
"props": {
55
"name": {
66
"status": "live",
@@ -91,13 +91,13 @@
9191
"status": "dead",
9292
"verifiedAt": "2026-08-26",
9393
"evidence": "packages/spec/src/security/permission.zod.ts (retiredKey tombstone — authored values REJECT with the prescription; z.input types the key never)",
94-
"note": "REMOVED 2026-08-26 (#12497, ADR-0049 enforce-or-remove — maintainer ruling accepting #1883's recommendation B) — the `restore` ObjectQL operation the bit claimed to gate has never existed (no destructive lifecycle verb in the engine's dispatch vocabulary, #8106 pin), so granting it delivered nothing. The former 'live' verdict cited only the evaluator's pre-mapping row, which retired in the same batch: a mapping onto an unwritable bit is a claim about a surface that rejects authoring. A dispatched `restore` stays denied fail-closed via DESTRUCTIVE_OPERATIONS. Tombstoned at the schema and stripped from sources by the protocol-18 conversion `permission-allow-restore-purge-removed`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the key RETURNS with the M2 lifecycle initiative (#1883, feature + RBAC in one batch) — drop the tombstone (and this entry) only if M2 respells it."
94+
"note": "REMOVED 2026-08-26 (#12497, ADR-0049 enforce-or-remove — maintainer ruling accepting #1883's recommendation B) — the `restore` ObjectQL operation the bit claimed to gate had never existed (no destructive lifecycle verb in the engine's dispatch vocabulary, #8106 pin), so granting it delivered nothing. The former 'live' verdict cited only the evaluator's pre-mapping row, which retired in the same batch: a mapping onto an unwritable bit is a claim about a surface that rejects authoring. A dispatched `restore` then stayed denied fail-closed via DESTRUCTIVE_OPERATIONS. Tombstoned at the schema and stripped from sources by the protocol-18 conversion `permission-allow-restore-purge-removed`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the key does NOT return: the `restore` operation now exists with the recycle bin (ADR-0148), and its grants are the NEW keys `allowRestoreOwnDeleted` / `manageRecycleBin` (ADR-0148 D5, their own rows below), never this tombstoned spelling."
9595
},
9696
"allowPurge": {
9797
"status": "dead",
9898
"verifiedAt": "2026-08-26",
9999
"evidence": "packages/spec/src/security/permission.zod.ts (retiredKey tombstone — authored values REJECT with the prescription; z.input types the key never)",
100-
"note": "REMOVED 2026-08-26 (#12497, ADR-0049 enforce-or-remove — maintainer ruling accepting #1883's recommendation B) — the `purge` ObjectQL operation the bit claimed to gate has never existed (no destructive lifecycle verb in the engine's dispatch vocabulary, #8106 pin), so granting it delivered nothing; worse, an admin who set `allowPurge: false` believed a lock on GDPR hard-deletion existed. The former 'live' verdict cited only the evaluator's pre-mapping row, which retired in the same batch. A dispatched `purge` stays denied fail-closed via DESTRUCTIVE_OPERATIONS. Tombstoned at the schema and stripped from sources by the protocol-18 conversion `permission-allow-restore-purge-removed`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the key RETURNS with the M2 lifecycle initiative (#1883, feature + RBAC in one batch) — drop the tombstone (and this entry) only if M2 respells it."
100+
"note": "REMOVED 2026-08-26 (#12497, ADR-0049 enforce-or-remove — maintainer ruling accepting #1883's recommendation B) — the `purge` ObjectQL operation the bit claimed to gate had never existed (no destructive lifecycle verb in the engine's dispatch vocabulary, #8106 pin), so granting it delivered nothing; worse, an admin who set `allowPurge: false` believed a lock on GDPR hard-deletion existed. The former 'live' verdict cited only the evaluator's pre-mapping row, which retired in the same batch. A dispatched `purge` then stayed denied fail-closed via DESTRUCTIVE_OPERATIONS. Tombstoned at the schema and stripped from sources by the protocol-18 conversion `permission-allow-restore-purge-removed`. The entry stays because retiredKey keeps the key in the walked shape (the rls.priority precedent); the key does NOT return: the `purge` operation now exists with the recycle bin (ADR-0148), and its grant is the NEW key `manageRecycleBin` (ADR-0148 D5, its own row below), never this tombstoned spelling."
101101
},
102102
"allowRestoreOwnDeleted": {
103103
"status": "live",

‎packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts‎

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,18 @@ import { isDict, stripKeys } from '../shared.js';
1919
* (protocol 18, #12497 — ADR-0049 enforce-or-remove, maintainer ruling
2020
* 2026-08-26 accepting #1883's recommendation B).
2121
*
22-
* The `restore` / `purge` ObjectQL operations the bits claimed to gate have
23-
* never existed: no destructive lifecycle verb is in the engine's dispatch
24-
* vocabulary (pinned by objectql's
25-
* `engine-middleware-operation-vocabulary.test.ts`, #8106). Authoring the bits
26-
* therefore granted nothing — an advertised switch with nothing behind it, on
27-
* the most destructive operations (undelete, GDPR hard-delete). A pure
28-
* lossless delete: a dispatched `restore`/`purge` was denied before (deny
29-
* unless a bit nothing could ever exercise was granted) and stays denied after
30-
* (the evaluator's `DESTRUCTIVE_OPERATIONS` fail-closed backstop — the
31-
* pre-mapping rows retired in the same batch, #12497). The keys RETURN with
32-
* the M2 lifecycle initiative (feature + RBAC in one batch); #1883 stays open.
22+
* The `restore` / `purge` ObjectQL operations the bits claimed to gate did
23+
* not exist when they were retired: no destructive lifecycle verb was then in
24+
* the engine's dispatch vocabulary (#8106). Authoring the bits therefore
25+
* granted nothing — an advertised switch with nothing behind it, on the most
26+
* destructive operations (undelete, GDPR hard-delete). A pure lossless delete:
27+
* a dispatched `restore`/`purge` was denied before (deny unless a bit nothing
28+
* could ever exercise was granted) and stayed denied after (the evaluator's
29+
* `DESTRUCTIVE_OPERATIONS` fail-closed backstop — the pre-mapping rows retired
30+
* in the same batch, #12497). The keys do NOT return: the operations now exist
31+
* with the recycle bin (ADR-0148), and their grants are NEW spellings,
32+
* `allowRestoreOwnDeleted` and `manageRecycleBin` (ADR-0148 D5), never these
33+
* tombstoned ones.
3334
*
3435
* `allowTransfer` — the third lifecycle bit — is ENFORCED (#3004) and stays.
3536
*
@@ -52,10 +53,9 @@ const permissionAllowRestorePurgeRemoved: MetadataConversion = {
5253
surface: 'permission.objects.<object>.allowRestore / permission.objects.<object>.allowPurge',
5354
summary:
5455
"object-permission keys 'allowRestore' and 'allowPurge' removed (ADR-0049 — the "
55-
+ '`restore`/`purge` operations they claimed to gate have never existed, so granting the '
56-
+ 'bits delivered nothing; dispatched destructive lifecycle verbs stay denied fail-closed. '
57-
+ 'The keys return with the M2 lifecycle initiative, which builds undelete and purge together '
58-
+ 'with the permission bits that gate them)',
56+
+ '`restore`/`purge` operations they claimed to gate did not exist then, so granting the '
57+
+ 'bits delivered nothing. The keys do not return: the operations now exist with the recycle '
58+
+ 'bin (ADR-0148), and their grants are spelled `allowRestoreOwnDeleted` and `manageRecycleBin`)',
5959
apply(stack, emit) {
6060
return mapCollection(stack, 'permissions', (ps, path) => {
6161
const objects = (ps as { objects?: unknown }).objects;

‎packages/spec/src/migrations/entries/retired-keys/18.security__EffectiveObjectPermission__allowPurge.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
// #12497 — the RESPONSE-side face of `security/ObjectPermission:allowPurge`
44
// (see that entry for the full rationale: ADR-0049 enforce-or-remove,
55
// maintainer ruling 2026-08-26 accepting #1883's recommendation B; the key
6-
// returns with the M2 lifecycle initiative). `EffectiveObjectPermissionSchema`
6+
// stays retired — the recycle bin's grants are new keys, ADR-0148 D5).
7+
// `EffectiveObjectPermissionSchema`
78
// extends the same closed base shape (`.extend({ apiOperations }).strip()`,
89
// both faces behind the #12840 residue stage since 2026-08-28) — the clone
910
// shares the authoring shape's per-property schema instances, so the

‎packages/spec/src/migrations/entries/retired-keys/18.security__EffectiveObjectPermission__allowRestore.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
// #12497 — the RESPONSE-side face of `security/ObjectPermission:allowRestore`
44
// (see that entry for the full rationale: ADR-0049 enforce-or-remove,
55
// maintainer ruling 2026-08-26 accepting #1883's recommendation B; the key
6-
// returns with the M2 lifecycle initiative). `EffectiveObjectPermissionSchema`
6+
// stays retired — the recycle bin's grants are new keys, ADR-0148 D5).
7+
// `EffectiveObjectPermissionSchema`
78
// extends the same closed base shape (`.extend({ apiOperations }).strip()`,
89
// both faces behind the #12840 residue stage since 2026-08-28) — the clone
910
// shares the authoring shape's per-property schema instances, so the

‎packages/spec/src/migrations/entries/retired-keys/18.security__ObjectPermission__allowPurge.ts‎

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@
22

33
// #12497 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-26, decision-
44
// inbox batch 5, accepting #1883's recommendation B). `allowPurge` claimed to
5-
// gate a `purge` (hard-delete / GDPR erase) ObjectQL operation that has never
6-
// existed: no destructive lifecycle verb is in the engine's dispatch
7-
// vocabulary (pinned by objectql's
8-
// `engine-middleware-operation-vocabulary.test.ts`, #8106). This was ADR-0049's
5+
// gate a `purge` (hard-delete / GDPR erase) ObjectQL operation that did not
6+
// exist then: no destructive lifecycle verb was in the engine's dispatch
7+
// vocabulary (#8106). This was ADR-0049's
98
// worst false-compliance shape — an admin who set `allowPurge: false` believed
109
// a lock on permanent deletion existed, when the operation itself did not.
1110
// The permission evaluator's pre-mapping row (`OPERATION_TO_PERMISSION`
1211
// purge→allowPurge) retired in the same batch: with the bit unwritable, a
1312
// mapping onto it was a claim about a surface that rejects authoring. A
14-
// dispatched `purge` stays denied fail-closed via the evaluator's
15-
// `DESTRUCTIVE_OPERATIONS` backstop — there is no ungated window in either
16-
// direction. THE KEY RETURNS with the M2 lifecycle initiative (feature + RBAC
17-
// in one batch, maintainer 2026-08-03); anchor card #1883 stays open.
13+
// dispatched `purge` then stayed denied fail-closed via the evaluator's
14+
// `DESTRUCTIVE_OPERATIONS` backstop — there was no ungated window in either
15+
// direction. THE KEY DOES NOT RETURN: the `purge` operation now exists with
16+
// the recycle bin (ADR-0148), and its grant is the NEW key `manageRecycleBin`
17+
// (ADR-0148 D5).
1818
//
1919
// Registered under 18, not 17: v17.0.0 was cut before this landed, so the
2020
// removal ships on the 17.x line (launch-window convention: accept-set

0 commit comments

Comments
 (0)