Skip to content

feat!: retire GET /api/v1/automation for GET /api/v1/meta/flow; ListAiConversationsResponse declares hasMore (#19543) - #20192

Merged
objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-19543-list-doors-3-4
Sep 27, 2026
Merged

objectstack-fleet[bot] merged 14 commits into
mainfrom
claude/issue-19543-list-doors-3-4

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

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 ④: 「退役,统一走 /meta/flow」

door ③: 「Ruled: the list is newest first.」 · 「This card owns the spec half. ListAiConversationsResponseSchema gains hasMore (and nextCursor, if declared, meaning "the id of the last conversation on the page"), cursor is described, and the SDK doc stays "newest first".」 · 「Land them together, or land cloud after this card.」

Door ④ — GET /api/v1/automation is retired; the flow list is GET /api/v1/meta/flow

The route's contract described a capability no build delivered: the request declared status / type / limit (default 50) / cursor and the handler read none of them; the response declared FlowSummary[] + total + nextCursor + hasMore and the handler answered bare names beside a literal hasMore: false.

surface before after
packages/runtime/src/dispatcher-plugin.ts server.get(base + '/automation') mounted (and its environment-scoped twin) not mounted for GET; POST at the same path (createFlow) unchanged
packages/runtime/src/domains/automation.ts GET / branch → listFlows() no branch; the domain declines (handled: false); the #7900 audit note kept for the surviving reads
packages/runtime/src/route-ledger.ts row GET /automation · automation.list row removed; census sentence 82 → 81 (check:route-ledger-census --fix)
@objectstack/client automation.list() removed (compile error on use)
@objectstack/spec/api ListFlowsRequestSchema, ListFlowsResponseSchema, FlowSummarySchema + 5 types removed — FlowSummarySchema had no reader but ListFlowsResponseSchema (grep of the tree: its own test and the ADR-0122 pin only)
AutomationApiContracts 9 entries incl. listFlows 8 entries; none is a GET at the bare path
ADR-0087 — D3 semantic entry automation-flow-list-route-retired + RETIRED_DEFS_BY_MAJOR[18]: api/ListFlowsRequest, api/ListFlowsResponse, api/FlowSummary

Every other /automation route 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/automation answers 405 METHOD_NOT_ALLOWED with Allow: 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, and listFlows is never called. It is a 405 and not a 404 because POST still 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/hono catch-all) gets handled: false and renders its own 404; the domain's anonymous floor still answers 401 first there (pinned in anonymous-gate-actions-automation.test.ts).

Door ③ — spec half: ListAiConversationsResponseSchema gains hasMore

Describe texts, quoted exactly (they ship in the JSON Schema and the references page):

  • cursor: "The id of 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, then id, both descending"
  • hasMore (new, required): "Whether at least one more conversation follows this page. When true, send the id of the last conversation in conversations as cursor to read the next page."

nextCursor is 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.mdx shows the next-page call (cursor = last id held).

The open choice this PR settles: hasMore required, nextCursor absent — four axes

axis hasMore required (taken) hasMore optional
实际业务需求 (measured) Readers today: none parse it — the SDK returns the array, objectui's useConversationList reads ?limit=50 page one only (packages/app-shell/src/hooks/useConversationList.ts at main 5c61e524 and pin f8a9d0fb). The need it serves is the truncated sidebar: a caller with more than limit conversations cannot tell a full page from the last one. The only producer is cloud, which cloud#2426 makes compute it. Same readers; the flag could be absent forever on a conforming server, so the need is served only by convention.
项目长远合理性 The declaration states what every server must do; the window until cloud#2426 lands is ruled ("land cloud after this card") and named, not baked in. Bakes the transition window into the permanent contract.
防 AI 写错 A server or mock written against ListAiConversationsResponse without hasMore is a tsc error and a parse refusal (pinned). Omission is spec-valid; every reader needs an absent-means-unknown fallback — the consumer-side tolerance the frame rejects.
创业阶段不扩散 No staged window, no new gate, no new field beyond the ruled one. —

