|
| 1 | +--- |
| 2 | +'@objectstack/objectql': minor |
| 3 | +'@objectstack/service-storage': minor |
| 4 | +'@objectstack/platform-objects': minor |
| 5 | +'@objectstack/verify': minor |
| 6 | +'@objectstack/rest': patch |
| 7 | +--- |
| 8 | + |
| 9 | +feat(attachments): sys_file orphan lifecycle + parent-derived attachment access (#2755) |
| 10 | + |
| 11 | +**Orphan lifecycle (ADR-0057).** Deleting a `sys_attachment` join row used to |
| 12 | +orphan the backing `sys_file` row and its storage bytes forever. `sys_file` |
| 13 | +now declares a lifecycle (`ttl 30d` on a new `deleted_at` tombstone for |
| 14 | +orphans; `retention 7d onlyWhen status=pending` for abandoned uploads), the |
| 15 | +storage plugin's new hooks tombstone a file when its LAST join row is deleted |
| 16 | +(attachments scope only — `Field.file`/`Field.image`/avatar scopes are never |
| 17 | +touched) and un-tombstone on re-attach, and a new LifecycleService **reap |
| 18 | +guard** seam (`registerReapGuard`) re-verifies zero references at sweep time |
| 19 | +and deletes the storage bytes before confirming each row reap. A guarded |
| 20 | +object is never blind-deleted; an erroring guard fails safe (rows retained). |
| 21 | + |
| 22 | +**Attachment access (ADR-0049, Salesforce parent-derived semantics).** |
| 23 | +`sys_attachment` create now requires caller READ visibility of the parent |
| 24 | +record (403 `ATTACHMENT_PARENT_ACCESS`) and server-stamps `uploaded_by` from |
| 25 | +the session (client value ignored); delete requires uploader-or-parent-editor |
| 26 | +(403 `ATTACHMENT_DELETE_DENIED`). The storage upload routes require an |
| 27 | +authenticated session when an auth service is wired (401 `AUTH_REQUIRED`; |
| 28 | +bare kernels stay open) and stamp `owner_id` on new files. |
| 29 | + |
| 30 | +**REMOVED — `sys_attachment.share_type` / `sys_attachment.visibility`.** |
| 31 | +Both fields were modeled in v1 with zero runtime consumers (ADR-0049 |
| 32 | +parsed-but-unenforced). There is no replacement key: attachment access is |
| 33 | +derived from the parent record by the hooks above. Writers of these fields |
| 34 | +should simply stop sending them (unknown-field validation will reject them); |
| 35 | +existing DB columns are left as unmanaged leftovers, no migration needed. |
| 36 | + |
| 37 | +`@objectstack/verify` gains `BootOptions.extraPlugins` for booting optional |
| 38 | +service pairs (e.g. storage + audit) in dogfood fixtures. |
0 commit comments