diff --git a/.changeset/17158-export-job-family-retired.md b/.changeset/17158-export-job-family-retired.md new file mode 100644 index 00000000000..7c2ecc44ec1 --- /dev/null +++ b/.changeset/17158-export-job-family-retired.md @@ -0,0 +1,36 @@ +--- +'@objectstack/spec': minor +--- + +**BREAKING** — the export-job API family, the `IExportService` contract and `ScheduleState` leave the public surface (#17158). + +A `major`-class change, recorded as `minor` under the launch-window convention. Maintainer ruling A (decision batch #122 item 3, 「同意」), landing route A (decision batch #221 item 2, 「同意」: objectui retired its side first, in objectui#10247), and a scope note (「同意」) that puts the export-job list pair in; ADR-0049 enforce-or-remove. + +**Why.** `@objectstack/spec` declared a complete asynchronous export API — create a job, poll its progress, fetch a download link, list jobs, schedule a recurring export, cancel — and nothing on the platform served any of it. `@objectstack/rest` mounts no `/api/v1/data/export` route and no `POST` on `/api/v1/data/:object/export`; `IExportService` had no provider; and no package, example, app or skill in this repository, in objectui at the pinned sha, or in cloud read any of the names. An AI following the generated API reference wrote calls that answer `404`. The scheduled-export shapes were worse than unserved: after the cron positions were deleted earlier in this release line, `ScheduledExport.schedule` and `ScheduleExportRequest.schedule` were REQUIRED blocks that could hold no schedule, so an author who filled in the `timezone` believed they had scheduled something. `ScheduleState` described the runtime state of a scheduled flow that no scheduler ever wrote or read. + +### FROM → TO + +| removed | from | what to write instead | +| --- | --- | --- | +| `ExportJobStatus`, `CreateExportJobRequestSchema` / `CreateExportJobResponseSchema`, `ExportJobProgressSchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/api` | nothing — no route ever created or tracked an export job. To export records, call the served synchronous door `GET /api/v1/data/:object/export` (the SDK's `data.export`), which answers the file itself as CSV, JSON or XLSX. | +| `GetExportJobDownloadRequestSchema` / `GetExportJobDownloadResponseSchema`, `ListExportJobsRequestSchema` / `ListExportJobsResponseSchema`, `ExportJobSummarySchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/api` | nothing — no job ever existed to download or list. | +| `ScheduledExportSchema`, `ScheduleExportRequestSchema` / `ScheduleExportResponseSchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/api` | a `Job` (`system/job.zod.ts`) whose handler performs the export, with its cadence on `Job.schedule.expression` — the one cron slot the platform evaluates. | +| `ExportApiContracts` | `@objectstack/spec/api` | nothing — every route it named was unserved. | +| `IExportService`, `CreateExportJobInput`, `CreateExportJobResult`, `ExportJobDownload`, `ListExportJobsOptions`, `ExportJobListResult`, `ScheduleExportInput` | `@objectstack/spec/contracts` | nothing — no provider ever bound the contract. | +| `ScheduleStateSchema`, `ScheduleState`, `ScheduleStateParsed` | `@objectstack/spec/automation` | nothing — a scheduled flow declares its cadence on its start node (`config.schedule`), and its run history is `ExecutionLog` / `FlowRunSummary`. | + +**The one-line fix: delete every import of the names above, and every request to `/api/v1/data/export/…` or `POST /api/v1/data/:object/export`.** The compiler finds the imports (`TS2305: Module '"@objectstack/spec/api"' has no exported member …`); a hard-coded path has to be searched for. No behaviour is lost — none of those requests was ever answered. + +**What stays.** `ExportFormat`, `ExportImportTemplateSchema`, the import validation shapes and the whole import-job family in the same module — `ImportJobStatus`, `CreateImportJob…`, `ImportJobProgress…`, `ListImportJobs…`, `ImportJobApiContracts` — are served and unchanged, as is `GET /api/v1/data/:object/export`. + +**Read together with the cron-positions retirement in this release.** That entry says `ScheduledExport.schedule` / `ScheduleExportRequest.schedule` keep their `timezone` and `ScheduleState` keeps `timezone`, `status` and `nextRunAt`; this retirement removes those defs whole, so none of those shapes remains to carry them. + +⚠️ Runtime behaviour is deliberately **unchanged**: nothing ever mounted a retired path or parsed a retired shape, so every request answers exactly as before. The removal retracts a false claim, not a capability. **No deprecation window** (startup-stage posture: retirements take effect immediately). + +⚠️ **The out-of-repo consumer population is NOT MEASURED.** Inside this repository the names occurred only in their declarations, their own tests, generated artifacts and prose; objectui at the pinned sha names none of them in code (it retired its unimplemented async-export path in objectui#10247), and cloud names none; `@objectstack/spec` is published, so readers elsewhere were not measured. + +The ADR-0087 D3 semantic entry `export-job-family-retired` carries the judgement, and the thirteen defs are registered in `RETIRED_DEFS_BY_MAJOR[18]`: none of these shapes is a stack collection or a metadata type, so there is no source for a D2 conversion to rewrite and no carrier key for a tombstone. + +Clause-②: no + + diff --git a/content/docs/references/api/export.mdx b/content/docs/references/api/export.mdx index 97dd9ddbbb4..fe5e780b535 100644 --- a/content/docs/references/api/export.mdx +++ b/content/docs/references/api/export.mdx @@ -7,13 +7,19 @@ description: Export protocol schemas Data Export & Import Protocol -Defines schemas for streaming data export, import validation, -template-based field mapping, and scheduled export jobs. +Defines the export file formats, import validation, template-based field +mapping, and the asynchronous import-job contracts. Industry alignment: Salesforce Data Export, Airtable CSV Export, Dynamics 365 Data Management. -Base path: /api/v1/data/\{object\}/export +The export the platform serves is the synchronous streaming door +`GET /api/v1/data/:object/export`, which answers the file itself as CSV, +JSON or XLSX. The asynchronous export-job API that used to be +declared here (export jobs, their progress / download / list shapes, +scheduled exports and `ExportApiContracts`) was never served by any route and +was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove); a recurring +export is a `Job` whose handler you write. **Source:** `packages/spec/src/api/export.zod.ts` @@ -22,85 +28,13 @@ Base path: /api/v1/data/\{object\}/export ## TypeScript Usage ```typescript -import { CreateExportJobRequestSchema, CreateExportJobResponseSchema, CreateImportJobRequestSchema, CreateImportJobResponseSchema, DeduplicationStrategy, ExportFormat, ExportImportTemplateSchema, ExportJobProgressSchema, ExportJobStatus, ExportJobSummarySchema, FieldMappingEntrySchema, GetExportJobDownloadRequestSchema, GetExportJobDownloadResponseSchema, ImportJobProgressSchema, ImportJobResultsSchema, ImportJobStatus, ImportJobSummarySchema, ImportMappingSchema, ImportRequestSchema, ImportResponseSchema, ImportRowResultSchema, ImportValidationConfigSchema, ImportValidationMode, ImportValidationResultSchema, ImportWriteMode, ListExportJobsRequestSchema, ListExportJobsResponseSchema, ListImportJobsRequestSchema, ListImportJobsResponseSchema, ScheduleExportRequestSchema, ScheduleExportResponseSchema, ScheduledExportSchema, UndoImportJobResponseSchema } from '@objectstack/spec/api'; -import type { CreateExportJobRequest, CreateExportJobResponse, CreateImportJobRequest, CreateImportJobResponse, DeduplicationStrategy, ExportFormat, ExportImportTemplate, ExportJobProgress, ExportJobStatus, ExportJobSummary, FieldMappingEntry, GetExportJobDownloadRequest, GetExportJobDownloadResponse, ImportJobProgress, ImportJobResults, ImportJobStatus, ImportJobSummary, ImportMapping, ImportRequest, ImportResponse, ImportRowResult, ImportValidationConfig, ImportValidationMode, ImportValidationResult, ImportWriteMode, ListExportJobsRequest, ListExportJobsResponse, ListImportJobsRequest, ListImportJobsResponse, ScheduleExportRequest, ScheduleExportResponse, ScheduledExport, UndoImportJobResponse } from '@objectstack/spec/api'; +import { CreateImportJobRequestSchema, CreateImportJobResponseSchema, DeduplicationStrategy, ExportFormat, ExportImportTemplateSchema, FieldMappingEntrySchema, ImportJobProgressSchema, ImportJobResultsSchema, ImportJobStatus, ImportJobSummarySchema, ImportMappingSchema, ImportRequestSchema, ImportResponseSchema, ImportRowResultSchema, ImportValidationConfigSchema, ImportValidationMode, ImportValidationResultSchema, ImportWriteMode, ListImportJobsRequestSchema, ListImportJobsResponseSchema, UndoImportJobResponseSchema } from '@objectstack/spec/api'; +import type { CreateImportJobRequest, CreateImportJobResponse, DeduplicationStrategy, ExportFormat, ExportImportTemplate, FieldMappingEntry, ImportJobProgress, ImportJobResults, ImportJobStatus, ImportJobSummary, ImportMapping, ImportRequest, ImportResponse, ImportRowResult, ImportValidationConfig, ImportValidationMode, ImportValidationResult, ImportWriteMode, ListImportJobsRequest, ListImportJobsResponse, UndoImportJobResponse } from '@objectstack/spec/api'; // Validate data -const result = CreateExportJobRequestSchema.parse(data); +const result = CreateImportJobRequestSchema.parse(data); ``` ---- - -## CreateExportJobRequest - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **object** | `string` | ✅ | Object name to export | -| **format** | `Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>` | optional (default: `"csv"`) | Export file format | -| **fields** | `string[]` | optional | Specific fields to include (omit for all fields) | -| **filter** | `Record` | optional | Filter criteria for records to export | -| **sort** | `{ field: string; direction: Enum<'asc' \| 'desc'> }[]` | optional | Sort order for exported records | -| **limit** | `integer` | optional | Maximum number of records to export | -| **includeHeaders** | `boolean` | optional (default: `true`) | Include header row (CSV/XLSX) | -| **encoding** | `string` | optional (default: `"utf-8"`) | Character encoding for the export file | -| **templateId** | `string` | optional | Export template ID for predefined field mappings | - -### Nested Shape: `CreateExportJobRequest.sort[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **field** | `string` | ✅ | Field name to sort by | -| **direction** | `Enum<'asc' \| 'desc'>` | optional (default: `"asc"`) | Sort direction | - - ---- - -## CreateExportJobResponse - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ jobId: string; status: Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>; estimatedRecords?: integer; createdAt: string }` | ✅ | | - -### Nested Shape: `CreateExportJobResponse.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - -### Nested Shape: `CreateExportJobResponse.meta` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | | -| **duration** | `integer` | optional | Server-side processing duration in milliseconds | -| **requestId** | `string` | optional | | -| **traceId** | `string` | optional | | - -### Nested Shape: `CreateExportJobResponse.data` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **jobId** | `string` | ✅ | Export job ID | -| **status** | `Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>` | ✅ | Initial job status | -| **estimatedRecords** | `integer` | optional | Estimated total records | -| **createdAt** | `string` | ✅ | Job creation timestamp | - - --- ## CreateImportJobRequest @@ -210,93 +144,6 @@ const result = CreateExportJobRequestSchema.parse(data); | **required** | `boolean` | optional (default: `false`) | Whether this field is required (import validation) | ---- - -## ExportJobProgress - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: integer; 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; … }` | ✅ | | - -### Nested Shape: `ExportJobProgress.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - -### Nested Shape: `ExportJobProgress.meta` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | | -| **duration** | `integer` | optional | Server-side processing duration in milliseconds | -| **requestId** | `string` | optional | | -| **traceId** | `string` | optional | | - -### Nested Shape: `ExportJobProgress.data` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **jobId** | `string` | ✅ | Export job ID | -| **status** | `Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>` | ✅ | Current job status | -| **format** | `Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>` | ✅ | Export format | -| **totalRecords** | `integer` | optional | Total records to export | -| **processedRecords** | `integer` | ✅ | Records processed so far | -| **percentComplete** | `number` | ✅ | Export progress percentage | -| **fileSize** | `integer` | optional | Current file size in bytes | -| **downloadUrl** | `string` | optional | Presigned download URL (available when status is "completed") | -| **downloadExpiresAt** | `string` | optional | Download URL expiration timestamp | -| **error** | `{ code: string; message: string }` | optional | Error details if job failed | -| **startedAt** | `string` | optional | Processing start timestamp | -| **completedAt** | `string` | optional | Completion timestamp | - - ---- - -## ExportJobStatus - -### Allowed Values - -* `pending` -* `processing` -* `completed` -* `failed` -* `cancelled` -* `expired` - - ---- - -## ExportJobSummary - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **jobId** | `string` | ✅ | Export job ID | -| **object** | `string` | ✅ | Object name that was exported | -| **status** | `Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>` | ✅ | Current job status | -| **format** | `Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>` | ✅ | Export file format | -| **totalRecords** | `integer` | optional | Total records exported | -| **fileSize** | `integer` | optional | File size in bytes | -| **createdAt** | `string` | ✅ | Job creation timestamp | -| **completedAt** | `string` | optional | Completion timestamp | -| **createdBy** | `string` | optional | User who initiated the export | - - --- ## FieldMappingEntry @@ -313,66 +160,6 @@ const result = CreateExportJobRequestSchema.parse(data); | **required** | `boolean` | optional (default: `false`) | Whether this field is required (import validation) | ---- - -## GetExportJobDownloadRequest - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **jobId** | `string` | ✅ | Export job ID | - - ---- - -## GetExportJobDownloadResponse - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ jobId: string; downloadUrl: string; fileName: string; fileSize: integer; … }` | ✅ | | - -### Nested Shape: `GetExportJobDownloadResponse.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - -### Nested Shape: `GetExportJobDownloadResponse.meta` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | | -| **duration** | `integer` | optional | Server-side processing duration in milliseconds | -| **requestId** | `string` | optional | | -| **traceId** | `string` | optional | | - -### Nested Shape: `GetExportJobDownloadResponse.data` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **jobId** | `string` | ✅ | Export job ID | -| **downloadUrl** | `string` | ✅ | Presigned download URL | -| **fileName** | `string` | ✅ | Suggested file name | -| **fileSize** | `integer` | ✅ | File size in bytes | -| **format** | `Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>` | ✅ | Export file format | -| **expiresAt** | `string` | ✅ | Download URL expiration timestamp | -| **checksum** | `string` | optional | File checksum (SHA-256) | - - --- ## ImportJobProgress @@ -679,65 +466,6 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo * `upsert` ---- - -## ListExportJobsRequest - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **object** | `string` | optional | Filter by object name | -| **status** | `Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>` | optional | Filter by job status | -| **limit** | `integer` | optional (default: `20`) | Maximum number of jobs to return | -| **cursor** | `string` | optional | Pagination cursor from a previous response | - - ---- - -## ListExportJobsResponse - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ jobs: object[]; nextCursor?: string; hasMore: boolean }` | ✅ | | - -### Nested Shape: `ListExportJobsResponse.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - -### Nested Shape: `ListExportJobsResponse.meta` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | | -| **duration** | `integer` | optional | Server-side processing duration in milliseconds | -| **requestId** | `string` | optional | | -| **traceId** | `string` | optional | | - -### Nested Shape: `ListExportJobsResponse.data` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **jobs** | `{ jobId: string; object: string; status: Enum<'pending' \| 'processing' \| 'completed' \| 'failed' \| 'cancelled' \| 'expired'>; format: Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>; … }[]` | ✅ | List of export jobs | -| **nextCursor** | `string` | optional | Cursor for the next page | -| **hasMore** | `boolean` | ✅ | Whether more jobs are available | - - --- ## ListImportJobsRequest @@ -781,127 +509,6 @@ Type: `{ sourceField: string; targetField: string; targetLabel?: string; transfo | **revertedAt** | `string` | optional | When the job was undone / rolled back (ISO 8601) | ---- - -## ScheduleExportRequest - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **name** | `string` | ✅ | Schedule name (snake_case) | -| **label** | `string` | optional | Human-readable label | -| **object** | `string` | ✅ | Object name to export | -| **format** | `Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>` | optional (default: `"csv"`) | Export file format | -| **fields** | `string[]` | optional | Fields to include | -| **filter** | `Record` | optional | Record filter criteria | -| **templateId** | `string` | optional | Export template ID for field mappings | -| **schedule** | `{ timezone: string }` | ✅ | Schedule timing configuration | -| **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration | - -### Nested Shape: `ScheduleExportRequest.schedule` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timezone** | `string` | optional (default: `"UTC"`) | IANA timezone | - -### Nested Shape: `ScheduleExportRequest.delivery` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **method** | `Enum<'email' \| 'storage' \| 'webhook'>` | ✅ | How to deliver the export file | -| **recipients** | `string[]` | optional | Email recipients (for email delivery) | -| **storagePath** | `string` | optional | Storage path (for storage delivery) | -| **webhookUrl** | `string` | optional | Webhook URL (for webhook delivery) | - - ---- - -## ScheduleExportResponse - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **success** | `boolean` | ✅ | Operation success status | -| **error** | `{ code: Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>; declaredCode?: string; message: string; userMessage?: string; … }` | optional | Error details if success is false | -| **meta** | `{ timestamp: string; duration?: integer; requestId?: string; traceId?: string }` | optional | Response metadata | -| **data** | `{ id: string; name: string; enabled: boolean; nextRunAt?: string; … }` | ✅ | | - -### Nested Shape: `ScheduleExportResponse.error` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **code** | `Enum<'VALIDATION_ERROR' \| 'INVALID_FIELD' \| 'MISSING_REQUIRED_FIELD' \| …>` | ✅ | Error code (e.g. VALIDATION_ERROR; StandardErrorCode ∪ the ledger the serving side registers — ERROR_CODE_LEDGER for framework packages) | -| **declaredCode** | `string` | optional | The producer-declared code, verbatim, when it is not a member of the closed `code` vocabulary — the open, author-authored channel (app-specific spellings; ADR-0112) | -| **message** | `string` | ✅ | Readable error message | -| **userMessage** | `string` | optional | Producer-marked user-facing refusal text, verbatim. Present exactly when the producer opted in at throw time; consumers render it to end users and keep their generic substitution for anything unmarked. Status-agnostic; never replaces `message`. | -| **refusal** | `true` | optional | Producer-declared: the 5xx this envelope carries is a deliberate refusal whose `message` is authored for the caller, so a boundary that reads the declaration keeps it verbatim (until the withhold arms read it, a declared refusal is still withheld). Absent (the default) on a declared fault, whose `message` is withheld from the body and logged for the operator; redundant on a 4xx. Presence is the declaration — `true` is the only value. | -| **category** | `string` | optional | Error category (e.g. validation, authorization) | -| **httpStatus** | `integer` | optional | HTTP status of the response carrying this error | -| **details** | `any` | optional | Additional error context (e.g. field validation errors) | -| **requestId** | `string` | optional | Request ID for tracking | - -### Nested Shape: `ScheduleExportResponse.meta` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | | -| **duration** | `integer` | optional | Server-side processing duration in milliseconds | -| **requestId** | `string` | optional | | -| **traceId** | `string` | optional | | - -### Nested Shape: `ScheduleExportResponse.data` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Scheduled export ID | -| **name** | `string` | ✅ | Schedule name | -| **enabled** | `boolean` | ✅ | Whether the schedule is active | -| **nextRunAt** | `string` | optional | Next scheduled execution | -| **createdAt** | `string` | ✅ | Creation timestamp | - - ---- - -## ScheduledExport - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | optional | Scheduled export ID | -| **name** | `string` | ✅ | Schedule name (snake_case) | -| **label** | `string` | optional | Human-readable label | -| **object** | `string` | ✅ | Object name to export | -| **format** | `Enum<'csv' \| 'json' \| 'jsonl' \| 'xlsx' \| 'parquet'>` | optional (default: `"csv"`) | Export file format | -| **fields** | `string[]` | optional | Fields to include | -| **filter** | `Record` | optional | Record filter criteria | -| **templateId** | `string` | optional | Export template ID for field mappings | -| **schedule** | `{ timezone: string }` | ✅ | Schedule timing configuration | -| **delivery** | `{ method: Enum<'email' \| 'storage' \| 'webhook'>; recipients?: string[]; storagePath?: string; webhookUrl?: string }` | ✅ | Export delivery configuration | -| **enabled** | `boolean` | optional (default: `true`) | Whether the scheduled export is active | -| **lastRunAt** | `string` | optional | Last execution timestamp | -| **nextRunAt** | `string` | optional | Next scheduled execution | -| **createdAt** | `string` | optional | Creation timestamp | -| **createdBy** | `string` | optional | User who created the schedule | - -### Nested Shape: `ScheduledExport.schedule` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timezone** | `string` | optional (default: `"UTC"`) | IANA timezone | - -### Nested Shape: `ScheduledExport.delivery` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **method** | `Enum<'email' \| 'storage' \| 'webhook'>` | ✅ | How to deliver the export file | -| **recipients** | `string[]` | optional | Email recipients (for email delivery) | -| **storagePath** | `string` | optional | Storage path (for storage delivery) | -| **webhookUrl** | `string` | optional | Webhook URL (for webhook delivery) | - - --- ## UndoImportJobResponse diff --git a/content/docs/references/automation/execution.mdx b/content/docs/references/automation/execution.mdx index 3c5997babf9..8f120d4e350 100644 --- a/content/docs/references/automation/execution.mdx +++ b/content/docs/references/automation/execution.mdx @@ -20,8 +20,8 @@ AWS Step Functions execution logs. ## TypeScript Usage ```typescript -import { CheckpointSchema, ConcurrencyPolicySchema, ExecutionErrorSchema, ExecutionErrorSeverity, ExecutionLogSchema, ExecutionStatus, ExecutionStepLogSchema, ExecutionStepMetricsSchema, ExecutionStepSkipReasonSchema, FlowRunGateSummarySchema, FlowRunNodeSummarySchema, FlowRunSummarySchema, ScheduleStateSchema } from '@objectstack/spec/automation'; -import type { Checkpoint, ConcurrencyPolicy, ExecutionError, ExecutionErrorSeverity, ExecutionLog, ExecutionStatus, ExecutionStepLog, ExecutionStepMetrics, ExecutionStepSkipReason, FlowRunGateSummary, FlowRunNodeSummary, FlowRunSummary, ScheduleState } from '@objectstack/spec/automation'; +import { CheckpointSchema, ConcurrencyPolicySchema, ExecutionErrorSchema, ExecutionErrorSeverity, ExecutionLogSchema, ExecutionStatus, ExecutionStepLogSchema, ExecutionStepMetricsSchema, ExecutionStepSkipReasonSchema, FlowRunGateSummarySchema, FlowRunNodeSummarySchema, FlowRunSummarySchema } from '@objectstack/spec/automation'; +import type { Checkpoint, ConcurrencyPolicy, ExecutionError, ExecutionErrorSeverity, ExecutionLog, ExecutionStatus, ExecutionStepLog, ExecutionStepMetrics, ExecutionStepSkipReason, FlowRunGateSummary, FlowRunNodeSummary, FlowRunSummary } from '@objectstack/spec/automation'; // Validate data const result = CheckpointSchema.parse(data); @@ -337,29 +337,3 @@ const result = CheckpointSchema.parse(data); --- -## ScheduleState - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **id** | `string` | ✅ | Schedule instance ID | -| **flowName** | `string` | ✅ | Flow machine name | -| **timezone** | `string` | optional (default: `"UTC"`) | IANA timezone for cron evaluation | -| **status** | `Enum<'active' \| 'paused' \| 'disabled' \| 'expired'>` | optional (default: `"active"`) | Current schedule status | -| **nextRunAt** | `string` | optional | Next scheduled execution timestamp | -| **lastRunAt** | `string` | optional | Last execution timestamp | -| **lastExecutionId** | `string` | optional | Execution ID of the last run | -| **lastRunStatus** | `Enum<'pending' \| 'running' \| 'paused' \| 'completed' \| 'failed' \| 'cancelled' \| 'timed_out' \| 'retrying' \| 'refused'>` | optional | Status of the last run | -| **totalRuns** | `integer` | optional (default: `0`) | Total number of executions | -| **consecutiveFailures** | `integer` | optional (default: `0`) | Consecutive failed executions | -| **startDate** | `string` | optional | Schedule effective start date | -| **endDate** | `string` | optional | Schedule expiration date | -| **maxRuns** | `integer` | optional | Maximum total executions before auto-disable | -| **createdAt** | `string` | ✅ | Schedule creation timestamp | -| **updatedAt** | `string` | optional | Last update timestamp | -| **createdBy** | `string` | optional | User who created the schedule | - - ---- - diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index d49933c191c..2ae2f94cc89 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1538 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1525 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -20,8 +20,8 @@ counts are sums of the rows they head. Regenerate with | Module | Pages | Schemas | Description | | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 12 | 68 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | -| [API Protocol](/docs/references/api) | 32 | 444 | REST contracts, endpoints, routing, realtime, batch, discovery. | -| [Automation Protocol](/docs/references/automation) | 14 | 75 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | +| [API Protocol](/docs/references/api) | 32 | 432 | REST contracts, endpoints, routing, realtime, batch, discovery. | +| [Automation Protocol](/docs/references/automation) | 14 | 74 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Data Protocol](/docs/references/data) | 29 | 175 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | | [Identity Protocol](/docs/references/identity) | 5 | 27 | Users and accounts, organizations, positions, SCIM provisioning. | | [Integration Protocol](/docs/references/integration) | 1 | 24 | The single connector protocol (ADR-0097) — catalog descriptors and provider-bound instances. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 34 | 275 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 159 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **196** | **1538** | 14 protocol modules | +| **Total** | **196** | **1525** | 14 protocol modules | --- @@ -62,7 +62,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations. ## API Protocol -**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **32 pages, 444 schemas** +**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **32 pages, 432 schemas** REST contracts, endpoints, routing, realtime, batch, discovery. @@ -81,7 +81,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. | [`error-code-ledger.zod.ts`](/docs/references/api/error-code-ledger) | `ErrorCode`, `ProvenanceWaiver`, `StandardSynonymWaiver` | | [`errors.zod.ts`](/docs/references/api/errors) | `EnhancedApiError`, `ErrorCategory`, `ErrorResponse`, `FieldError`, `FieldErrorCode`, `RetryStrategy`, `StandardErrorCode` | | [`events.zod.ts`](/docs/references/api/events) | `BulkDataEvent`, `BulkDataEventType`, `DataEvent`, `DataEventType`, `MetadataEvent`, `MetadataEventType` | -| [`export.zod.ts`](/docs/references/api/export) | `CreateExportJobRequest`, `CreateExportJobResponse`, `CreateImportJobRequest`, `CreateImportJobResponse`, `DeduplicationStrategy`, `ExportFormat`, `ExportImportTemplate`, `ExportJobProgress`, `ExportJobStatus`, `ExportJobSummary`, `FieldMappingEntry`, `GetExportJobDownloadRequest`, `GetExportJobDownloadResponse`, `ImportJobProgress`, `ImportJobResults`, `ImportJobStatus`, `ImportJobSummary`, `ImportMapping`, `ImportRequest`, `ImportResponse`, `ImportRowResult`, `ImportValidationConfig`, `ImportValidationMode`, `ImportValidationResult`, `ImportWriteMode`, `ListExportJobsRequest`, `ListExportJobsResponse`, `ListImportJobsRequest`, `ListImportJobsResponse`, `ScheduleExportRequest`, `ScheduleExportResponse`, `ScheduledExport`, `UndoImportJobResponse` | +| [`export.zod.ts`](/docs/references/api/export) | `CreateImportJobRequest`, `CreateImportJobResponse`, `DeduplicationStrategy`, `ExportFormat`, `ExportImportTemplate`, `FieldMappingEntry`, `ImportJobProgress`, `ImportJobResults`, `ImportJobStatus`, `ImportJobSummary`, `ImportMapping`, `ImportRequest`, `ImportResponse`, `ImportRowResult`, `ImportValidationConfig`, `ImportValidationMode`, `ImportValidationResult`, `ImportWriteMode`, `ListImportJobsRequest`, `ListImportJobsResponse`, `UndoImportJobResponse` | | [`http-cache.zod.ts`](/docs/references/api/http-cache) | `CacheControl`, `CacheDirective`, `CacheInvalidationRequest`, `CacheInvalidationResponse`, `CacheInvalidationTarget`, `ETag`, `MetadataCacheRequest`, `MetadataCacheResponse` | | [`metadata.zod.ts`](/docs/references/api/metadata) | `AppDefinitionResponse`, `ConceptListResponse`, `MetadataBulkRegisterRequest`, `MetadataBulkResponse`, `MetadataBulkUnregisterRequest`, `MetadataDeleteResponse`, `MetadataDependenciesResponse`, `MetadataDependentsResponse`, `MetadataExistsResponse`, `MetadataExportRequest`, `MetadataExportResponse`, `MetadataImportRequest`, `MetadataImportResponse`, `MetadataItemResponse`, `MetadataListResponse`, `MetadataNamesResponse`, `MetadataQueryRequest`, `MetadataQueryResponse`, `MetadataRegisterRequest`, `MetadataTypeInfoResponse`, `MetadataTypesResponse`, `MetadataValidateRequest`, `MetadataValidateResponse`, `ObjectDefinitionResponse` | | [`misc`](/docs/references/api/misc) *(no single source file)* | `ResolvedBook`, `ResolvedEntry`, `ResolvedGroup` | @@ -105,7 +105,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. ## Automation Protocol -**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 75 schemas** +**Source:** `packages/spec/src/automation/` · **Import:** `@objectstack/spec/automation` · **14 pages, 74 schemas** Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. @@ -115,7 +115,7 @@ Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execu | [`bpmn-interop.zod.ts`](/docs/references/automation/bpmn-interop) | `BpmnDiagnostic`, `BpmnElementMapping`, `BpmnExportOptions`, `BpmnImportOptions`, `BpmnInteropResult`, `BpmnUnmappedStrategy`, `BpmnVersion` | | [`builtin-node-config.zod.ts`](/docs/references/automation/builtin-node-config) | `AssignmentConfig`, `AssignmentExpressionValue`, `AssignmentValue`, `CreateRecordConfig`, `DeleteRecordConfig`, `EndConfig`, `GetRecordConfig`, `MapConfig`, `ScreenConfig`, `ScreenFieldConfig`, `UpdateRecordConfig` | | [`control-flow.zod.ts`](/docs/references/automation/control-flow) | `FlowRegion`, `LoopConfig`, `ParallelBranch`, `ParallelConfig`, `RetryPolicy`, `TryCatchConfig`, `TryCatchErrorValue` | -| [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary`, `ScheduleState` | +| [`execution.zod.ts`](/docs/references/automation/execution) | `Checkpoint`, `ConcurrencyPolicy`, `ExecutionError`, `ExecutionErrorSeverity`, `ExecutionLog`, `ExecutionStatus`, `ExecutionStepLog`, `ExecutionStepMetrics`, `ExecutionStepSkipReason`, `FlowRunGateSummary`, `FlowRunNodeSummary`, `FlowRunSummary` | | [`flow.zod.ts`](/docs/references/automation/flow) | `Flow`, `FlowEdge`, `FlowNode`, `FlowNodeAction`, `FlowVariable`, `FlowVersionHistory` | | [`flow-function.zod.ts`](/docs/references/automation/flow-function) | `FlowFunctionEffect`, `FlowFunctionLoweredDeclaration` | | [`io-node-config.zod.ts`](/docs/references/automation/io-node-config) | `HttpConfig`, `NotifyConfig` | diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index 57e83b94524..70b6d05f511 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -21,8 +21,8 @@ regenerate. | Measure | Value | |---|---| | Triaged directories | 5 | -| Object sites in them | 453 | -| Still-open (strip) sites | 126 | +| Object sites in them | 452 | +| Still-open (strip) sites | 125 | | Files carrying at least one | 22 | Remaining strip sites by class: @@ -31,7 +31,7 @@ Remaining strip sites by class: |---|---| | authorable — the ruling's forced scope | 1 | | unresolved — needs a per-schema verdict | 0 | -| wire / open — out of forced scope | 121 | +| wire / open — out of forced scope | 120 | | no door — no carrier, ADR-0049 territory | 3 | | no gate — carrier live, no parse | 0 | | covered — no carrier, no parse, guarded at every consumer | 1 | @@ -46,10 +46,10 @@ The `strict` column is the one the campaign schedules against; it counts both th |---|---|---|---|---|---| | `ui/` | 179 | 169 | 3 | 0 | 7 | | `data/` | 159 | 76 | 1 | 0 | 82 | -| `automation/` | 68 | 43 | 0 | 1 | 24 | +| `automation/` | 67 | 43 | 0 | 1 | 23 | | `security/` | 20 | 7 | 0 | 0 | 13 | | `studio/` | 27 | 27 | 0 | 0 | 0 | -| **total** | **453** | **322** | **4** | **1** | **126** | +| **total** | **452** | **322** | **4** | **1** | **125** | ## File-level triage — site counts @@ -117,7 +117,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit | `bpmn-interop.zod.ts` | 5 | | `builtin-node-config.zod.ts` | 10 | | `control-flow.zod.ts` | 6 | -| `execution.zod.ts` | 13 | +| `execution.zod.ts` | 12 | | `flow-function.zod.ts` | 1 | | `flow.zod.ts` | 11 | | `io-node-config.zod.ts` | 2 | @@ -126,7 +126,7 @@ classify and is not listed (it becomes reportable the day it grows its first sit | `state-machine.zod.ts` | 6 | | `time-relative-trigger.zod.ts` | 1 | | `webhook.zod.ts` | 1 | -| **total** | **68** | +| **total** | **67** | ### `security/` — sites @@ -204,22 +204,22 @@ over it is here. ### `automation/` — open -**24 strip of 68**, in 5 file(s). +**23 strip of 67**, in 5 file(s). | File | Strip | Sites | |---|---|---| | `bpmn-interop.zod.ts` | 5 | 5 | | `control-flow.zod.ts` | 1 | 6 | -| `execution.zod.ts` | 13 | 13 | +| `execution.zod.ts` | 12 | 12 | | `flow.zod.ts` | 1 | 11 | | `node-executor.zod.ts` | 4 | 4 | -| **total** | **24** | **68** | +| **total** | **23** | **67** | | Bucket | Sites | |---|---| | authorable — the ruling's forced scope | 0 | | unresolved — needs a per-schema verdict | 0 | -| wire / open — out of forced scope | 24 | +| wire / open — out of forced scope | 23 | | no door — no carrier, ADR-0049 territory | 0 | | no gate — carrier live, no parse | 0 | | covered — no carrier, no parse, guarded at every consumer | 0 | @@ -257,7 +257,7 @@ directory rather than per file. | Dir | Sites | |---|---| | `ai/` | 78 | -| `api/` | 454 | +| `api/` | 435 | | `identity/` | 32 | | `integration/` | 8 | | `kernel/` | 247 | diff --git a/packages/spec/api-surface/api.json b/packages/spec/api-surface/api.json index f2fe07b5ee2..b06d6b7afcd 100644 --- a/packages/spec/api-surface/api.json +++ b/packages/spec/api-surface/api.json @@ -199,12 +199,6 @@ "CreateDataRequestSchema (const)", "CreateDataResponse (type)", "CreateDataResponseSchema (const)", - "CreateExportJobRequest (type)", - "CreateExportJobRequestParsed (type)", - "CreateExportJobRequestSchema (const)", - "CreateExportJobResponse (type)", - "CreateExportJobResponseParsed (type)", - "CreateExportJobResponseSchema (const)", "CreateFlowRequest (type)", "CreateFlowRequestParsed (type)", "CreateFlowRequestSchema (const)", @@ -371,19 +365,11 @@ "EventPatternSchema (const)", "EventSubscription (type)", "EventSubscriptionSchema (const)", - "ExportApiContracts (const)", "ExportFormat (const)", "ExportFormat (type)", "ExportImportTemplate (type)", "ExportImportTemplateParsed (type)", "ExportImportTemplateSchema (const)", - "ExportJobProgress (type)", - "ExportJobProgressParsed (type)", - "ExportJobProgressSchema (const)", - "ExportJobStatus (const)", - "ExportJobStatus (type)", - "ExportJobSummary (type)", - "ExportJobSummarySchema (const)", "ExportRequest (type)", "ExportRequestParsed (type)", "ExportRequestSchema (const)", @@ -438,11 +424,6 @@ "GetEffectivePermissionsRequestSchema (const)", "GetEffectivePermissionsResponse (type)", "GetEffectivePermissionsResponseSchema (const)", - "GetExportJobDownloadRequest (type)", - "GetExportJobDownloadRequestSchema (const)", - "GetExportJobDownloadResponse (type)", - "GetExportJobDownloadResponseParsed (type)", - "GetExportJobDownloadResponseSchema (const)", "GetFieldLabelsRequest (type)", "GetFieldLabelsRequestSchema (const)", "GetFieldLabelsResponse (type)", @@ -584,12 +565,6 @@ "ListDraftsResponse (type)", "ListDraftsResponseParsed (type)", "ListDraftsResponseSchema (const)", - "ListExportJobsRequest (type)", - "ListExportJobsRequestParsed (type)", - "ListExportJobsRequestSchema (const)", - "ListExportJobsResponse (type)", - "ListExportJobsResponseParsed (type)", - "ListExportJobsResponseSchema (const)", "ListFlowsRequest (type)", "ListFlowsRequestParsed (type)", "ListFlowsRequestSchema (const)", @@ -943,15 +918,6 @@ "SaveMetaItemRequestSchema (const)", "SaveMetaItemResponse (type)", "SaveMetaItemResponseSchema (const)", - "ScheduleExportRequest (type)", - "ScheduleExportRequestParsed (type)", - "ScheduleExportRequestSchema (const)", - "ScheduleExportResponse (type)", - "ScheduleExportResponseParsed (type)", - "ScheduleExportResponseSchema (const)", - "ScheduledExport (type)", - "ScheduledExportParsed (type)", - "ScheduledExportSchema (const)", "SearchAllHit (type)", "SearchAllHitSchema (const)", "SearchAllPageHit (type)", diff --git a/packages/spec/api-surface/automation.json b/packages/spec/api-surface/automation.json index 7dd61f5447b..50c3da63117 100644 --- a/packages/spec/api-surface/automation.json +++ b/packages/spec/api-surface/automation.json @@ -209,9 +209,6 @@ "STRUCTURAL_CONDITION_SHAPE_REFUSAL (const)", "ScheduleOrganization (type)", "ScheduleOrganizationSchema (const)", - "ScheduleState (type)", - "ScheduleStateParsed (type)", - "ScheduleStateSchema (const)", "SchemalessNodeType (type)", "ScreenConfig (type)", "ScreenConfigParsed (type)", diff --git a/packages/spec/api-surface/contracts.json b/packages/spec/api-surface/contracts.json index eaf73d6033a..e16f7555137 100644 --- a/packages/spec/api-surface/contracts.json +++ b/packages/spec/api-surface/contracts.json @@ -63,8 +63,6 @@ "CoreServiceContract (type)", "CoreServiceContracts (interface)", "CounterIncrOptions (interface)", - "CreateExportJobInput (interface)", - "CreateExportJobResult (interface)", "CreateShareLinkInput (interface)", "CryptoContext (interface)", "CryptoHandle (interface)", @@ -88,8 +86,6 @@ "EngineTransactionOptions (interface)", "ExecuteUpgradeInput (interface)", "ExplainAccessRequest (interface)", - "ExportJobDownload (interface)", - "ExportJobListResult (interface)", "FinishReason (type)", "FlowRuntimeState (interface)", "GenerateDraftOpts (interface)", @@ -119,7 +115,6 @@ "IEmailService (interface)", "IEmailTransport (interface)", "IEmbedder (interface)", - "IExportService (interface)", "IExternalDatasourceService (interface)", "IFileAccessDelegate (interface)", "IHierarchyScopeResolver (interface)", @@ -184,7 +179,6 @@ "KnowledgeSearchOptions (interface)", "LLMAdapter (interface)", "LifecycleEventName (type)", - "ListExportJobsOptions (interface)", "ListShareLinksFilter (interface)", "LockAcquireOptions (interface)", "LockHandle (interface)", @@ -251,7 +245,6 @@ "RunListResult (interface)", "SEED_SETTLEMENT_SERVICE (const)", "SHARE_LINK_SERVICE (const)", - "ScheduleExportInput (interface)", "SchemaValidationReport (interface)", "SchemaValidationResult (interface)", "ScreenFieldSpec (interface)", diff --git a/packages/spec/authorable-defaults/api.json b/packages/spec/authorable-defaults/api.json index b0b39dc1982..1597a8d7902 100644 --- a/packages/spec/authorable-defaults/api.json +++ b/packages/spec/authorable-defaults/api.json @@ -38,9 +38,6 @@ "api/BatchOptions:returnRecords = false", "api/BulkRequest:allOrNone = true", "api/CacheInvalidationRequest:cascade = false", - "api/CreateExportJobRequest:encoding = \"utf-8\"", - "api/CreateExportJobRequest:format = \"csv\"", - "api/CreateExportJobRequest:includeHeaders = true", "api/CreateFlowRequest:runAs = \"user\"", "api/CreateFlowRequest:status = \"draft\"", "api/CreateFlowRequest:version = 1", @@ -87,7 +84,6 @@ "api/ImportValidationConfig:trimWhitespace = true", "api/InitiateChunkedUploadRequest:chunkSize = 5242880", "api/InitiateChunkedUploadRequest:scope = \"user\"", - "api/ListExportJobsRequest:limit = 20", "api/ListFlowsRequest:limit = 50", "api/ListImportJobsRequest:limit = 50", "api/ListImportJobsRequest:offset = 0", @@ -173,9 +169,6 @@ "api/RouteDefinition:public = false", "api/RouterConfig:basePath = \"/api\"", "api/RouterConfig:mounts = {\"ai\":\"/ai\",\"analytics\":\"/analytics\",\"auth\":\"/auth\",\"automation\":\"/automation\",\"data\":\"/data\",\"i18n\":\"/i18n\",\"metadata\":\"/meta\",\"notifications\":\"/notifications\",\"packages\":\"/packages\",\"realtime\":\"/realtime\",\"storage\":\"/storage\",\"ui\":\"/ui\"}", - "api/ScheduleExportRequest:format = \"csv\"", - "api/ScheduledExport:enabled = true", - "api/ScheduledExport:format = \"csv\"", "api/SessionUser:emailVerified = false", "api/SessionUser:roles = []", "api/VersioningConfig:headerName = \"ObjectStack-Version\"", diff --git a/packages/spec/authorable-defaults/automation.json b/packages/spec/authorable-defaults/automation.json index 1cd83127d43..cb2065d05e7 100644 --- a/packages/spec/authorable-defaults/automation.json +++ b/packages/spec/authorable-defaults/automation.json @@ -55,10 +55,6 @@ "automation/RetryPolicy:jitter = false", "automation/RetryPolicy:maxRetries = 0", "automation/RetryPolicy:maxRetryDelayMs = 30000", - "automation/ScheduleState:consecutiveFailures = 0", - "automation/ScheduleState:status = \"active\"", - "automation/ScheduleState:timezone = \"UTC\"", - "automation/ScheduleState:totalRuns = 0", "automation/ScreenConfig:mode = \"create\"", "automation/StateNode:type = \"atomic\"", "automation/TryCatchConfig:errorVariable = \"$error\"", diff --git a/packages/spec/authorable-surface/api.json b/packages/spec/authorable-surface/api.json index dcfe6ac3aef..017e706f763 100644 --- a/packages/spec/authorable-surface/api.json +++ b/packages/spec/authorable-surface/api.json @@ -353,19 +353,6 @@ "api/CreateDataResponse:id", "api/CreateDataResponse:object", "api/CreateDataResponse:record", - "api/CreateExportJobRequest:encoding", - "api/CreateExportJobRequest:fields", - "api/CreateExportJobRequest:filter", - "api/CreateExportJobRequest:format", - "api/CreateExportJobRequest:includeHeaders", - "api/CreateExportJobRequest:limit", - "api/CreateExportJobRequest:object", - "api/CreateExportJobRequest:sort", - "api/CreateExportJobRequest:templateId", - "api/CreateExportJobResponse:data", - "api/CreateExportJobResponse:error", - "api/CreateExportJobResponse:meta", - "api/CreateExportJobResponse:success", "api/CreateFlowRequest:_lock", "api/CreateFlowRequest:_lockDocsUrl", "api/CreateFlowRequest:_lockReason", @@ -661,19 +648,6 @@ "api/ExportImportTemplate:name", "api/ExportImportTemplate:object", "api/ExportImportTemplate:updatedAt", - "api/ExportJobProgress:data", - "api/ExportJobProgress:error", - "api/ExportJobProgress:meta", - "api/ExportJobProgress:success", - "api/ExportJobSummary:completedAt", - "api/ExportJobSummary:createdAt", - "api/ExportJobSummary:createdBy", - "api/ExportJobSummary:fileSize", - "api/ExportJobSummary:format", - "api/ExportJobSummary:jobId", - "api/ExportJobSummary:object", - "api/ExportJobSummary:status", - "api/ExportJobSummary:totalRecords", "api/FieldError:code", "api/FieldError:constraint", "api/FieldError:field", @@ -755,11 +729,6 @@ "api/GetDiscoveryResponse:version", "api/GetEffectivePermissionsResponse:objects", "api/GetEffectivePermissionsResponse:systemPermissions", - "api/GetExportJobDownloadRequest:jobId", - "api/GetExportJobDownloadResponse:data", - "api/GetExportJobDownloadResponse:error", - "api/GetExportJobDownloadResponse:meta", - "api/GetExportJobDownloadResponse:success", "api/GetFieldLabelsRequest:locale", "api/GetFieldLabelsRequest:object", "api/GetFieldLabelsResponse:labels", @@ -1023,14 +992,6 @@ "api/ListAiPendingActionsResponse:items", "api/ListAiPendingActionsResponse:total", "api/ListDraftsResponse:drafts", - "api/ListExportJobsRequest:cursor", - "api/ListExportJobsRequest:limit", - "api/ListExportJobsRequest:object", - "api/ListExportJobsRequest:status", - "api/ListExportJobsResponse:data", - "api/ListExportJobsResponse:error", - "api/ListExportJobsResponse:meta", - "api/ListExportJobsResponse:success", "api/ListFlowsRequest:cursor", "api/ListFlowsRequest:limit", "api/ListFlowsRequest:status", @@ -1604,34 +1565,6 @@ "api/SaveMetaItemResponse:state", "api/SaveMetaItemResponse:success", "api/SaveMetaItemResponse:version", - "api/ScheduleExportRequest:delivery", - "api/ScheduleExportRequest:fields", - "api/ScheduleExportRequest:filter", - "api/ScheduleExportRequest:format", - "api/ScheduleExportRequest:label", - "api/ScheduleExportRequest:name", - "api/ScheduleExportRequest:object", - "api/ScheduleExportRequest:schedule", - "api/ScheduleExportRequest:templateId", - "api/ScheduleExportResponse:data", - "api/ScheduleExportResponse:error", - "api/ScheduleExportResponse:meta", - "api/ScheduleExportResponse:success", - "api/ScheduledExport:createdAt", - "api/ScheduledExport:createdBy", - "api/ScheduledExport:delivery", - "api/ScheduledExport:enabled", - "api/ScheduledExport:fields", - "api/ScheduledExport:filter", - "api/ScheduledExport:format", - "api/ScheduledExport:id", - "api/ScheduledExport:label", - "api/ScheduledExport:lastRunAt", - "api/ScheduledExport:name", - "api/ScheduledExport:nextRunAt", - "api/ScheduledExport:object", - "api/ScheduledExport:schedule", - "api/ScheduledExport:templateId", "api/SearchAllHit:id", "api/SearchAllHit:object", "api/SearchAllHit:record", diff --git a/packages/spec/authorable-surface/automation.json b/packages/spec/authorable-surface/automation.json index b01591ff790..4216fcd8840 100644 --- a/packages/spec/authorable-surface/automation.json +++ b/packages/spec/authorable-surface/automation.json @@ -284,22 +284,6 @@ "automation/RetryPolicy:maxRetries", "automation/RetryPolicy:maxRetryDelayMs", "automation/RetryPolicy:retryDelayMs [RETIRED]", - "automation/ScheduleState:consecutiveFailures", - "automation/ScheduleState:createdAt", - "automation/ScheduleState:createdBy", - "automation/ScheduleState:endDate", - "automation/ScheduleState:flowName", - "automation/ScheduleState:id", - "automation/ScheduleState:lastExecutionId", - "automation/ScheduleState:lastRunAt", - "automation/ScheduleState:lastRunStatus", - "automation/ScheduleState:maxRuns", - "automation/ScheduleState:nextRunAt", - "automation/ScheduleState:startDate", - "automation/ScheduleState:status", - "automation/ScheduleState:timezone", - "automation/ScheduleState:totalRuns", - "automation/ScheduleState:updatedAt", "automation/ScreenConfig:defaults", "automation/ScreenConfig:description", "automation/ScreenConfig:fields", diff --git a/packages/spec/declaration-map/api.json b/packages/spec/declaration-map/api.json index 1ec93321851..ad6270b0076 100644 --- a/packages/spec/declaration-map/api.json +++ b/packages/spec/declaration-map/api.json @@ -151,10 +151,6 @@ "CreateDataRequestSchema": "api/CreateDataRequest", "CreateDataResponse": "api/CreateDataResponse", "CreateDataResponseSchema": "api/CreateDataResponse", - "CreateExportJobRequest": "api/CreateExportJobRequest", - "CreateExportJobRequestSchema": "api/CreateExportJobRequest", - "CreateExportJobResponse": "api/CreateExportJobResponse", - "CreateExportJobResponseSchema": "api/CreateExportJobResponse", "CreateFlowRequest": "api/CreateFlowRequest", "CreateFlowRequestSchema": "api/CreateFlowRequest", "CreateFlowResponse": "api/CreateFlowResponse", @@ -270,11 +266,6 @@ "ExportFormat": "api/ExportFormat", "ExportImportTemplate": "api/ExportImportTemplate", "ExportImportTemplateSchema": "api/ExportImportTemplate", - "ExportJobProgress": "api/ExportJobProgress", - "ExportJobProgressSchema": "api/ExportJobProgress", - "ExportJobStatus": "api/ExportJobStatus", - "ExportJobSummary": "api/ExportJobSummary", - "ExportJobSummarySchema": "api/ExportJobSummary", "ExportRequest": "api/ExportRequest", "ExportRequestSchema": "api/ExportRequest", "FieldError": "api/FieldError", @@ -318,10 +309,6 @@ "GetEffectivePermissionsRequestSchema": "api/GetEffectivePermissionsRequest", "GetEffectivePermissionsResponse": "api/GetEffectivePermissionsResponse", "GetEffectivePermissionsResponseSchema": "api/GetEffectivePermissionsResponse", - "GetExportJobDownloadRequest": "api/GetExportJobDownloadRequest", - "GetExportJobDownloadRequestSchema": "api/GetExportJobDownloadRequest", - "GetExportJobDownloadResponse": "api/GetExportJobDownloadResponse", - "GetExportJobDownloadResponseSchema": "api/GetExportJobDownloadResponse", "GetFieldLabelsRequest": "api/GetFieldLabelsRequest", "GetFieldLabelsRequestSchema": "api/GetFieldLabelsRequest", "GetFieldLabelsResponse": "api/GetFieldLabelsResponse", @@ -432,10 +419,6 @@ "ListAiPendingActionsResponseSchema": "api/ListAiPendingActionsResponse", "ListDraftsResponse": "api/ListDraftsResponse", "ListDraftsResponseSchema": "api/ListDraftsResponse", - "ListExportJobsRequest": "api/ListExportJobsRequest", - "ListExportJobsRequestSchema": "api/ListExportJobsRequest", - "ListExportJobsResponse": "api/ListExportJobsResponse", - "ListExportJobsResponseSchema": "api/ListExportJobsResponse", "ListFlowsRequest": "api/ListFlowsRequest", "ListFlowsRequestSchema": "api/ListFlowsRequest", "ListFlowsResponse": "api/ListFlowsResponse", @@ -684,12 +667,6 @@ "SaveMetaItemRequestSchema": "api/SaveMetaItemRequest", "SaveMetaItemResponse": "api/SaveMetaItemResponse", "SaveMetaItemResponseSchema": "api/SaveMetaItemResponse", - "ScheduleExportRequest": "api/ScheduleExportRequest", - "ScheduleExportRequestSchema": "api/ScheduleExportRequest", - "ScheduleExportResponse": "api/ScheduleExportResponse", - "ScheduleExportResponseSchema": "api/ScheduleExportResponse", - "ScheduledExport": "api/ScheduledExport", - "ScheduledExportSchema": "api/ScheduledExport", "SearchAllHit": "api/SearchAllHit", "SearchAllHitSchema": "api/SearchAllHit", "SearchAllPageHit": "api/SearchAllPageHit", diff --git a/packages/spec/declaration-map/automation.json b/packages/spec/declaration-map/automation.json index 97be6e386b4..f2cd56dbebf 100644 --- a/packages/spec/declaration-map/automation.json +++ b/packages/spec/declaration-map/automation.json @@ -109,8 +109,6 @@ "ParallelConfigSchema": "automation/ParallelConfig", "ScheduleOrganization": "automation/ScheduleOrganization", "ScheduleOrganizationSchema": "automation/ScheduleOrganization", - "ScheduleState": "automation/ScheduleState", - "ScheduleStateSchema": "automation/ScheduleState", "ScreenConfig": "automation/ScreenConfig", "ScreenConfigSchema": "automation/ScreenConfig", "ScreenFieldConfig": "automation/ScreenFieldConfig", diff --git a/packages/spec/export-origins/api.json b/packages/spec/export-origins/api.json index dc2f25bd619..d9998e71771 100644 --- a/packages/spec/export-origins/api.json +++ b/packages/spec/export-origins/api.json @@ -187,12 +187,6 @@ "CreateDataRequestSchema": "src/api/protocol.zod.ts#CreateDataRequestSchema (const)", "CreateDataResponse": "src/api/protocol.zod.ts#CreateDataResponse (type)", "CreateDataResponseSchema": "src/api/protocol.zod.ts#CreateDataResponseSchema (const)", - "CreateExportJobRequest": "src/api/export.zod.ts#CreateExportJobRequest (type)", - "CreateExportJobRequestParsed": "src/api/export.zod.ts#CreateExportJobRequestParsed (type)", - "CreateExportJobRequestSchema": "src/api/export.zod.ts#CreateExportJobRequestSchema (const)", - "CreateExportJobResponse": "src/api/export.zod.ts#CreateExportJobResponse (type)", - "CreateExportJobResponseParsed": "src/api/export.zod.ts#CreateExportJobResponseParsed (type)", - "CreateExportJobResponseSchema": "src/api/export.zod.ts#CreateExportJobResponseSchema (const)", "CreateFlowRequest": "src/api/automation-api.zod.ts#CreateFlowRequest (type)", "CreateFlowRequestParsed": "src/api/automation-api.zod.ts#CreateFlowRequestParsed (type)", "CreateFlowRequestSchema": "src/api/automation-api.zod.ts#CreateFlowRequestSchema (const)", @@ -352,17 +346,10 @@ "EventPatternSchema": "src/api/websocket.zod.ts#EventPatternSchema (const)", "EventSubscription": "src/api/websocket.zod.ts#EventSubscription (type)", "EventSubscriptionSchema": "src/api/websocket.zod.ts#EventSubscriptionSchema (const)", - "ExportApiContracts": "src/api/export.zod.ts#ExportApiContracts (const)", "ExportFormat": "src/api/export.zod.ts#ExportFormat (type)", "ExportImportTemplate": "src/api/export.zod.ts#ExportImportTemplate (type)", "ExportImportTemplateParsed": "src/api/export.zod.ts#ExportImportTemplateParsed (type)", "ExportImportTemplateSchema": "src/api/export.zod.ts#ExportImportTemplateSchema (const)", - "ExportJobProgress": "src/api/export.zod.ts#ExportJobProgress (type)", - "ExportJobProgressParsed": "src/api/export.zod.ts#ExportJobProgressParsed (type)", - "ExportJobProgressSchema": "src/api/export.zod.ts#ExportJobProgressSchema (const)", - "ExportJobStatus": "src/api/export.zod.ts#ExportJobStatus (type)", - "ExportJobSummary": "src/api/export.zod.ts#ExportJobSummary (type)", - "ExportJobSummarySchema": "src/api/export.zod.ts#ExportJobSummarySchema (const)", "ExportRequest": "src/api/contract.zod.ts#ExportRequest (type)", "ExportRequestParsed": "src/api/contract.zod.ts#ExportRequestParsed (type)", "ExportRequestSchema": "src/api/contract.zod.ts#ExportRequestSchema (const)", @@ -416,11 +403,6 @@ "GetEffectivePermissionsRequestSchema": "src/api/protocol.zod.ts#GetEffectivePermissionsRequestSchema (const)", "GetEffectivePermissionsResponse": "src/api/protocol.zod.ts#GetEffectivePermissionsResponse (type)", "GetEffectivePermissionsResponseSchema": "src/api/protocol.zod.ts#GetEffectivePermissionsResponseSchema (const)", - "GetExportJobDownloadRequest": "src/api/export.zod.ts#GetExportJobDownloadRequest (type)", - "GetExportJobDownloadRequestSchema": "src/api/export.zod.ts#GetExportJobDownloadRequestSchema (const)", - "GetExportJobDownloadResponse": "src/api/export.zod.ts#GetExportJobDownloadResponse (type)", - "GetExportJobDownloadResponseParsed": "src/api/export.zod.ts#GetExportJobDownloadResponseParsed (type)", - "GetExportJobDownloadResponseSchema": "src/api/export.zod.ts#GetExportJobDownloadResponseSchema (const)", "GetFieldLabelsRequest": "src/api/protocol.zod.ts#GetFieldLabelsRequest (type)", "GetFieldLabelsRequestSchema": "src/api/protocol.zod.ts#GetFieldLabelsRequestSchema (const)", "GetFieldLabelsResponse": "src/api/protocol.zod.ts#GetFieldLabelsResponse (type)", @@ -558,12 +540,6 @@ "ListDraftsResponse": "src/api/protocol.zod.ts#ListDraftsResponse (type)", "ListDraftsResponseParsed": "src/api/protocol.zod.ts#ListDraftsResponseParsed (type)", "ListDraftsResponseSchema": "src/api/protocol.zod.ts#ListDraftsResponseSchema (const)", - "ListExportJobsRequest": "src/api/export.zod.ts#ListExportJobsRequest (type)", - "ListExportJobsRequestParsed": "src/api/export.zod.ts#ListExportJobsRequestParsed (type)", - "ListExportJobsRequestSchema": "src/api/export.zod.ts#ListExportJobsRequestSchema (const)", - "ListExportJobsResponse": "src/api/export.zod.ts#ListExportJobsResponse (type)", - "ListExportJobsResponseParsed": "src/api/export.zod.ts#ListExportJobsResponseParsed (type)", - "ListExportJobsResponseSchema": "src/api/export.zod.ts#ListExportJobsResponseSchema (const)", "ListFlowsRequest": "src/api/automation-api.zod.ts#ListFlowsRequest (type)", "ListFlowsRequestParsed": "src/api/automation-api.zod.ts#ListFlowsRequestParsed (type)", "ListFlowsRequestSchema": "src/api/automation-api.zod.ts#ListFlowsRequestSchema (const)", @@ -903,15 +879,6 @@ "SaveMetaItemRequestSchema": "src/api/protocol.zod.ts#SaveMetaItemRequestSchema (const)", "SaveMetaItemResponse": "src/api/protocol.zod.ts#SaveMetaItemResponse (type)", "SaveMetaItemResponseSchema": "src/api/protocol.zod.ts#SaveMetaItemResponseSchema (const)", - "ScheduleExportRequest": "src/api/export.zod.ts#ScheduleExportRequest (type)", - "ScheduleExportRequestParsed": "src/api/export.zod.ts#ScheduleExportRequestParsed (type)", - "ScheduleExportRequestSchema": "src/api/export.zod.ts#ScheduleExportRequestSchema (const)", - "ScheduleExportResponse": "src/api/export.zod.ts#ScheduleExportResponse (type)", - "ScheduleExportResponseParsed": "src/api/export.zod.ts#ScheduleExportResponseParsed (type)", - "ScheduleExportResponseSchema": "src/api/export.zod.ts#ScheduleExportResponseSchema (const)", - "ScheduledExport": "src/api/export.zod.ts#ScheduledExport (type)", - "ScheduledExportParsed": "src/api/export.zod.ts#ScheduledExportParsed (type)", - "ScheduledExportSchema": "src/api/export.zod.ts#ScheduledExportSchema (const)", "SearchAllHit": "src/api/protocol.zod.ts#SearchAllHit (type)", "SearchAllHitSchema": "src/api/protocol.zod.ts#SearchAllHitSchema (const)", "SearchAllPageHit": "src/api/protocol.zod.ts#SearchAllPageHit (type)", diff --git a/packages/spec/export-origins/automation.json b/packages/spec/export-origins/automation.json index 8b1dd265d11..0cbf69dd6c9 100644 --- a/packages/spec/export-origins/automation.json +++ b/packages/spec/export-origins/automation.json @@ -204,9 +204,6 @@ "STRUCTURAL_CONDITION_SHAPE_REFUSAL": "src/automation/flow-node-expression-paths.ts#STRUCTURAL_CONDITION_SHAPE_REFUSAL (const)", "ScheduleOrganization": "src/automation/schedule-organization.zod.ts#ScheduleOrganization (type)", "ScheduleOrganizationSchema": "src/automation/schedule-organization.zod.ts#ScheduleOrganizationSchema (const)", - "ScheduleState": "src/automation/execution.zod.ts#ScheduleState (type)", - "ScheduleStateParsed": "src/automation/execution.zod.ts#ScheduleStateParsed (type)", - "ScheduleStateSchema": "src/automation/execution.zod.ts#ScheduleStateSchema (const)", "SchemalessNodeType": "src/automation/schemaless-node-config.zod.ts#SchemalessNodeType (type)", "ScreenConfig": "src/automation/builtin-node-config.zod.ts#ScreenConfig (type)", "ScreenConfigParsed": "src/automation/builtin-node-config.zod.ts#ScreenConfigParsed (type)", diff --git a/packages/spec/export-origins/contracts.json b/packages/spec/export-origins/contracts.json index 22d9cf26477..abd22243317 100644 --- a/packages/spec/export-origins/contracts.json +++ b/packages/spec/export-origins/contracts.json @@ -63,8 +63,6 @@ "CoreServiceContract": "src/contracts/core-service-contracts.ts#CoreServiceContract (type)", "CoreServiceContracts": "src/contracts/core-service-contracts.ts#CoreServiceContracts (interface)", "CounterIncrOptions": "src/contracts/cluster-service.ts#CounterIncrOptions (interface)", - "CreateExportJobInput": "src/contracts/export-service.ts#CreateExportJobInput (interface)", - "CreateExportJobResult": "src/contracts/export-service.ts#CreateExportJobResult (interface)", "CreateShareLinkInput": "src/contracts/share-link-service.ts#CreateShareLinkInput (interface)", "CryptoContext": "src/contracts/crypto-provider.ts#CryptoContext (interface)", "CryptoHandle": "src/contracts/crypto-provider.ts#CryptoHandle (interface)", @@ -88,8 +86,6 @@ "EngineTransactionOptions": "src/contracts/objectql-engine.ts#EngineTransactionOptions (interface)", "ExecuteUpgradeInput": "src/contracts/package-service.ts#ExecuteUpgradeInput (interface)", "ExplainAccessRequest": "src/contracts/security-service.ts#ExplainAccessRequest (interface)", - "ExportJobDownload": "src/contracts/export-service.ts#ExportJobDownload (interface)", - "ExportJobListResult": "src/contracts/export-service.ts#ExportJobListResult (interface)", "FinishReason": "node_modules/ai/dist/index.d.ts#FinishReason (type)", "FlowRuntimeState": "src/contracts/automation-service.ts#FlowRuntimeState (interface)", "GenerateDraftOpts": "src/contracts/external-datasource-service.ts#GenerateDraftOpts (interface)", @@ -119,7 +115,6 @@ "IEmailService": "src/contracts/email-service.ts#IEmailService (interface)", "IEmailTransport": "src/contracts/email-service.ts#IEmailTransport (interface)", "IEmbedder": "src/contracts/embedder.ts#IEmbedder (interface)", - "IExportService": "src/contracts/export-service.ts#IExportService (interface)", "IExternalDatasourceService": "src/contracts/external-datasource-service.ts#IExternalDatasourceService (interface)", "IFileAccessDelegate": "src/contracts/storage-service.ts#IFileAccessDelegate (interface)", "IHierarchyScopeResolver": "src/contracts/sharing-service.ts#IHierarchyScopeResolver (interface)", @@ -184,7 +179,6 @@ "KnowledgeSearchOptions": "src/contracts/knowledge-service.ts#KnowledgeSearchOptions (interface)", "LLMAdapter": "src/contracts/llm-adapter.ts#LLMAdapter (interface)", "LifecycleEventName": "src/contracts/plugin-lifecycle-events.ts#LifecycleEventName (type)", - "ListExportJobsOptions": "src/contracts/export-service.ts#ListExportJobsOptions (interface)", "ListShareLinksFilter": "src/contracts/share-link-service.ts#ListShareLinksFilter (interface)", "LockAcquireOptions": "src/contracts/cluster-service.ts#LockAcquireOptions (interface)", "LockHandle": "src/contracts/cluster-service.ts#LockHandle (interface)", @@ -251,7 +245,6 @@ "RunListResult": "src/contracts/automation-service.ts#RunListResult (interface)", "SEED_SETTLEMENT_SERVICE": "src/contracts/seed-settlement.ts#SEED_SETTLEMENT_SERVICE (const)", "SHARE_LINK_SERVICE": "src/contracts/share-link-service.ts#SHARE_LINK_SERVICE (const)", - "ScheduleExportInput": "src/contracts/export-service.ts#ScheduleExportInput (interface)", "SchemaValidationReport": "src/contracts/external-datasource-service.ts#SchemaValidationReport (interface)", "SchemaValidationResult": "src/contracts/external-datasource-service.ts#SchemaValidationResult (interface)", "ScreenFieldSpec": "src/contracts/automation-service.ts#ScreenFieldSpec (interface)", diff --git a/packages/spec/json-schema.manifest/api.json b/packages/spec/json-schema.manifest/api.json index 237945c8387..8c11f3d0c22 100644 --- a/packages/spec/json-schema.manifest/api.json +++ b/packages/spec/json-schema.manifest/api.json @@ -81,8 +81,6 @@ "api/CreateAiConversationRequest", "api/CreateDataRequest", "api/CreateDataResponse", - "api/CreateExportJobRequest", - "api/CreateExportJobResponse", "api/CreateFlowRequest", "api/CreateFlowResponse", "api/CreateImportJobRequest", @@ -149,9 +147,6 @@ "api/EventSubscription", "api/ExportFormat", "api/ExportImportTemplate", - "api/ExportJobProgress", - "api/ExportJobStatus", - "api/ExportJobSummary", "api/ExportRequest", "api/FieldError", "api/FieldErrorCode", @@ -174,8 +169,6 @@ "api/GetDiscoveryResponse", "api/GetEffectivePermissionsRequest", "api/GetEffectivePermissionsResponse", - "api/GetExportJobDownloadRequest", - "api/GetExportJobDownloadResponse", "api/GetFieldLabelsRequest", "api/GetFieldLabelsResponse", "api/GetFlowRequest", @@ -238,8 +231,6 @@ "api/ListAiPendingActionsRequest", "api/ListAiPendingActionsResponse", "api/ListDraftsResponse", - "api/ListExportJobsRequest", - "api/ListExportJobsResponse", "api/ListFlowsRequest", "api/ListFlowsResponse", "api/ListImportJobsRequest", @@ -376,9 +367,6 @@ "api/RuntimeAuthoringIssue", "api/SaveMetaItemRequest", "api/SaveMetaItemResponse", - "api/ScheduleExportRequest", - "api/ScheduleExportResponse", - "api/ScheduledExport", "api/SearchAllHit", "api/SearchAllPageHit", "api/SearchAllResponse", diff --git a/packages/spec/json-schema.manifest/automation.json b/packages/spec/json-schema.manifest/automation.json index c42c760c510..3058baed1ea 100644 --- a/packages/spec/json-schema.manifest/automation.json +++ b/packages/spec/json-schema.manifest/automation.json @@ -59,7 +59,6 @@ "automation/ParallelConfig", "automation/RetryPolicy", "automation/ScheduleOrganization", - "automation/ScheduleState", "automation/ScreenConfig", "automation/ScreenFieldConfig", "automation/ScriptConfig", diff --git a/packages/spec/src/api/export-job-family-retirement.test.ts b/packages/spec/src/api/export-job-family-retirement.test.ts new file mode 100644 index 00000000000..4ef5769f61c --- /dev/null +++ b/packages/spec/src/api/export-job-family-retirement.test.ts @@ -0,0 +1,351 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { describe, expect, it } from 'vitest'; + +import { + EXPORT_ENTRY_POINTS, + exportNamesOf, + holdersOf, +} from '../../scripts/lib/export-origins-testkit'; +import { CONVERSIONS_BY_MAJOR } from '../conversions/registry'; +import { MIGRATIONS_BY_MAJOR, RETIRED_DEFS_BY_MAJOR } from '../migrations/registry'; + +// ─── [#17158] the export-job API family and `ScheduleState` are RETIRED WHOLE ── +// +// ADR-0049 enforce-or-remove. Maintainer ruling A (decision batch #122 item 3): +// the export-job family in `api/export.zod.ts`, `IExportService` +// (`contracts/export-service.ts`) and `ScheduleExportInput` leave the public +// surface, and `ScheduleState` (`automation/execution.zod.ts`) goes with them +// unless a live consumer is measured. Landing route A (decision batch #221 +// item 2): objectui retired its side first (objectui#10247, merged as objectui +// PR #10264), and the pinned `.objectui-sha` already descends from that merge. +// Scope note: the export-job LIST pair (`ListExportJobsRequestSchema` with its +// `limit` / `cursor`, and its response) is in, absorbing #19543 door ②; the +// import-job family in the same module is served and is NOT in. +// +// The measurement that decided it, re-taken at this change's base with a lit +// control beside every zero: +// +// 1. objectstack — no code outside `packages/spec` names any of the +// forty-three exported names (control `ImportJobProgress`: 7 files). +// 2. objectui at the pinned sha — zero non-markdown files name any of them; +// the only hits are CHANGELOG prose recording objectui#10247 (controls +// `ImportJobProgress` 7 files, `GetMetaItemLayeredResponseSchema` 9). +// 3. cloud main — zero files (control `@objectstack/spec`: 537 files). +// 4. DOORS — `@objectstack/rest` mounts no `/api/v1/data/export` route and +// only the `GET` on `/api/v1/data/:object/export` (the synchronous +// streaming export, which stays); nothing ever bound `IExportService`. +// +// ## Why route 3, and why there is nothing to tombstone +// +// The retired shapes are HTTP bodies, a route map, a service interface and an +// unpersisted runtime record. None is a stack collection or a metadata type, so +// there is no carrier key for a `retiredKey()` tombstone and no authored +// document for an ADR-0087 D2 conversion to rewrite. The declared record is the +// D3 `SemanticMigration` `export-job-family-retired` plus the thirteen +// `RETIRED_DEFS_BY_MAJOR[18]` entries the manifest-deletion gate reads. +// +// Form follows `system/compliance-families-retirement.test.ts` (#15513): +// resolved symbol identity over every public entry via the build-time +// `export-origins/` artifact, the file-deletion probe, the in-package importer +// walk, the runtime-namespace cross-check, the generated-shard absences, the +// registration, and the tree-scoped absence leg whose walk radius is already +// declared for `@objectstack/spec` in `scripts/cross-package-test-inputs.mjs`. + +/** The forty-three names the retirement removed from the public surface. */ +const RETIRED_NAMES = [ + // api/export.zod.ts — 12 defs, 32 names, plus the route map + 'ExportJobStatus', + 'CreateExportJobRequestSchema', 'CreateExportJobRequest', 'CreateExportJobRequestParsed', + 'CreateExportJobResponseSchema', 'CreateExportJobResponse', 'CreateExportJobResponseParsed', + 'ExportJobProgressSchema', 'ExportJobProgress', 'ExportJobProgressParsed', + 'ScheduledExportSchema', 'ScheduledExport', 'ScheduledExportParsed', + 'GetExportJobDownloadRequestSchema', 'GetExportJobDownloadRequest', + 'GetExportJobDownloadResponseSchema', 'GetExportJobDownloadResponse', 'GetExportJobDownloadResponseParsed', + 'ListExportJobsRequestSchema', 'ListExportJobsRequest', 'ListExportJobsRequestParsed', + 'ExportJobSummarySchema', 'ExportJobSummary', + 'ListExportJobsResponseSchema', 'ListExportJobsResponse', 'ListExportJobsResponseParsed', + 'ScheduleExportRequestSchema', 'ScheduleExportRequest', 'ScheduleExportRequestParsed', + 'ScheduleExportResponseSchema', 'ScheduleExportResponse', 'ScheduleExportResponseParsed', + 'ExportApiContracts', + // contracts/export-service.ts — the interface and its six types + 'IExportService', 'CreateExportJobInput', 'CreateExportJobResult', 'ExportJobDownload', + 'ListExportJobsOptions', 'ExportJobListResult', 'ScheduleExportInput', + // automation/execution.zod.ts + 'ScheduleStateSchema', 'ScheduleState', 'ScheduleStateParsed', +] as const; + +/** The thirteen def keys, spelled as `json-schema.manifest/` and the registry spell them. */ +const RETIRED_DEFS = [ + 'api/CreateExportJobRequest', 'api/CreateExportJobResponse', 'api/ExportJobProgress', + 'api/ExportJobStatus', 'api/ExportJobSummary', 'api/GetExportJobDownloadRequest', + 'api/GetExportJobDownloadResponse', 'api/ListExportJobsRequest', 'api/ListExportJobsResponse', + 'api/ScheduleExportRequest', 'api/ScheduleExportResponse', 'api/ScheduledExport', + 'automation/ScheduleState', +] as const; + +const SEMANTIC_ID = 'export-job-family-retired'; + +/** Same-module neighbours that stay — the served import-job family, and the template shape with its format enum. */ +const MUST_SURVIVE_API = [ + 'ExportFormat', 'ExportImportTemplateSchema', 'ImportJobStatus', 'ImportJobProgressSchema', + 'ListImportJobsRequestSchema', 'ListImportJobsResponseSchema', 'ImportJobApiContracts', +] as const; +const MUST_SURVIVE_AUTOMATION = ['ExecutionLogSchema', 'FlowRunSummarySchema', 'ConcurrencyPolicySchema'] as const; +const MUST_SURVIVE_CONTRACTS = ['IAutomationService', 'RunListResult'] as const; + +const SPEC_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..'); +const SRC_ROOT = path.join(SPEC_ROOT, 'src'); + +describe('[#17158] export-job family retirement — the public surface', () => { + it('every retired name has ZERO holders on any public entry; the survivors still stand', () => { + for (const needed of ['.', './api', './contracts', './automation']) { + expect(EXPORT_ENTRY_POINTS, `exports map must include ${needed}`).toContain(needed); + } + expect(exportNamesOf('./api').length, './api must export a non-trivial surface').toBeGreaterThan(100); + + for (const name of RETIRED_NAMES) { + expect(holdersOf(name), `${name} must have zero holders after #17158`).toEqual([]); + } + const survivors: Array<[string, readonly string[]]> = [ + ['./api', MUST_SURVIVE_API], + ['./automation', MUST_SURVIVE_AUTOMATION], + ['./contracts', MUST_SURVIVE_CONTRACTS], + ]; + for (const [entry, names] of survivors) { + const exported = exportNamesOf(entry); + for (const name of names) expect(exported, `${name} must SURVIVE this retirement`).toContain(name); + } + }); + + it('runtime namespaces agree with the compiler view', async () => { + const api = await import('./index'); + const automation = await import('../automation/index'); + for (const name of RETIRED_NAMES.filter((n) => n.endsWith('Schema') || n === 'ExportJobStatus' || n === 'ExportApiContracts')) { + expect(name in api, `api must not export ${name}`).toBe(false); + expect(name in automation, `automation must not export ${name}`).toBe(false); + } + for (const name of MUST_SURVIVE_API) expect(name in api, `${name} must SURVIVE at runtime`).toBe(true); + for (const name of MUST_SURVIVE_AUTOMATION) expect(name in automation, `${name} must SURVIVE at runtime`).toBe(true); + }); + + it('the service contract module is gone from disk, and nothing in the package imports it any more', () => { + for (const f of ['export-service.ts', 'export-service.test.ts']) { + expect(fs.existsSync(path.join(SRC_ROOT, 'contracts', f)), `contracts/${f} must be deleted`).toBe(false); + } + // Anti-vacuity: the kept module and a kept sibling prove the probe looks in the right place. + expect(fs.existsSync(path.join(SRC_ROOT, 'api', 'export.zod.ts'))).toBe(true); + expect(fs.existsSync(path.join(SRC_ROOT, 'contracts', 'automation-service.ts'))).toBe(true); + + const importers: string[] = []; + const walk = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.name.endsWith('.ts')) { + const src = fs.readFileSync(full, 'utf-8'); + if (/(?:import|export)[^;]*['"][^'"]*\/export-service(?:\.js)?['"]/.test(src)) { + importers.push(path.relative(SRC_ROOT, full)); + } + } + } + }; + walk(SRC_ROOT); + expect(importers, 'a resurrected import means the retirement is being undone — re-read #17158').toEqual([]); + }); + + it('the generated shards no longer list any of the thirteen defs or forty-three names', () => { + const shard = (dir: string, category: string) => + fs.readFileSync(path.join(SPEC_ROOT, dir, `${category}.json`), 'utf-8'); + const manifest = (category: string) => (JSON.parse(shard('json-schema.manifest', category)) as { schemas: string[] }).schemas; + const listed = [...manifest('api'), ...manifest('automation')]; + for (const def of RETIRED_DEFS) { + expect(listed, `${def} must have left json-schema.manifest/`).not.toContain(def); + } + // Anti-vacuity: surviving neighbours are still there. + expect(listed).toContain('api/ExportFormat'); + expect(listed).toContain('api/ImportJobStatus'); + expect(listed).toContain('automation/ExecutionLog'); + + // Word-bounded rather than quoted: the three shards spell a row three ways + // (`"Name (kind)"`, `"Name": "…"`, a nested origin record), and a retired + // name must be absent under every spelling. + const exact = (name: string) => new RegExp(`\\b${name}\\b`); + const controls: Record = { api: 'ImportJobStatus', contracts: 'IAutomationService', automation: 'ExecutionLogSchema' }; + for (const dir of ['api-surface', 'declaration-map', 'export-origins']) { + for (const category of ['api', 'contracts', 'automation']) { + const file = path.join(SPEC_ROOT, dir, `${category}.json`); + if (!fs.existsSync(file)) continue; // declaration-map carries no contracts shard + const text = fs.readFileSync(file, 'utf-8'); + for (const name of RETIRED_NAMES) { + expect(text, `${dir}/${category}.json must not list ${name}`).not.toMatch(exact(name)); + } + expect(text, `${dir}/${category}.json anti-vacuity`).toMatch(exact(controls[category]!)); + } + } + for (const dir of ['authorable-surface', 'authorable-defaults']) { + const text = shard(dir, 'api') + shard(dir, 'automation'); + for (const def of RETIRED_DEFS) { + expect(text, `${dir}/ must carry no row under ${def}`).not.toMatch(new RegExp(`"${def}:`)); + } + expect(text, `${dir}/ anti-vacuity`).toMatch(/"api\/ImportRequest:/); + } + }); +}); + +describe('[#17158] ADR-0087 registration', () => { + it('declares all thirteen defs under major 18, with the D3 semantic entry wired and no D2 conversion', () => { + for (const def of RETIRED_DEFS) { + expect(RETIRED_DEFS_BY_MAJOR[18], `${def} must be declared`).toContain(def); + } + const step = MIGRATIONS_BY_MAJOR[18]; + expect(step).toBeDefined(); + const entry = step!.semantic.find((s) => s.id === SEMANTIC_ID); + expect(entry, `${SEMANTIC_ID} must be wired into the step-18 chain`).toBeDefined(); + expect(entry!.reason.length).toBeGreaterThan(0); + expect(entry!.acceptanceCriteria.length).toBeGreaterThan(0); + // The route is stated where the next reader looks: why D3 semantic and not D2. + expect(entry!.reason).toMatch(/not a D2 conversion/); + // No backticks in `surface`: the upgrade guide renders it inside a code span AND a table cell. + expect(entry!.surface).not.toMatch(/`/); + // The surface names every def and the three retired surfaces beside them. + for (const def of RETIRED_DEFS) expect(entry!.surface, `surface must name ${def}`).toContain(def); + for (const name of ['ExportApiContracts', 'IExportService', 'ScheduleExportInput', 'ScheduleStateSchema']) { + expect(entry!.surface, `surface must name ${name}`).toContain(name); + } + // The served import-job family is named as NOT retired, so no sweep takes it. + expect(entry!.replacement).toMatch(/import-job family .* is served and is NOT part of this retirement/s); + + // Deliberately no mechanical conversion: a transform over a stack that + // never carries these shapes would be a seam that never runs. + const conversionIds = Object.values(CONVERSIONS_BY_MAJOR).flatMap((entries) => entries.map((c) => c.id)); + expect(conversionIds.filter((id) => /export-job|schedule-state|scheduled-export/.test(id))).toEqual([]); + expect(step!.conversionIds.filter((id) => /export-job|schedule-state|scheduled-export/.test(id))).toEqual([]); + // Lit control: the conversion table is loaded and readable. + expect(conversionIds.length).toBeGreaterThan(10); + }); +}); + +// ─── Tree-scoped absence, inside the radius already declared ───────────────── +// +// The walk below reads the five repo roots `system/compliance-families-retirement.test.ts` +// reads — `packages`, `examples`, `skills`, `content`, `scripts` — with the +// same per-root extension sets, and every one of those globs is already +// declared for `@objectstack/spec` in `scripts/cross-package-test-inputs.mjs` +// and mirrored in `turbo.json`, so a resurrection inside the radius puts this +// suite into `turbo ls --affected` and moves the `test:repo` task's cache key. +// This file is listed in `vitest.repo-tests.json` for the same reason. +// +// The bound, stated: `docs/**`, `.claude/**`, `.github/**` and the repo-root +// files are outside the walk (prose-only for these names); `.tsx` under +// `packages/` and every code file under `examples/` are not scanned, because a +// typed import of a retired name fails `tsc` in its own package — the enforced +// channel there. The residue this leg covers is what `tsc` does not compile: +// JSON, YAML, MD, MDX and untyped `.js` / `.mjs` / `.cjs`. +describe('[#17158] tree-scoped absence: nothing inside the declared radius references a retired name', () => { + const REPO_ROOT = path.resolve(SPEC_ROOT, '../..'); + const THIS_FILE = path.relative(REPO_ROOT, fileURLToPath(import.meta.url)).split(path.sep).join('/'); + + const WALK_ROOTS = ['packages', 'examples', 'skills', 'content', 'scripts']; + const SCANNED_EXT = new Set(['.ts', '.mts', '.cts', '.js', '.mjs', '.cjs', '.json', '.md', '.mdx', '.yaml', '.yml']); + const EXAMPLES_EXT = new Set(['.json', '.md', '.mdx', '.yaml', '.yml']); + /** Build, SCM and cache state — not authored sources. */ + const SKIPPED_DIRS = new Set(['node_modules', 'dist', '.git', '.turbo', '.cache', '.objectstack', 'coverage', '.next', '.source']); + + const NAME = new RegExp('\\b(' + RETIRED_NAMES.join('|') + ')\\b'); + /** A reference, never a prose mention: an import/export specifier, a type position, a method call, a manifest def key. */ + const REFERENCE = new RegExp( + '(?:' + + '[{,]\\s*(?:type\\s+)?' + NAME.source + '\\s*[,}]' + + '|' + + '(?:typeof\\s+|:\\s*|<)' + NAME.source + '\\b' + + '|' + + NAME.source + '\\.\\w+\\(' + + '|' + + '"(?:' + RETIRED_DEFS.join('|') + ')"' + + ')', + ); + + /** Structural exclusions, each with its reason. NOT an allowlist file. */ + const EXCLUDED = new Set([ + // This pin names them to assert their absence. + THIS_FILE, + ]); + const EXCLUDED_PREFIXES = [ + // Registers the retirement by def (entries + the generated registry). + 'packages/spec/src/migrations/', + // Generated projection of the registry. + 'packages/spec/spec-changes.json', + // `gen:schema`'s gitignored per-def output (`.gitignore`: packages/spec/json-schema/), + // which a local tree may still hold from a build before this retirement. + 'packages/spec/json-schema/', + // Written only by `gen:authorable-surface-base` (never a normal build); its + // `baseRev` is allowed to lag, and `check:authorable-surface` is the gate. + 'packages/spec/authorable-surface.base.json', + // Release prose records the removal (release-owned; never edited by a code PR). + 'content/docs/releases/', + '.changeset/', + ]; + /** tsup's transient config bundle — a copy of `tsup.config.ts`, which the walk already reads. */ + const TSUP_BUNDLED_CONFIG = /\.bundled_[^./]+\.mjs$/; + + const readIfPresent = (full: string): string | undefined => { + try { + return fs.readFileSync(full, 'utf-8'); + } catch (err) { + // A path that vanished mid-walk cannot be a surviving reference; every other fault is re-raised. + if ((err as NodeJS.ErrnoException)?.code !== 'ENOENT') throw err; + return undefined; + } + }; + + it('the matcher recognises a reference and ignores a prose mention (anti-vacuity)', () => { + expect(REFERENCE.test("import { ExportJobStatus } from '@objectstack/spec/api';")).toBe(true); + expect(REFERENCE.test("import type { CreateExportJobInput } from '@objectstack/spec/contracts';")).toBe(true); + expect(REFERENCE.test('const s: ScheduleState = {};')).toBe(true); + expect(REFERENCE.test('ScheduledExportSchema.parse(value)')).toBe(true); + expect(REFERENCE.test('typeof ListExportJobsRequestSchema')).toBe(true); + expect(REFERENCE.test('"api/ExportJobSummary",')).toBe(true); + expect(REFERENCE.test('"automation/ScheduleState",')).toBe(true); + expect(REFERENCE.test('the `IExportService` contract was removed')).toBe(false); + // The served neighbours are not matched. + expect(REFERENCE.test("import { ImportJobStatus, ListImportJobsRequestSchema } from './export.zod';")).toBe(false); + expect(REFERENCE.test('"api/ImportJobSummary",')).toBe(false); + expect(REFERENCE.test('const f: ExportFormat = "csv";')).toBe(false); + }); + + it('no reference survives inside the declared radius outside the retirement kit', () => { + const offenders: string[] = []; + let visited = 0; + const walk = (dir: string) => { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + const rel = path.relative(REPO_ROOT, full).split(path.sep).join('/'); + if (entry.isDirectory()) { + if (SKIPPED_DIRS.has(entry.name) || entry.name.startsWith('.')) continue; + walk(full); + continue; + } + if (!entry.isFile()) continue; + const ext = path.extname(entry.name); + if (!(rel.startsWith('examples/') ? EXAMPLES_EXT : SCANNED_EXT).has(ext)) continue; + if (entry.name === 'CHANGELOG.md') continue; // release prose records the removal + if (EXCLUDED.has(rel) || EXCLUDED_PREFIXES.some((p) => rel.startsWith(p))) continue; + if (TSUP_BUNDLED_CONFIG.test(entry.name)) continue; + visited += 1; + const text = readIfPresent(full); + if (text === undefined) continue; + const m = REFERENCE.exec(text); + if (m) offenders.push(`${rel} references \`${m[0].trim()}\``); + } + }; + for (const root of WALK_ROOTS) walk(path.join(REPO_ROOT, root)); + // Anti-vacuity: the walk really covered the tree. + expect(visited).toBeGreaterThan(1000); + expect(offenders, 'a reference to a retired name means the retirement is being undone — re-read #17158').toEqual([]); + }); +}); diff --git a/packages/spec/src/api/export.test.ts b/packages/spec/src/api/export.test.ts index 74f2afccc37..7ac19ac1c38 100644 --- a/packages/spec/src/api/export.test.ts +++ b/packages/spec/src/api/export.test.ts @@ -2,25 +2,12 @@ import { describe, it, expect, expectTypeOf } from 'vitest'; import type { ImportRequest } from './export.zod'; import { ExportFormat, - ExportJobStatus, - CreateExportJobRequestSchema, - CreateExportJobResponseSchema, - ExportJobProgressSchema, ImportValidationMode, DeduplicationStrategy, ImportValidationConfigSchema, ImportValidationResultSchema, FieldMappingEntrySchema, ExportImportTemplateSchema, - ScheduledExportSchema, - ExportApiContracts, - GetExportJobDownloadRequestSchema, - GetExportJobDownloadResponseSchema, - ListExportJobsRequestSchema, - ListExportJobsResponseSchema, - ExportJobSummarySchema, - ScheduleExportRequestSchema, - ScheduleExportResponseSchema, ImportRequestSchema, CreateImportJobRequestSchema, } from './export.zod'; @@ -43,172 +30,6 @@ describe('ExportFormat', () => { }); }); -// ========================================== -// Export Job Status -// ========================================== - -describe('ExportJobStatus', () => { - it('should accept all valid statuses', () => { - const valid = ['pending', 'processing', 'completed', 'failed', 'cancelled', 'expired']; - valid.forEach((v) => { - expect(() => ExportJobStatus.parse(v)).not.toThrow(); - }); - }); - - it('should reject invalid statuses', () => { - expect(() => ExportJobStatus.parse('running')).toThrow(); - expect(() => ExportJobStatus.parse('PENDING')).toThrow(); - }); -}); - -// ========================================== -// Create Export Job Request -// ========================================== - -describe('CreateExportJobRequestSchema', () => { - it('should accept a minimal request with defaults', () => { - const req = CreateExportJobRequestSchema.parse({ - object: 'account', - }); - expect(req.object).toBe('account'); - expect(req.format).toBe('csv'); - expect(req.includeHeaders).toBe(true); - expect(req.encoding).toBe('utf-8'); - expect(req.fields).toBeUndefined(); - expect(req.filter).toBeUndefined(); - }); - - it('should accept a full request', () => { - const req = CreateExportJobRequestSchema.parse({ - object: 'contact', - format: 'xlsx', - fields: ['name', 'email', 'phone'], - filter: { status: 'active' }, - sort: [ - { field: 'name', direction: 'asc' }, - { field: 'created_at', direction: 'desc' }, - ], - limit: 10000, - includeHeaders: false, - encoding: 'utf-16', - templateId: 'tmpl_001', - }); - expect(req.format).toBe('xlsx'); - expect(req.fields).toHaveLength(3); - expect(req.sort).toHaveLength(2); - expect(req.limit).toBe(10000); - expect(req.includeHeaders).toBe(false); - }); - - it('should reject missing object', () => { - expect(() => CreateExportJobRequestSchema.parse({ - format: 'csv', - })).toThrow(); - }); - - it('should reject invalid format', () => { - expect(() => CreateExportJobRequestSchema.parse({ - object: 'account', - format: 'xml', - })).toThrow(); - }); -}); - -// ========================================== -// Create Export Job Response -// ========================================== - -describe('CreateExportJobResponseSchema', () => { - it('should accept a valid response', () => { - const resp = CreateExportJobResponseSchema.parse({ - success: true, - data: { - jobId: 'job_001', - status: 'pending', - estimatedRecords: 5000, - createdAt: '2026-02-01T10:00:00Z', - }, - }); - expect(resp.data.jobId).toBe('job_001'); - expect(resp.data.status).toBe('pending'); - expect(resp.data.estimatedRecords).toBe(5000); - }); - - it('should reject missing jobId', () => { - expect(() => CreateExportJobResponseSchema.parse({ - success: true, - data: { - status: 'pending', - createdAt: '2026-02-01T10:00:00Z', - }, - })).toThrow(); - }); -}); - -// ========================================== -// Export Job Progress -// ========================================== - -describe('ExportJobProgressSchema', () => { - it('should accept an in-progress job', () => { - const progress = ExportJobProgressSchema.parse({ - success: true, - data: { - jobId: 'job_001', - status: 'processing', - format: 'csv', - totalRecords: 10000, - processedRecords: 4500, - percentComplete: 45, - fileSize: 2048000, - startedAt: '2026-02-01T10:00:05Z', - }, - }); - expect(progress.data.processedRecords).toBe(4500); - expect(progress.data.percentComplete).toBe(45); - expect(progress.data.downloadUrl).toBeUndefined(); - }); - - it('should accept a completed job with download URL', () => { - const progress = ExportJobProgressSchema.parse({ - success: true, - data: { - jobId: 'job_001', - status: 'completed', - format: 'csv', - totalRecords: 10000, - processedRecords: 10000, - percentComplete: 100, - fileSize: 5242880, - downloadUrl: 'https://storage.example.com/exports/job_001.csv?token=abc', - downloadExpiresAt: '2026-02-02T10:00:00Z', - startedAt: '2026-02-01T10:00:05Z', - completedAt: '2026-02-01T10:05:00Z', - }, - }); - expect(progress.data.downloadUrl).toContain('storage.example.com'); - expect(progress.data.percentComplete).toBe(100); - }); - - it('should accept a failed job with error', () => { - const progress = ExportJobProgressSchema.parse({ - success: true, - data: { - jobId: 'job_002', - status: 'failed', - format: 'json', - processedRecords: 500, - percentComplete: 5, - error: { - code: 'EXPORT_TIMEOUT', - message: 'Export timed out after 30 minutes', - }, - }, - }); - expect(progress.data.error?.code).toBe('EXPORT_TIMEOUT'); - }); -}); - // ========================================== // Import Validation Mode & Deduplication Strategy // ========================================== @@ -422,304 +243,6 @@ describe('ExportImportTemplateSchema', () => { }); }); -// ========================================== -// Scheduled Export -// ========================================== - -describe('ScheduledExportSchema', () => { - it('should accept a valid scheduled export', () => { - const sched = ScheduledExportSchema.parse({ - name: 'weekly_account_export', - label: 'Weekly Account Export', - object: 'account', - format: 'csv', - fields: ['name', 'email', 'status'], - filter: { status: 'active' }, - // `schedule.cronExpression` was deleted outright (#16320) — the strip is - // pinned in `cron-typed-positions-retirement.test.ts`. - schedule: { - timezone: 'America/New_York', - }, - delivery: { - method: 'email', - recipients: ['admin@example.com', 'ops@example.com'], - }, - }); - expect(sched.name).toBe('weekly_account_export'); - expect(sched.schedule.timezone).toBe('America/New_York'); - expect(sched.delivery.method).toBe('email'); - expect(sched.enabled).toBe(true); - }); - - it('should apply defaults', () => { - const sched = ScheduledExportSchema.parse({ - name: 'daily_export', - object: 'order', - schedule: {}, - delivery: { method: 'storage', storagePath: '/exports/daily/' }, - }); - expect(sched.format).toBe('csv'); - expect(sched.enabled).toBe(true); - expect(sched.schedule.timezone).toBe('UTC'); - }); - - it('should reject invalid name (not snake_case)', () => { - expect(() => ScheduledExportSchema.parse({ - name: 'WeeklyExport', - object: 'account', - schedule: {}, - delivery: { method: 'email' }, - })).toThrow(); - }); - - it('should accept all delivery methods', () => { - const methods = ['email', 'storage', 'webhook']; - methods.forEach((m) => { - expect(() => ScheduledExportSchema.parse({ - name: 'test_export', - object: 'account', - schedule: {}, - delivery: { method: m }, - })).not.toThrow(); - }); - }); -}); - -// ========================================== -// Get Export Job Download -// ========================================== - -describe('GetExportJobDownloadRequestSchema', () => { - it('should accept a valid request', () => { - const req = GetExportJobDownloadRequestSchema.parse({ jobId: 'job_001' }); - expect(req.jobId).toBe('job_001'); - }); - - it('should reject missing jobId', () => { - expect(() => GetExportJobDownloadRequestSchema.parse({})).toThrow(); - }); -}); - -describe('GetExportJobDownloadResponseSchema', () => { - it('should accept a valid response', () => { - const resp = GetExportJobDownloadResponseSchema.parse({ - success: true, - data: { - jobId: 'job_001', - downloadUrl: 'https://storage.example.com/exports/job_001.csv?token=abc', - fileName: 'account_export_2026-02-01.csv', - fileSize: 5242880, - format: 'csv', - expiresAt: '2026-02-02T10:00:00Z', - checksum: 'sha256:abc123', - }, - }); - expect(resp.data.downloadUrl).toContain('storage.example.com'); - expect(resp.data.fileName).toContain('.csv'); - expect(resp.data.checksum).toBe('sha256:abc123'); - }); - - it('should accept response without optional checksum', () => { - const resp = GetExportJobDownloadResponseSchema.parse({ - success: true, - data: { - jobId: 'job_001', - downloadUrl: 'https://storage.example.com/exports/job_001.json', - fileName: 'export.json', - fileSize: 1024, - format: 'json', - expiresAt: '2026-02-02T10:00:00Z', - }, - }); - expect(resp.data.checksum).toBeUndefined(); - }); -}); - -// ========================================== -// List Export Jobs -// ========================================== - -describe('ListExportJobsRequestSchema', () => { - it('should apply defaults', () => { - const req = ListExportJobsRequestSchema.parse({}); - expect(req.limit).toBe(20); - expect(req.object).toBeUndefined(); - expect(req.status).toBeUndefined(); - expect(req.cursor).toBeUndefined(); - }); - - it('should accept a full request', () => { - const req = ListExportJobsRequestSchema.parse({ - object: 'account', - status: 'completed', - limit: 50, - cursor: 'cursor_abc', - }); - expect(req.object).toBe('account'); - expect(req.status).toBe('completed'); - expect(req.limit).toBe(50); - expect(req.cursor).toBe('cursor_abc'); - }); - - it('should reject limit exceeding max', () => { - expect(() => ListExportJobsRequestSchema.parse({ limit: 200 })).toThrow(); - }); -}); - -describe('ExportJobSummarySchema', () => { - it('should accept a valid summary', () => { - const summary = ExportJobSummarySchema.parse({ - jobId: 'job_001', - object: 'account', - status: 'completed', - format: 'csv', - totalRecords: 5000, - fileSize: 2048000, - createdAt: '2026-02-01T10:00:00Z', - completedAt: '2026-02-01T10:05:00Z', - createdBy: 'user_admin', - }); - expect(summary.jobId).toBe('job_001'); - expect(summary.totalRecords).toBe(5000); - }); -}); - -describe('ListExportJobsResponseSchema', () => { - it('should accept a response with jobs', () => { - const resp = ListExportJobsResponseSchema.parse({ - success: true, - data: { - jobs: [ - { - jobId: 'job_001', - object: 'account', - status: 'completed', - format: 'csv', - createdAt: '2026-02-01T10:00:00Z', - }, - ], - nextCursor: 'cursor_next', - hasMore: true, - }, - }); - expect(resp.data.jobs).toHaveLength(1); - expect(resp.data.nextCursor).toBe('cursor_next'); - expect(resp.data.hasMore).toBe(true); - }); - - it('should accept an empty list', () => { - const resp = ListExportJobsResponseSchema.parse({ - success: true, - data: { - jobs: [], - hasMore: false, - }, - }); - expect(resp.data.jobs).toHaveLength(0); - expect(resp.data.hasMore).toBe(false); - }); -}); - -// ========================================== -// Schedule Export Request/Response -// ========================================== - -describe('ScheduleExportRequestSchema', () => { - it('should accept a valid request', () => { - const req = ScheduleExportRequestSchema.parse({ - name: 'weekly_account_export', - label: 'Weekly Account Export', - object: 'account', - format: 'csv', - fields: ['name', 'email'], - // `schedule.cronExpression` was deleted outright (#16320) — the strip is - // pinned in `cron-typed-positions-retirement.test.ts`. - schedule: { - timezone: 'America/New_York', - }, - delivery: { - method: 'email', - recipients: ['admin@example.com'], - }, - }); - expect(req.name).toBe('weekly_account_export'); - expect(req.schedule.timezone).toBe('America/New_York'); - expect(req.delivery.method).toBe('email'); - }); - - it('should apply defaults', () => { - const req = ScheduleExportRequestSchema.parse({ - name: 'daily_export', - object: 'order', - schedule: {}, - delivery: { method: 'storage', storagePath: '/exports/daily/' }, - }); - expect(req.format).toBe('csv'); - expect(req.schedule.timezone).toBe('UTC'); - }); - - it('should reject invalid name (not snake_case)', () => { - expect(() => ScheduleExportRequestSchema.parse({ - name: 'WeeklyExport', - object: 'account', - schedule: {}, - delivery: { method: 'email' }, - })).toThrow(); - }); -}); - -describe('ScheduleExportResponseSchema', () => { - it('should accept a valid response', () => { - const resp = ScheduleExportResponseSchema.parse({ - success: true, - data: { - id: 'sched_001', - name: 'weekly_account_export', - enabled: true, - nextRunAt: '2026-02-24T06:00:00Z', - createdAt: '2026-02-21T09:00:00Z', - }, - }); - expect(resp.data.id).toBe('sched_001'); - expect(resp.data.enabled).toBe(true); - expect(resp.data.nextRunAt).toBeDefined(); - }); -}); - -// ========================================== -// Export API Contracts -// ========================================== - -describe('ExportApiContracts', () => { - it('should define 6 contracts', () => { - expect(Object.keys(ExportApiContracts)).toHaveLength(6); - }); - - it('should have correct HTTP methods', () => { - expect(ExportApiContracts.createExportJob.method).toBe('POST'); - expect(ExportApiContracts.getExportJobProgress.method).toBe('GET'); - expect(ExportApiContracts.getExportJobDownload.method).toBe('GET'); - expect(ExportApiContracts.listExportJobs.method).toBe('GET'); - expect(ExportApiContracts.scheduleExport.method).toBe('POST'); - expect(ExportApiContracts.cancelExportJob.method).toBe('POST'); - }); - - it('should have valid paths', () => { - expect(ExportApiContracts.createExportJob.path).toContain('/export'); - expect(ExportApiContracts.getExportJobProgress.path).toContain('/export'); - expect(ExportApiContracts.getExportJobDownload.path).toContain('/download'); - expect(ExportApiContracts.listExportJobs.path).toBe('/api/v1/data/export'); - expect(ExportApiContracts.scheduleExport.path).toContain('/schedules'); - expect(ExportApiContracts.cancelExportJob.path).toContain('/cancel'); - }); - - it('should have input/output schemas for all contracts', () => { - Object.values(ExportApiContracts).forEach((contract) => { - expect(contract.output).toBeDefined(); - }); - }); -}); - // ========================================== // Import Request — runAutomations declared default (#6704) // ========================================== diff --git a/packages/spec/src/api/export.zod.ts b/packages/spec/src/api/export.zod.ts index e4604a553ca..6d6086ba666 100644 --- a/packages/spec/src/api/export.zod.ts +++ b/packages/spec/src/api/export.zod.ts @@ -6,13 +6,19 @@ import { BaseResponseSchema } from './contract.zod'; /** * Data Export & Import Protocol * - * Defines schemas for streaming data export, import validation, - * template-based field mapping, and scheduled export jobs. + * Defines the export file formats, import validation, template-based field + * mapping, and the asynchronous import-job contracts. * * Industry alignment: Salesforce Data Export, Airtable CSV Export, * Dynamics 365 Data Management. * - * Base path: /api/v1/data/{object}/export + * The export the platform serves is the synchronous streaming door + * `GET /api/v1/data/:object/export`, which answers the file itself as CSV, + * JSON or XLSX. The asynchronous export-job API that used to be + * declared here (export jobs, their progress / download / list shapes, + * scheduled exports and `ExportApiContracts`) was never served by any route and + * was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove); a recurring + * export is a `Job` whose handler you write. */ // ========================================== @@ -33,100 +39,31 @@ export const ExportFormat = z.enum([ ]); export type ExportFormat = z.input; -/** - * Export Job Status - */ -export const ExportJobStatus = z.enum([ - 'pending', - 'processing', - 'completed', - 'failed', - 'cancelled', - 'expired', -]); -export type ExportJobStatus = z.input; - // ========================================== -// 2. Export Job Request / Response +// 2. Export Job API — RETIRED (#17158) // ========================================== -/** - * Create Export Job Request - * Initiates an asynchronous streaming export. +/* + * The export-job API family was DELETED here in @objectstack/spec 17 (ADR-0049 + * enforce-or-remove; maintainer ruling A on #17158, landing route A). It + * declared an asynchronous export API — `ExportJobStatus`, + * `CreateExportJobRequest` / `CreateExportJobResponse`, `ExportJobProgress`, + * `ScheduledExport`, `GetExportJobDownloadRequest` / `GetExportJobDownloadResponse`, + * `ListExportJobsRequest` / `ExportJobSummary` / `ListExportJobsResponse`, + * `ScheduleExportRequest` / `ScheduleExportResponse` and the + * `ExportApiContracts` route map (formerly sections 2 and 5–9 of this file) — + * that nothing served: no route under `/api/v1/data/export`, no `POST` on + * `/api/v1/data/:object/export`, no provider for the `IExportService` contract + * that retired with it, and no reader in any repo. Each def is registered in + * `RETIRED_DEFS_BY_MAJOR[18]`; the D3 semantic entry + * `export-job-family-retired` carries the prescription. * - * @example POST /api/v1/data/account/export - * { format: 'csv', fields: ['name', 'email', 'status'], filter: { status: 'active' }, limit: 10000 } + * The export that IS served is the synchronous streaming door + * `GET /api/v1/data/:object/export` (`@objectstack/rest`), which answers CSV, + * JSON or XLSX (its own `format` read, not `ExportFormat` above). A recurring + * export is a `Job` whose handler you write (`Job.schedule.expression`, + * `system/job.zod.ts`). */ -export const CreateExportJobRequestSchema = lazySchema(() => z.object({ - object: z.string().describe('Object name to export'), - format: ExportFormat.default('csv').describe('Export file format'), - fields: z.array(z.string()).optional() - .describe('Specific fields to include (omit for all fields)'), - filter: z.record(z.string(), z.unknown()).optional() - .describe('Filter criteria for records to export'), - sort: z.array(z.object({ - field: z.string().describe('Field name to sort by'), - direction: z.enum(['asc', 'desc']).default('asc').describe('Sort direction'), - })).optional().describe('Sort order for exported records'), - limit: z.number().int().min(1).optional() - .describe('Maximum number of records to export'), - includeHeaders: z.boolean().default(true) - .describe('Include header row (CSV/XLSX)'), - encoding: z.string().default('utf-8') - .describe('Character encoding for the export file'), - templateId: z.string().optional() - .describe('Export template ID for predefined field mappings'), -})); -export type CreateExportJobRequest = z.input; -/** Post-parse shape of {@link CreateExportJobRequest} — defaults applied, transforms run (ADR-0122). */ -export type CreateExportJobRequestParsed = z.infer; - -/** - * Export Job Response - * Returns the created export job with tracking info. - */ -export const CreateExportJobResponseSchema = lazySchema(() => BaseResponseSchema.extend({ - data: z.object({ - jobId: z.string().describe('Export job ID'), - status: ExportJobStatus.describe('Initial job status'), - estimatedRecords: z.number().int().optional().describe('Estimated total records'), - createdAt: z.string().datetime().describe('Job creation timestamp'), - }), -})); -export type CreateExportJobResponse = z.input; -/** Post-parse shape of {@link CreateExportJobResponse} — defaults applied, transforms run (ADR-0122). */ -export type CreateExportJobResponseParsed = z.infer; - -/** - * Export Job Progress - * Tracks the progress of an active export job. - * - * @example GET /api/v1/data/export/:jobId - */ -export const ExportJobProgressSchema = lazySchema(() => BaseResponseSchema.extend({ - data: z.object({ - jobId: z.string().describe('Export job ID'), - status: ExportJobStatus.describe('Current job status'), - format: ExportFormat.describe('Export format'), - totalRecords: z.number().int().optional().describe('Total records to export'), - processedRecords: z.number().int().describe('Records processed so far'), - percentComplete: z.number().min(0).max(100).describe('Export progress percentage'), - fileSize: z.number().int().optional().describe('Current file size in bytes'), - downloadUrl: z.string().optional() - .describe('Presigned download URL (available when status is "completed")'), - downloadExpiresAt: z.string().datetime().optional() - .describe('Download URL expiration timestamp'), - error: z.object({ - code: z.string().describe('Error code'), - message: z.string().describe('Error message'), - }).optional().describe('Error details if job failed'), - startedAt: z.string().datetime().optional().describe('Processing start timestamp'), - completedAt: z.string().datetime().optional().describe('Completion timestamp'), - }), -})); -export type ExportJobProgress = z.input; -/** Post-parse shape of {@link ExportJobProgress} — defaults applied, transforms run (ADR-0122). */ -export type ExportJobProgressParsed = z.infer; // ========================================== // 3. Import Validation & Deduplication @@ -424,8 +361,8 @@ export type ImportResponse = z.input; export const IMPORT_JOB_MAX_ROWS = 50_000; /** - * Import Job Status. Mirrors {@link ExportJobStatus} but with the terminal - * states the import worker actually uses (`succeeded` rather than `completed`). + * Import Job Status — the states the import worker actually moves a job + * through (`succeeded`, not `completed`, is the success terminal). */ export const ImportJobStatus = z.enum([ 'pending', // Row persisted, worker not yet started @@ -545,267 +482,6 @@ export const UndoImportJobResponseSchema = lazySchema(() => z.object({ })); export type UndoImportJobResponse = z.input; -// ========================================== -// 5. Scheduled Export Jobs -// ========================================== - -/** - * Scheduled Export Schema - * Defines a recurring data export job. - * - * @example - * { - * name: 'weekly_account_export', - * object: 'account', - * format: 'csv', - * schedule: { timezone: 'America/New_York' }, - * delivery: { method: 'email', recipients: ['admin@example.com'] }, - * } - */ -export const ScheduledExportSchema = lazySchema(() => z.object({ - id: z.string().optional().describe('Scheduled export ID'), - name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Schedule name (snake_case)'), - label: z.string().optional().describe('Human-readable label'), - object: z.string().describe('Object name to export'), - format: ExportFormat.default('csv').describe('Export file format'), - fields: z.array(z.string()).optional().describe('Fields to include'), - filter: z.record(z.string(), z.unknown()).optional().describe('Record filter criteria'), - templateId: z.string().optional().describe('Export template ID for field mappings'), - /** - * Schedule timing configuration. - * - * `cronExpression` was DELETED here in @objectstack/spec 17 (ADR-0049 - * enforce-or-remove, #16320): the whole `ExportJobApiContracts` family has zero - * consumers, rest-server serves no `/api/v1/data/export` route and `IExportService` - * has no provider binding, so the cron was parsed and never fired. Deleted outright — - * no `retiredKey()` tombstone, no D2 conversion, no D3 semantic entry (maintainer - * ruling 2026-09-10 on the retirement PR). The mechanism that does work is - * `Job.schedule.expression` (`system/job.zod.ts`), the one cron slot the platform - * evaluates: a recurring export is a job whose handler you write. - */ - schedule: z.object({ - timezone: z.string().default('UTC').describe('IANA timezone'), - }).describe('Schedule timing configuration'), - delivery: z.object({ - method: z.enum(['email', 'storage', 'webhook']) - .describe('How to deliver the export file'), - recipients: z.array(z.string()).optional() - .describe('Email recipients (for email delivery)'), - storagePath: z.string().optional() - .describe('Storage path (for storage delivery)'), - webhookUrl: z.string().optional() - .describe('Webhook URL (for webhook delivery)'), - }).describe('Export delivery configuration'), - enabled: z.boolean().default(true).describe('Whether the scheduled export is active'), - lastRunAt: z.string().datetime().optional().describe('Last execution timestamp'), - nextRunAt: z.string().datetime().optional().describe('Next scheduled execution'), - createdAt: z.string().datetime().optional().describe('Creation timestamp'), - createdBy: z.string().optional().describe('User who created the schedule'), -})); -export type ScheduledExport = z.input; -/** Post-parse shape of {@link ScheduledExport} — defaults applied, transforms run (ADR-0122). */ -export type ScheduledExportParsed = z.infer; - -// ========================================== -// 6. Get Export Job Download -// ========================================== - -/** - * Get Export Job Download Request - * Retrieves a presigned download link for a completed export job. - * - * @example GET /api/v1/data/export/:jobId/download - */ -export const GetExportJobDownloadRequestSchema = lazySchema(() => z.object({ - jobId: z.string().describe('Export job ID'), -})); -export type GetExportJobDownloadRequest = z.input; - -/** - * Get Export Job Download Response - * Returns the presigned download URL and metadata. - */ -export const GetExportJobDownloadResponseSchema = lazySchema(() => BaseResponseSchema.extend({ - data: z.object({ - jobId: z.string().describe('Export job ID'), - downloadUrl: z.string().describe('Presigned download URL'), - fileName: z.string().describe('Suggested file name'), - fileSize: z.number().int().describe('File size in bytes'), - format: ExportFormat.describe('Export file format'), - expiresAt: z.string().datetime().describe('Download URL expiration timestamp'), - checksum: z.string().optional().describe('File checksum (SHA-256)'), - }), -})); -export type GetExportJobDownloadResponse = z.input; -/** Post-parse shape of {@link GetExportJobDownloadResponse} — defaults applied, transforms run (ADR-0122). */ -export type GetExportJobDownloadResponseParsed = z.infer; - -// ========================================== -// 7. List Export Jobs -// ========================================== - -/** - * List Export Jobs Request - * Retrieves a paginated list of historical export jobs. - * - * @example GET /api/v1/data/export?object=account&status=completed&limit=20 - */ -export const ListExportJobsRequestSchema = lazySchema(() => z.object({ - object: z.string().optional().describe('Filter by object name'), - status: ExportJobStatus.optional().describe('Filter by job status'), - limit: z.number().int().min(1).max(100).default(20) - .describe('Maximum number of jobs to return'), - cursor: z.string().optional() - .describe('Pagination cursor from a previous response'), -})); -export type ListExportJobsRequest = z.input; -/** Post-parse shape of {@link ListExportJobsRequest} — defaults applied, transforms run (ADR-0122). */ -export type ListExportJobsRequestParsed = z.infer; - -/** - * Export Job Summary - * Compact representation of an export job for list views. - */ -export const ExportJobSummarySchema = lazySchema(() => z.object({ - jobId: z.string().describe('Export job ID'), - object: z.string().describe('Object name that was exported'), - status: ExportJobStatus.describe('Current job status'), - format: ExportFormat.describe('Export file format'), - totalRecords: z.number().int().optional().describe('Total records exported'), - fileSize: z.number().int().optional().describe('File size in bytes'), - createdAt: z.string().datetime().describe('Job creation timestamp'), - completedAt: z.string().datetime().optional().describe('Completion timestamp'), - createdBy: z.string().optional().describe('User who initiated the export'), -})); -export type ExportJobSummary = z.input; - -/** - * List Export Jobs Response - * Paginated list of export jobs with cursor-based pagination. - */ -export const ListExportJobsResponseSchema = lazySchema(() => BaseResponseSchema.extend({ - data: z.object({ - jobs: z.array(ExportJobSummarySchema).describe('List of export jobs'), - nextCursor: z.string().optional().describe('Cursor for the next page'), - hasMore: z.boolean().describe('Whether more jobs are available'), - }), -})); -export type ListExportJobsResponse = z.input; -/** Post-parse shape of {@link ListExportJobsResponse} — defaults applied, transforms run (ADR-0122). */ -export type ListExportJobsResponseParsed = z.infer; - -// ========================================== -// 8. Schedule Export Request/Response -// ========================================== - -/** - * Schedule Export Request - * Creates a new scheduled (recurring) export job. - * - * @example POST /api/v1/data/export/schedules - */ -export const ScheduleExportRequestSchema = lazySchema(() => z.object({ - name: z.string().regex(/^[a-z_][a-z0-9_]*$/).describe('Schedule name (snake_case)'), - label: z.string().optional().describe('Human-readable label'), - object: z.string().describe('Object name to export'), - format: ExportFormat.default('csv').describe('Export file format'), - fields: z.array(z.string()).optional().describe('Fields to include'), - filter: z.record(z.string(), z.unknown()).optional().describe('Record filter criteria'), - templateId: z.string().optional().describe('Export template ID for field mappings'), - /** - * Schedule timing configuration. - * - * `cronExpression` was DELETED here in @objectstack/spec 17 (ADR-0049 - * enforce-or-remove, #16320): the whole `ExportJobApiContracts` family has zero - * consumers, rest-server serves no `/api/v1/data/export` route and `IExportService` - * has no provider binding, so the cron was parsed and never fired. Deleted outright — - * no `retiredKey()` tombstone, no D2 conversion, no D3 semantic entry (maintainer - * ruling 2026-09-10 on the retirement PR). The mechanism that does work is - * `Job.schedule.expression` (`system/job.zod.ts`), the one cron slot the platform - * evaluates: a recurring export is a job whose handler you write. - */ - schedule: z.object({ - timezone: z.string().default('UTC').describe('IANA timezone'), - }).describe('Schedule timing configuration'), - delivery: z.object({ - method: z.enum(['email', 'storage', 'webhook']) - .describe('How to deliver the export file'), - recipients: z.array(z.string()).optional() - .describe('Email recipients (for email delivery)'), - storagePath: z.string().optional() - .describe('Storage path (for storage delivery)'), - webhookUrl: z.string().optional() - .describe('Webhook URL (for webhook delivery)'), - }).describe('Export delivery configuration'), -})); -export type ScheduleExportRequest = z.input; -/** Post-parse shape of {@link ScheduleExportRequest} — defaults applied, transforms run (ADR-0122). */ -export type ScheduleExportRequestParsed = z.infer; - -/** - * Schedule Export Response - * Returns the created scheduled export with generated ID and next run info. - */ -export const ScheduleExportResponseSchema = lazySchema(() => BaseResponseSchema.extend({ - data: z.object({ - id: z.string().describe('Scheduled export ID'), - name: z.string().describe('Schedule name'), - enabled: z.boolean().describe('Whether the schedule is active'), - nextRunAt: z.string().datetime().optional().describe('Next scheduled execution'), - createdAt: z.string().datetime().describe('Creation timestamp'), - }), -})); -export type ScheduleExportResponse = z.input; -/** Post-parse shape of {@link ScheduleExportResponse} — defaults applied, transforms run (ADR-0122). */ -export type ScheduleExportResponseParsed = z.infer; - -// ========================================== -// 9. Export API Contracts -// ========================================== - -/** - * Export API Contract Registry - * Used for generating SDKs, documentation, and route registration. - */ -export const ExportApiContracts = { - createExportJob: { - method: 'POST' as const, - path: '/api/v1/data/:object/export', - input: CreateExportJobRequestSchema, - output: CreateExportJobResponseSchema, - }, - getExportJobProgress: { - method: 'GET' as const, - path: '/api/v1/data/export/:jobId', - input: z.object({ jobId: z.string() }), - output: ExportJobProgressSchema, - }, - getExportJobDownload: { - method: 'GET' as const, - path: '/api/v1/data/export/:jobId/download', - input: GetExportJobDownloadRequestSchema, - output: GetExportJobDownloadResponseSchema, - }, - listExportJobs: { - method: 'GET' as const, - path: '/api/v1/data/export', - input: ListExportJobsRequestSchema, - output: ListExportJobsResponseSchema, - }, - scheduleExport: { - method: 'POST' as const, - path: '/api/v1/data/export/schedules', - input: ScheduleExportRequestSchema, - output: ScheduleExportResponseSchema, - }, - cancelExportJob: { - method: 'POST' as const, - path: '/api/v1/data/export/:jobId/cancel', - input: z.object({ jobId: z.string() }), - output: BaseResponseSchema, - }, -}; - // ========================================== // 10. Import API Contracts (async jobs) // ========================================== diff --git a/packages/spec/src/automation/execution.test.ts b/packages/spec/src/automation/execution.test.ts index 9b5efa87e1d..d3a852f0312 100644 --- a/packages/spec/src/automation/execution.test.ts +++ b/packages/spec/src/automation/execution.test.ts @@ -7,7 +7,6 @@ import { ExecutionErrorSchema, CheckpointSchema, ConcurrencyPolicySchema, - ScheduleStateSchema, FlowRunSummarySchema, FlowRunNodeSummarySchema, ExecutionStepMetricsSchema, @@ -715,84 +714,3 @@ describe('ConcurrencyPolicySchema', () => { expect(() => ConcurrencyPolicySchema.parse({ maxConcurrent: 0 })).toThrow(); }); }); - -// ========================================== -// Schedule State -// ========================================== - -describe('ScheduleStateSchema', () => { - it('should accept a valid schedule state', () => { - const state = ScheduleStateSchema.parse({ - id: 'sched_001', - flowName: 'daily_report', - // `cronExpression` was deleted outright (#16320) — the strip is pinned in - // `cron-typed-positions-retirement.test.ts`. - timezone: 'America/New_York', - status: 'active', - nextRunAt: '2026-02-03T14:00:00Z', - lastRunAt: '2026-01-31T14:00:00Z', - lastExecutionId: 'exec_100', - lastRunStatus: 'completed', - totalRuns: 42, - consecutiveFailures: 0, - startDate: '2026-01-01T00:00:00Z', - endDate: '2026-12-31T23:59:59Z', - maxRuns: 365, - createdAt: '2025-12-01T00:00:00Z', - updatedAt: '2026-01-31T14:00:05Z', - createdBy: 'user_admin', - }); - expect(state.id).toBe('sched_001'); - expect(state).not.toHaveProperty('cronExpression'); - expect(state.totalRuns).toBe(42); - expect(state.timezone).toBe('America/New_York'); - }); - - it('should apply defaults', () => { - const state = ScheduleStateSchema.parse({ - id: 'sched_002', - flowName: 'weekly_sync', - createdAt: '2026-01-01T00:00:00Z', - }); - expect(state.timezone).toBe('UTC'); - expect(state.status).toBe('active'); - expect(state.totalRuns).toBe(0); - expect(state.consecutiveFailures).toBe(0); - }); - - it('should accept all valid schedule statuses', () => { - const valid = ['active', 'paused', 'disabled', 'expired']; - valid.forEach((v) => { - const state = ScheduleStateSchema.parse({ - id: 'sched_test', - flowName: 'test', - createdAt: '2026-01-01T00:00:00Z', - status: v, - }); - expect(state.status).toBe(v); - }); - }); - - it('should reject missing required fields', () => { - expect(() => ScheduleStateSchema.parse({ - flowName: 'test', - createdAt: '2026-01-01T00:00:00Z', - })).toThrow(); // missing id - - expect(() => ScheduleStateSchema.parse({ - id: 'sched_003', - createdAt: '2026-01-01T00:00:00Z', - })).toThrow(); // missing flowName - - // `cronExpression` was the third required key until #16320 deleted it, so - // the requiredness left with the key: a state without it now PARSES. The - // positive half lives here so the former "missing cronExpression" refusal - // cannot quietly come back; the authored-value strip is pinned in - // `cron-typed-positions-retirement.test.ts`. - expect(() => ScheduleStateSchema.parse({ - id: 'sched_004', - flowName: 'test', - createdAt: '2026-01-01T00:00:00Z', - })).not.toThrow(); - }); -}); diff --git a/packages/spec/src/automation/execution.zod.ts b/packages/spec/src/automation/execution.zod.ts index b74b1b1ec7d..aa8b154b2c6 100644 --- a/packages/spec/src/automation/execution.zod.ts +++ b/packages/spec/src/automation/execution.zod.ts @@ -505,61 +505,22 @@ export const ConcurrencyPolicySchema = lazySchema(() => z.object({ export type ConcurrencyPolicy = z.input; // ========================================== -// 6. Scheduled Execution Persistence +// 6. Scheduled Execution Persistence — RETIRED (#17158) // ========================================== -/** - * Schedule State Schema - * Tracks the runtime state of scheduled flow executions. - * - * Persists next-run times, pause/resume state, and execution history references. +/* + * `ScheduleStateSchema` (and its `ScheduleState` / `ScheduleStateParsed` + * aliases) was DELETED here in @objectstack/spec 17 (ADR-0049 + * enforce-or-remove; ruled with the export-job family on #17158, item 2: + * "retired with the family unless a live consumer is measured" — none was, in + * objectstack, objectui at the pinned sha or cloud). It described the runtime + * state of a scheduled flow — `timezone`, `status`, `nextRunAt` and run + * counters — that no scheduler ever wrote or read; after #16320 deleted its + * required cron it no longer even declared a cadence. Registered in + * `RETIRED_DEFS_BY_MAJOR[18]` as `automation/ScheduleState`. A scheduled flow + * declares its cadence on the flow's start node (`config.schedule`); run + * history is `ExecutionLog` / `FlowRunSummary` above. */ -export const ScheduleStateSchema = lazySchema(() => z.object({ - /** Unique schedule ID */ - id: z.string().describe('Schedule instance ID'), - - /** Flow reference */ - flowName: z.string().describe('Flow machine name'), - - /* - * `cronExpression` was DELETED here in @objectstack/spec 17 (ADR-0049 - * enforce-or-remove, #16320). It was this schema's REQUIRED cron and was read by - * nothing: `ScheduleStateSchema` has no consumer outside `packages/spec`, and the - * schedule trigger that does run reads a flow start node's `config.schedule` - * through `trigger-schedule/schedule-trigger.ts` `normalizeSchedule` — a different - * shape this key never reached. Deleted outright — no `retiredKey()` tombstone, no - * D2 conversion, no D3 semantic entry (maintainer ruling 2026-09-10 on the - * retirement PR). `timezone` / `status` / `nextRunAt` stay: the ruling retires the - * cron position, not the def. A scheduled flow declares its cadence on the flow's - * start node (`config.schedule`); the one cron slot the platform evaluates is - * `Job.schedule.expression` (`system/job.zod.ts`). - */ - timezone: z.string().default('UTC').describe('IANA timezone for cron evaluation'), - - /** Runtime state */ - status: z.enum(['active', 'paused', 'disabled', 'expired']) - .default('active') - .describe('Current schedule status'), - nextRunAt: z.string().datetime().optional().describe('Next scheduled execution timestamp'), - lastRunAt: z.string().datetime().optional().describe('Last execution timestamp'), - lastExecutionId: z.string().optional().describe('Execution ID of the last run'), - lastRunStatus: ExecutionStatus.optional().describe('Status of the last run'), - - /** Execution tracking */ - totalRuns: z.number().int().min(0).default(0).describe('Total number of executions'), - consecutiveFailures: z.number().int().min(0).default(0).describe('Consecutive failed executions'), - - /** Bounds */ - startDate: z.string().datetime().optional().describe('Schedule effective start date'), - endDate: z.string().datetime().optional().describe('Schedule expiration date'), - maxRuns: z.number().int().min(1).optional().describe('Maximum total executions before auto-disable'), - - /** Metadata */ - createdAt: z.string().datetime().describe('Schedule creation timestamp'), - updatedAt: z.string().datetime().optional().describe('Last update timestamp'), - createdBy: z.string().optional().describe('User who created the schedule'), -})); -export type ScheduleState = z.input; // ========================================== // Type Exports @@ -571,4 +532,3 @@ export type FlowRunSummaryParsed = z.infer; export type ExecutionErrorParsed = z.infer; export type CheckpointParsed = z.infer; export type ConcurrencyPolicyParsed = z.infer; -export type ScheduleStateParsed = z.infer; diff --git a/packages/spec/src/contracts/automation-service.ts b/packages/spec/src/contracts/automation-service.ts index 4e5fd3fd473..2b2351d200e 100644 --- a/packages/spec/src/contracts/automation-service.ts +++ b/packages/spec/src/contracts/automation-service.ts @@ -591,8 +591,8 @@ export interface FlowRuntimeState { * One window of execution runs, plus the truncation fact the window alone * cannot carry (#19543). * - * The sibling shape is `ExportJobListResult` (`contracts/export-service.ts`), - * and the difference from it is deliberate: there is ⛔ NO `nextCursor` here. + * Unlike a cursor-paged list shape, and deliberately: there is ⛔ NO + * `nextCursor` here. * Nothing on this door has ever minted a continuation token, the request half * that would have spent one is a retired key, and a `nextCursor` no caller can * send back is the same declared-and-unusable shape #19543 exists to close. diff --git a/packages/spec/src/contracts/export-service.test.ts b/packages/spec/src/contracts/export-service.test.ts deleted file mode 100644 index 55fa38d3f01..00000000000 --- a/packages/spec/src/contracts/export-service.test.ts +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { describe, it, expect } from 'vitest'; -import type { IExportService } from './export-service'; - -describe('Export Service Contract', () => { - it('should allow a minimal IExportService implementation with all required methods', () => { - const service: IExportService = { - createExportJob: async () => ({ - jobId: 'job_001', - status: 'pending', - createdAt: new Date().toISOString(), - }), - getExportJobProgress: async () => null, - getExportJobDownload: async () => null, - cancelExportJob: async () => false, - listExportJobs: async () => ({ jobs: [], hasMore: false }), - scheduleExport: async () => ({ - name: 'test_schedule', - object: 'account', - schedule: { timezone: 'UTC' }, - delivery: { method: 'storage' }, - enabled: true, - }), - }; - - expect(typeof service.createExportJob).toBe('function'); - expect(typeof service.getExportJobProgress).toBe('function'); - expect(typeof service.getExportJobDownload).toBe('function'); - expect(typeof service.cancelExportJob).toBe('function'); - expect(typeof service.listExportJobs).toBe('function'); - expect(typeof service.scheduleExport).toBe('function'); - }); - - it('should create and track an export job', async () => { - const jobs = new Map(); - let counter = 0; - - const service: IExportService = { - createExportJob: async (input) => { - const jobId = `job_${++counter}`; - const job = { - jobId, - status: 'pending' as const, - estimatedRecords: input.limit, - createdAt: new Date().toISOString(), - }; - jobs.set(jobId, { ...job, input }); - return job; - }, - getExportJobProgress: async (jobId) => { - const job = jobs.get(jobId); - if (!job) return null; - return { - success: true, - data: { - jobId, - status: job.status, - format: job.input.format ?? 'csv', - processedRecords: 0, - percentComplete: 0, - }, - }; - }, - getExportJobDownload: async () => null, - cancelExportJob: async (jobId) => { - if (jobs.has(jobId)) { - jobs.get(jobId).status = 'cancelled'; - return true; - } - return false; - }, - listExportJobs: async () => ({ - jobs: Array.from(jobs.values()).map((j) => ({ - jobId: j.jobId, - object: j.input.object, - status: j.status, - format: j.input.format ?? 'csv', - createdAt: j.createdAt, - })), - hasMore: false, - }), - scheduleExport: async () => ({ - name: 'test', - object: 'account', - schedule: { timezone: 'UTC' }, - delivery: { method: 'storage' }, - enabled: true, - }), - }; - - const result = await service.createExportJob({ - object: 'account', - format: 'csv', - fields: ['name', 'email'], - limit: 1000, - }); - - expect(result.jobId).toBe('job_1'); - expect(result.status).toBe('pending'); - - const progress = await service.getExportJobProgress(result.jobId); - expect(progress).not.toBeNull(); - expect(progress!.data.jobId).toBe('job_1'); - - const list = await service.listExportJobs(); - expect(list.jobs).toHaveLength(1); - expect(list.hasMore).toBe(false); - - const cancelled = await service.cancelExportJob(result.jobId); - expect(cancelled).toBe(true); - }); - - it('should support optional template methods', () => { - const service: IExportService = { - createExportJob: async () => ({ - jobId: 'job_1', - status: 'pending', - createdAt: new Date().toISOString(), - }), - getExportJobProgress: async () => null, - getExportJobDownload: async () => null, - cancelExportJob: async () => false, - listExportJobs: async () => ({ jobs: [], hasMore: false }), - scheduleExport: async () => ({ - name: 'test', - object: 'account', - schedule: { timezone: 'UTC' }, - delivery: { method: 'storage' }, - enabled: true, - }), - getTemplate: async () => null, - listTemplates: async () => [], - }; - - expect(typeof service.getTemplate).toBe('function'); - expect(typeof service.listTemplates).toBe('function'); - }); -}); diff --git a/packages/spec/src/contracts/export-service.ts b/packages/spec/src/contracts/export-service.ts deleted file mode 100644 index cf8ec4b9060..00000000000 --- a/packages/spec/src/contracts/export-service.ts +++ /dev/null @@ -1,219 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -/** - * IExportService - Data Export Service Contract - * - * Defines the interface for streaming data export, import validation, - * template management, and scheduled export operations in ObjectStack. - * Concrete implementations (in-memory, database-backed, etc.) - * should implement this interface. - * - * Follows Dependency Inversion Principle - plugins depend on this interface, - * not on concrete export service implementations. - * - * Not a `CoreServiceName` member and not a registered runtime service - * slot — `IExportService` has no evidenced provider binding (see - * core-service-contracts.ts's evidence-bar note). - */ - -import type { - ExportFormat, - ExportJobStatus, - ExportJobProgress, - ExportJobSummary, - ExportImportTemplate, - ScheduledExport, -} from '../api/export.zod'; - -// ========================================== -// Export Job Types -// ========================================== - -/** - * Input for creating a new export job. - */ -export interface CreateExportJobInput { - /** Object name to export */ - object: string; - /** Export file format */ - format?: ExportFormat; - /** Specific fields to include (omit for all fields) */ - fields?: string[]; - /** Filter criteria for records to export */ - filter?: Record; - /** Sort order for exported records */ - sort?: Array<{ field: string; direction?: 'asc' | 'desc' }>; - /** Maximum number of records to export */ - limit?: number; - /** Include header row (CSV/XLSX) */ - includeHeaders?: boolean; - /** Character encoding */ - encoding?: string; - /** Export template ID for predefined field mappings */ - templateId?: string; -} - -/** - * Result of creating an export job. - */ -export interface CreateExportJobResult { - /** Export job ID */ - jobId: string; - /** Initial job status */ - status: ExportJobStatus; - /** Estimated total records */ - estimatedRecords?: number; - /** Job creation timestamp (ISO 8601) */ - createdAt: string; -} - -/** - * Download link info for a completed export job. - */ -export interface ExportJobDownload { - /** Export job ID */ - jobId: string; - /** Presigned download URL */ - downloadUrl: string; - /** Suggested file name */ - fileName: string; - /** File size in bytes */ - fileSize: number; - /** Export file format */ - format: ExportFormat; - /** Download URL expiration timestamp (ISO 8601) */ - expiresAt: string; - /** File checksum (SHA-256) */ - checksum?: string; -} - -/** - * Options for listing export jobs. - */ -export interface ListExportJobsOptions { - /** Filter by object name */ - object?: string; - /** Filter by job status */ - status?: ExportJobStatus; - /** Maximum number of jobs to return */ - limit?: number; - /** Pagination cursor */ - cursor?: string; -} - -/** - * Paginated list of export jobs. - */ -export interface ExportJobListResult { - /** Export job summaries */ - jobs: ExportJobSummary[]; - /** Cursor for the next page */ - nextCursor?: string; - /** Whether more jobs are available */ - hasMore: boolean; -} - -/** - * Input for scheduling a recurring export. - */ -export interface ScheduleExportInput { - /** Schedule name (snake_case) */ - name: string; - /** Human-readable label */ - label?: string; - /** Object name to export */ - object: string; - /** Export file format */ - format?: ExportFormat; - /** Fields to include */ - fields?: string[]; - /** Record filter criteria */ - filter?: Record; - /** Export template ID */ - templateId?: string; - /** - * Schedule timing configuration. `cronExpression` left this block with the spec - * positions it mirrored (`ScheduleExportRequest.schedule.cronExpression` / - * `ScheduledExport.schedule.cronExpression`, both DELETED under ADR-0049, #16320): - * the return type below no longer carries the key, so an input that still demanded - * it would ask the provider for a cadence it cannot store. - */ - schedule: { - timezone?: string; - }; - /** Export delivery configuration */ - delivery: { - method: 'email' | 'storage' | 'webhook'; - recipients?: string[]; - storagePath?: string; - webhookUrl?: string; - }; -} - -// ========================================== -// Service Interface -// ========================================== - -export interface IExportService { - // ---- Export Jobs ---- - - /** - * Create a new export job. - * @param input - Export job configuration - * @returns The created export job with tracking info - */ - createExportJob(input: CreateExportJobInput): Promise; - - /** - * Get the progress/status of an export job. - * @param jobId - Export job ID - * @returns Export job progress, or null if not found - */ - getExportJobProgress(jobId: string): Promise; - - /** - * Get the download link for a completed export job. - * @param jobId - Export job ID - * @returns Download link info, or null if not available - */ - getExportJobDownload(jobId: string): Promise; - - /** - * Cancel an in-progress export job. - * @param jobId - Export job ID - * @returns Whether the job was successfully cancelled - */ - cancelExportJob(jobId: string): Promise; - - /** - * List export jobs with optional filtering and pagination. - * @param options - Filter and pagination options - * @returns Paginated list of export jobs - */ - listExportJobs(options?: ListExportJobsOptions): Promise; - - // ---- Scheduled Exports ---- - - /** - * Create a scheduled recurring export. - * @param input - Schedule configuration - * @returns The created scheduled export - */ - scheduleExport(input: ScheduleExportInput): Promise; - - // ---- Templates (optional) ---- - - /** - * Get an export/import template by ID. - * @param templateId - Template ID - * @returns The template, or null if not found - */ - getTemplate?(templateId: string): Promise; - - /** - * List available export/import templates. - * @param object - Optional object filter - * @returns Array of templates - */ - listTemplates?(object?: string): Promise; -} diff --git a/packages/spec/src/contracts/index.ts b/packages/spec/src/contracts/index.ts index 2eeb795b01b..580d48bac47 100644 --- a/packages/spec/src/contracts/index.ts +++ b/packages/spec/src/contracts/index.ts @@ -45,7 +45,10 @@ export * from './i18n-service.js'; // contract instead of `any`, so a call outside it is a compile error. export * from './core-service-contracts.js'; -export * from './export-service.js'; +// './export-service.js' (IExportService and its six types, including +// ScheduleExportInput) removed (#17158): the export-job API family it served had +// no route, no provider binding and no reader in any repo. The export that is +// served is the synchronous `GET /api/v1/data/:object/export` door. export * from './email-service.js'; export * from './sms-service.js'; export * from './security-service.js'; diff --git a/packages/spec/src/cron-typed-positions-retirement.test.ts b/packages/spec/src/cron-typed-positions-retirement.test.ts index 1f4a96f9d88..c6299684a3f 100644 --- a/packages/spec/src/cron-typed-positions-retirement.test.ts +++ b/packages/spec/src/cron-typed-positions-retirement.test.ts @@ -3,8 +3,6 @@ import { describe, expect, it } from 'vitest'; import type { ZodTypeAny } from 'zod'; -import { ScheduledExportSchema, ScheduleExportRequestSchema, type ScheduledExport, type ScheduleExportRequest } from './api/export.zod'; -import { ScheduleStateSchema, type ScheduleState } from './automation/execution.zod'; import { CONVERSIONS_BY_MAJOR } from './conversions/registry'; import { ConnectorSchema, @@ -14,7 +12,7 @@ import { type DataSyncConfig, } from './integration/connector.zod'; import { getMetadataTypeSchema } from './kernel/metadata-type-schemas'; -import { MIGRATIONS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR } from './migrations/registry'; +import { MIGRATIONS_BY_MAJOR, RETIRED_DEFS_BY_MAJOR, RETIRED_KEYS_BY_MAJOR } from './migrations/registry'; import { CacheWarmupSchema, DistributedCacheConfigSchema, type CacheWarmup, type DistributedCacheConfig } from './system/cache.zod'; import { BackupConfigSchema, @@ -64,6 +62,18 @@ import { // ⇒ ⛔ Do not read these pins as "the author is never told". They pin the schema // layer. The author-facing loss is louder than a bare `safeParse` suggests, and // it is louder than the ruling comment's cost statement assumed. +// +// ─── [#17158] three of the seven left WITH THEIR DEFS ───────────────────────── +// +// `ScheduledExport.schedule.cronExpression`, `ScheduleExportRequest.schedule.cronExpression` +// and `ScheduleState.cronExpression` sat on defs that were themselves declared +// and unserved: the export-job API family and `ScheduleState` were retired +// WHOLE in the same major (ADR-0049, maintainer ruling A on #17158), so those +// three schemas no longer exist to strip anything. Their strip pins are gone +// with them; `LEFT_WITH_THEIR_DEFS` below keeps the two facts that still +// hold — no key-level registration was ever made for the three positions, and +// each enclosing def is now a `RETIRED_DEFS_BY_MAJOR[18]` entry. The four +// positions on live schemas are pinned exactly as before. const CRON = '0 6 * * MON'; /** The envelope the old schema normalized the bare string into — dropped just the same. */ @@ -71,13 +81,6 @@ const CRON_ENVELOPE = { dialect: 'cron', source: CRON }; // ── Well-formed fixtures: every required key, none of the deleted ones ────── -const EXPORT_WELL_FORMED = { - name: 'weekly_account_export', - object: 'account', - schedule: { timezone: 'America/New_York' }, - delivery: { method: 'email' as const, recipients: ['admin@example.com'] }, -}; -const STATE_WELL_FORMED = { id: 'sched_001', flowName: 'daily_report', createdAt: '2026-01-01T00:00:00Z' }; const SYNC_WELL_FORMED = { strategy: 'incremental' as const, direction: 'bidirectional' as const, batchSize: 500 }; const CONNECTOR_WELL_FORMED = { name: 'sap_erp', label: 'SAP ERP', type: 'saas' as const, syncConfig: SYNC_WELL_FORMED }; // [#17157] was `strategy: 'scheduled'` — that enum member was itself retired one card @@ -112,31 +115,18 @@ interface DeletedSite { keyPath: (string | number)[]; } +/** + * [#17158] The three positions whose enclosing def retired whole — the key-level + * spelling (never registered) and the def-level entry (now registered). + */ +const LEFT_WITH_THEIR_DEFS = [ + { registered: 'api/ScheduledExport:schedule.cronExpression', def: 'api/ScheduledExport' }, + { registered: 'api/ScheduleExportRequest:schedule.cronExpression', def: 'api/ScheduleExportRequest' }, + { registered: 'automation/ScheduleState:cronExpression', def: 'automation/ScheduleState' }, +] as const; + +/** The four positions whose schemas are still published. */ const SITES: DeletedSite[] = [ - { - registered: 'api/ScheduledExport:schedule.cronExpression', - qualified: 'ScheduledExport.schedule.cronExpression', - schema: ScheduledExportSchema, - wellFormed: EXPORT_WELL_FORMED, - authored: { ...EXPORT_WELL_FORMED, schedule: { ...EXPORT_WELL_FORMED.schedule, cronExpression: CRON } }, - keyPath: ['schedule', 'cronExpression'], - }, - { - registered: 'api/ScheduleExportRequest:schedule.cronExpression', - qualified: 'ScheduleExportRequest.schedule.cronExpression', - schema: ScheduleExportRequestSchema, - wellFormed: EXPORT_WELL_FORMED, - authored: { ...EXPORT_WELL_FORMED, schedule: { ...EXPORT_WELL_FORMED.schedule, cronExpression: CRON } }, - keyPath: ['schedule', 'cronExpression'], - }, - { - registered: 'automation/ScheduleState:cronExpression', - qualified: 'ScheduleState.cronExpression', - schema: ScheduleStateSchema, - wellFormed: STATE_WELL_FORMED, - authored: { ...STATE_WELL_FORMED, cronExpression: CRON }, - keyPath: ['cronExpression'], - }, { registered: 'integration/DataSyncConfig:schedule', qualified: 'connector.syncConfig.schedule', @@ -224,7 +214,7 @@ function readAt(doc: unknown, keyPath: (string | number)[]): { block: Record, leaf: String(keyPath[keyPath.length - 1]) }; } -describe('[#16320] the seven cron-typed positions no longer exist on their schemas', () => { +describe('[#16320] the four surviving cron-typed positions no longer exist on their schemas', () => { for (const site of SITES) { it(`\`${site.qualified}\` is gone — an authored value is accepted and STRIPPED, never materialized`, () => { const parsed = site.schema.safeParse(site.authored); @@ -241,9 +231,14 @@ describe('[#16320] the seven cron-typed positions no longer exist on their schem } it('the envelope spelling is dropped too — both shapes the old schema accepted are gone', () => { + const site = (qualified: string): DeletedSite => { + const found = SITES.find((s) => s.qualified === qualified); + expect(found, `no site named ${qualified}`).toBeDefined(); + return found!; + }; const envelopeSites: Array<[DeletedSite, unknown]> = [ - [SITES[3]!, { ...SYNC_WELL_FORMED, schedule: CRON_ENVELOPE }], - [SITES[0]!, { ...EXPORT_WELL_FORMED, schedule: { ...EXPORT_WELL_FORMED.schedule, cronExpression: CRON_ENVELOPE } }], + [site('connector.syncConfig.schedule'), { ...SYNC_WELL_FORMED, schedule: CRON_ENVELOPE }], + [site('CacheWarmup.schedule'), { ...WARMUP_WELL_FORMED, schedule: CRON_ENVELOPE }], ]; for (const [site, authored] of envelopeSites) { const parsed = site.schema.safeParse(authored); @@ -267,22 +262,10 @@ describe('[#16320] the seven cron-typed positions no longer exist on their schem } it('the surviving keys still materialize — the absences above are the deletions, not a dead parse', () => { - expect(ScheduledExportSchema.parse(EXPORT_WELL_FORMED).schedule.timezone).toBe('America/New_York'); - expect(ScheduleExportRequestSchema.parse({ ...EXPORT_WELL_FORMED, schedule: {} }).schedule.timezone).toBe('UTC'); - expect(ScheduleStateSchema.parse(STATE_WELL_FORMED).timezone).toBe('UTC'); expect(DataSyncConfigSchema.parse(SYNC_WELL_FORMED).realtimeSync).toBe(false); expect(CacheWarmupSchema.parse(WARMUP_WELL_FORMED).concurrency).toBe(10); expect(BackupConfigSchema.parse(BACKUP_WELL_FORMED).verifyAfterBackup).toBe(true); }); - - it('`ScheduleState.cronExpression` was REQUIRED — the requiredness left with the key', () => { - const parsed = ScheduleStateSchema.parse(STATE_WELL_FORMED); - expect(parsed.status).toBe('active'); - expect(parsed.timezone).toBe('UTC'); - // The other required keys are still required — the requiredness that left - // is exactly the deleted key's. - expect(ScheduleStateSchema.safeParse({ id: 'sched_002', createdAt: '2026-01-01T00:00:00Z' }).success).toBe(false); - }); }); describe('[#16320] the one manifest-reachable position — what an upgrading stack actually gets', () => { @@ -328,23 +311,8 @@ describe('[#16320] the one manifest-reachable position — what an upgrading sta }); }); -describe('[#16320] the tsc channel: the seven keys are not in their input types', () => { +describe('[#16320] the tsc channel: the four surviving keys are not in their input types', () => { it('fails tsc at every authoring site', () => { - const sched: ScheduledExport = { - ...EXPORT_WELL_FORMED, - // @ts-expect-error — `schedule.cronExpression` was deleted; it is not a key of this type. - schedule: { ...EXPORT_WELL_FORMED.schedule, cronExpression: CRON }, - }; - const request: ScheduleExportRequest = { - ...EXPORT_WELL_FORMED, - // @ts-expect-error — the request body's twin position, deleted with it. - schedule: { ...EXPORT_WELL_FORMED.schedule, cronExpression: CRON }, - }; - const state: ScheduleState = { - ...STATE_WELL_FORMED, - // @ts-expect-error — `cronExpression` was deleted (and was required before). - cronExpression: CRON, - }; const sync: DataSyncConfig = { ...SYNC_WELL_FORMED, // @ts-expect-error — `schedule` was deleted. @@ -379,9 +347,6 @@ describe('[#16320] the tsc channel: the seven keys are not in their input types' // key, which is what keeps this case from being vacuous — and is precisely // why the tsc channel is the ONLY loud one the bare deletion leaves. for (const [schema, value, keyPath] of [ - [ScheduledExportSchema, sched, ['schedule', 'cronExpression']], - [ScheduleExportRequestSchema, request, ['schedule', 'cronExpression']], - [ScheduleStateSchema, state, ['cronExpression']], [DataSyncConfigSchema, sync, ['schedule']], [ConnectorSchema, connector, ['syncConfig', 'schedule']], [CacheWarmupSchema, warmup, ['schedule']], @@ -399,10 +364,20 @@ describe('[#16320] the tsc channel: the seven keys are not in their input types' }); describe('[#16320] 直接删 — the ADR-0087 surfaces carry NOTHING for these seven', () => { + it('[#17158] the three positions that left with their defs are covered at DEF grain, not key grain', () => { + const retiredDefs18 = new Set(RETIRED_DEFS_BY_MAJOR[18] ?? []); + for (const gone of LEFT_WITH_THEIR_DEFS) { + expect(retiredDefs18.has(gone.def), `${gone.def} must be a RETIRED_DEFS_BY_MAJOR[18] entry`).toBe(true); + } + // Dark control — a def that was never retired reads absent. + expect(retiredDefs18.has('integration/DataSyncConfig')).toBe(false); + }); + const registered = new Set(Object.values(RETIRED_KEYS_BY_MAJOR).flatMap((keys) => [...keys])); it('no `RETIRED_KEYS_BY_MAJOR` entry names any of the seven, at any major', () => { for (const site of SITES) expect(registered.has(site.registered), site.registered).toBe(false); + for (const gone of LEFT_WITH_THEIR_DEFS) expect(registered.has(gone.registered), gone.registered).toBe(false); // Lit control — the table is populated and this reader can see it. A key // retired the tombstone way on the very same connector schema. expect(registered.has('integration/Connector:errorMapping')).toBe(true); diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__CreateExportJobRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__CreateExportJobRequest.ts new file mode 100644 index 00000000000..65a545df8f4 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__CreateExportJobRequest.ts @@ -0,0 +1,13 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/CreateExportJobRequest`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the request body of `POST /api/v1/data/:object/export` as an asynchronous job. +// That method and path are served by nothing; the served export is the +// synchronous `GET` on the same path. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/CreateExportJobRequest'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__CreateExportJobResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__CreateExportJobResponse.ts new file mode 100644 index 00000000000..c096d1a2b52 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__CreateExportJobResponse.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/CreateExportJobResponse`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the `{ jobId, status, estimatedRecords, createdAt }` answer of the unserved +// asynchronous export-job create. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/CreateExportJobResponse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobProgress.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobProgress.ts new file mode 100644 index 00000000000..ebe344cb5e1 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobProgress.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ExportJobProgress`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the progress body of `GET /api/v1/data/export/:jobId`, a route no package +// mounts. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ExportJobProgress'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobStatus.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobStatus.ts new file mode 100644 index 00000000000..938317200b5 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobStatus.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ExportJobStatus`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the job-status enum (`pending` … `expired`) of the export-job API family. No +// job store, worker or route ever set or read one. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ExportJobStatus'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobSummary.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobSummary.ts new file mode 100644 index 00000000000..eee4206bcd1 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ExportJobSummary.ts @@ -0,0 +1,11 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ExportJobSummary`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the row shape of the unserved export-job list. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ExportJobSummary'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__GetExportJobDownloadRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__GetExportJobDownloadRequest.ts new file mode 100644 index 00000000000..bb078d3aa68 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__GetExportJobDownloadRequest.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/GetExportJobDownloadRequest`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the request of `GET /api/v1/data/export/:jobId/download`, a route no package +// mounts. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/GetExportJobDownloadRequest'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__GetExportJobDownloadResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__GetExportJobDownloadResponse.ts new file mode 100644 index 00000000000..7833f8b52ee --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__GetExportJobDownloadResponse.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/GetExportJobDownloadResponse`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the presigned-link answer of `GET /api/v1/data/export/:jobId/download`, a +// route no package mounts. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/GetExportJobDownloadResponse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ListExportJobsRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ListExportJobsRequest.ts new file mode 100644 index 00000000000..06863a7563d --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ListExportJobsRequest.ts @@ -0,0 +1,13 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ListExportJobsRequest`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the query of `GET /api/v1/data/export` (`object`, `status`, `limit` default +// 20, `cursor`), a route no package mounts; the `limit` / `cursor` pair (#19543 +// door ②, absorbed into #17158) had no reader to spend it. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ListExportJobsRequest'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ListExportJobsResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ListExportJobsResponse.ts new file mode 100644 index 00000000000..b010fe77ffe --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ListExportJobsResponse.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ListExportJobsResponse`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the `{ jobs, nextCursor, hasMore }` answer of `GET /api/v1/data/export`, a +// route no package mounts. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ListExportJobsResponse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduleExportRequest.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduleExportRequest.ts new file mode 100644 index 00000000000..541261fd963 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduleExportRequest.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ScheduleExportRequest`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the request body of `POST /api/v1/data/export/schedules`, a route no package +// mounts; its `schedule` block held only `timezone` after #16320. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ScheduleExportRequest'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduleExportResponse.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduleExportResponse.ts new file mode 100644 index 00000000000..e0630c70081 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduleExportResponse.ts @@ -0,0 +1,11 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ScheduleExportResponse`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the answer of `POST /api/v1/data/export/schedules`, a route no package mounts. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ScheduleExportResponse'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduledExport.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduledExport.ts new file mode 100644 index 00000000000..0613ff9fced --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__ScheduledExport.ts @@ -0,0 +1,13 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `api/ScheduledExport`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// a recurring export definition (`schedule`, `delivery`, `nextRunAt`). #16320 +// had already deleted its `schedule.cronExpression`, leaving a required +// `schedule` block that could hold no schedule; no scheduler ever read it. +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'api/ScheduledExport'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.automation__ScheduleState.ts b/packages/spec/src/migrations/entries/retired-defs/18.automation__ScheduleState.ts new file mode 100644 index 00000000000..94271cf4e22 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.automation__ScheduleState.ts @@ -0,0 +1,15 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #17158 — `automation/ScheduleState`, retired whole with the export-job API family +// (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui +// retired its side first in objectui#10247). It declared +// the runtime state of a scheduled flow (`timezone`, `status`, `nextRunAt`, run +// counters). No scheduler ever wrote or read one, and after #16320 deleted its +// required cron it no longer declared a cadence. Retired with the family under +// ruling item 2 ("unless a live consumer is measured" — none was, in +// objectstack, objectui at the pin, or cloud). +// Zero readers in objectstack, in objectui at the pinned sha, and in cloud. +// No carrier key and no authored document, so no tombstone and no D2 +// conversion — this table plus the D3 semantic entry +// `export-job-family-retired` are the declaration. +export const entry = 'automation/ScheduleState'; diff --git a/packages/spec/src/migrations/entries/semantic/18.export-job-family-retired.ts b/packages/spec/src/migrations/entries/semantic/18.export-job-family-retired.ts new file mode 100644 index 00000000000..56feae04b1f --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.export-job-family-retired.ts @@ -0,0 +1,76 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'export-job-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the export-job API family, retired whole: the twelve defs api/ExportJobStatus, ' + + 'api/CreateExportJobRequest, api/CreateExportJobResponse, api/ExportJobProgress, ' + + 'api/ScheduledExport, api/GetExportJobDownloadRequest, api/GetExportJobDownloadResponse, ' + + 'api/ListExportJobsRequest, api/ExportJobSummary, api/ListExportJobsResponse, ' + + 'api/ScheduleExportRequest and api/ScheduleExportResponse with every name ' + + 'api/export.zod.ts exported for them from @objectstack/spec/api (the Schema consts, ' + + 'their z.input aliases and their Parsed aliases) and the ExportApiContracts route map; ' + + 'the IExportService contract with its six types (CreateExportJobInput, ' + + 'CreateExportJobResult, ExportJobDownload, ListExportJobsOptions, ExportJobListResult, ' + + 'ScheduleExportInput) from @objectstack/spec/contracts; and automation/ScheduleState ' + + '(ScheduleStateSchema, ScheduleState, ScheduleStateParsed) from @objectstack/spec/automation', + replacement: + 'nothing to re-declare for the job family — no route ever served it, so no caller holds ' + + 'a job id, a progress body or a download link to carry over. The export the platform ' + + 'DOES serve is the synchronous streaming door GET /api/v1/data/:object/export ' + + '(@objectstack/rest, the SDK method data.export): it answers the file itself as CSV, ' + + 'JSON or XLSX. ExportFormat stays published (ExportImportTemplate still references it). ' + + 'A recurring export is a Job (system/job.zod.ts) whose handler you write, with its ' + + 'cadence on Job.schedule.expression — the one cron slot the platform evaluates. A ' + + 'scheduled flow declares its cadence on its start node (config.schedule), and its run ' + + 'history is ExecutionLog / FlowRunSummary; ScheduleState had no counterpart to point at ' + + 'because no scheduler ever kept one. The import-job family in the same module ' + + '(ImportJob…, ListImportJobs…, ImportJobApiContracts) is served and is NOT part of this ' + + 'retirement', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling A on #17158 (decision batch #122 item 3, ' + + '「同意」 2026-09-12: retire the family, IExportService and ScheduleExportInput; ScheduleState ' + + 'retired with it unless a live consumer is measured), landing route A (decision batch #221 ' + + 'item 2, 「同意」 2026-09-24: objectui retires its side first, in objectui#10247, then this ' + + 'retirement), and a scope note (「同意」 2026-09-25) that absorbs #19543 door ② — the ' + + 'declared limit / cursor of the export-job list — into it. The family declared an ' + + 'asynchronous export API, create / progress / download / list / schedule / cancel under ' + + '/api/v1/data/export and a POST on /api/v1/data/:object/export, that NOTHING served: ' + + '@objectstack/rest mounts no /api/v1/data/export route and only the GET on ' + + '/api/v1/data/:object/export, IExportService recorded no evidenced provider binding, and ' + + 'the reader census over objectstack outside packages/spec, over objectui at the pinned ' + + 'sha (which carries objectui#10247) and over cloud main returned zero code files naming ' + + 'any of the forty-three exported names, each beside a lit control. An AI reading the ' + + 'contract found a complete, well-typed export-job API and wrote calls that answer 404 — ' + + 'and after #16320 deleted its cron positions, ScheduledExport / ScheduleExportRequest ' + + 'kept a REQUIRED schedule block that could hold no schedule, so an author who filled in ' + + 'its timezone believed they had scheduled something. ScheduleState described the ' + + 'runtime state of a scheduled flow that no scheduler wrote or read. Why D3 semantic and ' + + 'not a D2 conversion: the chain walks a normalized STACK and applyConversionsToStoredItem ' + + 'maps a metadata type onto one of its collections; none of these shapes is either — they ' + + 'are HTTP bodies, a route map, a service interface and an unpersisted runtime record — so ' + + 'a conversion would be a transform with no seam that ever runs, and with no carrier key ' + + 'there is no shape on which a tombstone could sit. The #16320 cron-position deletions ' + + 'on three of these defs registered nothing and stay unregistered; the defs themselves are ' + + 'now the RETIRED_DEFS_BY_MAJOR[18] entries.', + acceptanceCriteria: + 'No code imports any of the twelve export-job Schema consts or their type aliases from ' + + '@objectstack/spec or @objectstack/spec/api, reads ExportApiContracts, implements or ' + + 'imports IExportService or its six types from @objectstack/spec/contracts, or imports ' + + 'ScheduleStateSchema / ScheduleState / ScheduleStateParsed from ' + + '@objectstack/spec/automation: every such import is TS2305 after upgrade, and there is no ' + + 'working replacement to point at because nothing ever served them. The thirteen defs are ' + + 'absent from json-schema.manifest/api.json and json-schema.manifest/automation.json, from ' + + 'the api-surface / declaration-map / export-origins shards and from the generated ' + + 'reference docs. ExportFormat, ExportImportTemplate, the import validation shapes and ' + + 'the whole import-job family (including ImportJobApiContracts) are unaffected. ⚠️ Runtime ' + + 'behaviour is deliberately UNCHANGED and must be verified as such: GET ' + + '/api/v1/data/:object/export answers exactly as before, and every request to a retired ' + + 'path answers exactly as it always did, because nothing ever mounted one. ⚠️ Readers ' + + 'outside objectstack, objectui and cloud are NOT MEASURED — @objectstack/spec is ' + + 'published.', +}; diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index efdd597234b..d031c11e0b7 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -8367,6 +8367,78 @@ const step18: MigrationStep = { + 'legacy branch index only when the record predates the engine build that ' + 'writes `branch`.', }, + { + id: 'export-job-family-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'the export-job API family, retired whole: the twelve defs api/ExportJobStatus, ' + + 'api/CreateExportJobRequest, api/CreateExportJobResponse, api/ExportJobProgress, ' + + 'api/ScheduledExport, api/GetExportJobDownloadRequest, api/GetExportJobDownloadResponse, ' + + 'api/ListExportJobsRequest, api/ExportJobSummary, api/ListExportJobsResponse, ' + + 'api/ScheduleExportRequest and api/ScheduleExportResponse with every name ' + + 'api/export.zod.ts exported for them from @objectstack/spec/api (the Schema consts, ' + + 'their z.input aliases and their Parsed aliases) and the ExportApiContracts route map; ' + + 'the IExportService contract with its six types (CreateExportJobInput, ' + + 'CreateExportJobResult, ExportJobDownload, ListExportJobsOptions, ExportJobListResult, ' + + 'ScheduleExportInput) from @objectstack/spec/contracts; and automation/ScheduleState ' + + '(ScheduleStateSchema, ScheduleState, ScheduleStateParsed) from @objectstack/spec/automation', + replacement: + 'nothing to re-declare for the job family — no route ever served it, so no caller holds ' + + 'a job id, a progress body or a download link to carry over. The export the platform ' + + 'DOES serve is the synchronous streaming door GET /api/v1/data/:object/export ' + + '(@objectstack/rest, the SDK method data.export): it answers the file itself as CSV, ' + + 'JSON or XLSX. ExportFormat stays published (ExportImportTemplate still references it). ' + + 'A recurring export is a Job (system/job.zod.ts) whose handler you write, with its ' + + 'cadence on Job.schedule.expression — the one cron slot the platform evaluates. A ' + + 'scheduled flow declares its cadence on its start node (config.schedule), and its run ' + + 'history is ExecutionLog / FlowRunSummary; ScheduleState had no counterpart to point at ' + + 'because no scheduler ever kept one. The import-job family in the same module ' + + '(ImportJob…, ListImportJobs…, ImportJobApiContracts) is served and is NOT part of this ' + + 'retirement', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling A on #17158 (decision batch #122 item 3, ' + + '「同意」 2026-09-12: retire the family, IExportService and ScheduleExportInput; ScheduleState ' + + 'retired with it unless a live consumer is measured), landing route A (decision batch #221 ' + + 'item 2, 「同意」 2026-09-24: objectui retires its side first, in objectui#10247, then this ' + + 'retirement), and a scope note (「同意」 2026-09-25) that absorbs #19543 door ② — the ' + + 'declared limit / cursor of the export-job list — into it. The family declared an ' + + 'asynchronous export API, create / progress / download / list / schedule / cancel under ' + + '/api/v1/data/export and a POST on /api/v1/data/:object/export, that NOTHING served: ' + + '@objectstack/rest mounts no /api/v1/data/export route and only the GET on ' + + '/api/v1/data/:object/export, IExportService recorded no evidenced provider binding, and ' + + 'the reader census over objectstack outside packages/spec, over objectui at the pinned ' + + 'sha (which carries objectui#10247) and over cloud main returned zero code files naming ' + + 'any of the forty-three exported names, each beside a lit control. An AI reading the ' + + 'contract found a complete, well-typed export-job API and wrote calls that answer 404 — ' + + 'and after #16320 deleted its cron positions, ScheduledExport / ScheduleExportRequest ' + + 'kept a REQUIRED schedule block that could hold no schedule, so an author who filled in ' + + 'its timezone believed they had scheduled something. ScheduleState described the ' + + 'runtime state of a scheduled flow that no scheduler wrote or read. Why D3 semantic and ' + + 'not a D2 conversion: the chain walks a normalized STACK and applyConversionsToStoredItem ' + + 'maps a metadata type onto one of its collections; none of these shapes is either — they ' + + 'are HTTP bodies, a route map, a service interface and an unpersisted runtime record — so ' + + 'a conversion would be a transform with no seam that ever runs, and with no carrier key ' + + 'there is no shape on which a tombstone could sit. The #16320 cron-position deletions ' + + 'on three of these defs registered nothing and stay unregistered; the defs themselves are ' + + 'now the RETIRED_DEFS_BY_MAJOR[18] entries.', + acceptanceCriteria: + 'No code imports any of the twelve export-job Schema consts or their type aliases from ' + + '@objectstack/spec or @objectstack/spec/api, reads ExportApiContracts, implements or ' + + 'imports IExportService or its six types from @objectstack/spec/contracts, or imports ' + + 'ScheduleStateSchema / ScheduleState / ScheduleStateParsed from ' + + '@objectstack/spec/automation: every such import is TS2305 after upgrade, and there is no ' + + 'working replacement to point at because nothing ever served them. The thirteen defs are ' + + 'absent from json-schema.manifest/api.json and json-schema.manifest/automation.json, from ' + + 'the api-surface / declaration-map / export-origins shards and from the generated ' + + 'reference docs. ExportFormat, ExportImportTemplate, the import validation shapes and ' + + 'the whole import-job family (including ImportJobApiContracts) are unaffected. ⚠️ Runtime ' + + 'behaviour is deliberately UNCHANGED and must be verified as such: GET ' + + '/api/v1/data/:object/export answers exactly as before, and every request to a retired ' + + 'path answers exactly as it always did, because nothing ever mounted one. ⚠️ Readers ' + + 'outside objectstack, objectui and cloud are NOT MEASURED — @objectstack/spec is ' + + 'published.', + }, { id: 'field-currency-scale-refused', surface: 'object.fields..scale on a field whose `type` is `currency` — any declared value, ' @@ -18112,6 +18184,27 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // entry id by `gen:migration-registry` (#7297). Add an entry by adding a // FILE — never by editing between the markers, which is generated. // + // #17158 — `api/CreateExportJobRequest`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the request body of `POST /api/v1/data/:object/export` as an asynchronous job. + // That method and path are served by nothing; the served export is the + // synchronous `GET` on the same path. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/CreateExportJobRequest', + // #17158 — `api/CreateExportJobResponse`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the `{ jobId, status, estimatedRecords, createdAt }` answer of the unserved + // asynchronous export-job create. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/CreateExportJobResponse', // #14691 — `api/CrudEndpointPattern` (the `{ method, path, summary, description }` // value shape of `crud.patterns`) leaves with its carrier key: its ONLY consumer // was `CrudEndpointsConfigSchema.patterns`, tombstoned in the same change under @@ -18120,6 +18213,55 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // `GeneratedEndpointSchema.operation` still reads it. See // `retired-keys/18.api__CrudEndpointsConfig__patterns.ts` for the retirement record. 'api/CrudEndpointPattern', + // #17158 — `api/ExportJobProgress`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the progress body of `GET /api/v1/data/export/:jobId`, a route no package + // mounts. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ExportJobProgress', + // #17158 — `api/ExportJobStatus`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the job-status enum (`pending` … `expired`) of the export-job API family. No + // job store, worker or route ever set or read one. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ExportJobStatus', + // #17158 — `api/ExportJobSummary`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the row shape of the unserved export-job list. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ExportJobSummary', + // #17158 — `api/GetExportJobDownloadRequest`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the request of `GET /api/v1/data/export/:jobId/download`, a route no package + // mounts. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/GetExportJobDownloadRequest', + // #17158 — `api/GetExportJobDownloadResponse`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the presigned-link answer of `GET /api/v1/data/export/:jobId/download`, a + // route no package mounts. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/GetExportJobDownloadResponse', // #13823 — `api/HandlerStatus` (the `implemented` / `stub` / `planned` enum) // left with its two carriers: `RestApiEndpoint.handlerStatus` is tombstoned // in this same major (`RETIRED_KEYS_BY_MAJOR[18]`) and @@ -18130,6 +18272,27 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // objectstack, objectui (pinned sha) or cloud. See // `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. 'api/HandlerStatus', + // #17158 — `api/ListExportJobsRequest`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the query of `GET /api/v1/data/export` (`object`, `status`, `limit` default + // 20, `cursor`), a route no package mounts; the `limit` / `cursor` pair (#19543 + // door ②, absorbed into #17158) had no reader to spend it. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ListExportJobsRequest', + // #17158 — `api/ListExportJobsResponse`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the `{ jobs, nextCursor, hasMore }` answer of `GET /api/v1/data/export`, a + // route no package mounts. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ListExportJobsResponse', // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes // the widened surface). Part of the whole-module removal of @@ -18243,6 +18406,49 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // (`packages/runtime/src/route-ledger.ts`). See // `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. 'api/RouteCoverageReport', + // #17158 — `api/ScheduleExportRequest`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the request body of `POST /api/v1/data/export/schedules`, a route no package + // mounts; its `schedule` block held only `timezone` after #16320. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ScheduleExportRequest', + // #17158 — `api/ScheduleExportResponse`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the answer of `POST /api/v1/data/export/schedules`, a route no package mounts. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ScheduleExportResponse', + // #17158 — `api/ScheduledExport`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // a recurring export definition (`schedule`, `delivery`, `nextRunAt`). #16320 + // had already deleted its `schedule.cronExpression`, leaving a required + // `schedule` block that could hold no schedule; no scheduler ever read it. + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'api/ScheduledExport', + // #17158 — `automation/ScheduleState`, retired whole with the export-job API family + // (ADR-0049 enforce-or-remove; maintainer ruling A, landing route A — objectui + // retired its side first in objectui#10247). It declared + // the runtime state of a scheduled flow (`timezone`, `status`, `nextRunAt`, run + // counters). No scheduler ever wrote or read one, and after #16320 deleted its + // required cron it no longer declared a cadence. Retired with the family under + // ruling item 2 ("unless a live consumer is measured" — none was, in + // objectstack, objectui at the pin, or cloud). + // Zero readers in objectstack, in objectui at the pinned sha, and in cloud. + // No carrier key and no authored document, so no tombstone and no D2 + // conversion — this table plus the D3 semantic entry + // `export-job-family-retired` are the declaration. + 'automation/ScheduleState', // #16325 — `cloud/developer-portal.zod.ts` left `@objectstack/spec` with the `./cloud` subpath // (maintainer ruling, option B "cut by owner": the cloud control plane's contracts are // the cloud repo's own declarations, not an open-source protocol). Prescription: the diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index 8debed6e2f9..1a368cab282 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -275,7 +275,7 @@ import type * as M187 from './shared/duration.zod.js'; import type * as M188 from './ai/build-progress.zod.js'; // --------------------------------------------------------------------------- -// 790 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 787 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -497,9 +497,6 @@ export type Iso_api_events__MetadataEventType = Assert, z.infer< typeof M22.CreateImportJobResponseSchema > >>; export type Iso_api_export__DeduplicationStrategy = Assert, z.infer< typeof M22.DeduplicationStrategy > >>; export type Iso_api_export__ExportFormat = Assert, z.infer< typeof M22.ExportFormat > >>; -export type Iso_api_export__ExportJobStatus = Assert, z.infer< typeof M22.ExportJobStatus > >>; -export type Iso_api_export__ExportJobSummarySchema = Assert, z.infer< typeof M22.ExportJobSummarySchema > >>; -export type Iso_api_export__GetExportJobDownloadRequestSchema = Assert, z.infer< typeof M22.GetExportJobDownloadRequestSchema > >>; export type Iso_api_export__ImportJobProgressSchema = Assert, z.infer< typeof M22.ImportJobProgressSchema > >>; export type Iso_api_export__ImportJobResultsSchema = Assert, z.infer< typeof M22.ImportJobResultsSchema > >>; export type Iso_api_export__ImportJobStatus = Assert, z.infer< typeof M22.ImportJobStatus > >>; @@ -1662,7 +1659,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 790 isomorphic pins', () => { + it('still declares all 787 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes @@ -2285,7 +2282,17 @@ describe('ADR-0122 type-alias convention', () => { // release carried it: `KanbanConfigSchema` loses the `limit` whose applied // default had split its two shapes, `KanbanConfigParsed` is deleted and the // schema is re-pinned as Iso_ui_view__KanbanConfigSchema. +1 added. - expect(pins).toHaveLength(790); + // + // 790 -> 787 is #17158's retirement of the export-job API family (ADR-0049 + // enforce-or-remove, whole-def removal, `RETIRED_DEFS_BY_MAJOR[18]`): three + // of its schemas were isomorphic and pinned here — + // Iso_api_export__ExportJobStatus, Iso_api_export__ExportJobSummarySchema and + // Iso_api_export__GetExportJobDownloadRequestSchema — and they leave with the + // schemas. The family's other defs each carried an `XParsed` alias and were + // never on this list, so they leave with nothing to unpin; `ScheduleState` + // (retired in the same change) carried `ScheduleStateParsed` likewise. The M22 + // slot stays occupied by the module's surviving import-job pins. -3 removed. + expect(pins).toHaveLength(787); // The count is stated in PROSE twice as well — this case's title and the // section header above the pin list — and until #6605 nothing read either diff --git a/packages/spec/vitest.repo-tests.json b/packages/spec/vitest.repo-tests.json index f735a56a0d3..6974d93083a 100644 --- a/packages/spec/vitest.repo-tests.json +++ b/packages/spec/vitest.repo-tests.json @@ -22,6 +22,7 @@ "scripts/strictness-ledger.test.ts", "src/ai/tool-confirmation-prescription-tense.pin.test.ts", "src/api/error-catalog-docs.test.ts", + "src/api/export-job-family-retirement.test.ts", "src/data/api-methods-batch-conformance.test.ts", "src/identity/position-delegatable-enforcer.pin.test.ts", "src/integration/connector-connection-timeout-retirement.test.ts",