Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .changeset/upload-session-terminal-statuses.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
---
'@objectstack/service-storage': patch
'@objectstack/spec': patch
---

storage: `sys_upload_session.status` `failed` / `expired` now have producers

Both statuses were declared on the object, reaped on by the retention backstop
(`onlyWhen: { status: { $in: ['completed', 'failed', 'expired'] } }`), and
published to clients by `UploadProgressSchema` — while nothing in the service
ever wrote either one. A scan of every session row could only return
`in_progress` / `completed`, so retention named two states the system could not
enter. Under ADR-0049 (enforce-or-remove) this takes the enforce branch:
removing them would have forked the object from the spec's progress contract,
and the two failure states they name are real.

- **`failed`** — a chunked completion whose backend `completeChunkedUpload`
threw left the row at `completing`: a non-terminal status the 7d retention
backstop never reaped, and one a progress poll reported as "still assembling"
forever. The completion route now stamps `failed` on that path. It records an
attempt rather than locking the session — a retry of the same `uploadId` runs
the happy path and overwrites it with `completed`.
- **`expired`** — a session past its own `expires_at` kept answering
`in_progress` and kept accepting chunks until the TTL sweep deleted the row
out from under the caller, so the deadline the init response already announced
(`expiresAt`) bound nothing. A chunk `PUT` or a `complete` against an
overdue session is now refused with **410 `UPLOAD_SESSION_EXPIRED`** (new code,
registered under `@objectstack/service-storage` in `ERROR_CODE_LEDGER`) and the
row is durably stamped `expired`. `GET .../progress` reports the status instead
of refusing — `expired` is a declared member of `UploadProgressSchema.status`,
and the SDK's `resumeUpload` reads progress first.

A session with no `expires_at` carries no declared deadline and is left alone,
and a `completed` row does not become `expired` by waiting for the reaper.
6 changes: 3 additions & 3 deletions content/docs/references/api/analytics.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -44,7 +44,7 @@ const result = AnalyticsEndpoint.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; title?: string; measures: object[]; dimensions: object[] }[]` | ✅ | Available cubes, each as the `CubeMeta` discovery projection — the cube name, its title, and the measures/dimensions a client may name in a query. A bare array: there is no `cubes` wrapper object, and no cube `sql` is published. |

Expand DownExpand Up@@ -79,7 +79,7 @@ const result = AnalyticsEndpoint.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ rows: Record<string, any>[]; fields: object[]; sql?: string }` | ✅ | |

Expand All@@ -93,7 +93,7 @@ const result = AnalyticsEndpoint.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ sql: string; params: any[] }` | ✅ | |

Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/api/auth.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -117,7 +117,7 @@ const result = AuthProvider.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ session: object; user: object; token?: string }` | ✅ | |

Expand DownExpand Up@@ -153,7 +153,7 @@ const result = AuthProvider.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ id: string; email: string; emailVerified: boolean; name: string; … }` | ✅ | |

Expand Down
18 changes: 9 additions & 9 deletions content/docs/references/api/automation-api.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -119,7 +119,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; label: string; description?: string; successMessage?: string; … }` | ✅ | The created flow definition |

Expand All@@ -144,7 +144,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; deleted: boolean }` | ✅ | |

Expand DownExpand Up@@ -187,7 +187,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; label: string; description?: string; successMessage?: string; … }` | ✅ | Full flow definition |

Expand All@@ -213,7 +213,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ id: string; flowName: string; flowVersion?: integer; status: Enum<'pending' \| 'running' \| 'paused' \| 'completed' \| 'failed' \| 'cancelled' \| … +2 more>; … }` | ✅ | Full execution log with step details |

Expand DownExpand Up@@ -241,7 +241,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ flows: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | |

Expand DownExpand Up@@ -269,7 +269,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ runs: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | |

Expand All@@ -295,7 +295,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; enabled: boolean }` | ✅ | |

Expand DownExpand Up@@ -325,7 +325,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ success: boolean; output?: any; error?: string; durationMs?: number }` | ✅ | |

Expand All@@ -351,7 +351,7 @@ const result = AutomationApiErrorCode.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **data** | `{ name: string; label: string; description?: string; successMessage?: string; … }` | ✅ | The updated flow definition |

Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/api/batch.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -55,7 +55,7 @@ const result = BatchConfigSchema.parse(data);
| :--- | :--- | :--- | :--- |
| **id** | `string` | optional | Record ID if operation succeeded |
| **success** | `boolean` | ✅ | Whether this record was processed successfully |
| **errors** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }[]` | optional | Array of errors if operation failed. Branch on `errors[0].code` — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error (#4793). A NON-atomic batch that stopped (the `continueOnError: false` default) marks its un-attempted tail with the same NOT_ATTEMPTED code — rows before the failure stay written and keep reporting success, since nothing was rolled back (#7539). |
| **errors** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }[]` | optional | Array of errors if operation failed. Branch on `errors[0].code` — an atomic batch that rolled back marks rows that were written then undone with code ROLLED_BACK and rows never reached with NOT_ATTEMPTED, while the causal row keeps its own error (#4793). A NON-atomic batch that stopped (the `continueOnError: false` default) marks its un-attempted tail with the same NOT_ATTEMPTED code — rows before the failure stay written and keep reporting success, since nothing was rolled back (#7539). |
| **data** | `Record<string, any>` | optional | Full record data (if returnRecords=true) |
| **index** | `number` | optional | Index of the record in the request array |
| **droppedFields** | `{ object: string; fields: string[]; reason: Enum<'readonly' \| 'readonly_when' \| 'primary_key'> }[]` | optional | Write-observability (#3407/#3431/#3455): caller-supplied fields LEGALLY stripped from THIS row before it was written — static `readonly` (#2948) / TRUE `readonlyWhen` (#3042) on update, or the #3043 create-ingress strip. Per-row because a batch can drop different fields on different rows (`readonlyWhen` is record-state-dependent). Present ONLY when ≥1 field was dropped for this row; the row still succeeded (success unchanged). A single response header cannot express per-row drops, so this body field is the canonical bulk channel — REST does not emit `X-ObjectStack-Dropped-Fields` for batches. Optional — omit-when-empty keeps the shape backward-compatible. |
Expand DownExpand Up@@ -122,7 +122,7 @@ const result = BatchConfigSchema.parse(data);
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **success** | `boolean` | ✅ | Operation success status |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }` | optional | Error details if success is false |
| **meta** | `{ timestamp: string; duration?: number; requestId?: string; traceId?: string }` | optional | Response metadata |
| **operation** | `Enum<'create' \| 'update' \| 'upsert' \| 'delete'>` | optional | Operation type that was performed |
| **total** | `number` | ✅ | Total number of records in the batch |
Expand Down
Loading
Loading