From c213ee8ec1a9d2fc2ff69ee2b58aabedef3d183b Mon Sep 17 00:00:00 2001 From: os-help Date: Tue, 11 Aug 2026 20:30:44 +0000 Subject: [PATCH 1/2] fix(service-storage): give `failed`/`expired` upload-session statuses a producer (#7667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `sys_upload_session.status` declared `failed` and `expired`, the retention backstop reaped on both, and `UploadProgressSchema` published both to every client reading the contract — while nothing in the service ever wrote either. A scan of every session row could only return `in_progress`/`completed`, so the retention rule named two states the system could not enter. ADR-0049 enforce-or-remove, taking the ENFORCE branch: removal would have forked the object from the spec's progress contract, and both failure states are real and were previously invisible. - `failed`: a completion whose backend `completeChunkedUpload` threw left the row at `completing` — non-terminal, so the 7d retention backstop never reaped it and a progress poll read "still assembling" indefinitely. The completion route now stamps `failed` on that path. It records an attempt rather than locking the session: a retry 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 announced bound nothing. A chunk PUT or a complete against an overdue session is now refused 410 `UPLOAD_SESSION_EXPIRED` (registered under `@objectstack/service-storage` in `ERROR_CODE_LEDGER`) and the row is durably stamped `expired`. Progress REPORTS the status rather than refusing — `expired` is a declared member of `UploadProgressSchema.status` and the SDK's `resumeUpload` polls it first. A row with no `expires_at` carries no declared deadline and is left alone; a `completed` row does not become `expired` by waiting for the reaper. The `failed` stamp is best-effort and loud on failure, so a metadata-store error never replaces the real backend cause on its way to the 500. Checklist item `attachments-storage.upload-session-abort` revision 3 records the producers, adds steps that drive both statuses, and records transient `completing` as a knownGap rather than an unreachable-variant FAIL. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0198Jr94CUGy2vDGtT1L8pka --- .../upload-session-terminal-statuses.md | 34 ++++ .../areas/attachments-storage.json | 22 ++- .../src/error-envelope.conformance.test.ts | 27 +++ .../objects/system-upload-session.object.ts | 12 +- .../src/storage-routes.test.ts | 155 ++++++++++++++++++ .../service-storage/src/storage-routes.ts | 126 +++++++++++++- .../spec/src/api/error-code-ledger.zod.ts | 1 + 7 files changed, 362 insertions(+), 15 deletions(-) create mode 100644 .changeset/upload-session-terminal-statuses.md diff --git a/.changeset/upload-session-terminal-statuses.md b/.changeset/upload-session-terminal-statuses.md new file mode 100644 index 0000000000..184318c34d --- /dev/null +++ b/.changeset/upload-session-terminal-statuses.md @@ -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. diff --git a/docs/qa/platform-checklist/areas/attachments-storage.json b/docs/qa/platform-checklist/areas/attachments-storage.json index 904187130b..56317a8c7a 100644 --- a/docs/qa/platform-checklist/areas/attachments-storage.json +++ b/docs/qa/platform-checklist/areas/attachments-storage.json @@ -518,7 +518,7 @@ "title": "Chunked uploads are resumable; abandoned sessions are reaped and their backend multipart uploads ABORTED before the row (the only pointer) is deleted", "since": "v15.1", "status": "active", - "revision": 2, + "revision": 3, "priority": "P2", "surface": "api", "personas": ["seeded admin (admin@objectos.ai)"], @@ -528,7 +528,8 @@ "the four chunked routes at the default base: POST /upload/chunked, PUT /upload/chunked/:uploadId/chunk/:chunkIndex, POST /upload/chunked/:uploadId/complete, GET /upload/chunked/:uploadId/progress" ], "knownGaps": [ - "the billable-stranded-parts consequence (S3 keeps initiated-but-uncompleted multipart parts invisible until AbortMultipartUpload) is only observable against a real S3 backend — stock local-adapter runs prove the session-row lifecycle and that the guard invokes abortChunkedUpload; the S3 key re-seeding path (setUploadKey on a cold sweep) is pinned by unit tests in attachment-lifecycle.test.ts, not demonstrable on local" + "the billable-stranded-parts consequence (S3 keeps initiated-but-uncompleted multipart parts invisible until AbortMultipartUpload) is only observable against a real S3 backend — stock local-adapter runs prove the session-row lifecycle and that the guard invokes abortChunkedUpload; the S3 key re-seeding path (setUploadKey on a cold sweep) is pinned by unit tests in attachment-lifecycle.test.ts, not demonstrable on local", + "'completing' is a genuinely transient status: the complete route writes it, then overwrites it with 'completed' (success) or 'failed' (#7667) in the same request. A scan of settled rows will never show it, and that is correct — it is observable only mid-request or on a row whose process died between the two writes, so a run that cannot produce it is not a defect" ] }, "steps": [ @@ -538,7 +539,9 @@ "abandon the first session mid-flight; backdate its expires_at past the 1d TTL (system write) and trigger the lifecycle sweep", "verify the abandoned session's row is reaped AND the storage adapter's abortChunkedUpload was invoked for its backend_upload_id (dogfood suite instruments this)", "verify the completed session's row is reaped by the 7d terminal-status retention WITHOUT an abort attempt (an abort on a finalized multipart would NoSuchUpload-error and wedge the reap)", - "simulate an abort failure (test seam) and verify the row is VETOED — kept so backend_upload_id survives for the retry" + "simulate an abort failure (test seam) and verify the row is VETOED — kept so backend_upload_id survives for the retry", + "drive 'failed' (#7667): open a third session and POST its /complete with a backend completion that throws (test seam / an eTag list the backend rejects) — the route answers 500 and the sys_upload_session row must read 'failed', NOT the 'completing' it stopped at before #7667; the same uploadId re-completed successfully afterwards must overwrite it with 'completed' (failed records an attempt, it does not lock the session)", + "drive 'expired' (#7667): backdate a live session's expires_at (system write) WITHOUT running the sweep, then (a) PUT a chunk → 410 UPLOAD_SESSION_EXPIRED and the row reads 'expired' with uploaded_chunks unchanged, (b) GET /progress → 200 with status 'expired' (progress REPORTS the expiry rather than refusing it — 'expired' is a declared member of UploadProgressSchema.status and the SDK's resumeUpload polls it first)" ], "acceptance": [ { @@ -568,8 +571,14 @@ { "clause": "every sys_upload_session status enum variant (in_progress / completing / completed / failed / expired) is reachable and terminal ones fall under the 7d retention backstop", "oracle": "api", - "verify": "status reads across the scenarios cover the enum; retention onlyWhen matches {status: {$in: [completed, failed, expired]}}", + "verify": "status reads across the scenarios cover the enum; retention onlyWhen matches {status: {$in: [completed, failed, expired]}}. Each terminal member has a NAMED producer in storage-routes.ts since #7667 — 'completed' on a finished assembly, 'failed' in the completion route's catch, 'expired' when a chunk/complete/progress call is handed a session past its own expires_at — so a scan that finds only ['in_progress','completed'] now means a producer regressed, not that the enum over-declares ('completing' excepted, see knownGaps)", "evidence": "per-variant status reads" + }, + { + "clause": "a session past its own expires_at stops accepting bytes: chunk PUT and complete answer 410 UPLOAD_SESSION_EXPIRED and the row is durably stamped 'expired' (#7667)", + "oracle": "api", + "verify": "the two writes are refused with that code, the row reads 'expired' afterwards, and uploaded_chunks did not move; a session still inside its deadline is unaffected", + "evidence": "410 bodies + the sys_upload_session row read" } ], "negative": [ @@ -584,11 +593,14 @@ "packages/services/service-storage/src/objects/system-upload-session.object.ts (status enum = the variants list; ttl expires_at+1d, retention 7d terminal statuses)", "packages/services/service-storage/src/storage-route-ledger.ts (upload-chunked family)", "packages/services/service-storage/src/storage-routes.ts:274-281 (POST /upload/chunked init contract: filename/mimeType/totalSize required, else 400 INVALID_REQUEST; chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880), i.e. a 5 MiB floor)", + "packages/services/service-storage/src/storage-routes.ts (expireIfPastDeadline / markSessionFailed — the #7667 producers for 'expired' and 'failed'; chunk + complete refuse 410 UPLOAD_SESSION_EXPIRED, progress reports the status)", + "packages/spec/src/api/storage.zod.ts (UploadProgressSchema.status — the client-facing declaration the enum must stay in step with)", "docs/plans/release-15.1-test-plan.md §C4 (#2970 item 4)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item: chunked-session lifecycle + multipart-abort guard from the reap-guard source, variants pinned to the sys_upload_session status enum; S3-only consequences honestly recorded as a known gap", "ref": "claude/platform-test-checklist-ocwugl" }, - { "revision": 2, "date": "2026-08-11", "change": "step-text correction from run #7635: the chunked-init step said `size`, but the route destructures `totalSize` and 400s INVALID_REQUEST without it — a runner following the old text could not open a session at all. Step now names totalSize and the 5 MiB chunkSize floor (which silently rewrites totalChunks when a caller asks for less), with storage-routes.ts:274-281 added to source as the grounding. Clause 4's failed/expired enforce-or-remove finding from the same run is deliberately NOT touched here — it is owned by #7667", "ref": "#7671" } + { "revision": 2, "date": "2026-08-11", "change": "step-text correction from run #7635: the chunked-init step said `size`, but the route destructures `totalSize` and 400s INVALID_REQUEST without it — a runner following the old text could not open a session at all. Step now names totalSize and the 5 MiB chunkSize floor (which silently rewrites totalChunks when a caller asks for less), with storage-routes.ts:274-281 added to source as the grounding. Clause 4's failed/expired enforce-or-remove finding from the same run is deliberately NOT touched here — it is owned by #7667", "ref": "#7671" }, + { "revision": 3, "date": "2026-08-11", "change": "closes the clause-5 finding revision 2 deferred: 'failed' and 'expired' were declared, reaped on, and published by UploadProgressSchema with NO producer, so run #7635's scan could only ever return ['in_progress','completed']. #7667 took the ENFORCE branch of ADR-0049 (removal would have forked the object from the spec's progress contract), so both statuses now have named writers — two steps added to drive them, a new acceptance clause for the 410 UPLOAD_SESSION_EXPIRED refusal, and the transient 'completing' recorded as a knownGap instead of an unreachable-variant FAIL", "ref": "#7667" } ] }, { diff --git a/packages/services/service-storage/src/error-envelope.conformance.test.ts b/packages/services/service-storage/src/error-envelope.conformance.test.ts index 8722022bd1..5f962afda2 100644 --- a/packages/services/service-storage/src/error-envelope.conformance.test.ts +++ b/packages/services/service-storage/src/error-envelope.conformance.test.ts @@ -175,6 +175,33 @@ describe('storage error envelope (#3675)', () => { }); }, }, + { + // #7667: `expired` used to be a status nothing wrote and no route + // enforced — a session past its own `expires_at` kept taking chunks + // until the TTL sweep deleted the row mid-upload. + name: 'chunk upload against a session past its own expires_at', + status: 410, + code: 'UPLOAD_SESSION_EXPIRED', + run: async () => { + const store = new StorageMetadataStore(null); + await store.createSession({ + id: 'sess-gone', + file_id: 'f-2', + key: 'user/f-2.bin', + filename: 'f.bin', + mime_type: 'application/octet-stream', + total_size: 10, + chunk_size: 5, + total_chunks: 2, + status: 'in_progress', + expires_at: new Date(Date.now() - 60_000).toISOString(), + } as any); + const routes = mount(await tmpAdapter(), store); + return drive(routes, 'PUT', `${BASE}/upload/chunked/:uploadId/chunk/:chunkIndex`, { + params: { uploadId: 'sess-gone', chunkIndex: '0' }, + }); + }, + }, { name: 'anonymous upload when a session resolver is wired', status: 401, diff --git a/packages/services/service-storage/src/objects/system-upload-session.object.ts b/packages/services/service-storage/src/objects/system-upload-session.object.ts index 8972e32393..1450bdb62b 100644 --- a/packages/services/service-storage/src/objects/system-upload-session.object.ts +++ b/packages/services/service-storage/src/objects/system-upload-session.object.ts @@ -125,7 +125,17 @@ export const SystemUploadSession = ObjectSchema.create({ // #2755 covers files, not sessions). The TTL reaps any row 1d past its own // `expires_at` (abandoned in-progress sessions included); the retention // backstop reaps terminal-status rows by age even if `expires_at` was - // never set. NOTE: this reaps the session ROW only — a reap guard that + // never set. + // + // Every status this clause names is one the service actually writes (#7667): + // `completed` on a finished assembly, `failed` when the backend completion + // throws, `expired` when a route is handed a session past its own + // `expires_at` — all three in `storage-routes.ts`. Until #7667 the last two + // had NO producer, so this rule reaped on two states the system could never + // enter and `completed` was the only member doing any work. Adding a member + // here without a writer re-opens exactly that hole (ADR-0049). + // + // NOTE: this reaps the session ROW only — a reap guard that // aborts the backend multipart upload for partial S3 sessions is a filed // follow-up (row reap is the declared scope of this item). lifecycle: { diff --git a/packages/services/service-storage/src/storage-routes.test.ts b/packages/services/service-storage/src/storage-routes.test.ts index 7c7344f540..cdd4a93e42 100644 --- a/packages/services/service-storage/src/storage-routes.test.ts +++ b/packages/services/service-storage/src/storage-routes.test.ts @@ -217,6 +217,161 @@ describe('Storage REST Routes', () => { }); }); + // ── terminal session statuses (#7667) ────────────────────────────────── + // `failed` and `expired` are declared on `sys_upload_session.status`, reaped + // on by the retention backstop, and published to clients by + // `UploadProgressSchema` — and until #7667 no code path wrote either. Each + // test below asserts the DURABLE ROW (`store.getSession`), not just the + // response body: a status that exists only in a response is the same dead + // declaration wearing a smaller coat. + describe('terminal upload-session statuses (#7667)', () => { + /** Drive an init and hand back its ids. */ + async function initSession(overrides: Record = {}) { + const initHandler = httpServer._getHandler('POST', '/api/v1/storage/upload/chunked')!; + const initReq = createMockReq({ + body: { filename: 'term.bin', mimeType: 'application/octet-stream', totalSize: 100, ...overrides }, + }); + const initRes = createMockRes(); + await initHandler(initReq, initRes); + expect(initRes._status).toBe(200); + return initRes._json.data as { uploadId: string; resumeToken: string; fileId: string }; + } + + /** Backdate the session's own deadline — the state a day-old row is in. */ + async function backdate(uploadId: string) { + await store.updateSession(uploadId, { expires_at: new Date(Date.now() - 60_000).toISOString() }); + } + + async function putChunk(uploadId: string, resumeToken: string, bytes = 100) { + const chunkHandler = httpServer._getHandler('PUT', '/api/v1/storage/upload/chunked/:uploadId/chunk/:chunkIndex')!; + const res = createMockRes(); + await chunkHandler( + createMockReq({ + params: { uploadId, chunkIndex: '0' }, + headers: { 'x-resume-token': resumeToken }, + rawBody: async () => Buffer.from('x'.repeat(bytes)), + } as any), + res, + ); + return res; + } + + async function getProgress(uploadId: string) { + const progressHandler = httpServer._getHandler('GET', '/api/v1/storage/upload/chunked/:uploadId/progress')!; + const res = createMockRes(); + await progressHandler(createMockReq({ params: { uploadId } }), res); + return res; + } + + async function complete(uploadId: string, parts: Array<{ chunkIndex: number; eTag: string }> = []) { + const completeHandler = httpServer._getHandler('POST', '/api/v1/storage/upload/chunked/:uploadId/complete')!; + const res = createMockRes(); + await completeHandler(createMockReq({ params: { uploadId }, body: { parts } }), res); + return res; + } + + it("stamps 'failed' on the row when the backend completion throws", async () => { + const { uploadId } = await initSession(); + vi.spyOn(adapter, 'completeChunkedUpload').mockRejectedValue(new Error('NoSuchUpload')); + + const res = await complete(uploadId); + + expect(res._status).toBe(500); + // Pre-#7667 the row stuck at `completing` — a NON-terminal status the 7d + // retention backstop never reaps and progress reads as still assembling. + expect((await store.getSession(uploadId))!.status).toBe('failed'); + expect((await getProgress(uploadId))._json.data.status).toBe('failed'); + }); + + it("lets a retried completion overwrite 'failed' with 'completed'", async () => { + const { uploadId, resumeToken } = await initSession(); + const chunkRes = await putChunk(uploadId, resumeToken); + const parts = [{ chunkIndex: 0, eTag: chunkRes._json.data.eTag }]; + + const spy = vi.spyOn(adapter, 'completeChunkedUpload').mockRejectedValueOnce(new Error('transient blip')); + await complete(uploadId, parts); + expect((await store.getSession(uploadId))!.status).toBe('failed'); + + // `failed` records an attempt; it does not lock the session. Nothing + // reads it as a refusal, so a retry runs the happy path. + spy.mockRestore(); + const retry = await complete(uploadId, parts); + expect(retry._status).toBe(200); + expect((await store.getSession(uploadId))!.status).toBe('completed'); + }); + + it("stamps 'expired' and 410s a chunk PUT past the session deadline", async () => { + const { uploadId, resumeToken } = await initSession(); + await backdate(uploadId); + + const res = await putChunk(uploadId, resumeToken); + + expect(res._status).toBe(410); + expect(res._json.error.code).toBe('UPLOAD_SESSION_EXPIRED'); + const row = (await store.getSession(uploadId))!; + expect(row.status).toBe('expired'); + // The chunk was refused, not quietly accepted against a dead session. + expect(row.uploaded_chunks ?? 0).toBe(0); + }); + + it("stamps 'expired' and 410s a completion past the session deadline", async () => { + const { uploadId } = await initSession(); + await backdate(uploadId); + + const res = await complete(uploadId); + + expect(res._status).toBe(410); + expect(res._json.error.code).toBe('UPLOAD_SESSION_EXPIRED'); + expect((await store.getSession(uploadId))!.status).toBe('expired'); + }); + + it("reports 'expired' on progress rather than refusing the poll", async () => { + const { uploadId } = await initSession(); + await backdate(uploadId); + + const res = await getProgress(uploadId); + + // A resuming client (the SDK polls progress first) is told the session is + // gone, in the shape `UploadProgressSchema` already declares. + expect(res._status).toBe(200); + expect(res._json.data.status).toBe('expired'); + expect((await store.getSession(uploadId))!.status).toBe('expired'); + }); + + it('does not expire a session that is still inside its deadline', async () => { + const { uploadId, resumeToken } = await initSession(); + + const res = await putChunk(uploadId, resumeToken); + + expect(res._status).toBe(200); + expect((await store.getSession(uploadId))!.status).toBe('in_progress'); + }); + + it('leaves a completed session alone once its deadline passes', async () => { + const { uploadId, resumeToken } = await initSession(); + const chunkRes = await putChunk(uploadId, resumeToken); + await complete(uploadId, [{ chunkIndex: 0, eTag: chunkRes._json.data.eTag }]); + expect((await store.getSession(uploadId))!.status).toBe('completed'); + + await backdate(uploadId); + + // A finished upload does not become `expired` by sitting around waiting + // for the reaper — the retention backstop reaps it as `completed`. + expect((await getProgress(uploadId))._json.data.status).toBe('completed'); + expect((await store.getSession(uploadId))!.status).toBe('completed'); + }); + + it('leaves a session with no declared deadline in progress', async () => { + const { uploadId } = await initSession(); + // The route always stamps `expires_at`; a row without one carries no + // declared deadline, and this guard enforces the row's own deadline + // rather than inventing one. + await store.updateSession(uploadId, { expires_at: undefined }); + + expect((await getProgress(uploadId))._json.data.status).toBe('in_progress'); + }); + }); + describe('GET /files/:fileId/url', () => { it('should return download URL for committed file', async () => { // Create and commit a file diff --git a/packages/services/service-storage/src/storage-routes.ts b/packages/services/service-storage/src/storage-routes.ts index 9edea6eaf9..12bb09c949 100644 --- a/packages/services/service-storage/src/storage-routes.ts +++ b/packages/services/service-storage/src/storage-routes.ts @@ -4,7 +4,7 @@ import { randomUUID } from 'node:crypto'; import type { IHttpServer, IHttpRequest, IHttpResponse, IStorageService } from '@objectstack/spec/contracts'; // The declared envelope is written in ONE place for the whole platform (#3973). import { sendOk, sendError } from '@objectstack/types'; -import type { StorageMetadataStore, FileRecord } from './metadata-store.js'; +import type { StorageMetadataStore, FileRecord, UploadSessionRecord } from './metadata-store.js'; import type { LocalStorageAdapter } from './local-storage-adapter.js'; import { contentDispositionValue } from './content-disposition.js'; @@ -163,6 +163,74 @@ export function registerStorageRoutes( return session; }; + // ── Terminal upload-session statuses (#7667) ───────────────────────────── + // `sys_upload_session.status` declares `failed` and `expired`, the retention + // backstop reaps on them (`onlyWhen: { status: { $in: ['completed', + // 'failed', 'expired'] } }`), and `UploadProgressSchema` + // (packages/spec/src/api/storage.zod.ts) publishes both to every client that + // reads the contract. Until #7667 NOTHING wrote either one — a declaration + // with no producer, which ADR-0049 treats as enforce-or-remove. Both are + // enforced here rather than removed, because the two failure states are real + // and were previously invisible: + // + // - `failed`: a completion whose backend `completeChunkedUpload` threw left + // the row at `completing` FOREVER. That is a non-terminal status, so the + // retention backstop never reaped it, and a progress poller read "still + // assembling" for a session that had already given up. + // - `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`) was advisory on the way in and abrupt on + // the way out. + // + // The reap guard (`createUploadSessionReapGuard`) already handled both — it + // aborts the backend multipart for any non-`completed` row with a + // `backend_upload_id` — so this closes the loop rather than opening one. + + /** Statuses no longer in flight — never re-statused by the expiry guard. */ + const TERMINAL_SESSION_STATUSES = new Set(['completed', 'failed', 'expired']); + + /** + * Status an in-flight session `expired` once it is past its own `expires_at`, + * and hand back the row as it now stands. + * + * A row with no `expires_at` (or an unparseable one) has no declared deadline + * and is left alone: this enforces the deadline the session itself carries, + * it does not invent one. Terminal rows are returned untouched — a + * `completed` upload does not become `expired` by sitting around. + */ + const expireIfPastDeadline = async (session: UploadSessionRecord): Promise => { + if (TERMINAL_SESSION_STATUSES.has(session.status)) return session; + const deadline = session.expires_at ? Date.parse(session.expires_at) : NaN; + if (!Number.isFinite(deadline) || deadline > Date.now()) return session; + const updated = await store.updateSession(session.id, { status: 'expired' }); + // `updateSession` answers null only when the row went away under us (the + // TTL sweep, most likely) — the caller is refused either way. + return updated ?? { ...session, status: 'expired' }; + }; + + /** + * Best-effort `failed` stamp for a completion that threw. + * + * Deliberately swallowing-but-loud: the caller is already on its way to a + * 500 carrying the REAL cause, and letting the status write replace that + * cause would trade a diagnosable backend error for a metadata-store one. + * The row staying at `completing` is the pre-#7667 behaviour, so the warn + * names the consequence rather than pretending nothing happened. + */ + const markSessionFailed = async (uploadId: string): Promise => { + try { + await store.updateSession(uploadId, { status: 'failed' }); + } catch (statusErr: any) { + opts.logger?.warn( + `[storage] upload session ${uploadId} failed to complete, and the 'failed' status could not be ` + + `persisted (${statusErr?.message ?? statusErr}) — the row stays at 'completing', so the 7d ` + + 'retention backstop will not reap it and progress reads will overstate it. Restore the data ' + + 'engine; the reap guard still aborts the backend multipart when the TTL sweep reaches the row.', + ); + } + }; + // --------------------------------------------------------------------------- // POST /storage/upload/presigned // --------------------------------------------------------------------------- @@ -369,6 +437,19 @@ export function registerStorageRoutes( return; } + // Expiry is checked AFTER the resume token: a caller who cannot prove it + // owns the session learns nothing about its state (#7667). + const live = await expireIfPastDeadline(session); + if (live.status === 'expired') { + sendError( + res, + 410, + 'UPLOAD_SESSION_EXPIRED', + `Upload session expired at ${live.expires_at}; start a new chunked upload`, + ); + return; + } + // Get raw body (binary data) let data: Buffer; if (req.rawBody) { @@ -422,6 +503,17 @@ export function registerStorageRoutes( return; } + const live = await expireIfPastDeadline(session); + if (live.status === 'expired') { + sendError( + res, + 410, + 'UPLOAD_SESSION_EXPIRED', + `Upload session expired at ${live.expires_at}; start a new chunked upload`, + ); + return; + } + await store.updateSession(uploadId, { status: 'completing' }); const partsFromBody = (req.body?.parts ?? []) as Array<{ chunkIndex: number; eTag: string }>; @@ -431,13 +523,23 @@ export function registerStorageRoutes( })); let finalKey = session.key; - if (storage.completeChunkedUpload) { - finalKey = await storage.completeChunkedUpload(uploadId, partsForBackend); - } + try { + if (storage.completeChunkedUpload) { + finalKey = await storage.completeChunkedUpload(uploadId, partsForBackend); + } - // Update file + session - await store.updateFile(session.file_id, { status: 'committed', key: finalKey }); - await store.updateSession(uploadId, { status: 'completed' }); + // Update file + session + await store.updateFile(session.file_id, { status: 'committed', key: finalKey }); + await store.updateSession(uploadId, { status: 'completed' }); + } catch (completionErr) { + // Terminal for THIS attempt, not a lock: nothing here reads `failed` as + // a refusal, so a client that retries the same uploadId after a + // transient backend blip runs the happy path again and overwrites it + // with `completed`. What the stamp buys is that an attempt which is + // NOT retried stops claiming to be in flight (#7667). + await markSessionFailed(uploadId); + throw completionErr; + } sendOk(res, { fileId: session.file_id, @@ -458,12 +560,18 @@ export function registerStorageRoutes( try { if ((await requireUploadSession(req, res)) === false) return; const { uploadId } = req.params; - const session = await store.getSession(uploadId); - if (!session) { + const stored = await store.getSession(uploadId); + if (!stored) { sendError(res, 404, 'UPLOAD_SESSION_NOT_FOUND', 'Upload session not found'); return; } + // Progress REPORTS the expiry rather than refusing it: `expired` is a + // declared member of `UploadProgressSchema.status`, and a resuming client + // (the SDK's `resumeUpload` polls this first) needs to be told the + // session is gone, not handed a 410 it has to interpret (#7667). + const session = await expireIfPastDeadline(stored); + const uploadedChunks = session.uploaded_chunks ?? 0; const uploadedSize = session.uploaded_size ?? 0; const percentComplete = session.total_size > 0 diff --git a/packages/spec/src/api/error-code-ledger.zod.ts b/packages/spec/src/api/error-code-ledger.zod.ts index 4e22b4f594..e3bb963b60 100644 --- a/packages/spec/src/api/error-code-ledger.zod.ts +++ b/packages/spec/src/api/error-code-ledger.zod.ts @@ -209,6 +209,7 @@ export const ERROR_CODE_LEDGER = { 'INTERNAL', 'INVALID_REQUEST', 'INVALID_RESUME_TOKEN', + 'UPLOAD_SESSION_EXPIRED', // chunk/complete against a session past its own expires_at (#7667) 'UPLOAD_SESSION_NOT_FOUND', ], '@objectstack/service-i18n': [ From d4cceb667bf8cb21fb8757b35d47ce0d31babc64 Mon Sep 17 00:00:00 2001 From: os-help Date: Tue, 11 Aug 2026 22:43:35 +0000 Subject: [PATCH 2/2] docs(spec): regenerate API reference for the UPLOAD_SESSION_EXPIRED ledger entry (#7667) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 产物随源走: registering `UPLOAD_SESSION_EXPIRED` in `ERROR_CODE_LEDGER` widens the `ErrorCode` union every enveloped response references, so all 11 `content/docs/references/api/*.mdx` pages that render it were stale and `check:docs` (`build-docs.ts --check`) failed the TypeScript Type Check job. The whole diff is that one addition propagating: a new `UPLOAD_SESSION_EXPIRED` bullet in `error-code-ledger.mdx`, and the union arity in every rendered `error` column moving `+260 more` → `+261 more`. No unrelated drift was absorbed. Generated, not hand-written: `pnpm --filter @objectstack/spec gen:schema && gen:docs` on a clean tree with no merge in progress (#5370). `authorable-surface.base.json` was not touched, so no re-anchoring rode along; `json-schema/openapi.json` was not cleared by the rmSync and was refreshed anyway (#5371, gitignored either way). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_0198Jr94CUGy2vDGtT1L8pka --- content/docs/references/api/analytics.mdx | 6 +-- content/docs/references/api/auth.mdx | 4 +- .../docs/references/api/automation-api.mdx | 18 ++++----- content/docs/references/api/batch.mdx | 4 +- content/docs/references/api/contract.mdx | 15 ++++---- .../docs/references/api/error-code-ledger.mdx | 1 + content/docs/references/api/export.mdx | 12 +++--- content/docs/references/api/metadata.mdx | 38 +++++++++---------- content/docs/references/api/package-api.mdx | 16 ++++---- content/docs/references/api/protocol.mdx | 6 +-- content/docs/references/api/storage.mdx | 16 ++++---- 11 files changed, 69 insertions(+), 67 deletions(-) diff --git a/content/docs/references/api/analytics.mdx b/content/docs/references/api/analytics.mdx index 4d1c6b8eef..ead97a0d6f 100644 --- a/content/docs/references/api/analytics.mdx +++ b/content/docs/references/api/analytics.mdx @@ -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. | @@ -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[]; fields: object[]; sql?: string }` | ✅ | | @@ -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[] }` | ✅ | | diff --git a/content/docs/references/api/auth.mdx b/content/docs/references/api/auth.mdx index 350be76b82..642db72530 100644 --- a/content/docs/references/api/auth.mdx +++ b/content/docs/references/api/auth.mdx @@ -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 }` | ✅ | | @@ -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; … }` | ✅ | | diff --git a/content/docs/references/api/automation-api.mdx b/content/docs/references/api/automation-api.mdx index f82f9e67f4..2aa81457f2 100644 --- a/content/docs/references/api/automation-api.mdx +++ b/content/docs/references/api/automation-api.mdx @@ -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 | @@ -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 }` | ✅ | | @@ -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 | @@ -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 | @@ -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 }` | ✅ | | @@ -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 }` | ✅ | | @@ -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 }` | ✅ | | @@ -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 }` | ✅ | | @@ -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 | diff --git a/content/docs/references/api/batch.mdx b/content/docs/references/api/batch.mdx index 5c40c9768e..6a82367034 100644 --- a/content/docs/references/api/batch.mdx +++ b/content/docs/references/api/batch.mdx @@ -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` | 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. | @@ -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 | diff --git a/content/docs/references/api/contract.mdx b/content/docs/references/api/contract.mdx index d7d1d77761..3373449c56 100644 --- a/content/docs/references/api/contract.mdx +++ b/content/docs/references/api/contract.mdx @@ -27,7 +27,7 @@ const result = ApiErrorSchema.parse(data); | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +256 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | +| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| 'INVALID_FORMAT' \| 'VALUE_TOO_LONG' \| 'VALUE_TOO_SHORT' \| 'VALUE_OUT_OF_RANGE' \| … +257 more>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | | **message** | `string` | ✅ | Readable error message | | **category** | `string` | optional | Error category (e.g. validation, authorization) | | **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | @@ -292,6 +292,7 @@ const result = ApiErrorSchema.parse(data); * `UNSUPPORTED` * `UNSUPPORTED_QUERY_PARAM` * `UNSUPPORTED_TRANSFORM` +* `UPLOAD_SESSION_EXPIRED` * `UPLOAD_SESSION_NOT_FOUND` * `USER_ALREADY_EXISTS` * `VALIDATION_FAILED` @@ -310,7 +311,7 @@ const result = ApiErrorSchema.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 | @@ -349,7 +350,7 @@ const result = ApiErrorSchema.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; success: boolean; errors?: object[]; index?: number; … }[]` | ✅ | Results for each item in the batch | @@ -391,7 +392,7 @@ const result = ApiErrorSchema.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 | | **id** | `string` | ✅ | ID of the deleted record | @@ -443,7 +444,7 @@ const result = ApiErrorSchema.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** | `Record[]` | ✅ | Array of matching records | | **pagination** | `{ total?: number; limit?: number; offset?: number; cursor?: string; … }` | ✅ | Pagination info | @@ -459,7 +460,7 @@ const result = ApiErrorSchema.parse(data); | :--- | :--- | :--- | :--- | | **id** | `string` | optional | Record ID if processed | | **success** | `boolean` | ✅ | | -| **errors** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +260 more>; message: string; category?: string; httpStatus?: integer; … }[]` | optional | | +| **errors** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| … +261 more>; message: string; category?: string; httpStatus?: integer; … }[]` | optional | | | **index** | `number` | optional | Index in original request | | **data** | `any` | optional | Result data (e.g. created record) | @@ -492,7 +493,7 @@ const result = ApiErrorSchema.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** | `Record` | ✅ | The requested or modified record | diff --git a/content/docs/references/api/error-code-ledger.mdx b/content/docs/references/api/error-code-ledger.mdx index 980787b5c6..7bdb404dec 100644 --- a/content/docs/references/api/error-code-ledger.mdx +++ b/content/docs/references/api/error-code-ledger.mdx @@ -364,6 +364,7 @@ const result = ErrorCode.parse(data); * `UNSUPPORTED` * `UNSUPPORTED_QUERY_PARAM` * `UNSUPPORTED_TRANSFORM` +* `UPLOAD_SESSION_EXPIRED` * `UPLOAD_SESSION_NOT_FOUND` * `USER_ALREADY_EXISTS` * `VALIDATION_FAILED` diff --git a/content/docs/references/api/export.mdx b/content/docs/references/api/export.mdx index d42ae97b35..1523eb0ffa 100644 --- a/content/docs/references/api/export.mdx +++ b/content/docs/references/api/export.mdx @@ -57,7 +57,7 @@ const result = CreateExportJobRequestSchema.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** | `{ jobId: string; status: Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>; estimatedRecords?: integer; createdAt: string }` | ✅ | | @@ -157,7 +157,7 @@ const result = CreateExportJobRequestSchema.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** | `{ jobId: string; status: Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>; format: Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>; totalRecords?: integer; … }` | ✅ | | @@ -231,7 +231,7 @@ const result = CreateExportJobRequestSchema.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** | `{ jobId: string; downloadUrl: string; fileName: string; fileSize: integer; … }` | ✅ | | @@ -449,7 +449,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | 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** | `{ totalRecords: integer; validRecords: integer; invalidRecords: integer; duplicateRecords: integer; … }` | ✅ | | @@ -488,7 +488,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | 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** | `{ jobs: object[]; nextCursor?: string; hasMore: boolean }` | ✅ | | @@ -546,7 +546,7 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | 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; name: string; enabled: boolean; nextRunAt?: string; … }` | ✅ | | diff --git a/content/docs/references/api/metadata.mdx b/content/docs/references/api/metadata.mdx index 33cc8906ee..1f896ea7c8 100644 --- a/content/docs/references/api/metadata.mdx +++ b/content/docs/references/api/metadata.mdx @@ -51,7 +51,7 @@ const result = AppDefinitionResponseSchema.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 \| Record; description?: string \| Record; icon?: string; … }` | ✅ | Full App Configuration | @@ -65,7 +65,7 @@ const result = AppDefinitionResponseSchema.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; icon?: string; description?: string }[]` | ✅ | List of available concepts (Objects, Apps, Flows) | @@ -92,7 +92,7 @@ const result = AppDefinitionResponseSchema.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** | `{ total: integer; succeeded: integer; failed: integer; errors?: object[] }` | ✅ | Bulk operation result | @@ -117,7 +117,7 @@ const result = AppDefinitionResponseSchema.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** | `{ type: string; name: string }` | ✅ | | @@ -131,7 +131,7 @@ const result = AppDefinitionResponseSchema.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** | `{ sourceType: string; sourceName: string; targetType: string; targetName: string; … }[]` | ✅ | Items this item depends on | @@ -145,7 +145,7 @@ const result = AppDefinitionResponseSchema.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** | `{ sourceType: string; sourceName: string; targetType: string; targetName: string; … }[]` | ✅ | Items that depend on this item | @@ -159,7 +159,7 @@ const result = AppDefinitionResponseSchema.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** | `Record` | optional | Effective metadata with all overlays applied | @@ -173,7 +173,7 @@ const result = AppDefinitionResponseSchema.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** | `{ exists: boolean }` | ✅ | | @@ -200,7 +200,7 @@ const result = AppDefinitionResponseSchema.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** | `any` | ✅ | Exported metadata bundle | @@ -228,7 +228,7 @@ const result = AppDefinitionResponseSchema.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** | `{ total: integer; imported: integer; skipped: integer; failed: integer; … }` | ✅ | Import result | @@ -242,7 +242,7 @@ const result = AppDefinitionResponseSchema.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** | `{ type: string; name: string; definition: Record }` | ✅ | Metadata item | @@ -256,7 +256,7 @@ const result = AppDefinitionResponseSchema.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** | `Record[]` | ✅ | Array of metadata definitions | @@ -270,7 +270,7 @@ const result = AppDefinitionResponseSchema.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** | `string[]` | ✅ | Array of metadata item names | @@ -284,7 +284,7 @@ const result = AppDefinitionResponseSchema.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; baseType: string; baseName: string; packageId?: string; … }` | optional | Overlay definition, undefined if none | @@ -348,7 +348,7 @@ Metadata query with filtering, sorting, and pagination | 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** | `{ items: object[]; total: integer; page: integer; pageSize: integer }` | ✅ | Paginated query result | @@ -406,7 +406,7 @@ Metadata query with filtering, sorting, and pagination | 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** | `{ type: string; label: string; description?: string; filePatterns: string[]; … }` | optional | Type info | @@ -420,7 +420,7 @@ Metadata query with filtering, sorting, and pagination | 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** | `string[]` | ✅ | Registered metadata type identifiers | @@ -446,7 +446,7 @@ Metadata query with filtering, sorting, and pagination | 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** | `{ valid: boolean; errors?: object[]; warnings?: object[] }` | ✅ | Validation result | @@ -460,7 +460,7 @@ Metadata query with filtering, sorting, and pagination | 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; pluralLabel?: string; description?: string; … }` | ✅ | Full Object Schema | diff --git a/content/docs/references/api/package-api.mdx b/content/docs/references/api/package-api.mdx index e3afb88273..691645aef3 100644 --- a/content/docs/references/api/package-api.mdx +++ b/content/docs/references/api/package-api.mdx @@ -57,7 +57,7 @@ Get installed package response | 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** | `{ manifest: object; status?: Enum<'installed' \| 'disabled' \| 'installing' \| 'upgrading' \| 'uninstalling' \| 'error'>; enabled?: boolean; installedAt?: string; … }` | ✅ | Installed package details | @@ -89,7 +89,7 @@ List installed packages response | 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** | `{ packages: object[]; total?: integer; nextCursor?: string; hasMore: boolean }` | ✅ | | @@ -143,7 +143,7 @@ Install package response | 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** | `{ package: object; dependencyResolution?: object; namespaceConflicts?: object[]; message?: string }` | ✅ | | @@ -185,7 +185,7 @@ Rollback package response | 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; restoredVersion?: string; message?: string }` | ✅ | | @@ -220,7 +220,7 @@ Upgrade package response | 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; phase: string; plan?: object; snapshotId?: string; … }` | ✅ | | @@ -250,7 +250,7 @@ Resolve dependencies response | 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** | `{ dependencies: object[]; canProceed: boolean; requiredActions: object[]; installOrder: string[]; … }` | ✅ | Dependency resolution result with topological sort | @@ -277,7 +277,7 @@ Uninstall package response | 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** | `{ packageId: string; success: boolean; message?: string }` | ✅ | | @@ -309,7 +309,7 @@ Upload artifact response | 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; artifactRef?: object; submissionId?: string; message?: string }` | ✅ | | diff --git a/content/docs/references/api/protocol.mdx b/content/docs/references/api/protocol.mdx index d9c5a5ddfb..d6dd9c4c74 100644 --- a/content/docs/references/api/protocol.mdx +++ b/content/docs/references/api/protocol.mdx @@ -280,7 +280,7 @@ const result = AiAgentCapabilitiesSchema.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 | @@ -428,7 +428,7 @@ const result = AiAgentCapabilitiesSchema.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 | @@ -1528,7 +1528,7 @@ Uninstall package response | 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 | diff --git a/content/docs/references/api/storage.mdx b/content/docs/references/api/storage.mdx index bafd14469f..4863e3bac3 100644 --- a/content/docs/references/api/storage.mdx +++ b/content/docs/references/api/storage.mdx @@ -46,7 +46,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ fileId: string; key: string; size: integer; mimeType: string; … }` | ✅ | | @@ -72,7 +72,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ url: string }` | ✅ | | @@ -101,7 +101,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ path: string; name: string; size: integer; mimeType: string; … }` | ✅ | Uploaded file metadata | @@ -147,7 +147,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ uploadId: string; resumeToken: string; fileId: string; totalChunks: integer; … }` | ✅ | | @@ -161,7 +161,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ uploadUrl: string; downloadUrl?: string; fileId: string; method: Enum<'PUT' \| 'POST'>; … }` | ✅ | | @@ -175,7 +175,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ key: string }` | ✅ | | @@ -202,7 +202,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ chunkIndex: integer; eTag: string; bytesReceived: integer }` | ✅ | | @@ -216,7 +216,7 @@ const result = CompleteChunkedUploadRequestSchema.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** | `{ uploadId: string; fileId: string; filename: string; totalSize: integer; … }` | ✅ | |