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 0d59704
Browse filesBrowse the repository at this point in the historyBrowse files
docs(adr): ADR-0148 names ADR-0087's kit rule, the ref a snapshot would hold, and resolve-at-sweep as the rejected alternative
Delta review 6649866889 on #30263 passed with four should-fixes, all text:
- "Aged out of the load path" named the wrong concept: both conversions are
retiredFromLoadPath. The difference main carries is ADR-0087's kit rule — the
enable.trash kit's conversion was removed when 18 was cut (its tombstone stays),
the allowRestore/allowPurge kit is carried at 18 and ages out at 19, and neither
replays at load. ADR-0049's Status note and ADR-0148's Builds on, Leaves
unchanged, Context and D5 now say "kit".
- D4's reason for keeping secrets out of the snapshot no longer conflates the value
with the ref: under ADR-0100 the row carries only the secret ref, so the snapshot
would hold a ref the committed delete released, and encrypting the value into the
entry would open a second store outside sys_secret.
- D7 gains the two choices it omitted: no object-level authoring key (by rule, not
by a flag) and the privacy rules as standing rules that bind v1 and everything
after it; D5 and D4 mark them where they are made.
- D3 names resolve-at-sweep (the LifecycleService's lifecycle-namespace windows,
global and per tenant; the sys_file reap's deleted_at plus a static expireAfter)
as the alternative the record rejects, and leaves the reap shape on the stamped
column to #30237.
docs/adr/README.md is unchanged by this commit (the Status lines did not move).
Claude-Session: https://claude.ai/code/session_01WGufzfC3wLXmeCXwxa963a
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/adr/0049-no-unenforced-security-properties.md
+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
@@ -1,6 +1,6 @@
1
1
# ADR-0049: Spec must not declare security properties the runtime does not enforce (enforce-or-remove gate)
2
2
3
-
**Status**: Accepted (2026-06-15) — implemented: fail-closed `DESTRUCTIVE_OPERATIONS` (`packages/plugins/plugin-security/src/permission-evaluator.ts#DESTRUCTIVE_OPERATIONS`), lifecycle bits RBAC-gated, `apiEnabled` enforced (`runtime/src/api-exposure.ts`), `PolicySchema` removed, EXPERIMENTAL tag convention live. Two gate-valid disposition deviations: agent access-control shipped experimental-tagged (not enforced), `flow.runAs` kept + enforced (not removed). `action.disabled` CEL enforcement to confirm in objectui. **Amended 2026-09-04** (#14402) — recording that an author-written validation rule's `message` became translatable in 17.3.0 (#14381) under the object-scoped bundle key `objects.<object_name>._validations.<rule_name>.message`. That is this gate being **enforced, not reversed**: the 17.0.0 retirement of the top-level `validationMessages` group stands, and the two are not the same route. See **"Amendment (2026-09-04): validation-rule messages are translatable again — enforced, not reversed"** at the end. **Amended 2026-11-05** ([ADR-0148](./0148-recycle-bin-separate-store-restore-under-original-ids-retention-in-days.md), #30234, ruling record 6629858351 on #3146) — the recycle bin returns as a **new** surface whose readers that record names (a separate `sys_recycle_bin` store, restore under the original ids, retention in days through a host hook); the keys this gate removed — `enable.trash` and `softDelete` (16.x, #2377), `allowRestore` and `allowPurge` (#12497) — **stay removed**: their tombstones stand; the `allowRestore` / `allowPurge` conversion (`packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts`) stands; the 16.x `enable.trash` conversion has aged out of the load path as ADR-0087 provides (`object-enable-trash-mru-removed`, retired in `packages/spec/src/conversions/retired-after.census.json`, no entry file); `softDelete` never had one; and none returns under its old spelling. The gate is enforced, not reversed; this decision text is unchanged.
3
+
**Status**: Accepted (2026-06-15) — implemented: fail-closed `DESTRUCTIVE_OPERATIONS` (`packages/plugins/plugin-security/src/permission-evaluator.ts#DESTRUCTIVE_OPERATIONS`), lifecycle bits RBAC-gated, `apiEnabled` enforced (`runtime/src/api-exposure.ts`), `PolicySchema` removed, EXPERIMENTAL tag convention live. Two gate-valid disposition deviations: agent access-control shipped experimental-tagged (not enforced), `flow.runAs` kept + enforced (not removed). `action.disabled` CEL enforcement to confirm in objectui. **Amended 2026-09-04** (#14402) — recording that an author-written validation rule's `message` became translatable in 17.3.0 (#14381) under the object-scoped bundle key `objects.<object_name>._validations.<rule_name>.message`. That is this gate being **enforced, not reversed**: the 17.0.0 retirement of the top-level `validationMessages` group stands, and the two are not the same route. See **"Amendment (2026-09-04): validation-rule messages are translatable again — enforced, not reversed"** at the end. **Amended 2026-11-05** ([ADR-0148](./0148-recycle-bin-separate-store-restore-under-original-ids-retention-in-days.md), #30234, ruling record 6629858351 on #3146) — the recycle bin returns as a **new** surface whose readers that record names (a separate `sys_recycle_bin` store, restore under the original ids, retention in days through a host hook); the keys this gate removed — `enable.trash` and `softDelete` (16.x, #2377), `allowRestore` and `allowPurge` (#12497) — **stay removed**: their tombstones stand; under ADR-0087's kit rule the `allowRestore` / `allowPurge` kit (`packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts`, retired after 17.2.0) is carried at 18 and ages out at 19, while the `enable.trash` kit's conversion (`object-enable-trash-mru-removed`, 16.x) was removed when 18 was cut and only its tombstone remains; neither conversion replays at load (both `retiredFromLoadPath`, `packages/spec/src/conversions/retired-after.census.json`); `softDelete` never had one; and none returns under its old spelling. The gate is enforced, not reversed; this decision text is unchanged.
that never entered the engine's dispatch vocabulary (pinned by
120
124
`packages/objectql/src/engine-middleware-operation-vocabulary.test.ts`); they were removed with #12497 (the maintainer
@@ -215,9 +219,14 @@ number — zero and anything that is not such a number are refused as a configur
215
219
**When the days become a deadline — stamped at delete time.** The ruling decided days-through-a-hook and no more; this
216
220
record decides that the resolver is asked once, at delete, and its answer is written on the entry as the purge
217
221
deadline. Reasons: the toast and the admin page show the person a date, and a later change of the host's answer (a plan
218
-
change, a new default) must not shorten a window a person was already shown; the sweep then reads one stored column,
219
-
as the `sys_file` reap reads `deleted_at`, and calls no resolver per entry. The consequence is accepted: a longer window
220
-
applies to deletes made after the change, not before. No entry is purged before its stamped deadline.
222
+
change, a new default) must not shorten a window a person was already shown. The alternative the record rejects is
223
+
the one the LifecycleService already runs: resolving an object's window at sweep time from the `lifecycle` settings
224
+
namespace, globally and per tenant (ADR-0057 §3.2, `packages/objectql/src/lifecycle/lifecycle-service.ts`), as the
225
+
`sys_file` reap does with `deleted_at` plus a static `ttl.expireAfter` — a window resolved at sweep can move after the
226
+
person was shown it, and a host's days are not a per-object setting. With the deadline stamped, the sweep reads one
227
+
stored column and asks no resolver; how the reap is declared on that column (a `ttl` on the deadline with no added
228
+
window, or a reap guard) is #30237's. The consequence is accepted: a longer window applies to deletes made after the
229
+
change, not before. No entry is purged before its stamped deadline.
221
230
222
231
**The TTL purge runs on the existing LifecycleService** (ADR-0057 §3.3: one sweeper, never a bespoke one), following
223
232
the `sys_file` tombstone pattern: an entry is purged once its stamped deadline has passed, by the platform sweep, under
@@ -252,15 +261,17 @@ slice say so.
252
261
the rows held (#27581), and v1 leaves that as it is;
253
262
- a member-facing 「我删除的」 page.
254
263
255
-
**The privacy rules**, which bind v1 and everything after it:
264
+
**The privacy rules**, which bind v1 and everything after it (the ruling lists them among the risks to plan for;
265
+
making them standing rules is the record's choice, [D7](#d7--what-this-record-fixes-that-the-ruling-left-open)):
256
266
257
267
-**FLS applies when listing.** A snapshot holds every field; the bin shows a caller a field only where the caller may
258
268
read it on the live object.
259
269
-**Secret fields stay out of the snapshot, or are encrypted.** The ruling allows either; this record chooses **out**
260
-
for v1, because the ruling's later item (holding attachments and `sys_secret` rows until purge) leaves the committed delete
261
-
releasing the handles in v1, so an encrypted copy of a value would point at a handle that no longer exists. So the
262
-
`secret` channel's values (ADR-0100) are not written into the entry, and a restored record's secret fields come back
263
-
empty, to be re-entered. Holding them until purge is the later item, and it may revisit this choice.
270
+
for v1. Under ADR-0100 a business row carries only the opaque `secret:<id>` ref and the value lives in `sys_secret`;
271
+
the committed delete releases those refs in v1 (#27581 — holding `sys_secret` rows until purge is the later item), so
272
+
a snapshot of the row would hold a ref that dangles, and encrypting the value into the entry would open a second
273
+
store for a secret outside `sys_secret`. So the `secret` channel's refs are not written into the entry, and a restored
274
+
record's secret fields come back empty, to be re-entered. The later item may revisit this choice.
264
275
-**Purge on organization or environment deletion.** An organization's or environment's entries go with it.
265
276
-**Bin storage counts toward quota and is shown separately.**
266
277
@@ -287,18 +298,20 @@ with the card that lands it. Every row below is new; none is a tombstoned spelli
287
298
288
299
**The old keys stay retired.**`enable.trash`, `softDelete`, `allowRestore` and `allowPurge` are tombstones and none of
289
300
them returns: an author who writes one is refused with the prescription the tombstone carries. What `main` carries for
290
-
them under ADR-0087 is exactly this: the `allowRestore` / `allowPurge` conversion
291
-
(`packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts`) stands; the 16.x conversion
292
-
`object-enable-trash-mru-removed` has aged out of the load path as ADR-0087 provides (retired in
293
-
`packages/spec/src/conversions/retired-after.census.json`, no entry file), so a 15.x artifact that still writes
294
-
`enable.trash` is refused by the tombstone and migrated by hand or on a release that still carries it; `softDelete` never
295
-
had a conversion. This record changes none of that. The new surface uses new spellings on purpose, so that a key which
301
+
them is what ADR-0087's kit rule leaves at this major: the `allowRestore` / `allowPurge` kit
302
+
(`packages/spec/src/conversions/entries/18/permission-allow-restore-purge-removed.ts`, retired after 17.2.0) is carried
303
+
at 18 and ages out at 19; the `enable.trash` kit's conversion (`object-enable-trash-mru-removed`, 16.x) was removed when
304
+
18 was cut and only its tombstone remains, so a 15.x artifact that still writes `enable.trash` is refused by the
305
+
tombstone and migrated by hand or on a release that still carries it; neither conversion replays at load (both are
306
+
`retiredFromLoadPath` in `packages/spec/src/conversions/retired-after.census.json`); `softDelete` never had a
307
+
conversion. This record changes none of that. The new surface uses new spellings on purpose, so that a key which
296
308
once meant "nothing happens" never comes to mean "a snapshot is kept". The retired `apiMethods` values `restore` and
297
309
`purge` (`packages/spec/src/data/api-derivation.ts`) are likewise unchanged. ADR-0049's Status line carries the dated
298
310
note.
299
311
300
312
**No object-level authoring key is named by this record.** v1 applies to every business object and to no `sys_*`
301
-
object by rule, not by a flag. Per-object retention is enterprise and lives in cloud; if an open per-object key is
313
+
object by rule, not by a flag — the ruling said only "business objects, not `sys_*`", and that no key carries it is the
314
+
record's choice ([D7](#d7--what-this-record-fixes-that-the-ruling-left-open)). Per-object retention is enterprise and lives in cloud; if an open per-object key is
302
315
ever ruled, it arrives with its reader in the same change, as ADR-0049 requires.
303
316
304
317
### D6 — What this record does not decide
@@ -326,7 +339,12 @@ Each of these is the record's choice, not the ruling's, and is marked as such wh
326
339
- secret fields kept out of the snapshot in v1, of the two the ruling allowed
0 commit comments