Skip to content

fix(runtime): on the standalone server every /actions verb reaches the actions domain, so an anonymous caller gets 401, and no Allow lists a verb mounted only to refuse - #24244

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24228-actions-anonymous-floor
Oct 18, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-24228-actions-anonymous-floor

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #24228

Clause-②: no

Basis for the Clause-② line: every request whose answer changes is refused before and after. On the standalone server an anonymous non-POST request to /api/v1/actions/... moves from the HTTP adapter's 405 to the actions domain's 401, and a request under a verb no route takes on /keys, the /notifications faces or /mcp/skill moves from the adapter's 405 (whose Allow listed refusal-only verbs) to the domain's own refusal. No verb that was refused is admitted, no route is added, and no accept set or export widens.

What changed

  • packages/runtime/src/dispatcher-plugin.ts, three parts:
    1. A declaration of the paths whose domain decides every verb (domainDecidesEveryVerb). Each one is the serving route's verb and pattern, made beside that route: the three POST /actions patterns in registerActionRoutes (so both the unscoped and the /environments/:environmentId base), POST /keys, the three /notifications faces and GET /mcp/skill.
    2. The dispatcher's existing fallback seam answers those paths. setFallbackHandler runs only when the router matched no route, and before the adapter's own unmatched answer. When the transport's router resolves the request path, under a declared verb, to a declared pattern (IHttpServer.resolveMountedRoute, the router's own verdict, no second matcher), the request goes to dispatch() as createHonoApp's catch-all sends it: req.method, the whole sub-path after the prefix (so dispatch() reads the environment off a scoped path, since an unrouted request has no route params), req.query, and no body. The domain answers. Its anonymous baseline is its first statement (ADR-0138 D2), and its 405 carries its own Allow. Any other unmatched request is handled as before: the declarative-endpoint step, then the adapter's 404/405.
    3. Absence is loud. A transport with the fallback seam but no resolveMountedRoute keeps the adapter's answer on those paths, and the boot says so once, at warn, with the consequence and the remedy. The existing no-setFallbackHandler warning names the same consequence in one more clause.
  • Pins (real Hono socket, HonoServerPlugin + createDispatcherPlugin, CORS off):
    • new packages/runtime/src/actions-every-verb.hono.integration.test.ts (79 cases): three path shapes on both prefixes. For GET, PUT, PATCH, DELETE and TRACE, an anonymous call gets 401 UNAUTHENTICATED, error.httpStatus 401, no error.details and no Allow. A signed-in call gets 405 METHOD_NOT_ALLOWED, Allow: POST, error.httpStatus 405 and no error.details, which is the domain's door and not the adapter's. HEAD is checked by status and header. Anonymous OPTIONS gets the domain's 405 + Allow: POST (see Acceptance notes). Controls: anonymous POST 401 unchanged, and a four-segment path stays the adapter's 404.
    • keys-every-verb.hono.integration.test.ts: OPTIONS and TRACE, 4 cases (anonymous 401 no Allow; signed-in domain 405 with Allow: POST).
    • notifications-every-verb.hono.integration.test.ts: OPTIONS and TRACE on each face, 12 cases (anonymous 401; signed-in the dispatcher's 404 ROUTE_NOT_FOUND, no Allow).
    • mcp-skill-method-not-allowed.hono.integration.test.ts: its PUT boundary case pinned the adapter's Allow ['DELETE', 'GET', 'HEAD', 'POST'], which is the defect. It is replaced by PUT, PATCH, OPTIONS and TRACE getting the domain branch's 405 with Allow: GET.
    • dispatcher-plugin.fallback-absence-warn.test.ts: the new warning, once at warn, and silence when the resolver exists.
    • domains/keys-actions-method-not-allowed-allow.test.ts (PR fix(runtime): the keys and actions domains answer their 405 with an Allow header naming POST #24227's pin, the control): assertions unchanged, still green. Its docblock and one title said the standalone /actions 405 is the adapter's because GET is not mounted. After this change that is false, so the wording now says it is the domain's, through the fallback seam.
  • .changeset/24228-actions-every-verb-anonymous-floor.md: @objectstack/runtime patch.

Not touched: packages/spec (the IHttpServer contract line included), packages/plugins/plugin-hono-server, domains/actions.ts, the dispatcher.dispatch(… req.body …) sites (no existing mount changed), readGatedSessionUser, route-ledger.ts (no route is mounted, so no row and no census move).

The route, and why it is not refusal-only mounts (H2, measured)

  • The adapter cannot tell a refusal-only mount apart. Its Allow is allowedMethodsForPath over registeredRoutes, and only get/post/put/delete/patch(path, handler) fill registeredRoutes. Nothing in that signature marks a mount as refusal-only. A marker would have to ride on the handler object, which is a cross-package protocol the contract does not declare, or be a new parameter, which is a packages/spec change. This PR takes neither.
  • The adapter does not need to tell them apart. Its unmatched answer is the only producer of the wrong Allow. That answer runs after the fallback seam declines. So if the domain answers every request the router cannot route on its paths, the adapter never builds an Allow there. This is the first candidate in H2. It needs no packages/spec change and no adapter change, and the contract line stays true where the adapter does answer.
  • Why not the /keys shape for /actions. With the five IHttpServer verbs mounted, a verb IHttpServer cannot register still reaches the adapter. That is OPTIONS when CORS is off. It is also TRACE or an extension method such as PROPFIND with CORS on too, because CORS answers only OPTIONS. The adapter's Allow there is every registered verb. So refusal-only mounts on /actions would turn today's accurate Allow: POST into DELETE, GET, HEAD, PATCH, POST, PUT. With the fallback the four mounts are not needed. One mechanism covers every verb on /actions, and it covers the verbs left over on the families that already have the mounts.

Measured

Standalone composition in process: LiteKernel, a fake auth that answers a session only for a test header, HonoServerPlugin on a real socket, createDispatcherPlugin, and registerShareLinkRoutes at kernel:ready. Four compositions: CORS off or on, each with project scoping off or on (enableProjectScoping: true mounts both prefixes). For each, 14 paths × 9 verbs (GET HEAD POST PUT PATCH DELETE OPTIONS TRACE PROPFIND) × anonymous and signed in, through raw node:http. That is 1008 requests at base 2bcd406a2 and 1008 at head d60989609. The implementation blob 24648a251fc8 has been the same from there to the final head 6c8625968. 368 rows changed. Every changed row was the adapter's 405 at base and is the domain's answer at head: 148 now 401, 172 now the domain's 405, 30 now the dispatcher's 404, and 18 HEAD rows went from 405 to 401. No served answer moved.

H1, confirmed. At base, every anonymous GET/HEAD/PUT/PATCH/DELETE/TRACE/PROPFIND on all three /actions shapes and both prefixes got 405 + Allow: POST with the adapter's error.details, CORS on and off. OPTIONS got the same with CORS off and 204 with CORS on.

/api/v1/actions/task/close (all shapes, both prefixes alike) base head createHonoApp
anonymous GET PUT PATCH DELETE TRACE/PROPFIND 405, Allow: POST, adapter 401 UNAUTHENTICATED, no Allow 401, no Allow
anonymous HEAD 405, Allow: POST 401, no Allow 401
signed-in non-POST 405, Allow: POST, adapter envelope 405, Allow: POST, domain envelope 405, Allow: POST
anonymous OPTIONS, CORS off 405, Allow: POST, adapter 405, Allow: POST, domain 405, Allow: POST
OPTIONS, CORS on 204 204 204

The createHonoApp column: createHonoApp({ kernel, prefix: '/api/v1' }) over the same fake auth, driven with app.request, CORS off and on. PROPFIND stands in for TRACE there, because the fetch Request refuses TRACE.

H3, the Allow each refusal-only family answers (a verb no route takes: OPTIONS with CORS off, TRACE/PROPFIND with CORS either way, and on /mcp/skill also PUT/PATCH):

family served Allow at base at head
/keys POST DELETE, GET, HEAD, PATCH, POST, PUT anonymous 401 no Allow; signed-in domain 405 Allow: POST
/notifications GET DELETE, GET, HEAD, PATCH, POST, PUT anonymous 401; signed-in 404 ROUTE_NOT_FOUND; no Allow
/notifications/read POST same six same as above
/notifications/read/all POST same six same as above
/mcp/skill GET DELETE, GET, HEAD, POST domain 405 Allow: GET
/actions (3 shapes × 2 prefixes) POST POST (accurate; the defect was the missing 401) domain Allow: POST signed in; anonymous 401
/share-links (root) GET, POST GET, HEAD, POST unchanged, measured-clean
/share-links/abc DELETE DELETE, GET, HEAD, PATCH, POST, PUT unchanged, see Acceptance notes
/share-links/abc/resolve GET same six unchanged, see Acceptance notes

The head column matches the createHonoApp answers row for row on /actions, /keys, /notifications/read and /mcp/skill for GET/PUT/OPTIONS/extension verbs. Two exceptions predate this PR and are listed in Acceptance notes.

H4, confirmed. PR #24227's pin is green with its assertions unchanged. Its standalone /actions row (405, Allow: POST) now reads the domain's answer instead of the adapter's.

Tests

  • Targeted pins at 194b25dc2. The runtime tree at 194b25dc2 equals the final head for every file this PR touches, and the implementation blob is the same (24648a251fc8). Command: pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 over the six touched test files plus dispatcher-plugin.routes, share-links-every-path, dispatcher-plugin.endpoint-fallback and dispatcher-plugin.streaming-fallback. Result: Test Files 10 passed (10), Tests 234 passed (234).
  • Full suite at the final head 6c8625968, after merging origin/main and rebuilding (pnpm install --frozen-lockfile, turbo run build --filter='@objectstack/hono...'): pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 gives Test Files 413 passed (413), Tests 6411 passed | 19 skipped (6430).
  • pnpm --filter @objectstack/runtime typecheck at 6c8625968: exit 0, check:test-typecheck: OK with the ledger unchanged. All six touched test files are in the tsconfig.test.json program (--listFiles).
  • pnpm exec eslint --no-inline-config over the seven changed .ts files: exit 0.
  • test:repo (the repo vitest project) was not run locally. No file of that project changed, and CI's Test Core runs it.

Ablations (one per member, not kept). Each ran after the fix was committed, through node scripts/ablation-replace.mjs in WRAP mode, inside a script that also carries an EXIT/INT/TERM trap restoring from HEAD. Every pin imports ./dispatcher-plugin.js from source, and @objectstack/plugin-hono-server (read from its dist/) is not mutated, so no dist/ of the mutated module sits on the resolution path.

  • Member 1: the three /actions declarations replaced by a marker. The anchor went 1 to 0 and the blob 24648a251fc8 to 4bcfc24a0ac7. Result: Tests 72 failed | 27 passed (99) over the actions pin, the keys pin and PR fix(runtime): the keys and actions domains answer their 405 with an Allow header naming POST #24227's control. The 72 are every anonymous, signed-in, HEAD and OPTIONS row of the actions pin (expected 405 to be 401, expected undefined to be 405). Green: its 6 POST controls, its 404 control, the keys pin and the control file. Restore: blob equals HEAD (24648a251fc8), git diff HEAD is empty, and the marker count is 0.
  • Member 2: the /keys, /notifications and /mcp/skill declarations, three nested anchors, each 1 to 0 (blob 24648a251fc8, then 1bfef7b8cabc, then 970cf8d3dba7, then 258148332710). Result: Tests 20 failed | 124 passed (144). The 20 are the new OPTIONS/TRACE rows and the four /mcp/skill rows. Messages include expected 'DELETE, GET, HEAD, PATCH, POST, PUT' to be 'POST', expected 'DELETE, GET, HEAD, POST' to be 'GET', expected 405 to be 401 and expected 405 to be 404. The actions pin stayed green. Restore was proven at each of the three levels (blob equals HEAD, git diff HEAD is empty).

Gates

node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack at the final head 6c8625968 derived 62 commands. Each was run with its exit code captured before any pipe and recorded as COMMAND :: exit N. --ran reconciles: 62 derived, 61 run, 1 NOT-MEASURED, 0 UNRUN. The 61 that ran exit 0, check:route-envelope and check:nul-bytes among them.

  • NOT MEASURED: pnpm check:dual-build-cjs-loads. Reason: PREREQUISITE NOT MET (exit 3). It reads the built dist/ of every package, 39 have none in this worktree, and the whole repository was not built locally. This diff changes no exports, no package.json and no build config. CI's Lint & Repo Gates runs it.
  • Not run: node scripts/check-platform-checklist.mjs (no exported symbol renamed or removed), and the local-only check:dev-prereqs and check:skill-frame-freshness (nothing under packages/spec or the skill frame changed). The repo-wide pnpm lint is the PR's Lint & Repo Gates check-run.

Acceptance notes

  • /share-links/* still lists refusal-only verbs in Allow. On the standalone server /share-links is plugin-sharing's family (registerShareLinkRoutes), not the dispatcher's. Its ${base}/* catch-all is mounted for the five verbs. So OPTIONS with CORS off, and TRACE/PROPFIND with CORS either way, on /api/v1/share-links/abc (served: DELETE) and /api/v1/share-links/abc/resolve (served: GET) get the adapter's 405 with Allow: DELETE, GET, HEAD, PATCH, POST, PUT. This holds at base and at head. The route here does not reach it: the fallback seam is one handler and the dispatcher's, and a dispatcher answer on /share-links would be a second owner of that surface. plugin-sharing is outside this claim's file surface. Reported to the seat with the options.
  • Anonymous OPTIONS on /actions (CORS off only) gets the domain's 405 + Allow: POST. That is the same answer as on a createHonoApp host. The shared anonymous decision lets every OPTIONS through as a CORS preflight, and domains/actions.ts is read-only here. This is PR fix(runtime): the keys and actions domains answer their 405 with an Allow header naming POST #24227's open question, unchanged.
  • Answers the fallback writes skip the per-route instrumentation. There is no X-Request-Id echo, because the seam is installed on the raw server. That is the same as the adapter answer it replaces, and the transport's response observation still labels them unmatched.
  • Divergences that predate this PR and are unchanged by it:
  • The adapter's own matcher and the Hono router disagree on the /share-links root. Hono routes PUT /api/v1/share-links to the /share-links/* catch-all, but the adapter's matchesRoutePattern does not count that pattern for the root. So Allow there lists only GET, HEAD, POST, which happens to be accurate. Read from the measurement, no wrong answer. Carrier: none.

Generated by Claude Code

…sport's Allow never answers a path whose domain decides every verb

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime, touching 5 documentable anchor(s).

9 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via actions.invoke (sdk, the route ledger binds it to POST /actions/:object/:action; the route ledger binds it to POST /actions/:object/:action/:recordId))
  • content/docs/api/environment-routing.mdx (via /environments/:id (route, a path literal in a comment in createDispatcherPlugin))
  • content/docs/api/error-catalog.mdx (via actions.invoke (sdk, the route ledger binds it to POST /actions/:object/:action; the route ledger binds it to POST /actions/:object/:action/:recordId))
  • content/docs/automation/flows.mdx (via /actions/:object/:action (route, a path literal in createDispatcherPlugin))
  • content/docs/concepts/north-star.mdx (via /environments/:id (route, a path literal in a comment in createDispatcherPlugin))
  • content/docs/deployment/production-readiness.mdx (via createDispatcherPlugin (symbol, a top-level function))
  • content/docs/plugins/packages.mdx (via createDispatcherPlugin (symbol, a top-level function))
  • content/docs/protocol/kernel/metadata-service.mdx (via /environments/:id (route, a path literal in a comment in createDispatcherPlugin))
  • content/docs/ui/actions.mdx (via /actions/:object/:action (route, a path literal in createDispatcherPlugin))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/implementation-status.mdx (via /environments/:id (route, a path literal in a comment in createDispatcherPlugin))
  • content/docs/releases/v17/17-0.mdx (via /actions/:object/:action (route, a path literal in createDispatcherPlugin))
  • content/docs/releases/v17/17-1.mdx (via /actions/:object/:action (route, a path literal in createDispatcherPlugin))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 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 — 26 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 af284b05e55c171718c456195620094fe058b267 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from bb40fe50ccccc2c95f69e0f0a9cc56a20d627c73 — the merge of head 6c8625968682d5c24c67e5132aad2ada42e98ca9 into base af284b05e55c171718c456195620094fe058b267, 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 bb40fe50ccccc2c95f69e0f0a9cc56a20d627c73 && git checkout bb40fe50ccccc2c95f69e0f0a9cc56a20d627c73
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin af284b05e55c171718c456195620094fe058b267 6c8625968682d5c24c67e5132aad2ada42e98ca9 && git checkout -B drift-repro af284b05e55c171718c456195620094fe058b267 && git merge --no-ff 6c8625968682d5c24c67e5132aad2ada42e98ca9

node scripts/docs-audit/affected-docs.mjs --json af284b05e55c171718c456195620094fe058b267

⚠️ 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 af284b05e55c171718c456195620094fe058b267 → 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

席位核对 — ACCEPT · PR #24244 at 6c8625968 (#24228)

domain:cli seat 2 · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb · 2026-10-18T02:08Z · holder of claim 6243737468 · triage 6243379401. The dev's report is comment 6244534422 on #24228. The seat reviewed against this PR, not the report's own account.

  1. Shape. Draft → main. The first line is Fixes #24228, the only closing keyword. Clause-②: no is line-initial, and the assignee is marchtian. 8 files, +559 / −29: one source file (dispatcher-plugin.ts, +117 / −1), six test files and one @objectstack/runtime patch changeset. check-governed-merges.mjs --pr 24244: NOT governed. Nothing under packages/spec, plugin-hono-server or domains/actions.ts is touched, as the triage and the claim required.
  2. The mechanism, read on the diff.
  3. The PM's suggested route, measured and rejected: accepted. Refusal-only GET / PUT / PATCH / DELETE mounts on /actions would still leave OPTIONS (CORS off) and TRACE / extension verbs (any CORS) to the adapter's 405. Its Allow would then widen from POST to six verbs, which is the triage's "a fix copying runtime(dispatcher): anonymous GET / PUT / PATCH / DELETE on /api/v1/keys answer 405 METHOD_NOT_ALLOWED instead of the 401 UNAUTHENTICATED the v18 anonymous floor states for every /keys verb — only POST is mounted #23909's mounts would widen the second".
  4. Evidence.
    • The reach was measured on a real socket: 4 compositions (CORS off and on × scoping off and on), 14 paths × 9 verbs × anonymous / signed-in, 1008 requests at base and at head. 368 rows changed, every one from the adapter's 405 to the domain's answer (401, the domain's 405, the dispatcher's 404). No served answer moved. createHonoApp parity was measured in process.
    • Ablations, with the fix committed first and restores proven by blob:
    • Runtime local: 413 files, 6411 passed; typecheck exit 0. Gates: 62 derived, 61 run, all exit 0. One was NOT MEASURED: check:dual-build-cjs-loads (exit 3, no dist). This diff moves no export, package.json or build config, and CI's Lint & Repo Gates runs it.
  5. members_measured, against the card's "Done when":
  6. Clause-②: no, read. Every request whose answer changes was refused before and stays refused: 405 to 401 for anonymous callers, and a narrower Allow. Nothing is admitted that was refused, and no accept set or export moves, so no contract-review face is owed.
    Changeset, read sentence by sentence:
    • The /actions bullet matches item 5. Its OPTIONS-anonymous sentence (the shared anonymous decision lets a CORS preflight through, so the answer is the domain's 405 + Allow: POST) is the measured createHonoApp parity.
    • The Allow bullet's three answers match the measurement.
    • "Every request that was served is served as before, and no route is added" holds against the 1008-request table.
  7. Accepted deviations:
    • the route in item 3;
    • PR fix(runtime): the keys and actions domains answer their 405 with an Allow header naming POST #24227's control pin: its docblock and one test title said the standalone /actions 405 is the adapter's. This change makes that false, so it is corrected, with the assertions untouched;
    • the mcp-skill boundary case that pinned the defect, replaced by domain pins;
    • one new boot warn and one clause on the existing one;
    • the base 2bcd406a2 plus a merge of 7be3cc728 (the files are identical across the range);
    • the first gate pass cut by the dev's own timeout, with every command recorded exactly once.
  8. Open question 1 (/share-links), decided by the seat: A. Fixes #24228 stands. The triage settled the Allow half "in this lane", and the card's families are the dispatcher's. The /share-links/:token residual has another owner (plugin-sharing, domain:services) and needs another mechanism (a ruling, or domain:spec's IHttpServer route). It is filed as plugin-sharing: on the standalone server, OPTIONS (CORS off) and TRACE on /api/v1/share-links/:token and /:token/resolve answer the adapter's 405 with Allow listing six verbs while each path serves one (#24228's Allow member) #24246 with the dev's measurement.
  9. Out of scope, filed and noted:
  10. Landing. Ordinary queue landing once every check on 6c8625968 is green or a roster skip. Fixes #24228 closes the card, and the seat strips pm:dispatched in the same act. runtime(dispatcher): on the standalone server an anonymous request whose JSON body cannot be parsed is answered 400 INVALID_REQUEST ahead of the domain's 401 floor, because the mounts read req.body before dispatch (20 sites) #24151, serial behind this card, is re-priced after it lands.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 18, 2026 02:26
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 18, 2026 02:26
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit cbfa5e4 Oct 18, 2026
41 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-24228-actions-anonymous-floor branch October 18, 2026 02:47
marchtian pushed a commit that referenced this pull request Oct 18, 2026
Brings in cbfa5e4 (#24244, packages/runtime only); no path overlaps this
branch.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
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/l tests tooling

Projects

None yet

2 participants