You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(runtime): /actions answers the #9378 flow-dispatch status table, from one shared definition (#9584)
* fix(runtime): /actions answers the #9378 flow-dispatch status table (#9446)
`dispatchFlowAction` mapped every `success: false` automation result to
`400 FLOW_FAILED` under a comment asserting "The flow RAN and rejected" —
false for two of the four exits it caught. A disabled flow invoked through
an action told the caller a run had failed when no node ever executed, and
the producer's own `result.code` was available and ignored.
The table now lives in one module, `flow-dispatch-status.ts`, read by both
the `/actions` door and the trigger door: 404 (flow not found) / 409
FLOW_DISABLED / 422 FLOW_NO_START_NODE / 400 FLOW_FAILED. Maintainer ruling
2026-08-18, verbatim 「同意」: the table is a property of the flow-dispatch
contract, not of the trigger route.
Co-Authored-By: Claude <noreply@anthropic.com>
* docs(actions): the action door answers the flow-dispatch table, and add the changeset (#9446)
`ui/actions.mdx` documented the divergence this change closes; it now states
the converged table. `automation/flows.mdx` gains the cross-door note, and
`http-protocol.mdx`'s declared-endpoint row points at #9462 — the door that is
still unconverged — instead of at this card, which would read as "fixed" once
this closes.
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
`POST /api/v1/actions/:object/:action` answers the flow-dispatch status table instead of one blanket `400 FLOW_FAILED` (#9446).
6
+
7
+
**What a caller sees differently.** A `type: 'flow'` action whose dispatch is REFUSED no longer reports a failed run. Three answers changed:
8
+
9
+
| the flow behind the action | before | now |
10
+
|---|---|---|
11
+
| is not registered |`400``FLOW_FAILED`|`404``RESOURCE_NOT_FOUND`|
12
+
| is switched off |`400``FLOW_FAILED`|`409``FLOW_DISABLED`|
13
+
| has no `start` node |`400``FLOW_FAILED`|`422``FLOW_NO_START_NODE`|
14
+
| ran and was rejected |`400``FLOW_FAILED`|`400``FLOW_FAILED` (unchanged) |
15
+
16
+
These are the same four rows `POST /api/v1/automation/:name/trigger` has answered since #9378 + #9415, and they now come from one shared definition both doors read, so the two cannot drift apart again.
17
+
18
+
**Behaviourally breaking for a caller that branches on the status or the code.** Every one of these was a `400` before, so a caller treating `400` as "the run failed" was being told something false in three of the four cases: nothing had dispatched and no node had executed. A client that lumps all four together keeps working — they are all still refusals, all still `success: false` with no inner envelope — but one that reports "the flow failed" on a `400` should now distinguish. **Retry semantics differ per row**, which is the practical reason to: `409 FLOW_DISABLED` is reversible operational state (enable the flow and the identical request succeeds), while `404` and `422 FLOW_NO_START_NODE` are authoring defects that no retry fixes. `400 FLOW_FAILED` remains terminal, exactly as the console already treats it.
19
+
20
+
**Unchanged on purpose.** A successful run still answers `200` with the single `data` wrap (#3962). The `400 FLOW_FAILED` message keeps its existing wording (`Flow '<target>' failed: …`), which names the flow the action dispatches — the trigger route's URL carries that name and this route's does not. A `success: false` result the automation engine did not classify still refuses with `400 FLOW_FAILED` rather than falling back to `200 {success:true,data:{success:false}}` — the double envelope #3962 removed from this route.
21
+
22
+
**Not in scope.** Declared endpoints (`type: 'flow'` endpoints, `endpoint-executor.ts`) still answer `200` for every outcome. That door converges in its own change (#9462), where the envelope flip is a breaking change for consumers of the current double envelope and is sequenced against them.
Copy file name to clipboardExpand all lines: content/docs/protocol/kernel/http-protocol.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1216,7 +1216,7 @@ declaration to shadow a built-in route:
1216
1216
| Endpoint declares | Answer |
1217
1217
|:---|:---|
1218
1218
|`type: 'object_operation'`| delegated to the same `callData` binding that serves `/api/v1/data/{object}` — byte-identical `data`|
1219
-
|`type: 'flow'`| delegated to the same automation pipeline as `POST /api/v1/automation/{name}/trigger` — the same execution context builder and the same `execute` call, so the run itself is identical. **The response is not**: the trigger route classifies a refused or failed run into real status codes (404 / 409 `FLOW_DISABLED` / 422 `FLOW_NO_START_NODE` / 400 `FLOW_FAILED`), while this seam still answers `200` with the result in `data` for every outcome ([#9446](https://github.com/objectstack-ai/objectstack/issues/9446)). Read `data.success` here, not the status |
1219
+
|`type: 'flow'`| delegated to the same automation pipeline as `POST /api/v1/automation/{name}/trigger` — the same execution context builder and the same `execute` call, so the run itself is identical. **The response is not**: the trigger route classifies a refused or failed run into real status codes (404 / 409 `FLOW_DISABLED` / 422 `FLOW_NO_START_NODE` / 400 `FLOW_FAILED`), and `POST /api/v1/actions/{object}/{action}` answers that same table since #9446, while this seam still answers `200` with the result in `data` for every outcome ([#9462](https://github.com/objectstack-ai/objectstack/issues/9462)). Read `data.success` here, not the status |
1220
1220
|`authRequired: true` (or omitted) + anonymous caller |`401``UNAUTHENTICATED`, the same envelope every seam answers |
1221
1221
|`rateLimit` armed and exhausted |`429` + `Retry-After`, never with a cache directive |
1222
1222
|`cacheTtl: 30` on a successful GET |`Cache-Control: private, max-age=30` — `private` is a security rule, not tuning: any response can be RLS-trimmed |
Copy file name to clipboardExpand all lines: content/docs/ui/actions.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -343,7 +343,7 @@ The endpoint dispatches on the **declared `type`**, exactly like the MCP
343
343
|`type`| Over REST |
344
344
|:---|:---|
345
345
|`script`| Runs the registered handler / inline body. |
346
-
|`flow`| Runs `target` on the automation engine, with your identity forwarded (a `runAs: 'user'` flow enforces RLS as you). Dispatches the same flow as `POST /api/v1/automation/:target/trigger`, without having to know the flow name. ⚠️ It does **not** answer the same way: any unsuccessful outcome comes back as **400**`FLOW_FAILED`, where the trigger route separates a run that failed (400) from one that was never dispatched (404 / 409 / 422) — see [#9446](https://github.com/objectstack-ai/objectstack/issues/9446). |
346
+
|`flow`| Runs `target` on the automation engine, with your identity forwarded (a `runAs: 'user'` flow enforces RLS as you). Dispatches the same flow as `POST /api/v1/automation/:target/trigger`, without having to know the flow name — **and answers the same way**: a run that ran and was rejected is **400**`FLOW_FAILED`, while a dispatch that never happened is separated out (**404** unknown flow / **409**`FLOW_DISABLED` / **422**`FLOW_NO_START_NODE`). See [Run a flow via API](/docs/automation/flows#run-a-flow-via-api) for the full table — it is one table, read by both doors. |
347
347
|`api`|**400** — it dispatches on `target`; call that endpoint directly. |
348
348
|`url` / `modal` / `form`|**400** — client-side navigation; there is nothing for the server to run. |
0 commit comments