Uh oh!
There was an error while loading. Please reload this page.
feat(runtime): carry the flow author's errorMessage and run summary through the /actions door via a typed refusal carrier (#9585) - #9950
Conversation
…hrough the /actions door via a typed refusal carrier (#9585) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WeN7F6jQFpcqW2BN56RdPa
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 23 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 3877a6b1bbf6ffa9f5c40004ba62706ef4309a4f && git checkout 3877a6b1bbf6ffa9f5c40004ba62706ef4309a4f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 301bf26be117bd59d0b31f594a433f2a4ecfd6f0 6a5ac829a58b90b5e4f967c0fd7b0fb87c474f68 && git checkout -B drift-repro 301bf26be117bd59d0b31f594a433f2a4ecfd6f0 && git merge --no-ff 6a5ac829a58b90b5e4f967c0fd7b0fb87c474f68
node scripts/docs-audit/affected-docs.mjs --json 301bf26be117bd59d0b31f594a433f2a4ecfd6f0
|
huangyiirene
commented
Aug 19, 2026
PM review — ACCEPTReviewed against the maintainer's Option B ruling on #9585 and the dispatch brief (claim comment 5339708379). Report received GitHub-first as instructed (#9585 comment 5340371701, The ruling's three hard constraints — all held, and I checked each on |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#9585
What
A flow dispatched through
POST /api/v1/actions/:object/:action(atype: 'flow'action — the documented record-page path) that ran and failed answered400 FLOW_FAILEDwith only the raw engine error. The trigger door additionally ships two fields inerror.detailsof the ADR-0112 envelope:errorMessage(the flow author's own failure text — the single field objectuiflowResponse.tsreads) andsummary(the per-node run accounting). At the action door the author's text was declared-but-never-delivered.Maintainer ruling (2026-08-19, Option B, recorded on the issue — verbatim authorization 「接受你的所有建议。」): a typed refusal carrier that
dispatchFlowActionthrows and the/actionsroute recognises before the generic catch, carrying exactly those two fields; the shared resolver stays untouched.How
packages/runtime/src/action-execution.ts— newFlowActionRefusalcarrier (branded viaSymbol.for, plus theisFlowActionRefusalguard). The classified ran-and-failed row of the The other two flow-dispatch doors never got the #9378 status table —/actionsmislabels never-dispatched refusals, declared endpoints still answer 200 #9446 table now throws it, witherrorMessage/summaryread off the engine result exactly as the trigger door reads them: present when the producer wrote them, never invented. The never-dispatched rows (404 / 409 / 422) and the unclassified residual keep their plain throws — a refused dispatch has no run to report, the same no-inventing rule the trigger door states on its 400 arm.packages/runtime/src/domains/actions.ts— recognition ahead of the generic catch: a try/catch scoped to the flow dispatch recognises the brand and answersdeps.error(message, status, { code, ...runDetails })— the trigger door's own exit, withdetails.codepromoted by the shared envelope builder. Everything else rethrows to the generic catch, which is unchanged.flow-dispatch-status.ts(the "what the table does not answer" note) andhttp-dispatcher.test.ts(a comment that described the pre-A flow ACTION that fails cannot carry the author'serrorMessageor the runsummary— the/actionsdoor has no channel for them #9585 gap).The ruling's boundaries, kept mechanically
packages/types/src/thrown-http-error.tsuntouched — its deliberately-narrowdetailslist (The direct-mount REST package door answers 500 INTERNAL_ERROR for coded 4xx errors the dispatcher twin maps correctly #8016 / [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106) remains the rule for every other thrower. Option A (any throw declares wire payload) was rejected by name. A new pin drivesresolveThrownHttpErrorover the thrown carrier and assertsdetailsstays undefined — a future widening of the shared resolver reddens there instead of landing silently.packages/runtime/src/dispatcher-plugin.tsuntouched (held by sibling card runtime dispatcher's two discovery bodies (.well-known + REST-less {prefix}/discovery fallback) are the machine-read { data } class #9436 ruled on, and sit outside check-route-envelope's scan #9813 this round). Recognition did not need the route registration: it lands entirely in the domain handler.run_actionbridge sharesdispatchFlowActionand has no recognition branch — by scope. Its safety property is pinned: the carrier stampsstatus/code/messageidentically to the plain throw it replaces, so an unrecognising caller serves exactly the previous answer, with only the run details staying behind.400 FLOW_FAILED(The other two flow-dispatch doors never got the #9378 status table —/actionsmislabels never-dispatched refusals, declared endpoints still answer 200 #9446 suite re-run green).Tests
Six new pins in
actions-flow-dispatch-status.test.ts, the paired-door file from #9446 — including the drift pin the card asked for: one failed run driven through BOTH doors,error.detailscompared door-against-door (anchored once so twoundefineds cannot satisfy it). Also pinned: absent artefacts invent none at either door; a never-dispatched refusal carrying incidental run fields ships them at NEITHER door; brand-based recognition rejects a field-shape lookalike.Reverse verification from the committed state: reverting only the recognition branch (
domains/actions.tsto origin/main) reddened exactly the 2 predicted pins (the/actionsdetails pin and the drift pin) while all 13 #9446 pins stayed green — the carrier degrades to yesterday's answer, never a different one. Restored: 19/19.At
6a5ac829a5(this head): targeted suite 19/19; full@objectstack/runtimesuite 176 files / 2634 tests green;pnpm --filter @objectstack/runtime typecheckgreen. Gates (readings, not exit codes):pnpm lintclean;check:dispatcher-error-vocabulary"OK — 17 unregistered code-stamping site(s), all classified" (the carrier stamps only the registeredFLOW_FAILED, no new site);check:route-envelope11 modules / 8 conformant / 0 ratcheted / 3 exempt;check:engine-double-contractOK 321 pinned;check:where-matcher/check:query-options-erasurebaselines verified, no files added;check:type-check-coverageOK 64/77;check:type-check-debt --re-measure"none above its recorded number"; changeset gates (adr-0087 / no-major / empty / self-tests),check:cross-package-test-inputs,check:slot-lookup,check:nul-bytes,check-affected-docsall green. Gate set derived bynode scripts/pm/dispatch-gates.mjs(no args, script-derived change set).Changeset
@objectstack/runtimeminor — additive envelope payload at one door; not breaking, so no ADR-0087 disposition marker is required (check:adr-0087-registrationgreen).Generated by Claude Code