feat!: retire GET /api/v1/automation for GET /api/v1/meta/flow; ListAiConversationsResponse declares hasMore (#19543) - #20192
Conversation
…stAiConversationsResponse gains hasMore Door 4: ListFlowsRequestSchema, ListFlowsResponseSchema, FlowSummarySchema and the AutomationApiContracts listFlows entry leave with the route; the list is GET /api/v1/meta/flow. Registered as three RETIRED_DEFS_BY_MAJOR[18] rows and the D3 semantic entry automation-flow-list-route-retired. Door 3 (spec half): ListAiConversationsResponseSchema gains a required hasMore, cursor is described as the id of the last conversation held, and the list is declared newest first. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
json-schema.manifest, authorable-surface and authorable-defaults drop the three retired defs (whole-def removals, path 3); api-surface, export-origins, declaration-map, the references docs and the strictness-ledger counts are regenerated with check:generated --fix; ListAiConversationsResponse:hasMore joins the authorable surface. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
…nt.automation.list Door 4 of the list-door card: the dispatcher no longer mounts GET at the bare automation path and the domain keeps no GET / branch, the route-ledger row and the SDK method go with it, and every test that used the list as a convenient probe now reads GET /automation/_status. The socket test pins the wire answer (the host's 405 + Allow: POST, byte-identical to a POST-only control path); docs, the platform checklist, the authz census row and the changeset follow. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
dispatch() hands a declined domain path back as handled: false, so the transport answers: the dispatcher plugin never mounts GET at the bare path (Hono then answers 405 + Allow: POST) and a catch-all adapter answers its own 404. Pins, comments, the ADR-0087 entry and the changeset now say exactly that. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
…ger shrinks by one Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
…ck edit Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
check:route-ledger-census --fix, after the GET /automation row left the ledger deliberately. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
📓 Docs Drift CheckThis PR changes 4 package(s): 19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 143 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 13b15ebd38122c91ab36470199f3192e73fde4a6 && git checkout 13b15ebd38122c91ab36470199f3192e73fde4a6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e7f69dbba6764e980c49d2f54c975573830228ee f3ed706f010285f31d2fef52fad46db3432b93ac && git checkout -B drift-repro e7f69dbba6764e980c49d2f54c975573830228ee && git merge --no-ff f3ed706f010285f31d2fef52fad46db3432b93ac
node scripts/docs-audit/affected-docs.mjs --json e7f69dbba6764e980c49d2f54c975573830228ee
|
Contract reviewServed-tier: ① Derived judgmentsRuling read from card comment
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL
|
…and every 82-row ledger figure - retired-defs/18.api__ListFlowsRequest.ts cited #8715, which never resolved; it now names the precedent by its ADR-0087 entry id (package-rollback-response-retired). Registry regenerated. - The runtime route ledger holds 81 rows over 21 domains (derived from the table, domains unchanged): the #17111-pinned figure in authz-conformance.matrix.ts, the census reading in authz-probe-blind-spot.census.ts and the dated note in authz-ledger-population.baseline.ts now say so; lint.yml's comment on the census gate states its 26-of-82 reading as the one taken when the gate landed. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
…st-doors-3-4 # Conflicts: # packages/spec/src/migrations/registry.ts # packages/spec/src/type-alias-convention.pin.test.ts
…20194) os-regen-merge.sh took main's side of every generated path both sides moved; this commit re-derives them from the merged sources. The two hand deletions a generator cannot reproduce (json-schema.manifest/api.json -3, authorable-surface/api.json -16, the whole-def removals of api/FlowSummary, api/ListFlowsRequest, api/ListFlowsResponse) are re-applied on top of main's bytes; registry.ts is regenerated from both sides' entries (+95 lines, no deletions); check:generated --fix rebuilt spec and rewrote the five it proved stale. Delta vs origin/main is exactly this PR's: the three retired defs out, ListAiConversationsResponse:hasMore in, strictness-ledger api/ 435 -> 432. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
It is historical prose no gate reads, and a workflow-touching diff is a named reason a PR cannot enter the merge queue. The comment's "82 rows" stays as a reading taken when that gate landed; noted in the PR's acceptance notes. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rjy9MeetSfq34PKn81CRiN
Contract reviewServed-tier: ① Derived judgmentsThis is a DELTA review over round 0's record (PR comment Blocking item 1 (citation Blocking item 2 (82-row ledger figures) — RESOLVED. Measured with the #17111 pin's own instrument (scoped to Merge integrity — INTACT, nothing lost or corrupted on either side.
CI at ② Semver levelUnchanged from round 0: ③ Boundary flags
Implemented-by: VERDICT: PASS |
Fixes #19543
Clause-②: yes
This finishes the card: door ③'s spec half and door ④. Door ① landed in #19493 and door ② is absorbed by #17158, so nothing of the card's ruled work is left after this PR. Door ③'s server half is objectstack-ai/cloud#2426 (open) and is ⛔ not touched here.
Rulings executed (card comment 5825819437, maintainer's words verbatim):
Door ④ —
GET /api/v1/automationis retired; the flow list isGET /api/v1/meta/flowThe route's contract described a capability no build delivered: the request declared
status/type/limit(default 50) /cursorand the handler read none of them; the response declaredFlowSummary[]+total+nextCursor+hasMoreand the handler answered bare names beside a literalhasMore: false.packages/runtime/src/dispatcher-plugin.tsserver.get(base + '/automation')mounted (and its environment-scoped twin)POSTat the same path (createFlow) unchangedpackages/runtime/src/domains/automation.tsGET /branch →listFlows()handled: false); the#7900audit note kept for the surviving readspackages/runtime/src/route-ledger.tsGET /automation·automation.listcheck:route-ledger-census --fix)@objectstack/clientautomation.list()@objectstack/spec/apiListFlowsRequestSchema,ListFlowsResponseSchema,FlowSummarySchema+ 5 typesFlowSummarySchemahad no reader butListFlowsResponseSchema(grep of the tree: its own test and the ADR-0122 pin only)AutomationApiContractslistFlowsautomation-flow-list-route-retired+RETIRED_DEFS_BY_MAJOR[18]:api/ListFlowsRequest,api/ListFlowsResponse,api/FlowSummaryEvery other
/automationroute is unchanged (runs,/_status, actions and connectors catalogs, create / update / delete / trigger / toggle / clone / resume / cancel / restore-suspension / screen). #20056's table stays true:automation-api-contract-mounts.test.ts(every contract route is mounted at the default prefix AND is a ledger row) is green with the entry and the row gone together.What the wire answers now — measured, not assumed. On a real socket (
HonoServerPlugin+createDispatcherPlugin,dispatcher-plugin.anonymous-gate.integration.test.ts):GET /api/v1/automationanswers405 METHOD_NOT_ALLOWEDwithAllow: POST, anonymous and signed in alike, byte-identical (once the echoed path is factored out) to a GET on the POST-only control path/api/v1/automation/:name/toggle, andlistFlowsis never called. It is a 405 and not a 404 becausePOSTstill lives at the path; the host's own unmatched answer says exactly that, and the retired route leaves no text of its own. A transport that forwards every automation path to the dispatcher (the@objectstack/honocatch-all) getshandled: falseand renders its own 404; the domain's anonymous floor still answers 401 first there (pinned inanonymous-gate-actions-automation.test.ts).Door ③ — spec half:
ListAiConversationsResponseSchemagainshasMoreDescribe texts, quoted exactly (they ship in the JSON Schema and the references page):
cursor: "Theidof the last conversation on the previous page. The next page starts with the conversation created immediately before it, continuing newest first. Omit it to read the first page. An id that names no conversation of the caller is refused rather than read as the start of the list."conversations: "The caller's conversations, newest first — ordered by creation time, thenid, both descending"hasMore(new, required): "Whether at least one more conversation follows this page. Whentrue, send theidof the last conversation inconversationsascursorto read the next page."nextCursoris not declared. The SDK is unchanged:client.ai.conversations.list()still resolves to the array and its doc still reads "newest first";content/docs/api/client-sdk.mdxshows the next-page call (cursor= last id held).The open choice this PR settles:
hasMorerequired,nextCursorabsent — four axeshasMorerequired (taken)hasMoreoptionaluseConversationListreads?limit=50page one only (packages/app-shell/src/hooks/useConversationList.tsat main5c61e524and pinf8a9d0fb). The need it serves is the truncated sidebar: a caller with more thanlimitconversations cannot tell a full page from the last one. The only producer is cloud, which cloud#2426 makes compute it.ListAiConversationsResponsewithouthasMoreis a tsc error and a parse refusal (pinned).nextCursor: zero readers anywhere, and by the ruling's own definition it equals the last conversation'sid, already on the page — a second field is a second place for one value to disagree (startup axis: no pull, no surface). What the SDK does whenhasMoreis absent: nothing — it never reads it, so the window between this PR and cloud#2426 changes nothing any in-repo caller sees.Zone-2 measurements (PM mechanism assumptions)
8d1f7ab7:ListAiConversationsResponseSchemawas{ conversations }only (protocol.zod.ts:2946);ListFlows*/FlowSummarySchemaatautomation-api.zod.ts:59-109;listFlowsat:661-666; all three exported inapi-surface,declaration-map,export-origins— confirmed.client.automation.list/ListFlows*/FlowSummary/ a bare GET of the list path: zero callers outside their own tests and the ledger row in objectstack (branch base8d1f7ab7), objectui pinf8a9d0fband main5c61e524, cloud main48d70663. Positive controls on the same instruments: objectuiapps/console/src/pages/developer/FlowRunsPage.tsx:152client.meta.getItems('flow')andpackages/app-shell/src/views/setup/PackagedAutomationPage.tsx:144GET /meta/flowhit at both objectui refs; cloudpackages/service-ai/src/routes/ai-routes.tshit for the conversation route. objectui'suseApiDiscovery.tsnames/api/v1/automationonly as a route prefix with aPOST /triggerendpoint — not the list. Confirmed.AutomationApiContractsequal to the served paths; the removal takes the entry and the mount together, and its pin is green — confirmed.48d70663:ai-routes.tsanswers{ conversations }with nohasMore;objectql-conversation-service.tsorders ascending and keyset-pages on(created_at, id)— confirmed; cloud ⛔ not edited.json-schema.manifest/api.json(−3 keys) andauthorable-surface/api.json(−16 keys, reported by the build as "def no longer emitted by this build" — path 3); thengen:migration-registry,check:generated --fix(api-surface, export-origins, declaration-map, references docs, strictness-ledger counts),gen:test-typecheck-debt(runtime ledger −1 signature, thelistFlowsTS2339 it recorded vanished),check:route-ledger-census --fix.authorable-surface.base.jsonuntouched. Three merges oforigin/mainwent throughscripts/pm/os-regen-merge.sh(the third brought feat(spec)!: retire the export-job API family, IExportService and ScheduleState (ADR-0049) #20194, see Patch round 1); after each,check:generatedreported all 15 artifacts current and main's sibling entries (ui-report-joined-container-selection-refused,export-job-family-retired) are present inregistry.ts.Pins (accept and refuse)
packages/runtime/src/dispatcher-plugin.anonymous-gate.integration.test.ts— real socket: GET → 405 +Allow: POST+METHOD_NOT_ALLOWED+details(anonymous and with a session), byte-equal to the control,listFlowsnever called; POST at the same path still mounted (anonymous → 401); the inventory-privacy probe moved toGET /api/v1/automation/_status→ 401.packages/runtime/src/domain-handler-registry.test.ts— realdispatch():GET /automation→ exactly{ handled: false }, identical to a never-served sub-path,listFlowsnever called;GET /automation/_statuson the same dispatcher → 200 (anti-vacuity).packages/runtime/src/domains/anonymous-gate-actions-automation.test.ts— anonymousGET /still 401 (floor precedes routing), authenticatedGET /unhandled; inventory reads moved to/_status.packages/runtime/src/http-dispatcher.test.ts,automation-write-capability-gate.test.ts,automation-run-read-permission-gate.test.ts,http-dispatcher.tenancy-posture-outage.test.ts— the cases that used the list as a convenient probe now read/_status(their subjects — service resolution, stub/degraded slots, tenancy verdicts, the finding: the whole /automation read domain is gated only by "authenticated" — run-detail returns the triggering record's fields without that record's own FLS #7900 audit — are route-independent); the retired row leaves the audit table with a note.packages/client/src/client.test.ts—'list' in client.automationis false, with a@ts-expect-erroron the access (the client test layer compiles with 0 debt, so the directive is live);meta.getItems('flow')targetsGET /api/v1/meta/flow.packages/spec/src/api/automation-api.zod.test.ts— the three names are not exported (with a surviving-export control); the contract map has 8 entries, nolistFlows, noGET /api/v1/automation, and stillPOST /api/v1/automation.packages/spec/src/api/protocol.test.ts— acceptshasMoretrue/false and keeps it; refuses a page without it: issuecodeinvalid_type,path["hasMore"], messageInvalid input: expected boolean, received undefined; refuseshasMore: 'false'; the response shape is exactlyconversations+hasMore; the request keepsagentId/limit/cursor.packages/spec/src/type-alias-convention.pin.test.ts— theFlowSummarySchemapin leaves with the schema. At the merged head the count is 786: [Decision] the export-job API contract family (ScheduledExport/ScheduleExportRequest/ExportJob…) has zero consumers and, after #16320, an emptyscheduleblock — retire the family, keep the contract, or build the scheduler? #17158 (landed first) took 790 → 787 and this PR's receipt reads 787 → 786. Re-derived from the merged file (grep -c '^export type Iso_'= 786; the test's own recompute agrees), not by arithmetic.packages/qa/dogfood/test/authz-probe-blind-spot.census.ts—route-ledger.tspopulation 82 → 81 (controls re-measured by grep: 82 at base, 81 now), and its prose reading "82 rows over 21 domains" → 81;authz-conformance.matrix.ts's [finding]authz-conformance.matrix.ts:27states the route ledger holds "94 rows / 19 families" in the present tense — measured 91 today, and the cause is #14503, not the gate move it sits next to #17111-pinned docblock figure (82 rows / 21 domains) → 81 / 21, and the dated note inauthz-ledger-population.baseline.tsrecords the 82 → 81 move — rows and domains derived from theROUTE_LEDGERtable (81 rows, 21 distinct domains; domains unchanged);showcase-anonymous-deny-surfaces.dogfood.test.ts— the automation probes read/automation/_status.Ablation (one-shot, not a standing test). With the fix committed,
scripts/ablation-replace.mjsre-planted theGET base + '/automation'mount indispatcher-plugin.ts(anchor 1 → 0, blobacbf6f93→01d21238, marker count 1): the socket pin went red —expected 401 to be 405— and the restore leg proved blob == HEAD and an emptygit diff HEAD. The first attempt was a no-op the tool refused (the replacement contained its own anchor); the second is the one reported.Tests and gates (read at the final head, quoted from real output)
Head
f3ed706f(after Patch round 1). The test matrix ran at3b8a66d6; the only change from3b8a66d6tof3ed706fis the revert of a comment in.github/workflows/lint.yml(git diff --stat: 1 file, +2 / −3), which no test suite reads. The gate union and the citation check ran atf3ed706f. Heavy runs went throughscripts/pm/os-verify-lock.sh.@objectstack/specvitest run --project local@objectstack/runtimevitest run --project local@objectstack/clientvitest run@objectstack/dogfood: the wholeauthz-conformance.test.ts(the #17111 pins that were red in CI),showcase-anonymous-deny-surfaces.dogfood.test.ts(real showcase boot),authz-probe-blind-spot.test.tstypecheckfor spec, runtime, client and dogfoodpnpm --filter @objectstack/spec check:generatednode scripts/check-issue-citations.mjs(live, diff-scoped)dispatch-gates.mjs --commandsunion: 116 families derived for this diff atf3ed706f--ran: "116 derived famil(ies) accounted for — 116 run, 0 NOT-MEASURED (a DERIVED zero — all 116 recorded an exit code and none of them is 3)"check:authz-resolver,check:error-code-casing,check:filter-alias-parity,check:route-ledger-census.ts/.mjsfiles (round 0)--format json: 24 files, 0 errors, 0 warnings. Population: thefiles: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']block ineslint.config.mjs. Invariance: that config enables no type-aware linting (eslint.config.mjs:328), so this diff cannot change the verdict on an untouched file. The repo-widepnpm lintruns in CI.Consumer direction: the removed spec exports have zero importers outside
packages/spec, so the downstream check is the client and runtime typechecks above. The removed SDK method has zero callers in all three repos. NOT MEASURED locally and left to CI: the path-scheduled CI jobs (Test Core shards, Temporal Conformance, the full Dogfood gate, Build Core) and the workspace type-check lanes.Patch round 1 — the two CI reds at
eb08fb39, fixedcheck-issue-citations: "2 citation(s) THIS CHANGE ADDS do not resolve". The citation was#8715, which was allocated but never resolved (REST 404). It appeared inretired-defs/18.api__ListFlowsRequest.tsand in its generated copy inregistry.ts. The file now names the precedent by its ADR-0087 entry id,package-rollback-response-retired, and itsapi/PackageRollbackResponserow, with no guessed number. The registry was regenerated.authz-conformance.test.ts[finding]authz-conformance.matrix.ts:27states the route ledger holds "94 rows / 19 families" in the present tense — measured 91 today, and the cause is #14503, not the gate move it sits next to #17111 pins: "packages/runtime/src/route-ledger.ts: docblock says 82 rows, the table holds 81". The fix is the matrix docblock → "81 rows / 21 domains". I derived both numbers from theROUTE_LEDGERtable: 81 rows, 21 distinct domains, so the domain count did not move. The same sweep fixed the census reading inauthz-probe-blind-spot.census.tsand the dated note inauthz-ledger-population.baseline.ts. Two statements remain that are dated and historically true: the reading inscripts/check-route-ledger-census.mjs's header ("at the commit that added this gate") and thelint.ymlcomment (see Acceptance notes).ScheduledExport/ScheduleExportRequest/ExportJob…) has zero consumers and, after #16320, an emptyscheduleblock — retire the family, keep the contract, or build the scheduler? #17158) merged (4db1bf17, an ancestor of this head),origin/maincame in throughos-regen-merge.shas mergefd76315a:registry.tstook main's side and was then regenerated from both sides' entries (+95 lines, no deletions).3b8a66d6, with this PR's two hand deletions (manifest −3, authorable-surface −16) re-applied on top of main's bytes.origin/mainis exactly this PR's: the three retired defs are out,ListAiConversationsResponse:hasMoreis in, and strictness-ledgerapi/goes 435 → 432.Acceptance notes
packages/adapters/hono/src/hono.test.ts:454"GET /api/automation delegates to dispatch()" is an adapter-delegation test against a mock dispatcher and stays true (the catch-all forwards any path); not edited. carrier: none.packages/qa/dogfood/test/authz-conformance.matrix.ts:251namesGET /automationin prose describing the pre-[17.0-rc2验收] 安全:REST /actions 与 /automation 派发路由缺少匿名拒绝门 —— 未认证调用者可触发 system 提权的 RLS/FLS 绕过写入 #5519 ungated state; historical, not edited.ai.conversations.list()does not surfacehasMore(out of this card's ruled scope; see the report's open question)..github/workflows/lint.yml's census-gate comment still says 82 (historical prose, left as is).authz-probe-blind-spot.census.ts's census paragraph also says "Nine more ledgers exist repo-wide (290 rows in total)". The nine other*-route-ledger.tsfiles hold 117 rows today, and all eleven hold 282 at the base and 281 here, so the 290 was already stale before this PR. It is left as is; carrier: none.packages/services/service-automation/README.mddrops the list line and points atGET /api/v1/meta/flow;content/docs/api/plugin-endpoints.mdxteaches both doors;docs/qa/platform-checklist/areas/access-security.jsonre-points its automation probes to/_statusand to a single-flow read.Changeset:
.changeset/19543-list-doors-3-4.md—minorfor spec / client / runtime, a BREAKING banner with FROM → TO per surface, the Clause-② line with its(narrowing)arm, and the ADR-0087 disposition markerregistered automation-flow-list-route-retired. Nocontent/docs/releases/edit.Written by the
domain:specseat-1 dispatch (sessionsession_01Rjy9MeetSfq34PKn81CRiN), branchclaude/issue-19543-list-doors-3-4.