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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/27734-import-job-failed-wording.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@objectstack/spec': patch
---

docs(spec): `ImportJobStatus` and `ImportJobProgressSchema.error` describe `failed` as the REST server now ends it: a job aborted on a fatal error, or a job whose every row failed (#27734)

Clause-②: no
Metadata-protocol: none

Only the declared meaning changes; no enum value, key, type or export does, and the wire is unchanged.

- **`ImportJobStatus`.** `failed` covered only a job aborted on a fatal error. Since the async import worker in `@objectstack/rest` ends a job that had at least one row and created, updated or skipped none of them `failed` (maintainer ruling B, record 6509036166), the docblock and the value comments say so. `succeeded` still covers a job with no rows, a job whose every row was skipped and a job whose rows only partly failed; a dry run follows the same rule. A cancel wins over that counting rule, so over both of its outcomes, but not over a fatal abort: an error thrown after the job was cancelled still ends it `failed`.
- **`ImportJobProgressSchema.error`.** Described as the fatal error only; it now also says that, when every row failed, it carries one sentence pointing at the job's `results`, which hold each row's reason. The reference page `api/export` is regenerated from it.
4 changes: 2 additions & 2 deletions content/docs/references/api/export.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ const result = CreateImportJobRequestSchema.parse(data);
| **percentComplete** | `number` | ✅ | processed / total as a percentage |
| **undoable** | `boolean` | ✅ | Whether this job can still be logically rolled back (undo log captured, terminal state, not yet reverted) |
| **revertedAt** | `string` | optional | When the job was undone / rolled back (ISO 8601): set only once every row is reverted. An undo that left rows unreverted sets nothing, and the job stays undoable for those rows. |
| **error** | `string` | optional | Fatal error message (when status = failed) |
| **error** | `string` | optional | Why the job failed (when status = failed): the fatal error that aborted it, or, when every row failed, one sentence pointing at the job's `results`, which carry each row's reason |
| **startedAt** | `string` | optional | Processing start timestamp (ISO 8601) |
| **completedAt** | `string` | optional | Completion timestamp (ISO 8601) |
| **createdAt** | `string` | ✅ | Job creation timestamp (ISO 8601) |
Expand Down Expand Up @@ -214,7 +214,7 @@ const result = CreateImportJobRequestSchema.parse(data);
| **percentComplete** | `number` | ✅ | processed / total as a percentage |
| **undoable** | `boolean` | ✅ | Whether this job can still be logically rolled back (undo log captured, terminal state, not yet reverted) |
| **revertedAt** | `string` | optional | When the job was undone / rolled back (ISO 8601): set only once every row is reverted. An undo that left rows unreverted sets nothing, and the job stays undoable for those rows. |
| **error** | `string` | optional | Fatal error message (when status = failed) |
| **error** | `string` | optional | Why the job failed (when status = failed): the fatal error that aborted it, or, when every row failed, one sentence pointing at the job's `results`, which carry each row's reason |
| **startedAt** | `string` | optional | Processing start timestamp (ISO 8601) |
| **completedAt** | `string` | optional | Completion timestamp (ISO 8601) |
| **createdAt** | `string` | ✅ | Job creation timestamp (ISO 8601) |
Expand Down
18 changes: 15 additions & 3 deletions packages/spec/src/api/export.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,21 @@ export const IMPORT_JOB_MAX_ROWS = 50_000;
/**
* Import Job Status — the states the import worker actually moves a job
* through (`succeeded`, not `completed`, is the success terminal).
*
* `succeeded` and `failed` split on whether the job accomplished anything, not
* on whether some row failed (maintainer ruling B, record 6509036166). A job
* that had at least one row and created, updated or skipped none of them ends
* `failed`, as does a job aborted on a fatal error. A job with no rows, a job
* whose every row was skipped and a job whose rows only partly failed end
* `succeeded`. A dry run follows the same rule. A cancel wins over that
* counting rule, so over both of its outcomes, but not over a fatal abort: an
* error thrown after the job was cancelled still ends it `failed`.
*/
export const ImportJobStatus = z.enum([
'pending', // Row persisted, worker not yet started
'running', // Worker streaming through the batch
'succeeded', // Finished (rows may still have per-row errors)
'failed', // Aborted on a fatal error
'succeeded', // Finished with a row created, updated or skipped, or with no rows (other rows may still have per-row errors)
'failed', // Aborted on a fatal error, or every row failed (rows existed and none was created, updated or skipped)
'cancelled', // Cancelled by the caller before completion
]);
export type ImportJobStatus = z.input<typeof ImportJobStatus>;
Expand Down Expand Up @@ -539,7 +548,10 @@ export const ImportJobProgressSchema = lazySchema(() => z.object({
percentComplete: z.number().min(0).max(100).describe('processed / total as a percentage'),
undoable: z.boolean().describe('Whether this job can still be logically rolled back (undo log captured, terminal state, not yet reverted)'),
revertedAt: z.string().optional().describe('When the job was undone / rolled back (ISO 8601): set only once every row is reverted. An undo that left rows unreverted sets nothing, and the job stays undoable for those rows.'),
error: z.string().optional().describe('Fatal error message (when status = failed)'),
error: z.string().optional().describe(
'Why the job failed (when status = failed): the fatal error that aborted it, or, when every row failed, '
+ 'one sentence pointing at the job\'s `results`, which carry each row\'s reason',
),
startedAt: z.string().optional().describe('Processing start timestamp (ISO 8601)'),
completedAt: z.string().optional().describe('Completion timestamp (ISO 8601)'),
createdAt: z.string().describe('Job creation timestamp (ISO 8601)'),
Expand Down
Loading