fix(spec): AutomationApiContracts names the served /api/v1/automation paths - #20056
Conversation
… paths The nine flow endpoints were declared under /api/automation while the dispatcher mounts the automation door only at its prefix (default /api/v1) plus /automation, so every declared path answered 404 on the default composition. The nine path values, the Base path docblock, the endpoint list and every in-file path quote (including the retired-cursor tombstone text) move to /api/v1/automation; the spec pin moves with them. A runtime test boots the dispatcher plugin with its default prefix and requires every contract route to be mounted and to be a route-ledger row under the documented /api/v1 wire prefix. The pending runs-cursor changeset quoted the tombstone text and is corrected to the new path. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude <noreply@anthropic.com>
Generated by `pnpm --filter @objectstack/spec gen:docs` after the contract moved to /api/v1/automation. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 141 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 244dd0322131a56c20485c489615199af0f72c1d && git checkout 244dd0322131a56c20485c489615199af0f72c1d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56 10b117632855d4ccf9bebd2cdde3f0dfb02c53f2 && git checkout -B drift-repro b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56 && git merge --no-ff 10b117632855d4ccf9bebd2cdde3f0dfb02c53f2
node scripts/docs-audit/affected-docs.mjs --json b76aad5f6fac71cbbcd4ea6bfdf21a59e2fd4d56
|
Contract reviewServed-tier: 77/77 Isolated at-tier reviewer subagent, run by the Inputs read: card #20034 body + comments 5823177456 (triage), 5823821835 (claim), 5824409816 (os-dev-report); origin comment 5822432366 on #19966 ( ① Derived judgments(a) The fix — correct and complete inside the file.
(b) Consumer search — the dev is right; remedy 1 is correct.
(c) The drift pin guards the real thing.
(d) Changeset
(e) Deliberate correction of
(f) Things left false — verified; acceptable as a follow-up, but it needs a carrier.
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…v1 runs path Patch round 1 on the AutomationApiContracts path fix: - The ADR-0087 D3 entry automation-runs-cursor-retired named GET /api/automation/:name/runs three times; it now names GET /api/v1/automation/:name/runs, the path the contract publishes and the dispatcher mounts. registry.ts regenerated with gen:migration-registry; gen:upgrade-guide and gen:spec-changes re-run with no byte change (the entry is in step 18). - Two comments (runtime query-param.ts, service-automation run-list-truncation.test.ts) quote the same path in the v1 form. - The changeset declares Clause-② no: no key added, no accept set widened, no export grown. The level stays minor. Claude-Session: https://claude.ai/code/session_019c3Hi6ZMU1p6m6aA6Bz45d Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: 127/127 Isolated at-tier reviewer subagent, run by the Inputs read: card #20034 body + comments 5823177456, 5823821835, 5824409816, 5824987999 (round-1 os-dev-report); ① Derived judgments(a) Round-1 diff is exactly the list — nothing missing, nothing beyond. (b) D3 entry — the three sentences are true at the head; nothing else falsified; mirror byte-identical to its generator.
(c) (d)
(e) PR body — one factual sentence is false; the rest checks.
(f) Nothing round 1 should have fixed and did not. Tree-wide residue at head is the Acceptance list; a second sweep ② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL — one blocking item: the PR-body sentence "None of this PR's 10 paths is touched by those commits" is false ( Generated by Claude Code |
Contract reviewServed-tier: 162/162 Isolated at-tier reviewer subagent, run by the Re-issue after the seat's body edit. Inputs read: everything the first record ( Body change is exactly one line. ① Derived judgments(a) Round-1 diff is exactly the list — nothing missing, nothing beyond. Unchanged from (b) D3 entry true at head; mirror byte-identical to its generator. Unchanged: (c) (d) (e) PR body — the false sentence is gone; the replacement bullet's claims measure true, with two precision notes.
(f) Nothing round 1 should have fixed and did not. Unchanged: residue sweeps exit 1 beyond the Acceptance list; both ② Semver levelUnchanged: ③ Boundary flags
Implemented-by: VERDICT: PASS — same head Generated by Claude Code |
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:automation-runs-cursor-retired:packages/spec/src/migrations/entries/semantic/18.automation-runs-cursor-retired.ts:11,:43and:72now nameGET /api/v1/automation/:name/runs, the path this PR's contract publishes and the dispatcher mounts.packages/spec/src/migrations/registry.tswas regenerated withpnpm --filter @objectstack/spec gen:migration-registry(not hand-edited; the diff is the same three lines at:5943,:5975and:6004).gen:upgrade-guideandgen:spec-changeswere re-run and changed no bytes, because the entry is in step 18, beyondPROTOCOL_MAJOR17. The text still ships today as data inMIGRATIONS_BY_MAJOR[18], which is why it is corrected now. Open PR fix(spec): the edge-condition upgrade entry and its pending changeset namePOST /api/v1/automation, not the nonexistentPOST /flows#20031 regenerates a different region ofregistry.ts; whichever of the two lands second regenerates.packages/runtime/src/query-param.ts:179andpackages/services/service-automation/src/run-list-truncation.test.ts:6now quote the/api/v1path. Both are comments only..changeset/20034-automation-contract-api-v1-paths.md:9and this body's line 2 now readClause-②: 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 staysminor..changeset/19365-automation-runs-cursor-hasmore.md:117carries 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 atconfig.prefix || '/api/v1'plus/automation, andobjectstack servepasses no prefix, so every declared path answered404 ENDPOINT_NOT_FOUNDon 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 ninepathvalues, the module'sBase pathline and endpoint list, and every other in-file path quote (section headers,@examples, the resume docblock, and thecursortombstone textListRunsRequestSchemaraises) 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 withpnpm --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/specminor..changeset/19365-automation-runs-cursor-hasmore.md: a deliberate correction of a pending note, below.migrations/entries/semantic/18.automation-runs-cursor-retired.tsand the regeneratedmigrations/registry.ts, plus comments inpackages/runtime/src/query-param.tsandpackages/services/service-automation/src/run-list-truncation.test.ts.Reproduction, at base
adbbc5d01eautomation-api.zod.ts:14Base path: /api/automation;:658–:706ninepathvalues under/api/automation; the test pinned all nine to themselves (automation-api.zod.test.ts:803–:811).dispatcher-plugin.ts:909const prefix = config.prefix || '/api/v1';;registerAutomationRoutes(base)mounts${base}/automation…(:1465onwards), called withprefixat:1746, and with${prefix}/environments/:environmentIdat:1742/:1750when project scoping is on.route-ledger.ts:429POST /automation(clientautomation.create) and siblings; the header (:17) says to prepend/api/v1for the wire path.packages/cli/src/commands/serve.ts:4412callscreateDispatcherPlugin({ scoping, enforceProjectMembership, observability, rateLimit }), noprefix; scoping defaults to off (:4340).Consumer search: nothing depends on the unversioned form
path?packages/adapters/hono/src/hono.test.ts:453(GET /api/automation delegates to dispatch())createHonoAppwith the adapter's own defaultprefix(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/api/v1/automationconvention (getRoute('automation')); it never namesAutomationApiContracts. Two comments name the spec test fileautomation-api.zod.test.ts, not the constant.AutomationApiContractsoccurs only in its declaring file, its spec test,api-surface/api.json(name only) andexport-origins/api.json. No generator reads the path values..objectui-sha62597c588git grep -Fat that commit:AutomationApiContracts0 files,/api/automation0 files; positive control/api/v1/automation33 files.objectstack-ai/cloudand npm consumersOne served surface does use the unversioned form: a host built with
createHonoApp({ kernel })and noprefixserves 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*ApiContractsrow (/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 thecursortombstone 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 fromto
Nothing else in that note changes. This is the DELIBERATE CORRECTION class that
check-empty-changeset.mjsnames.skip-changesetis not applied, andCheck Changesetstays 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. Thepathtype staysstring, 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 thePackageApiContracts.installPackage.pathrebind (.changeset/18058-install-door-contract-rebind.md,minor, not breaking). The declaration isClause-②: nowith 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.minoris valid underno: a published constant's value moves, andpatchis a floor, not a ceiling. The first head declaredyes, 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.tshas two legs:createDispatcherPluginwith noprefix(the compositionobjectstack servebuilds) on a server that records registrations, and requires every contractMETHOD pathto be one of them. The prefix comes from the plugin's own default, not from a constant in the test.route-ledger.tsrow under the documented/api/v1wire 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 readssrc/, not a build. The ablation was run on the committed tree (4c216561c6) withscripts/ablation-replace.mjs, one leg at a time, with restores anchored onHEAD:getRun.pathback to/api/automation/:name/runs/:runIdgetRunnamed), 1 passed'/api/v1'→'/api/v2'HEAD,git diff HEADempty for both paths)Environment-scoped mount
The contract does not carry
/api/v1/environments/:environmentId/automation…, and this PR does not add it. No*ApiContractsmap 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 underprojectResolution: 'required'none of the nine unscoped paths is mounted (dispatcher-plugin.required-scoping-mounts.integration.test.tspins that).Verification (head
10b1176328, patch round 1)batch-last-exit 0: the last part of the batch requires all three suites to exit 0, and the batch printedSUITES 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.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.check:generatedreports "All 15 generated artifacts are up to date".check:migration-registryreports "src/migrations/registry.ts is current (242 semantic, 210 retired-key, 183 retired-def)".check:spec-changesandcheck:upgrade-guideboth report up to date.check:api-surfacereports "public API surface + factory signatures unchanged", andcheck:docsreports "225 generated files in sync".git fetch origin main(dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, merge baseadbbc5d01, 10 paths): 113 families, 6 more than round 0 (check:migration-registry,check:spec-changes,check:upgrade-guide,check:future-spec-major, andcheck-tenant-audit-censuswith its self-test). All 113 ran, and--ranreports "113 run, 0 NOT-MEASURED" with 0 UNRUN. 112 exited 0.node scripts/check-empty-changeset.mjs --base origin/mainexits 1 on the deliberate correction, as designed. No family answered PREREQUISITE NOT MET this round.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: chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036 (0bf85eaae6), fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #19909 (5b9402d89b) and fix(lint): walk page filterBy and lookup-field lookupFilters as authored filters #19818 (66960564d9).git merge-tree --write-treeoforigin/mainand this head is clean, and the at-tier re-review measured that chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036's and fix(spec,objectql)!: retirescalefrom the currency field type — refused at parse, no longer enforced on writes #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 isscripts/sdui-manifest.record.json, from chore(objectui): bump the console pin to f8a9d0fb0596 (carries objectui#10221 and the objectui#9910 re-cut) with re-measured pin citations and the lockstep re-record #20036. (Corrected by the seat after the at-tier review5825376693found the earlier sentence, "None of this PR's 10 paths is touched by those commits", false.)pull_requestevent:check-changeset-no-major --eventprints "✓ This diff introduces nomajorbump." and "✓ LEVEL AXIS: this PR declares clause-②no, so no package here is declared to have grown a published surface." (declaration lineClause-②: no, no arm).check-adr-0087-registrationprints "✓ … 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.--print-configresolves each).--no-inline-config --format jsonreports 7 files, 0 errors, 0 warnings.eslint.config.mjsenables no type-aware linting: all sevenparserOptionsblocks are{ ecmaVersion, sourceType }only, with noprojectorprojectService. So this diff cannot change the verdict on any untouched file. The repo-widepnpm lintis left to CI.4c216561c6, and the spec and runtimetypecheckruns, 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) defaultsbasePathto/apiwithmounts.automation: '/automation'. That spec-only declaration has no runtime reader in this repo.registerAutomationRoutesmounts. The ones not listed are resume, cancel, restore-suspension, screen, actions, connectors,_statusand the legacy trigger form./api/automationpath at head10b1176328, and why it stays (git grep -n "/api/automation\b", with theautomation-apifile-name hits filtered out):.changeset/20034-automation-contract-api-v1-paths.md:5and: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 defaultprefix(/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 forRouterConfigSchema.CHANGELOG.mdentries:packages/client/CHANGELOG.md:3230,packages/runtime/CHANGELOG.md:11589,packages/services/service-automation/CHANGELOG.md:5802andpackages/spec/CHANGELOG.md:32723quoteGET /api/automation/:name/runsin 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:14982names the fileautomation-api.mdx, not a path.Implemented in session
session_019c3Hi6ZMU1p6m6aA6Bz45d(claim 5823821835; patch round 1 dispatched by thedomain:specseat 4).