Commit 1df29df
fix(spec): AutomationApiContracts names the served /api/v1/automation paths (#20056)
Fixes #20034
Clause-②: no
## Patch round 1 (head `10b1176328`)
Added on top of the reviewed head `4c216561c6` (at-tier review PASS,
comment 5824559177). It carries the implementer's own two out-of-scope
findings and the reviewer's Clause ② reading:
- **ADR-0087 D3 entry `automation-runs-cursor-retired`**:
`packages/spec/src/migrations/entries/semantic/18.automation-runs-cursor-retired.ts`
`:11`, `:43` and `:72` now name `GET /api/v1/automation/:name/runs`, the
path this PR's contract publishes and the dispatcher mounts.
`packages/spec/src/migrations/registry.ts` was regenerated with `pnpm
--filter @objectstack/spec gen:migration-registry` (not hand-edited; the
diff is the same three lines at `:5943`, `:5975` and `:6004`).
`gen:upgrade-guide` and `gen:spec-changes` were re-run and changed no
bytes, because the entry is in step 18, beyond `PROTOCOL_MAJOR` 17. The
text still ships today as data in `MIGRATIONS_BY_MAJOR[18]`, which is
why it is corrected now. Open PR #20031 regenerates a different region
of `registry.ts`; whichever of the two lands second regenerates.
- **Two comments**: `packages/runtime/src/query-param.ts:179` and
`packages/services/service-automation/src/run-list-truncation.test.ts:6`
now quote the `/api/v1` path. Both are comments only.
- **Clause ②**: `.changeset/20034-automation-contract-api-v1-paths.md:9`
and this body's line 2 now read `Clause-②: no`, with no arm. This diff
adds no key, widens no accepted input and adds no export
(`scripts/pm/clause2-line.mjs:70`). The level stays `minor`.
- No pending changeset quotes a sentence of the D3 entry.
`.changeset/19365-automation-runs-cursor-hasmore.md:117` carries only
the registration marker naming the entry's id, and the id is unchanged.
So this round needs no further deliberate correction.
## What this changes
`AutomationApiContracts` (`@objectstack/spec/api`) declared its nine
flow endpoints under `/api/automation`. The dispatcher mounts the
automation door at `config.prefix || '/api/v1'` plus `/automation`, and
`objectstack serve` passes no prefix, so every declared path answered
`404 ENDPOINT_NOT_FOUND` on the default composition (measured on a
composed runtime by the #19966 dev). This PR takes remedy 1: the
contract moves to the served paths. **The runtime and dispatcher are
unchanged.**
- `packages/spec/src/api/automation-api.zod.ts`: the nine `path` values,
the module's `Base path` line and endpoint list, and every other in-file
path quote (section headers, `@example`s, the resume docblock, and the
`cursor` tombstone text `ListRunsRequestSchema` raises) move from
`/api/automation…` to `/api/v1/automation…`. After the edit the file
holds 0 occurrences of `/api/automation` (28 moved, plus the 10 docblock
lines rewritten).
- `packages/spec/src/api/automation-api.zod.test.ts`: the nine path pins
move with the values.
- `content/docs/references/api/automation-api.mdx`: regenerated with
`pnpm --filter @objectstack/spec gen:docs` (not hand-edited).
- `packages/runtime/src/automation-api-contract-mounts.test.ts` (new):
the drift pin, below.
- `.changeset/20034-automation-contract-api-v1-paths.md` (new):
`@objectstack/spec` `minor`.
- `.changeset/19365-automation-runs-cursor-hasmore.md`: a deliberate
correction of a pending note, below.
- Patch round 1:
`migrations/entries/semantic/18.automation-runs-cursor-retired.ts` and
the regenerated `migrations/registry.ts`, plus comments in
`packages/runtime/src/query-param.ts` and
`packages/services/service-automation/src/run-list-truncation.test.ts`.
## Reproduction, at base `adbbc5d01e`
- Spec: `automation-api.zod.ts:14` `Base path: /api/automation`;
`:658`–`:706` nine `path` values under `/api/automation`; the test
pinned all nine to themselves (`automation-api.zod.test.ts:803`–`:811`).
- Runtime: `dispatcher-plugin.ts:909` `const prefix = config.prefix ||
'/api/v1';`; `registerAutomationRoutes(base)` mounts
`${base}/automation…` (`:1465` onwards), called with `prefix` at
`:1746`, and with `${prefix}/environments/:environmentId` at `:1742` /
`:1750` when project scoping is on.
- Route ledger: `route-ledger.ts:429` `POST /automation` (client
`automation.create`) and siblings; the header (`:17`) says to prepend
`/api/v1` for the wire path.
- CLI: `packages/cli/src/commands/serve.ts:4412` calls
`createDispatcherPlugin({ scoping, enforceProjectMembership,
observability, rateLimit })`, no `prefix`; scoping defaults to off
(`:4340`).
## Consumer search: nothing depends on the unversioned form
| candidate | reads the contract's `path`? | verdict |
| --- | --- | --- |
| `packages/adapters/hono/src/hono.test.ts:453` (`GET /api/automation
delegates to dispatch()`) | no | Not a consumer. It drives
`createHonoApp` with the adapter's own default `prefix` (`options.prefix
\|\| '/api'`, `hono/src/index.ts:303`) against a mocked dispatcher and
asserts the dispatcher-internal `/automation`. It never imports the
contract. |
| `packages/client` | no | Builds automation URLs from discovery or its
`/api/v1/automation` convention (`getRoute('automation')`); it never
names `AutomationApiContracts`. Two comments name the spec test file
`automation-api.zod.test.ts`, not the constant. |
| everything else in this repo | no | `AutomationApiContracts` occurs
only in its declaring file, its spec test, `api-surface/api.json` (name
only) and `export-origins/api.json`. No generator reads the path values.
|
| objectui at the pinned `.objectui-sha` `62597c588` | no | `git grep
-F` at that commit: `AutomationApiContracts` 0 files, `/api/automation`
0 files; positive control `/api/v1/automation` 33 files. |
| `objectstack-ai/cloud` and npm consumers | not measured | not checked
out here |
One served surface does use the unversioned form: a host built with
`createHonoApp({ kernel })` and no `prefix` serves the whole dispatcher,
automation included, under `/api`. That is a documented adapter default,
and it applies to every contract family: under that host every other
`*ApiContracts` row (`/api/v1/…`) is off by the same segment. The old
automation paths matched it by coincidence, not by design, and no code
reads the contract under that host, so remedy 2 does not apply. The
changeset says how such a host maps the contract paths.
## Deliberate correction of a pending release note
`.changeset/19365-automation-runs-cursor-hasmore.md` (pending, not yet
released) quotes the `cursor` tombstone text in its FROM/TO block. That
text is one of the path quotes this PR moves, so the note became false.
Its line 32 changes from
-> throws: '`cursor` was removed from GET /api/automation/:name/runs in
to
-> throws: '`cursor` was removed from GET /api/v1/automation/:name/runs
in
Nothing else in that note changes. This is the DELIBERATE CORRECTION
class that `check-empty-changeset.mjs` names. `skip-changeset` is not
applied, and `Check Changeset` stays red **by design**. The same-head
at-tier review (comment 5824559177) names the note and judges the
changed sentence. No other pending changeset quotes an unversioned
automation path. At the base, `git grep -n "/api/automation" --
'.changeset/*.md'` showed only that line. Patch round 1 corrected no
further note: no pending changeset quotes the D3 entry's sentences.
## Changeset level
`minor`, not declared breaking. The `path` type stays `string`, no
accepted input narrows, no method changes, and the old values named
paths that no route served on the default composition, so a caller that
read the constant gets a working URL now without changing code. This
follows the precedent of the `PackageApiContracts.installPackage.path`
rebind (`.changeset/18058-install-door-contract-rebind.md`, `minor`, not
breaking). The declaration is `Clause-②: no` with no arm, in both this
body and the changeset. It answers the reader's question "does this
widen an accepted input or grow the public surface?" and the answer here
is no: no key added, no accepted input widened, no export grown. It is
not `(narrowing)` either, because nothing an author writes is removed.
`minor` is valid under `no`: a published constant's value moves, and
`patch` is a floor, not a ceiling. The first head declared `yes`, copied
from the claim, and the reviewer judged that over-declared.
## The drift pin, and proof that it can fail
`packages/runtime/src/automation-api-contract-mounts.test.ts` has two
legs:
1. **mount**: it starts `createDispatcherPlugin` with **no** `prefix`
(the composition `objectstack serve` builds) on a server that records
registrations, and requires every contract `METHOD path` to be one of
them. The prefix comes from the plugin's own default, not from a
constant in the test.
2. **ledger**: every contract route must be a `route-ledger.ts` row
under the documented `/api/v1` wire prefix. The live-mount parity gate
probes those rows through the real router.
The runtime vitest config aliases `@objectstack/spec/*` to spec
**source**, so the spec side of the pin reads `src/`, not a build. The
ablation was run on the committed tree (`4c216561c6`) with
`scripts/ablation-replace.mjs`, one leg at a time, with restores
anchored on `HEAD`:
| leg | mutation (landed on disk: anchor 1 → 0, blob moved) | pin result
|
| --- | --- | --- |
| contract | `getRun.path` back to `/api/automation/:name/runs/:runId` |
mount red, ledger red (`getRun` named), 1 passed |
| dispatcher | default prefix `'/api/v1'` → `'/api/v2'` | mount red (all
nine named), 2 passed |
| restored | none (blobs equal `HEAD`, `git diff HEAD` empty for both
paths) | 3 passed |
## Environment-scoped mount
The contract does not carry
`/api/v1/environments/:environmentId/automation…`, and this PR does not
add it. No `*ApiContracts` map declares the scoped variants. Scoping is
one mount-time transformation the dispatcher applies to automation,
actions, AI and packages alike, and the client derives scoped URLs from
discovery. If the variants are ever declared, that belongs once in a
contract shared by all the families, not copied into each map. Note that
under `projectResolution: 'required'` none of the nine unscoped paths is
mounted (`dispatcher-plugin.required-scoping-mounts.integration.test.ts`
pins that).
## Verification (head `10b1176328`, patch round 1)
- Tests, each package's full local project, under the verify lock at
this head. The lock's verdict is `batch-last-exit 0`: the last part of
the batch requires all three suites to exit 0, and the batch printed
`SUITES spec=0 service-automation=0 runtime=0`.
- `@objectstack/spec`: 532 files, 15648 passed, 2 todo.
- `@objectstack/service-automation`: 144 files, 1725 passed.
- `@objectstack/runtime` (`--project local`): 277 files, 3896 passed, 1
skipped, including the drift pin's 3 cases.
- Build: `turbo run build --filter='./packages/*'
--filter='./packages/*/*'` at this head: 72 of 72 tasks succeeded,
including the tsup and declaration builds of spec, runtime and
service-automation.
- Spec generated artifacts: `check:generated` reports "All 15 generated
artifacts are up to date". `check:migration-registry` reports
"src/migrations/registry.ts is current (242 semantic, 210 retired-key,
183 retired-def)". `check:spec-changes` and `check:upgrade-guide` both
report up to date. `check:api-surface` reports "public API surface +
factory signatures unchanged", and `check:docs` reports "225 generated
files in sync".
- Derived gate set, taken after `git fetch origin main`
(`dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`,
merge base `adbbc5d01`, 10 paths): 113 families, 6 more than round 0
(`check:migration-registry`, `check:spec-changes`,
`check:upgrade-guide`, `check:future-spec-major`, and
`check-tenant-audit-census` with its self-test). All 113 ran, and
`--ran` reports "113 run, 0 NOT-MEASURED" with 0 UNRUN. 112 exited 0.
`node scripts/check-empty-changeset.mjs --base origin/main` exits 1 on
the deliberate correction, as designed. No family answered PREREQUISITE
NOT MET this round.
- The branch is behind `origin/main` (15 commits at the seat's re-read).
Three of those commits regenerated one of this PR's 10 paths,
`packages/spec/src/migrations/registry.ts`: #20036 (`0bf85eaae6`),
#19909 (`5b9402d89b`) and #19818 (`66960564d9`). `git merge-tree
--write-tree` of `origin/main` and this head is clean, and the at-tier
re-review measured that #20036's and #19909's hunks do not touch this
PR's region (`:5940`–`:6004`). The merge queue's rebuilt generation
regenerates the file. The derivation's one changed family input across
that range is `scripts/sdui-manifest.record.json`, from #20036.
(Corrected by the seat after the at-tier review `5825376693` found the
earlier sentence, "None of this PR's 10 paths is touched by those
commits", false.)
- Clause ② and ADR-0087, run offline with this body as the
`pull_request` event:
- `check-changeset-no-major --event` prints "✓ This diff introduces no
`major` bump." and "✓ LEVEL AXIS: this PR declares clause-② `no`, so no
package here is declared to have grown a published surface."
(declaration line `Clause-②: no`, no arm).
- `check-adr-0087-registration` prints "✓ … this PR adds no
declared-breaking changeset (2 non-breaking changeset(s) seen)". This
gate reads the Clause ② arm from the changeset body
(`readClause2Line(parsed.body)`), not from the PR event. Its verdict is
the same with and without `--event`.
- Lint, narrowed and proven: all 7 touched TS files are in the eslint
population (`--print-config` resolves each). `--no-inline-config
--format json` reports 7 files, 0 errors, 0 warnings.
`eslint.config.mjs` enables no type-aware linting: all seven
`parserOptions` blocks are `{ ecmaVersion, sourceType }` only, with no
`project` or `projectService`. So this diff cannot change the verdict on
any untouched file. The repo-wide `pnpm lint` is left to CI.
- Round-0 evidence still stands, and its sources are unchanged in round
1: the ablation above on `4c216561c6`, and the spec and runtime
`typecheck` runs, both exit 0. Round 1 changes only string literals (the
D3 entry and its registry mirror), comments and one changeset line. The
type-check-debt gate re-measured at this head: "4 ledger entr(ies) …
none above its recorded number".
## Acceptance notes (observations, not filed)
- `RouterConfigSchema` (`spec/src/api/router.zod.ts`) defaults
`basePath` to `/api` with `mounts.automation: '/automation'`. That
spec-only declaration has no runtime reader in this repo.
- The contract lists nine of the 17 routes `registerAutomationRoutes`
mounts. The ones not listed are resume, cancel, restore-suspension,
screen, actions, connectors, `_status` and the legacy trigger form.
- **Every remaining `/api/automation` path at head `10b1176328`, and why
it stays** (`git grep -n "/api/automation\b"`, with the `automation-api`
file-name hits filtered out):
- `.changeset/20034-automation-contract-api-v1-paths.md` `:5` and
`:15`-`:20`: the FROM column of this PR's own FROM/TO table.
- `packages/adapters/hono/src/hono.test.ts:453`-`:454`: the Hono
adapter's own default `prefix` (`/api`), driven against a mocked
dispatcher. It does not read the contract.
- `packages/runtime/src/automation-api-contract-mounts.test.ts:9`: the
pin's docblock, describing the drift it guards.
- `packages/spec/scripts/file-description.test.ts:878`-`:937`: synthetic
fixtures for the docblock-description extractor. They do not quote the
contract.
- `packages/spec/src/api/router.test.ts:343`: a custom-mounts fixture
for `RouterConfigSchema`.
- Released `CHANGELOG.md` entries: `packages/client/CHANGELOG.md:3230`,
`packages/runtime/CHANGELOG.md:11589`,
`packages/services/service-automation/CHANGELOG.md:5802` and
`packages/spec/CHANGELOG.md:32723` quote `GET
/api/automation/:name/runs` in the released ExecutionStatus-filter
entry. These are release-owned and never edited in a code PR; an
amendment would be a dedicated docs-only PR.
`packages/spec/CHANGELOG.md:14982` names the file `automation-api.mdx`,
not a path.
Implemented in session `session_019c3Hi6ZMU1p6m6aA6Bz45d` (claim
5823821835; patch round 1 dispatched by the `domain:spec` seat 4).
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 736c63a commit 1df29df
10 files changed
Lines changed: 221 additions & 67 deletions
File tree
- .changeset
- content/docs/references/api
- packages
- runtime/src
- services/service-automation/src
- spec/src
- api
- migrations
- entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
32 | | - | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
14 | 20 | | |
15 | 21 | | |
16 | 22 | | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
25 | | - | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
26 | 32 | | |
27 | 33 | | |
28 | 34 | | |
| |||
524 | 530 | | |
525 | 531 | | |
526 | 532 | | |
527 | | - | |
| 533 | + | |
528 | 534 | | |
529 | 535 | | |
530 | 536 | | |
| |||
Lines changed: 114 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
176 | 176 | | |
177 | 177 | | |
178 | 178 | | |
179 | | - | |
| 179 | + | |
180 | 180 | | |
181 | 181 | | |
182 | 182 | | |
| |||
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
800 | 800 | | |
801 | 801 | | |
802 | 802 | | |
803 | | - | |
804 | | - | |
805 | | - | |
806 | | - | |
807 | | - | |
808 | | - | |
809 | | - | |
810 | | - | |
811 | | - | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
| 810 | + | |
| 811 | + | |
812 | 812 | | |
813 | 813 | | |
814 | 814 | | |
| |||
0 commit comments