nextCursor: zero readers anywhere, and by the ruling's own definition it equals the last conversation's id, 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 when hasMore is 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)

  1. At 8d1f7ab7: ListAiConversationsResponseSchema was { conversations } only (protocol.zod.ts:2946); ListFlows* / FlowSummarySchema at automation-api.zod.ts:59-109; listFlows at :661-666; all three exported in api-surface, declaration-map, export-origins — confirmed.
  2. 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 base 8d1f7ab7), objectui pin f8a9d0fb and main 5c61e524, cloud main 48d70663. Positive controls on the same instruments: objectui apps/console/src/pages/developer/FlowRunsPage.tsx:152 client.meta.getItems('flow') and packages/app-shell/src/views/setup/PackagedAutomationPage.tsx:144 GET /meta/flow hit at both objectui refs; cloud packages/service-ai/src/routes/ai-routes.ts hit for the conversation route. objectui's useApiDiscovery.ts names /api/v1/automation only as a route prefix with a POST /trigger endpoint — not the list. Confirmed.
  3. fix(spec): AutomationApiContracts names the served /api/v1/automation paths #20056 keeps AutomationApiContracts equal to the served paths; the removal takes the entry and the mount together, and its pin is green — confirmed.
  4. cloud main 48d70663: ai-routes.ts answers { conversations } with no hasMore; objectql-conversation-service.ts orders ascending and keyset-pages on (created_at, id) — confirmed; cloud ⛔ not edited.
  5. Generated surfaces, regenerated with the tooling, never by hand except the two deletions the gates prescribe by name: json-schema.manifest/api.json (−3 keys) and authorable-surface/api.json (−16 keys, reported by the build as "def no longer emitted by this build" — path 3); then gen:migration-registry, check:generated --fix (api-surface, export-origins, declaration-map, references docs, strictness-ledger counts), gen:test-typecheck-debt (runtime ledger −1 signature, the listFlows TS2339 it recorded vanished), check:route-ledger-census --fix. authorable-surface.base.json untouched. Three merges of origin/main went through scripts/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:generated reported all 15 artifacts current and main's sibling entries (ui-report-joined-container-selection-refused, export-job-family-retired) are present in registry.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, listFlows never called; POST at the same path still mounted (anonymous → 401); the inventory-privacy probe moved to GET /api/v1/automation/_status → 401.
  • packages/runtime/src/domain-handler-registry.test.ts — real dispatch(): GET /automation → exactly { handled: false }, identical to a never-served sub-path, listFlows never called; GET /automation/_status on the same dispatcher → 200 (anti-vacuity).
  • packages/runtime/src/domains/anonymous-gate-actions-automation.test.ts — anonymous GET / still 401 (floor precedes routing), authenticated GET / 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.automation is false, with a @ts-expect-error on the access (the client test layer compiles with 0 debt, so the directive is live); meta.getItems('flow') targets GET /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, no listFlows, no GET /api/v1/automation, and still POST /api/v1/automation.
  • packages/spec/src/api/protocol.test.ts — accepts hasMore true/false and keeps it; refuses a page without it: issue code invalid_type, path ["hasMore"], message Invalid input: expected boolean, received undefined; refuses hasMore: 'false'; the response shape is exactly conversations + hasMore; the request keeps agentId / limit / cursor.
  • packages/spec/src/type-alias-convention.pin.test.ts — the FlowSummarySchema pin 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 empty schedule block — 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.ts population 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:27 states 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 in authz-ledger-population.baseline.ts records the 82 → 81 move — rows and domains derived from the ROUTE_LEDGER table (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.mjs re-planted the GET base + '/automation' mount in dispatcher-plugin.ts (anchor 1 → 0, blob acbf6f93 → 01d21238, marker count 1): the socket pin went red — expected 401 to be 405 — and the restore leg proved blob == HEAD and an empty git 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 at 3b8a66d6; the only change from 3b8a66d6 to f3ed706f is 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 at f3ed706f. Heavy runs went through scripts/pm/os-verify-lock.sh.

run reading
@objectstack/spec vitest run --project local 540 files, 15872 passed, 2 todo
@objectstack/runtime vitest run --project local 279 files, 3909 passed, 1 skipped
@objectstack/client vitest run 50 files, 636 passed
@objectstack/dogfood: the whole authz-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.ts 3 files, 130 passed
typecheck for spec, runtime, client and dogfood exit 0 each; the test layers are OK (runtime ledger: 190 errors / 68 signatures, one fewer than base; client: 0)
pnpm --filter @objectstack/spec check:generated all 15 artifacts current
node scripts/check-issue-citations.mjs (live, diff-scoped) "✅ check-issue-citations: every citation this change adds resolves (or is a declared cross-repo reference)." — 22 judged: 18 resolve, 2 resolve as pull requests, 2 cross-repo
dispatch-gates.mjs --commands union: 116 families derived for this diff at f3ed706f 116 run, all exit 0. --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)"
roster gates whose roster sits in this diff's directories: check:authz-resolver, check:error-code-casing, check:filter-alias-parity, check:route-ledger-census exit 0 each
eslint, narrowed to the added or modified .ts/.mjs files (round 0) --format json: 24 files, 0 errors, 0 warnings. Population: the files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] block in eslint.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-wide pnpm lint runs 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, fixed

  1. Lint & Repo Gates → check-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 in retired-defs/18.api__ListFlowsRequest.ts and in its generated copy in registry.ts. The file now names the precedent by its ADR-0087 entry id, package-rollback-response-retired, and its api/PackageRollbackResponse row, with no guessed number. The registry was regenerated.
  2. Dogfood Regression Gate (1/3) → authz-conformance.test.ts [finding] authz-conformance.matrix.ts:27 states 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 the ROUTE_LEDGER table: 81 rows, 21 distinct domains, so the domain count did not move. The same sweep fixed the census reading in authz-probe-blind-spot.census.ts and the dated note in authz-ledger-population.baseline.ts. Two statements remain that are dated and historically true: the reading in scripts/check-route-ledger-census.mjs's header ("at the commit that added this gate") and the lint.yml comment (see Acceptance notes).
  3. Merge: after feat(spec)!: retire the export-job API family, IExportService and ScheduleState (ADR-0049) #20194 ([Decision] the export-job API contract family (ScheduledExport / ScheduleExportRequest / ExportJob…) has zero consumers and, after #16320, an empty schedule block — retire the family, keep the contract, or build the scheduler? #17158) merged (4db1bf17, an ancestor of this head), origin/main came in through os-regen-merge.sh as merge fd76315a:
    • registry.ts took main's side and was then regenerated from both sides' entries (+95 lines, no deletions).
    • The type-alias pin test was resolved by hand, keeping both receipts.
    • Main's generated shards were taken and regenerated in 3b8a66d6, with this PR's two hand deletions (manifest −3, authorable-surface −16) re-applied on top of main's bytes.
    • The generated delta against origin/main is exactly this PR's: the three retired defs are out, ListAiConversationsResponse:hasMore is in, and strictness-ledger api/ 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:251 names GET /automation in prose describing the pre-[17.0-rc2验收] 安全:REST /actions 与 /automation 派发路由缺少匿名拒绝门 —— 未认证调用者可触发 system 提权的 RLS/FLS 绕过写入 #5519 ungated state; historical, not edited.
  • With no automation service registered, a catch-all transport answers the retired path with the domain's 501 (the capability probe precedes routing domain-wide, by design, so a 501-vs-404 does not fingerprint deployments); pre-existing ordering, unchanged.
  • The SDK's ai.conversations.list() does not surface hasMore (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.ts files 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.md drops the list line and points at GET /api/v1/meta/flow; content/docs/api/plugin-endpoints.mdx teaches both doors; docs/qa/platform-checklist/areas/access-security.json re-points its automation probes to /_status and to a single-flow read.

