diff --git a/.changeset/flow-refusal-enumeration-four-pages.md b/.changeset/flow-refusal-enumeration-four-pages.md new file mode 100644 index 0000000000..028ea4bf6e --- /dev/null +++ b/.changeset/flow-refusal-enumeration-four-pages.md @@ -0,0 +1,37 @@ +--- +"@objectstack/docs": patch +--- + +fix(docs): four pages enumerating the flow refusal codes now name `FLOW_INPUT_SCHEMA_INVALID` (#13720) + +`FlowRefusalCode` gained a fourth member in `packages/runtime/src/flow-dispatch-status.ts` +(`b6d3d76b5`), answered `422` and classified never-dispatched. Three pages were updated with +it; four others enumerate the same union and were not, so each stated the enumeration as +**complete** while it was one code short — a teaching surface telling a reader that a status +they will really receive does not exist. + +| page | the row that was short | +|:---|:---| +| `content/docs/api/declarative-endpoints.mdx` | the `type: 'flow'` delegation row | +| `content/docs/api/plugin-endpoints.mdx` | `POST /automation/:name/trigger` | +| `content/docs/protocol/kernel/http-protocol.mdx` | the declared-endpoint `type: 'flow'` answer row | +| `content/docs/ui/actions.mdx` | the `type: 'flow'` over-REST row | + +Prose only — no schema, no runtime behaviour and no generated artifact moves. The two +generated reference pages (`references/api/contract.mdx`, +`references/api/error-code-ledger.mdx`) already carried the code, which is why the +generator needed nothing here. + +**Which group the new code joins was read off the source, not inferred from the status.** +`classifyFlowRefusal` tests `FLOW_INPUT_SCHEMA_INVALID` inside the +`── never dispatched: the producer says WHICH refusal ──` arm block, above the +`result.status === 'failed'` arm that answers `400 FLOW_FAILED`. `ui/actions.mdx` is the +one page that splits its enumeration into "a run that ran and was rejected" versus "a +dispatch that never happened", so the code is placed in the second group there; putting it +beside `FLOW_FAILED` would have said the run started. + +`422` is now carried by two codes (`FLOW_NO_START_NODE` and `FLOW_INPUT_SCHEMA_INVALID`). +Each page spells the status together with its code, so every entry stays a self-contained +pair rather than a claim about what `422` alone means — the discriminator is `error.code`, +which is what `http-protocol.mdx` already tells readers to branch on. The full table with +per-code guidance stays where it is, in `content/docs/automation/flows.mdx`. diff --git a/content/docs/api/declarative-endpoints.mdx b/content/docs/api/declarative-endpoints.mdx index 1552a8e159..4d59c14167 100644 --- a/content/docs/api/declarative-endpoints.mdx +++ b/content/docs/api/declarative-endpoints.mdx @@ -100,7 +100,7 @@ declaration to shadow a built-in one. Match → policy chain (`rateLimit` → `a | `type` | Delegates to | Request shape | |:---|:---|:---| | `object_operation` | the same `callData` binding that serves `/api/v1/data/{object}` | `find` reads its criteria from the query string; `get` / `update` / `delete` take the record id from `query.id`; `create` / `update` take the body. `create` answers `201`, the rest `200` | -| `flow` | the same automation pipeline as `POST /api/v1/automation/{name}/trigger` | the request body is the flow input. A refused or failed run answers a real status: `404` unknown flow, `409` `FLOW_DISABLED`, `422` `FLOW_NO_START_NODE`, `400` `FLOW_FAILED` | +| `flow` | the same automation pipeline as `POST /api/v1/automation/{name}/trigger` | the request body is the flow input. A refused or failed run answers a real status: `404` unknown flow, `409` `FLOW_DISABLED`, `422` `FLOW_NO_START_NODE`, `422` `FLOW_INPUT_SCHEMA_INVALID`, `400` `FLOW_FAILED` | The frozen vocabulary has **no path-template syntax**, so an endpoint cannot express `/leads/{id}` — a record id travels as `?id=…`. A method that no declaration claims diff --git a/content/docs/api/plugin-endpoints.mdx b/content/docs/api/plugin-endpoints.mdx index f28d949c20..e653c572e2 100644 --- a/content/docs/api/plugin-endpoints.mdx +++ b/content/docs/api/plugin-endpoints.mdx @@ -48,7 +48,7 @@ Approve/reject were never workflow routes (ADR-0019): approval is a flow node, a | Method | Endpoint | Description | |:-------|:---------|:------------| -| POST | `/automation/:name/trigger` | Trigger an automation flow by name (legacy alias: `/automation/trigger/:name`). Failures answer real status codes, not a `200` wrapping an inner failure: **404** unknown flow, **409** `FLOW_DISABLED`, **422** `FLOW_NO_START_NODE`, **400** `FLOW_FAILED` for a run that ran and was rejected — see [Run a flow via API](/docs/automation/flows#run-a-flow-via-api) | +| POST | `/automation/:name/trigger` | Trigger an automation flow by name (legacy alias: `/automation/trigger/:name`). Failures answer real status codes, not a `200` wrapping an inner failure: **404** unknown flow, **409** `FLOW_DISABLED`, **422** `FLOW_NO_START_NODE`, **422** `FLOW_INPUT_SCHEMA_INVALID`, **400** `FLOW_FAILED` for a run that ran and was rejected — see [Run a flow via API](/docs/automation/flows#run-a-flow-via-api) | The automation dispatcher also exposes flow CRUD (`GET`/`POST /automation`, `GET`/`PUT`/`DELETE /automation/:name`) and run observability/resume routes — see [Durable pause & resume](/docs/automation/flows#durable-pause--resume-adr-0019). diff --git a/content/docs/protocol/kernel/http-protocol.mdx b/content/docs/protocol/kernel/http-protocol.mdx index 925c1fbf48..1cf488689c 100644 --- a/content/docs/protocol/kernel/http-protocol.mdx +++ b/content/docs/protocol/kernel/http-protocol.mdx @@ -1225,7 +1225,7 @@ declaration to shadow a built-in route: | Endpoint declares | Answer | |:---|:---| | `type: 'object_operation'` | delegated to the same `callData` binding that serves `/api/v1/data/{object}` — byte-identical `data` | -| `type: 'flow'` | delegated to the same automation pipeline as `POST /api/v1/automation/{name}/trigger` — the same execution context builder, the same `execute` call, and **the same response contract**: a refused or failed run is classified into the same real status codes (404 / 409 `FLOW_DISABLED` / 422 `FLOW_NO_START_NODE` / 400 `FLOW_FAILED`), from one shared definition all three flow doors read. Branch on the status and `error.code`, never on an inner success flag | +| `type: 'flow'` | delegated to the same automation pipeline as `POST /api/v1/automation/{name}/trigger` — the same execution context builder, the same `execute` call, and **the same response contract**: a refused or failed run is classified into the same real status codes (404 / 409 `FLOW_DISABLED` / 422 `FLOW_NO_START_NODE` / 422 `FLOW_INPUT_SCHEMA_INVALID` / 400 `FLOW_FAILED`), from one shared definition all three flow doors read. Branch on the status and `error.code`, never on an inner success flag | | `authRequired: true` (or omitted) + anonymous caller | `401` `UNAUTHENTICATED`, the same envelope every seam answers | | `rateLimit` armed and exhausted | `429` + `Retry-After`, never with a cache directive | | `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 | diff --git a/content/docs/ui/actions.mdx b/content/docs/ui/actions.mdx index 0ad92acebf..fc4efb999b 100644 --- a/content/docs/ui/actions.mdx +++ b/content/docs/ui/actions.mdx @@ -346,7 +346,7 @@ The endpoint dispatches on the **declared `type`**, exactly like the MCP | `type` | Over REST | |:---|:---| | `script` | Runs the registered handler / inline body. | -| `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. | +| `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` / **422** `FLOW_INPUT_SCHEMA_INVALID`). 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. | | `api` | **400** — it dispatches on `target`; call that endpoint directly. | | `url` / `modal` / `form` | **400** — client-side navigation; there is nothing for the server to run. |