Skip to content

Commit 4db1bf1

Browse files
feat(spec)!: retire the export-job API family, IExportService and ScheduleState (ADR-0049) (#20194)
Fixes #17158 Clause-②: no Retires the export-job API contract family, the `IExportService` contract (with `ScheduleExportInput`) and `automation/ScheduleState` from `@objectstack/spec`: ADR-0049 enforce-or-remove, route 3 (whole-def removal). Nothing ever served, bound or read any of them. The served export door `GET /api/v1/data/:object/export` and the import-job family in the same module are untouched. ## Rulings executed (quoted, not re-ruled) - `5644350616`, decision batch #122 item 3, maintainer 「同意」: the family in `api/export.zod.ts`, `IExportService` and `ScheduleExportInput` leave the public surface through `RETIRED_DEFS_BY_MAJOR[18]` rows, one D3 semantic entry and refusal pins, with the `api-surface/` and `json-schema.manifest/` ratchets moving and the reference pages regenerated. `ScheduleState` goes too, "retired with the family unless a live consumer is measured". **BREAKING** at `minor`, with the ADR-0087 disposition and `Clause-②: no`. - `5815151991`, decision batch #221 item 2, letter A, 「同意」: objectui retires its side first (objectui#10247), then this card retires the whole family plus `ScheduleState` and carries the pin. - `5825830323`, scope note, 「同意」: the export-job LIST pair (`ListExportJobsRequestSchema` with `limit` default 20 / `cursor`, and its response) is in. The import-job family (`ListImportJobs*`, `ImportJob*`) is NOT, because it is served. ## Premise re-measured at this change's base (every zero beside a lit control) | reading | tree | family names (43) | control | | --- | --- | --- | --- | | objectstack, code outside `packages/spec` | base `49144fcc` | 0 code files: only prose (2 unreleased changesets, generated reference pages, `docs/qa` FOLLOW-UPS, 3 comment lines in the dogfood D7 ledger) | `ImportJobProgress(Schema)`: 7 files | | objectui at the pinned `.objectui-sha` `f8a9d0fb0596` | pin | 0 non-markdown files; the only hits are CHANGELOG prose recording objectui#10247 | `ImportJobProgress` 7, `GetMetaItemLayeredResponseSchema` 9, `@objectstack/spec/api` 34 | | cloud `main` | `48d70663` | 0 files (`git grep` exit 1) | `@objectstack/spec`: 537 files | | served routes | base | `@objectstack/rest` mounts no `/api/v1/data/export` route and no `POST` on `/api/v1/data/:object/export`; the route ledger lists only `GET /api/v1/data/:object/export` | the `import/jobs` routes are served and ledgered | | ADRs naming the family | base | 0 (`git grep` exit 1) | "enforce-or-remove": 43 ADRs | **Pin: not moved.** objectui#10247 landed as objectui PR #10264, merge `8b1f066192a4`. `git merge-base --is-ancestor 8b1f066192a4 f8a9d0fb0596` exits **0**, which is self-proving on any checkout, and the pin is 58 commits ahead of it and 0 behind. The ruling's pin condition is already met, so `.objectui-sha` is untouched. **`ScheduleState`: no live consumer** in any of the three repos (the rows above include its three names), so it retires per ruling item 2. ## What leaves the surface - `@objectstack/spec/api`: `ExportJobStatus`; `CreateExportJobRequest*` / `CreateExportJobResponse*`, `ExportJobProgress*`, `ScheduledExport*`, `GetExportJobDownloadRequest*` / `GetExportJobDownloadResponse*`, `ListExportJobsRequest*` / `ExportJobSummary*` / `ListExportJobsResponse*`, `ScheduleExportRequest*` / `ScheduleExportResponse*` (Schema consts, input aliases, Parsed aliases); and `ExportApiContracts`. - `@objectstack/spec/contracts`: `IExportService`, `CreateExportJobInput`, `CreateExportJobResult`, `ExportJobDownload`, `ListExportJobsOptions`, `ExportJobListResult`, `ScheduleExportInput`. The module `contracts/export-service.ts` and its test are deleted, and the barrel line is replaced by a note. - `@objectstack/spec/automation`: `ScheduleStateSchema`, `ScheduleState`, `ScheduleStateParsed`. - Stays: `ExportFormat`, `ExportImportTemplateSchema`, the import validation shapes, the whole import-job family including `ImportJobApiContracts`, and `GET /api/v1/data/:object/export`. ## Route and registration (ADR-0087) Route 3: none of these shapes is a stack collection or a metadata type, so there is no carrier key for a `retiredKey()` tombstone and no authored document for a D2 conversion. The declaration is: - 13 `RETIRED_DEFS_BY_MAJOR[18]` entry files under `migrations/entries/retired-defs/`: 12 `api/…` plus `automation/ScheduleState`. - The D3 semantic entry `export-job-family-retired`, whose `reason` states why it is not a D2 conversion. - `registry.ts` regenerated by `gen:migration-registry`. The step-18 `rationale` is not touched. - The route's own evidence, kept as proof. The first `gen:schema` refused with `❌ 13 previously published schema(s) disappeared from this build`, naming exactly the 13 defs. After the deliberate manifest deletion, the build reads each one as `json-schema/api/….json — RETIRED_DEFS_BY_MAJOR, major 18.`, and the authorable-surface gate records `12 baseline deletion(s) … carry their own proof (#4650)`, each `def no longer emitted by this build`. `ExportJobStatus` is an enum with no key rows. Ratchet movement is all removals, as a whole-def removal must show (an enum-value narrowing would show none): | artifact | lines | | --- | --- | | `json-schema.manifest/{api,automation}` | −12 / −1 | | `authorable-surface/{api,automation}` | −67 / −16 | | `authorable-defaults/{api,automation}` | −7 / −4 | | `api-surface/{api,automation,contracts}` | −34 / −3 / −7 | | `export-origins/{api,automation,contracts}` | −33 / −3 / −7 | | `declaration-map/{api,automation}` | −23 / −2 | `authorable-surface.base.json` is untouched (written only by `gen:authorable-surface-base`). ## Changed prose, quoted for the contract review No `.describe()` or refusal text changed; the removed schemas' describe strings left with them. Four docblocks changed: - `api/export.zod.ts` module docblock, which renders into `content/docs/references/api/export.mdx`: "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." - `ImportJobStatus` docblock, which used to link the retired enum: "Import Job Status — the states the import worker actually moves a job through (`succeeded`, not `completed`, is the success terminal)." - `RunListResult` docblock (`contracts/automation-service.ts`), which cited `ExportJobListResult`: "Unlike a cursor-paged list shape, and deliberately: there is ⛔ NO `nextCursor` here." - The retirement notes at the old section 2 of `api/export.zod.ts` and section 6 of `automation/execution.zod.ts`, plus the `contracts/index.ts` barrel note (code comments, not rendered). The prose says "@objectstack/spec 17" and not 18, because `check:future-spec-major` requires naming the release line (ADR-0087 amended): a pre-GA retirement ships as a `minor` of 17. The registry rows stay under major 18. ## Pins (new and flipped) - **New** `packages/spec/src/api/export-job-family-retirement.test.ts` (7 tests; added to `vitest.repo-tests.json`, `repo` project). It follows the `system/compliance-families-retirement.test.ts` form: - zero holders on every public entry for all 43 names via `export-origins/`, with survivors on `./api`, `./automation` and `./contracts`; - the runtime namespaces agree; - `contracts/export-service.ts` is gone, with no in-package importer; - the 13 defs are absent from `json-schema.manifest/`, and the 43 names from the `api-surface/`, `declaration-map/` and `export-origins/` shards, each with a lit control; - no authorable rows remain under the defs; - registration: all 13 defs in `RETIRED_DEFS_BY_MAJOR[18]`, the D3 entry wired with its "not a D2 conversion" reason, a backtick-free `surface`, a `replacement` naming the import-job family as NOT retired, and no conversion id for the family; - a **tree-scoped absence** leg over `packages` / `examples` / `skills` / `content` / `scripts`. That radius is already declared for `@objectstack/spec` in `scripts/cross-package-test-inputs.mjs` and `turbo.json`, and `check:cross-package-test-inputs` is green. - **Flipped** `cron-typed-positions-retirement.test.ts`: the three sites whose defs retire whole (`ScheduledExport` / `ScheduleExportRequest` `schedule.cronExpression`, `ScheduleState.cronExpression`) move to `LEFT_WITH_THEIR_DEFS`. Two facts stay asserted: no key-level registration ever, and each enclosing def is now a `RETIRED_DEFS_BY_MAJOR[18]` entry (dark control `integration/DataSyncConfig`). The four live positions are pinned unchanged, and the envelope case now uses `CacheWarmup.schedule`. - **Flipped** `type-alias-convention.pin.test.ts`: the three isomorphic pins leave with their schemas, 790 → 787, receipt added. - **Removed with their subjects**: the family's blocks in `api/export.test.ts`, the `ScheduleStateSchema` block in `automation/execution.test.ts`, and `contracts/export-service.test.ts`. ## Ablations (one-shot; the fix was committed first; `scripts/ablation-replace.mjs` wrap mode under `trap … EXIT INT TERM` with absolute paths) The subject resolves from `src/` and committed JSON (no package specifier, no `dist/`), so no rebuild is needed per leg. These ran at `6c35939e`. | leg | mutation (on disk: anchor count → 0, blob moved) | result | | --- | --- | --- | | A1b | re-plant `ExportJobStatus` const + type in `api/export.zod.ts` (blob `501ee4a5` → `2d84be39`) | RED 2: "runtime namespaces agree…" (`api must not export ExportJobStatus`) and the tree-scoped leg (`packages/spec/src/api/export.zod.ts references typeof ExportJobStatus`) | | A2 | delete `'automation/ScheduleState',` from the generated `RETIRED_DEFS_BY_MAJOR[18]` region (blob `6a3b0b58` → `58ab1619`) | RED 2: "declares all thirteen defs…" and the cron file's "[#17158] … covered at DEF grain" | | A3b | re-add `"api/ExportJobStatus"` to `json-schema.manifest/api.json` (blob `8c11f3d0` → `5ea54e58`) | RED 2: "the generated shards…" (`must have left json-schema.manifest/`) and the tree-scoped leg | - Every restore was proven: blob equals HEAD, and `git diff HEAD` is empty. - ⚠️ The first A1/A3 attempts were **no-ops**: each replacement contained its own anchor, so the tool refused, restored and never ran the tests. Those readings are void; A1b/A3b are the re-runs with consuming anchors. - Under A1b the `export-origins` "zero holders" leg stayed green, as designed: it reads the committed artifact. A re-planted export is caught by `check:api-surface` / `check:export-origins` in CI. ## Tests and gates at `a050a8a7` (final head) - `@objectstack/spec`: build + `check:generated --fix` (15 of 15 current; only `check:docs` was regenerated); `test` (local) **539 files, 15777 passed, 2 todo**; `test:repo` **32 files, 586 passed**; `typecheck` (tsc + scripts + test-typecheck) exit 0 (at `d08bc4e2`, before a comment-only commit). - Consumer suites, direction stated: these are the non-spec suites that read what this diff edits. - `@objectstack/vitest-filter-preflight` `test/config-wiring-sweep.test.ts` reads spec's `vitest.repo-tests.json`: 65/65. - `@objectstack/dogfood` `test/expression-conformance.test.ts`, the D7 ledger in the retirement radius: 7/7. - Both ran at `d08bc4e2`. No package outside spec imports a removed name (measured above), so no downstream closure was rebuilt for an absence check. - Gates: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`, derived at `a050a8a7`, gives 111. All were run, and `--ran` reconciles: "111 derived famil(ies) accounted for — 109 run, 2 NOT-MEASURED". Highlights: - `check:adr-0087-registration` "1 declared-breaking changeset(s), each carrying an ADR-0087 disposition"; - `check:changeset-no-major` "no `major` bump"; - `check:api-surface` green; - `check:spec-parsed-alias` "787 pinned isomorphic"; - `check:nul-bytes` OK; - `check:cross-package-test-inputs` OK; - the live `node scripts/check-issue-citations.mjs` "every citation this change adds resolves". - **NOT MEASURED: `check:dual-build-cjs-loads` and `check:type-check-debt`**, reason: PREREQUISITE NOT MET (exit 3). Each needs a whole-workspace build that does not fit the foreground cap on this shared box; CI builds that closure first in lint.yml. - Lint, narrowed and proven: 1. population: `eslint --print-config` resolves the config for the 24 changed JS/TS files, and none is ignored; 2. `eslint --no-inline-config --format json` gives 24 files, 0 errors, 0 warnings; 3. invariance: the resolved `parserOptions` carry only `ecmaVersion` / `sourceType` (no type-aware project), so no untouched file's verdict can move. - Merge: `origin/main` was merged at `369bcbed` through `scripts/pm/os-regen-merge.sh`. The one deferred shard (`authorable-surface/automation.json`) was regenerated in its own commit, keeping main's `automation/DecisionConfig:mode` and dropping the 16 retired `ScheduleState` rows again. `registry.ts` against `origin/main` is +206 / −0. Main has since moved one commit (`e2c4e125`, core security only, no overlapping path), which is not merged here. ## Acceptance notes (noted, not filed) - **Served door vs `ExportFormat`** (read-only inference): `GET /api/v1/data/:object/export` reads `format` as csv, json or xlsx, and any other value falls back to csv with a 200. `ExportFormat` also declares jsonl and parquet, and its only in-repo reader is `ExportImportTemplateSchema`, which has no reader outside spec either. Neither is in the ruling. A first draft of this PR's prose tied the served door to `ExportFormat`, and it was corrected before opening. carrier: 承接者:无 - `.changeset/19365-automation-runs-cursor-hasmore.md` (another PR's unreleased changeset) cites "the shape `IExportService.listExportJobs` already uses" as precedent. Once this lands it names a removed contract. It is not edited here, because a changeset from the merge base is not modified by a code PR (`check:empty-changeset`). carrier: 承接者:无 - `.changeset/cron-typed-positions-retired.md` (unreleased, same release) says the export `schedule` blocks and `ScheduleState` keep their surviving keys. This PR's changeset says so explicitly and supersedes it ("Read together with…"). - `docs/qa/platform-checklist/FOLLOW-UPS.md` §9c lists the async export-job surface and `ScheduleStateSchema.status` as enforce-or-remove candidates, which are now resolved. §9d is append-only and outside this card's surface. carrier: 承接者:无 - Ordinary concurrency: #19543's branch also moves the `type-alias-convention.pin.test.ts` count (790 → 789). Whichever lands second rebases, and the counts compose to 786. --- _Generated by [Claude Code](https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 560b724 commit 4db1bf1

46 files changed

Lines changed: 970 additions & 2074 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
**BREAKING** — the export-job API family, the `IExportService` contract and `ScheduleState` leave the public surface (#17158).
6+
7+
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.
8+
9+
**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.
10+
11+
### FROM → TO
12+
13+
| removed | from | what to write instead |
14+
| --- | --- | --- |
15+
| `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. |
16+
| `GetExportJobDownloadRequestSchema` / `GetExportJobDownloadResponseSchema`, `ListExportJobsRequestSchema` / `ListExportJobsResponseSchema`, `ExportJobSummarySchema` (with their types and `…Parsed` aliases) | `@objectstack/spec/api` | nothing — no job ever existed to download or list. |
17+
| `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. |
18+
| `ExportApiContracts` | `@objectstack/spec/api` | nothing — every route it named was unserved. |
19+
| `IExportService`, `CreateExportJobInput`, `CreateExportJobResult`, `ExportJobDownload`, `ListExportJobsOptions`, `ExportJobListResult`, `ScheduleExportInput` | `@objectstack/spec/contracts` | nothing — no provider ever bound the contract. |
20+
| `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`. |
21+
22+
**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.
23+
24+
**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`.
25+
26+
**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.
27+
28+
⚠️ 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).
29+
30+
⚠️ **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.
31+
32+
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.
33+
34+
Clause-②: no
35+
36+
<!-- adr-0087: registered export-job-family-retired -->

0 commit comments

Comments
 (0)