Changeset: .changeset/19543-list-doors-3-4.md — minor for spec / client / runtime, a BREAKING banner with FROM → TO per surface, the Clause-② line with its (narrowing) arm, and the ADR-0087 disposition marker registered automation-flow-list-route-retired. No content/docs/releases/ edit.

Written by the domain:spec seat-1 dispatch (session session_01Rjy9MeetSfq34PKn81CRiN), branch claude/issue-19543-list-doors-3-4.

…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
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
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/client, @objectstack/runtime, @objectstack/service-automation, @objectstack/spec, touching 24 documentable anchor(s). ⚠️ 8 changed file(s) yielded no anchor (packages/runtime/test-typecheck-debt.json, packages/services/service-automation/README.md, packages/spec/api-surface/api.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

19 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json e7f69dbba6764e980c49d2f54c975573830228ee.

⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 8 changed file(s) yielded no anchor (packages/runtime/test-typecheck-debt.json, packages/services/service-automation/README.md, packages/spec/api-surface/api.json, …) — pages documenting those are invisible to this run
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 143 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e7f69dbba6764e980c49d2f54c975573830228ee → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 13b15ebd38122c91ab36470199f3192e73fde4a6 — the merge of head f3ed706f010285f31d2fef52fad46db3432b93ac into base e7f69dbba6764e980c49d2f54c975573830228ee, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e7f69dbba6764e980c49d2f54c975573830228ee → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: eb08fb39db3716f8d7cd0cfd0f77583ca28859cf

① Derived judgments

Ruling read from card comment 5825819437 (door ④ 「退役,统一走 /meta/flow」; door ③ 「Ruled: the list is newest first」, 「This card owns the spec half. ListAiConversationsResponseSchema gains hasMore (and nextCursor, if declared, meaning "the id of the last conversation on the page"), cursor is described, and the SDK doc stays "newest first"」, 「Land them together, or land cloud after this card」). cloud#2426 is an open ISSUE (bug, pm:queue, priority:p1, unassigned, zero comments, no PR) whose body asks for desc order, { conversations, hasMore } from limit + 1, unknown cursor → 400, and "land with or after the spec half".

  • Door ④ — GET /api/v1/automation unmounted, twin included: RIGHT. packages/runtime/src/dispatcher-plugin.ts at head: registerAutomationRoutes mounts POST base/automation first and no GET at the bare path; it is invoked for prefix and for ${prefix}/environments/:environmentId (head L1741, L1745, L1749), so the environment-scoped twin is unmounted by the same edit. The mounted verb/path list at main vs head differs by exactly the one removed server!.get(base/automation); _status, actions, connectors, :name (GET/PUT/DELETE), trigger/:name, :name/trigger, :name/toggle, :name/runs, :name/runs/:runId, resume, cancel, restore-suspension, screen and the env-scoped copies are byte-identical (git diff --stat: one hunk, +8/−9).
  • Door ④ — domain declines GET /: RIGHT. packages/runtime/src/domains/automation.ts has two hunks only (the docblock at ~L1821 and the branch removal at ~L2035). Order inside handleAutomationRequest is unchanged: [17.0-rc2验收] 安全:REST /actions 与 /automation 派发路由缺少匿名拒绝门 —— 未认证调用者可触发 system 提权的 RLS/FLS 绕过写入 #5519 anonymous floor (L1903–1933) → Automation flow write routes (POST/PUT/DELETE /api/v1/automation) lack the manage_metadata gate — a plain tenant edits/deletes flows for every organization on a walled shared-database deployment #10145 capability check → service probe returning capabilityUnavailable 501 (L2007–2018) → routes → return { handled: false } at the foot. So an anonymous catch-all caller still gets 401 first, an authenticated caller with no service gets 501, and with a service gets the transport's own 404. Pinned in domain-handler-registry.test.ts ({ handled: false }, listFlows never called, /_status 200 anti-vacuity) and anonymous-gate-actions-automation.test.ts (anonymous GET / still 401; authed GET / unhandled).
  • Door ④ — wire answer 405 + Allow: POST on the Hono host: RIGHT, and pre-existing host behaviour. Producer is packages/plugins/plugin-hono-server/src/adapter.ts:1174–1197 (METHOD_NOT_ALLOWED, c.header('Allow', …)), untouched by this PR (no packages/plugins file in the 40-file list). Pinned on a real socket in dispatcher-plugin.anonymous-gate.integration.test.ts with a POST-only control path and a byte-equality check. The catch-all: packages/adapters/hono/src/index.ts:424–513 toResponse → errorJson(c, 'Not Found', 404) when !result.handled — the changeset's "answers its own 404" is accurate.
  • Door ④ — client.automation.list removed: RIGHT. packages/client/src/index.ts ~L5417 (method replaced by a comment); client.test.ts:1227 carries a live @ts-expect-error (client test layer at 0 debt) plus meta.getItems('flow') → GET /api/v1/meta/flow control.
  • Door ④ — spec removals: RIGHT. ListFlowsRequestSchema, ListFlowsResponseSchema, FlowSummarySchema and the five types are gone from automation-api.zod.ts; AutomationApiContracts 9 → 8 with no GET /api/v1/automation and POST /api/v1/automation kept (pinned in automation-api.zod.test.ts). Generated artefacts follow (api-surface −8, declaration-map −6, export-origins −8, json-schema.manifest −3, authorable-surface −16/+1, authorable-defaults −1, references docs, strictness counts, type-alias pin 790 → 789, runtime debt ledger −1). CI at head: "Migration registry matches its entry files" (Lint step 11) success, "Route-ledger census guard" (step 136) success, Spec property liveness success, Build Core success.
  • Door ④ — re-pointed runtime tests still test their original subjects: RIGHT. http-dispatcher.test.ts async/sync/getServiceAsync resolution cases and the degraded-engine case now drive GET /_status and assert on getFlowRuntimeStates output (subject: service resolution, route-independent); the 501-no-service case keeps 501 because the probe precedes routing; the stub-slot table swaps the '' GET row for _status and still asserts the stub is never called; tenancy-posture-outage reads GET /api/v1/automation/_status — tenancy verdict, anonymous floor and 501 probe all run ahead of routing, so the three legs keep their readings; automation-run-read-permission-gate drops the '' row from the AUTHENTICATED_ONLY audit table (nothing left to audit); write-capability-gate keeps GET /:name. No subject was lost.
  • Door ④ — /meta/flow is a real replacement: RIGHT. flow is a registered metadata type (packages/spec/src/kernel/metadata-plugin.zod.ts:105, :887 with allowRuntimeCreate: true, domain: 'automation'); GET /api/v1/meta/:type is a served REST list (packages/rest/src/execctx-consumer-census.test.ts:526); at the objectui pin f8a9d0fb both real flow lists already read it (apps/console/src/pages/developer/FlowRunsPage.tsx:152 client.meta.getItems('flow'); packages/app-shell/src/views/setup/PackagedAutomationPage.tsx:144 GET /meta/flow). It answers full definitions, not the retired bare-name shape — the changeset says so.
  • Door ④ — zero live callers of the retired surface: CONFIRMED. objectui pin f8a9d0fb and main 5c61e524: no automation.list( / ListFlows* / FlowSummary; apps/console/src/pages/developer/hooks/useApiDiscovery.ts:176 names /api/v1/automation only as a group prefix with a single POST /trigger endpoint. cloud main 48d70663: none. objectstack head: only the retirement's own prose and absence pins.
  • Door ③ — hasMore REQUIRED, nextCursor absent, cursor described, newest first: RIGHT on the words. packages/spec/src/api/protocol.zod.ts:2950–2977 at head: hasMore: z.boolean() (required), shape exactly ['conversations', 'hasMore'] (pinned in protocol.test.ts:2726–2766, including the refusal invalid_type at ["hasMore"]), cursor described as "The id of the last conversation on the previous page … An id that names no conversation of the caller is refused …" — the same meaning as the ruling's "the id of the last conversation on the page" and cloud#2426's unknown-cursor → 400. conversations describe states the full order (creation time then id, both descending). SDK packages/client/src/index.ts:6549–6560 unchanged: doc "List the caller's conversations, newest first", returns body?.conversations ?? []. The ruling names no optionality; REQUIRED is the stricter reading and matches door ①'s precedent (ListRunsResponseSchema.hasMore: z.boolean(), automation-api.zod.ts:570). The four-axis case in the PR body holds: nothing in-repo needs an absent-means-unknown fallback.
  • Door ③ — who parses or constructs this response today: MEASURED, nothing breaks. objectstack: the SDK is the only reader and does not validate — unwrapResponse (index.ts:7224–7232) is a success/data unwrap with no zod; packages/runtime/src/domains/ai.ts only forwards to __aiRoutes supplied by service-ai; no in-repo mock or producer constructs { conversations: [...] } outside spec/client tests. objectui pin f8a9d0fb (file identical at main 5c61e524): packages/app-shell/src/hooks/useConversationList.ts uses fetch and reads conversations only; no import of ListAiConversations* anywhere in objectui. cloud main 48d70663: packages/service-ai/src/routes/ai-routes.ts:661 answers { conversations } with no hasMore; cloud imports no ListAiConversations* symbol (git grep on a fresh clone: none) and pins objectstack at bdea10a1 (.objectstack-sha), so it does not even take this spec version until a re-cut. Consequence: no typecheck or runtime break anywhere; cloud's wire answer is off-contract until cloud#2426 lands — exactly 「land cloud after this card」, not worse.

② Semver level

  • minor for spec / client / runtime with a BREAKING banner is the launch-window convention stated in scripts/check-changeset-no-major.mjs ("we ship breaking changes as minor"); both "Check Changeset" runs at head are success. Clause-②: yes (narrowing) is a recognised arm (scripts/check-adr-0087-registration.mjs:2248 self-test case).
  • ADR-0087 registration shape: entries/semantic/18.automation-flow-list-route-retired.ts has exactly the SemanticMigration fields (id, surface, replacement, reason, acceptanceCriteria; packages/spec/src/migrations/types.ts:37–45), no backticks in surface; registry.ts is generated (gen:migration-registry, Lint step 11 green); the three RETIRED_DEFS_BY_MAJOR[18] rows sit in sorted order (api/CrudEndpointPattern, api/FlowSummary, api/HandlerStatus, api/ListFlowsRequest, api/ListFlowsResponse, …). Major 18 is right for spec 17.4.0 (same convention as the sibling 18.api__MetadataEffectiveResponse.ts). Text is truthful against measurement (405 + Allow: POST on the Hono host, 404 on the catch-all, 401 floor first, listFlows never called over HTTP, 8 contract entries, TS2305 on import) — except that reason prose in the two retired-defs files cites #8715, which no longer resolves (see ③, item 1).
  • FROM → TO in .changeset/19543-list-doors-3-4.md is accurate per surface (route incl. twin, SDK method, three schemas + five types, contract entry, door ③ response) — each checked above.
  • Door ③ without a ledger entry of its own: acceptable. ADR-0087's D3 chain prescribes migrations for authored metadata; nobody authors or persists a ListAiConversationsResponse, and the changeset prose carries the FROM → TO and "who notices". The marker grammar does offer not-required (no-migration-prescription); the changeset's only marker is registered automation-flow-list-route-retired, which the registration gate accepted. Not blocking; a one-line explicit disposition for door ③ would be the cleaner record.

③ Boundary flags

  1. BLOCKING — Lint & Repo Gates at the head concluded failure (job 108571747003, step 189 "Issue citations this change adds resolve on the board"): #8715 is allocated-but-absent at packages/spec/src/migrations/entries/retired-defs/18.api__ListFlowsRequest.ts:13 and packages/spec/src/migrations/registry.ts:18312 ("the [finding] The ApiKey reference table documents better-auth's apiKey-plugin schema — a plugin this platform does not load and a shape sys_api_key does not have #8715 route-3 shape"). Verified: GET /issues/8715 → 404 (control #8716 → 200). Steps 190–191 never ran (unmeasured tail). The gate's own remedy: name a resolving target, or keep the number and say in prose that it no longer resolves and what the live record is. (main carries the same phrase in 18.api__MetadataEffectiveResponse.ts:24, pre-existing and not judged because it was not added.)
  2. BLOCKING — Dogfood Regression Gate (1/3) at the head concluded failure (job 108571788457): packages/qa/dogfood/test/authz-conformance.test.ts [finding] authz-conformance.matrix.ts:27 states 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 "every figure the docblock states equals what its ledger holds TODAY" and "the anchor is the PATH …" — AssertionError: expected [ Array(1) ] to deeply equal []. Cause: packages/qa/dogfood/test/authz-conformance.matrix.ts:27–28 still states "packages/runtime/src/route-ledger.ts (82 rows / 21 domains)" while this PR moved the ledger to 81 rows (route-ledger.ts:280, check:route-ledger-census --fix). The pin (DOCBLOCK_LEDGER_CLAIMS, authz-conformance.test.ts:1511–1524) reads the table live. main at d7c02413 is green on the same shard, so this is PR-caused; the dev's local dogfood run covered two files, not this one. The rollup Dogfood Regression Gate is red for the same reason. Fix is one figure (82 → 81) in that docblock.
  3. Same drift, unpinned: packages/qa/dogfood/test/authz-probe-blind-spot.census.ts:109 still says "82 rows over 21 domains" while the PR edited the same file's population to 81 at ~L419. Should be corrected in the same act.
  4. 405 versus the saved-report precedent's 404: the ruling names no status; 405 is the Hono host's pre-existing method-mismatch answer because POST createFlow keeps the path. An anonymous caller now learns Allow: POST before the auth floor — the same as for any POST-only path (the /:name/toggle control is byte-identical), so no new information leaks. Accepted as measured; the flag is that a deployment on the catch-all transport answers differently (401 anonymous, then 404 / 501 by service presence), so the wire answer is transport-dependent and the docs (plugin-endpoints.mdx:55) name only the default host's 405.
  5. Catch-all 404 and the no-service 501: pre-existing dispatcher 其余服务域仍只判槽位占用、不读 handlerReady —— #4000 在 analytics 一域落地后剩下的类推面 #4058 ordering (probe before routing), unchanged; a deployment with no automation service answers 501 for the retired path exactly as for every path of the domain. Not a regression.
  6. SDK client.ai.conversations.list() still resolves to the bare array, so hasMore — the field this PR makes required — is unreadable through the SDK, and content/docs/api/client-sdk.mdx:81–83 teaches paging by cursor: page.at(-1)?.id with no stop signal other than a short page. The ruling's "the SDK doc stays newest first" keeps this out of scope; it needs its own card when a paging consumer arrives (the PR reports carrier: none).
  7. The cloud window: cloud#2426 is an unassigned issue with no PR, and cloud pins objectstack at bdea10a1; after this lands the spec declares a required field no server sends until cloud re-cuts and lands docs(adr-0078): the completeness gate — Zod-valid-but-inert metadata must fail loudly at author time #2426. That is the ruled order, but it is open-ended today.
  8. Docs and objectui: no page or example in objectstack at head, and no objectui code at the pin, still teaches or calls GET /api/v1/automation as a list or client.automation.list (measured by grep over content, docs, skills, examples, apps, package READMEs). packages/adapters/hono/src/hono.test.ts:454 names GET /api/automation for adapter delegation to a mock and stays true; authz-conformance.matrix.ts:251 prose is historical.
  9. docs/qa/platform-checklist/areas/access-security.json: two string edits (anonymous probe → GET /api/v1/automation/_status; the post-DELETE verify → authed GET /api/v1/automation/showcase_reassign_wizard 200), file parses, meanings preserved (GET /:name is authenticated-only per 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 table). Note pnpm check:platform-checklist is maintainer-run, not CI (lint.yml:3061–3067), so it is not measured here.
  10. The PR body's "116 derived families, all exit 0" is honest but incomplete for this head: it declared check-issue-citations --census and the full dogfood gate NOT MEASURED, and both are the reds above. Final tally of the head's check-runs at review time: 31 success, 5 skipped, 3 failure (Lint & Repo Gates, Dogfood Regression Gate (1/3), Dogfood Regression Gate); Test Core 1–6/6, Type Check workspace/consumer/source/debt, Build Core, Temporal Conformance, Governed Surface Queue Guard, Spec property liveness and both Check Changeset runs are success.

Implemented-by: claude/issue-19543-list-doors-3-4
Reviewed-by: session_01Rjy9MeetSfq34PKn81CRiN

VERDICT: FAIL

  1. Lint & Repo Gates red at eb08fb39: the two new #8715 citations (entries/retired-defs/18.api__ListFlowsRequest.ts:13, registry.ts:18312) do not resolve; steps 190–191 unmeasured.
  2. Dogfood Regression Gate (1/3) red at eb08fb39: authz-conformance.matrix.ts:28 docblock says 82 route-ledger rows, the ledger this PR ratcheted holds 81 ([finding] authz-conformance.matrix.ts:27 states 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 pin, PR-caused; main green).

…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
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: f3ed706f010285f31d2fef52fad46db3432b93ac

① Derived judgments

This is a DELTA review over round 0's record (PR comment 5853906979, head eb08fb39, verdict FAIL on two CI reds). Round 0's non-CI judgments stand: nothing in the delta touches door ④'s route/domain/SDK/spec removals, door ③'s hasMore declaration, or the re-pointed runtime tests — each of those files' +/- lines against the PR's base are byte-identical between round 0 and this head (measured below). The delta is one push of four commits on top of eb08fb39: c69a8c96 (the fixes), fd76315a (merge of origin/main 172b4cf3, bringing #20194), 3b8a66d6 (regeneration on the merged tree), f3ed706f (revert of a lint.yml comment edit, net zero). Merge base of origin/main and f3ed706f is 172b4cf3; of origin/main and eb08fb39 it was e2c4e125.

Blocking item 1 (citation #8715) — RESOLVED. packages/spec/src/migrations/entries/retired-defs/18.api__ListFlowsRequest.ts:13-15 now reads 「the whole-def route-3 shape, as the precedent entry package-rollback-response-retired (and its api/PackageRollbackResponse row) recorded it」; no #8715 and no substituted number. The target resolves on origin/main: entries/semantic/18.package-rollback-response-retired.ts (id: 'package-rollback-response-retired') and entries/retired-defs/18.api__PackageRollbackResponse.ts both exist, and the latter's own prose is the "route-3 shape" precedent the new text points at. The generated copy in registry.ts (RETIRED_DEFS_BY_MAJOR[18], hunk at L18686) carries the same three lines. The only issue number the delta ADDS to a PR file is #19543 (the card, in authz-ledger-population.baseline.ts:63) — it resolves. CI: Lint & Repo Gates step 11 「Migration registry matches its entry files」 success and step 136 「Route-ledger census guard」 success at this head; step 189 「Issue citations this change adds resolve on the board」 — see the CI reading below.

Blocking item 2 (82-row ledger figures) — RESOLVED. Measured with the #17111 pin's own instrument (scoped to export const ROUTE_LEDGER, rows = route: ' occurrences, domains = DISTINCT domain: ' values): packages/runtime/src/route-ledger.ts holds 81 rows / 21 domains at f3ed706f (groupOccurrences 81 = rows, so the scope reads only rows); 82 / 21 at the base 172b4cf3 and at e2c4e125. The one removed row is GET /automation (domain /automation, 17 → 16 rows there); the domain set is unchanged. rest-route-ledger.ts holds 83 / 18 at head and base. The matrix docblock (authz-conformance.matrix.ts:27-28) folded per the pin gives exactly one present-tense figure per ledger — (83 rows / 18 families) and (81 rows / 21 domains) — both equal to the readings, and both wrap mid-phrase so the pin's CONTROL leg (unfolded = 0, folded = 1) holds. The sibling authz-probe-blind-spot.census.ts:109 reads 「81 rows over 21 domains」. authz-ledger-population.baseline.ts:61-65's dated note — 「80 rows / 21 domains — 82 after the two operator run-lifecycle rows landed, and 81 since #19543 retired the GET /automation flow-list row, all three moves under the already-classified /automation domain, so the key arithmetic below is unmoved」 — is truthful: 82 at base, 81 at head; the removed row and every run-lifecycle row (…/runs/:runId/resume, /cancel, /restore-suspension) sit in /automation, and the baseline keys are domain-level so none moves. The 80 and the rest figure 94 rows / 19 families are the pre-existing 2026-08-31 reading, not touched by this delta.

Merge integrity — INTACT, nothing lost or corrupted on either side.

.github/** — ABSENT. git diff --stat 172b4cf3 f3ed706f -- .github is empty; c69a8c96's lint.yml edit is exactly reverted by f3ed706f (blob c168ee6d restored).

CI at f3ed706f (/commits/f3ed706f…/check-runs?per_page=100, polled 09:57Z → 10:09Z): Lint & Repo Gates success (job 108596732752, completed 10:08:43Z; step 11 「Migration registry matches its entry files」, 136 「Route-ledger census guard」, 182 「Spec type-alias convention gate (ADR-0122)」, 189 「Issue citations this change adds resolve on the board」, 190 and 191 — the round-0 red and its unmeasured tail — all success). Dogfood Regression Gate success — shards 1/3 (job 108596777498, the #17111 pins, 10:07:59Z), 2/3, 3/3 and the rollup all success. Tally at 10:09Z: 40 check-runs — 29 success, 4 skipped, 0 failure, 7 in progress: Test Core (1/6)–(6/6) and Type Check · workspace. Those seven were success at eb08fb39 (round 0), the delta touches no source they compile or run beyond comment lines, count constants and the merged origin/main (green on main), and the merge queue will not admit the PR without them; they are named here as still running at the time of this record, not as measured green. Build Core, Build Docs, Dogfood Verify CLI, Temporal Conformance, Type Check · source / consumer / debt ledger, Spec property liveness, Governed Surface Queue Guard and both Check Changeset runs are success.

② Semver level

Unchanged from round 0: .changeset/19543-list-doors-3-4.md (@objectstack/spec / @objectstack/client / @objectstack/runtime minor, BREAKING banner, Clause-②: yes (narrowing), marker registered automation-flow-list-route-retired) and entries/semantic/18.automation-flow-list-route-retired.ts are byte-identical in +/- lines between e2c4e125..eb08fb39 and 172b4cf3..f3ed706f; the three retired-defs rows are the same three. Both Check Changeset runs at this head are success. minor with a BREAKING banner remains the launch-window convention (scripts/check-changeset-no-major.mjs).

③ Boundary flags

  1. packages/qa/dogfood/test/authz-probe-blind-spot.census.ts:114 「Nine more ledgers exist repo-wide (290 rows in total)」 — pre-existing at e2c4e125 and untouched here. Re-measured: the nine other *-route-ledger.ts files hold 117 rows (94 in the five with the { route: spelling + 23 in the other four), so all eleven hold 281 at this head (282 at base); the 290 was stale before this PR. Unpinned, carrier: none — the dev's Acceptance note is accurate. Not blocking.
  2. .github/workflows/lint.yml:3589-3590 「a UNION that still holds 26 of the 82 rows' wire patterns」 — present-tense prose in a workflow comment that no gate reads; the dev's attempt to date it was reverted (a .github diff blocks the merge queue), so the PR is net zero there and the comment stays stale. Historical, not blocking; a maintainer-side one-liner when the file is next touched.
  3. authz-ledger-population.baseline.ts:61 still opens with the dated 2026-08-31 rest reading 「94 rows / 19 families」 while the matrix (pinned) says 83 / 18 today; the dev extended only the runtime clause. Dated and unpinned, pre-existing; consistent with the file's own SHRINK-ONLY rule. Noted, not blocking.
  4. The precedent the new citation names, 18.api__PackageRollbackResponse.ts:15, and main's 18.api__MetadataEffectiveResponse.ts:24 themselves still say 「the [finding] The ApiKey reference table documents better-auth's apiKey-plugin schema — a plugin this platform does not load and a shape sys_api_key does not have #8715 route-3 shape」 — pre-existing on main, not added by this PR, so outside the citation gate's diff scope and outside this review; the new text avoids repeating the number.
  5. Merge mechanics: fd76315a is not self-consistent on its own (registry and shards at main's state), which is the documented os-regen-merge.sh deferral; the head is. No standing gate runs per commit, so this is a note only.
  6. Round 0's flags 4–9 (transport-dependent 405/404, the no-service 501, the SDK not surfacing hasMore, the open cloud#2426 window, hono.test.ts:454 and matrix.ts:251 historical prose, the maintainer-run platform checklist) are unchanged by the delta and stand as written.

Implemented-by: claude/issue-19543-list-doors-3-4
Reviewed-by: session_01Rjy9MeetSfq34PKn81CRiN

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 27, 2026 10:19
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 27, 2026
Merged via the queue into main with commit 3875ae6 Sep 27, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-19543-list-doors-3-4 branch September 27, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants