|
| 1 | +--- |
| 2 | +'@objectstack/spec': major |
| 3 | +--- |
| 4 | + |
| 5 | +**BREAKING** — `automation/control-flow` and `automation/state-machine` reject unknown keys (#4001 批 10, ADR-0078) |
| 6 | + |
| 7 | +Eleven authoring shapes that silently discarded undeclared keys now refuse them with a |
| 8 | +named surface, the offending key echoed back, and a rename or prescription. Metadata that |
| 9 | +used to parse "successfully" while losing the key you wrote now returns 422. |
| 10 | + |
| 11 | +**`automation/control-flow.zod.ts`** — `FlowRegionSchema`, `LoopConfigSchema`, |
| 12 | +`ParallelBranchSchema`, `ParallelConfigSchema`, `TryCatchConfigSchema`. |
| 13 | + |
| 14 | +**`automation/state-machine.zod.ts`** — `ActionRefSchema` (object branch), |
| 15 | +`GuardRefSchema` (object branch), `TransitionSchema`, `StateNodeSchema`, its `meta` block, |
| 16 | +and `StateMachineSchema`. |
| 17 | + |
| 18 | +## What was actually being lost |
| 19 | + |
| 20 | +A `state_machine` on an agent's `lifecycle` with `onn` where `on` was meant parsed clean |
| 21 | +and came back with **no transitions at all** — the declaration whose entire purpose is to |
| 22 | +deny undeclared transitions, silently emptied and reported valid. A `loop` config with |
| 23 | +`maxIteration` (singular) came back uncapped. A `parallel` branch with `label` instead of |
| 24 | +`name` came back unnamed. |
| 25 | + |
| 26 | +## Migration — FROM → TO |
| 27 | + |
| 28 | +Renames the rejection now suggests for you: |
| 29 | + |
| 30 | +| you wrote | write instead | on | |
| 31 | +|---|---|---| |
| 32 | +| `guard` | `cond` | a state transition (XState v5 renamed it the other way; this protocol kept `cond`) | |
| 33 | +| `action` | `actions` | a state transition | |
| 34 | +| `itemVariable` | `iteratorVariable` | a `loop` config | |
| 35 | +| `maxIteration` | `maxIterations` | a `loop` config | |
| 36 | +| `label` | `name` | a `parallel` branch | |
| 37 | +| `onn` / `entery` / typos | `on` / `entry` | a state node | |
| 38 | + |
| 39 | +Keys with no replacement, and what to do instead: |
| 40 | + |
| 41 | +- **`finally` on `try_catch`** — there is no `finally` region. The node's ordinary |
| 42 | + out-edges run whichever way the protected region went; put the always-run steps in the |
| 43 | + nodes **after** the container. |
| 44 | +- **`join` / `joinGateway` on `parallel`** — the join is implicit; the block continues once |
| 45 | + when every branch completes. `join_gateway` is a BPMN interop node type, never a |
| 46 | + `parallel` config key. |
| 47 | +- **`flowName` on `loop`** — that key belongs to the `map` node, which runs a subflow per |
| 48 | + item. A `loop` runs an inline region: move the steps into `config.body`, or change the |
| 49 | + node `type` to `map`. |
| 50 | +- **`name` / `label` on a region** — a `loop` body, a `try` region and a `catch` region are |
| 51 | + not named; only a `parallel` branch carries a `name`. |
| 52 | +- **`transitions` on a state node** — a state node declares transitions as `on`, keyed by |
| 53 | + event type. `transitions` is the key on the object-level `state_machine` **validation |
| 54 | + rule** (`validations[].transitions`), a different declaration. |
| 55 | +- **`context` on a state machine** — this protocol declares only the context SHAPE, as |
| 56 | + `contextSchema`. There is no key for seeding initial values, so the two are not a rename |
| 57 | + of each other. |
| 58 | + |
| 59 | +## Two notes for upgraders |
| 60 | + |
| 61 | +`ActionRef` / `GuardRef` are unions, so a rejected key on their object branch surfaces as |
| 62 | +zod's `invalid_union` (`"Invalid input"`) with the real prescription nested one level down |
| 63 | +in `issue.errors[]` rather than in the top-level message. The prescription is present in |
| 64 | +`ZodError.message` and in REST error bodies; single-line formatters drop it. |
| 65 | + |
| 66 | +`StateNodeSchema.meta` is **closed**, not a passthrough bag. XState treats `meta` as open, |
| 67 | +but the hand-written `StateNodeConfig` type here declares exactly `label` / `description` / |
| 68 | +`color` / `aiInstructions`, nothing in the platform reads any other key, and the previous |
| 69 | +behaviour was not openness but strip — an authored `meta` arrived as `{}`. |
| 70 | + |
| 71 | +All three example apps (`app-showcase`, `app-crm`, `app-todo`) validate unchanged, so no |
| 72 | +ADR-0087 conversion accompanies this change. |
0 commit comments