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 e4977aa
Browse filesBrowse the repository at this point in the historyBrowse files
feat(spec,service-storage,client)!: one upload-scope list — the upload requests' `scope` and the SDK's `storage.upload` close to the new `UploadScope` enum, the `sys_file` scope select is built from it, and the upload doors answer any other scope with `400` naming the allowed values instead of `500 INTERNAL` (#22470)
**BREAKING** — an accept-set narrowing on a published request contract and on the SDK method that sends it, shipped as `minor` under the launch-window convention for accept-set narrowings, plus one new export.
14
+
15
+
The presigned and chunked upload requests declared `scope` an open string, while the stored file record (`sys_file.scope`, a closed select) only ever took `user`, `tenant`, `private`, `temp` and `attachments`. Any other value reached the `sys_file` insert, the data engine refused it as an invalid option, and the upload door answered that caller error as `500 INTERNAL`, with a message telling the operator to restore the data engine.
16
+
17
+
### What changes
18
+
19
+
-**`UploadScopeSchema` / `UploadScope`** (`@objectstack/spec`, new, exported from `@objectstack/spec/api`): the upload-scope vocabulary, declared once — `user`, `tenant`, `private`, `temp`, `attachments`. It is a different list from `StorageScopeSchema`, which classifies a storage configuration and is not read by any upload.
20
+
-**`GetPresignedUrlRequestSchema.scope` and `InitiateChunkedUploadRequestSchema.scope`** read it. The default stays `user`. A literal outside the list fails `tsc`, and a parse refuses it on the `scope` key.
21
+
-**`client.storage.upload(file, scope)`** (`@objectstack/client`): the `scope` parameter is typed `UploadScope` instead of `string`, default `user` unchanged, so a scope outside the list fails `tsc` at the SDK call. `client.storage.getPresignedUrl` and `client.storage.initChunkedUpload` take the request types above and narrow with them.
22
+
-**The `sys_file` scope select** (`@objectstack/service-storage`) takes its options from the enum, in its order, with the same labels. The stored values do not change.
23
+
-**The presigned upload door and the chunked upload door** answer a scope outside the list — any string, a case variant, `null`, a number — with `400 INVALID_REQUEST`, naming the allowed values, before a file record, a session record, an upload URL or a backend upload exists. `public` is refused by the same gate and keeps its remedy (`acl: 'public_read'` on the stored file record). An omitted scope is the default `user`, as before. A real data-engine fault still answers `500`.
24
+
25
+
### FROM → TO
26
+
27
+
| before | what to write instead |
28
+
| --- | --- |
29
+
| an upload naming a scope outside the list (a key prefix such as `avatars`, a folder, a record path) | one of `user`, `tenant`, `private`, `temp`, `attachments`, or no `scope` for the default `user`|
30
+
|`client.storage.upload(file, scope)` called with a `scope` typed `string` (`@objectstack/client`) | pass one of the five, or type the value `UploadScope` (`import type { UploadScope } from '@objectstack/spec/api'`) |
31
+
| a caller passing a scope typed `string` into either upload request | type it `UploadScope`|
32
+
33
+
**The one-line fix: send one of the five upload scopes, or none.**`attachments` is for a file whose referrers are record attachment rows (it is what orphan tombstoning reads); `temp` for a scratch file; otherwise `user` or `tenant`.
34
+
35
+
**No stored file record moves**: no upload naming another scope ever succeeded, so no stored record carries one.
36
+
37
+
### The kit
38
+
39
+
-**D3 entry `upload-request-scope-closed`** carries the judgement no rewrite can make: which scope a file that was sent under a free name really belongs under. No D2 conversion: no metadata type carries the upload request, so `os migrate meta` has no authored source to rewrite.
40
+
-**Liveness.** No ledger row: the liveness ledger walks metadata types, and no metadata type carries the upload request.
const result =CompleteChunkedUploadRequestSchema.parse(data);
@@ -224,7 +224,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data);
224
224
|**filename**|`string`| ✅ | Original filename |
225
225
|**mimeType**|`string`| ✅ | File MIME type |
226
226
|**size**|`number`| ✅ | File size in bytes |
227
-
|**scope**|`string`| optional (default: `"user"`) | Storage scope the new file is filed under (default user; attachments for the record attachments surface). Not an access setting: the upload doors refuse public, and a file is served without sign-in only when its stored file record carries acl 'public_read' (ADR-0104). The upload request carries no acl; every upload is stored private. |
227
+
|**scope**|`Enum<'user' \| 'tenant' \| 'private' \| 'temp' \| 'attachments'>`| optional (default: `"user"`) | Storage scope the new file is filed under (default user; attachments for the record attachments surface). Not an access setting: the upload doors refuse public, and a file is served without sign-in only when its stored file record carries acl 'public_read' (ADR-0104). The upload request carries no acl; every upload is stored private. |
228
228
|**bucket**|`string`| optional | Specific bucket override (admin only) |
229
229
230
230
@@ -240,7 +240,7 @@ const result = CompleteChunkedUploadRequestSchema.parse(data);
240
240
|**mimeType**|`string`| ✅ | File MIME type |
241
241
|**totalSize**|`integer`| ✅ | Total file size in bytes |
242
242
|**chunkSize**|`integer`| optional (default: `5242880`) | Size of each chunk in bytes (minimum 5MB per S3 spec) |
243
-
|**scope**|`string`| optional (default: `"user"`) | Storage scope the new file is filed under (default user; attachments for the record attachments surface). Not an access setting: the upload doors refuse public, and a file is served without sign-in only when its stored file record carries acl 'public_read' (ADR-0104). The upload request carries no acl; every upload is stored private. |
243
+
|**scope**|`Enum<'user' \| 'tenant' \| 'private' \| 'temp' \| 'attachments'>`| optional (default: `"user"`) | Storage scope the new file is filed under (default user; attachments for the record attachments surface). Not an access setting: the upload doors refuse public, and a file is served without sign-in only when its stored file record carries acl 'public_read' (ADR-0104). The upload request carries no acl; every upload is stored private. |
244
244
|**bucket**|`string`| optional | Specific bucket override (admin only) |
|[Automation Protocol](/docs/references/automation)| 13 | 70 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. |
26
26
|[Data Protocol](/docs/references/data)| 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. |
0 commit comments