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
* docs(changeset): changesets for the stranded-request inspection and the harness store (#4469, #4470)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
* docs(automation): document the screen resume contract and the override marker (#4477, #4466, #4469)
flows.mdx — a new subsection on the durable-pause seam, beside the existing
`$`-namespace rule it mirrors: a `screen` node's `config.fields` is a contract,
`resume` enforces it server-side with 400 `INVALID_SCREEN_INPUT`, `visibleWhen`
is evaluated against the submitted values so a hidden field's `required` never
fires, the refusal precedes consuming the suspension, and the three shapes that
declare no contract keep the pass-through. The pausing-node table's `screen` row
now says the inputs are validated rather than merely posted.
approvals.mdx — the admin-override callout now states the rule it actually
implements ("the actor is an admin", not "the slate is unstaffed"), which is why
`sys_approval_action.via_override` records which door the decision came through,
including why an admin who IS an approver records `false` and why a legacy row's
absent value reads as "not recorded". The dead-run callout gains the terminal
request shape its pending-only scan cannot see, and why the new inspection
reports rather than rewrites.
content/docs/references/ is generated and untouched.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
* docs: point the "filed separately" references at #4518
Both the durable-suspend proof's KNOWN GAP note and the `@objectstack/verify`
changeset told the reader the harness persistence gap was "filed separately"
without naming it — a pointer to nothing, which is the shape of stale record
this branch exists to avoid. It is #4518.
Comment/changeset text only; no behaviour change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017gEHJN2NFpS9VMeURvakgD
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/automation/flows.mdx
+43-1Lines changed: 43 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -468,7 +468,7 @@ POST /api/v1/automation/{flow}/runs/{runId}/resume
468
468
| Pausing node | Suspends until… | Resumed by |
469
469
| :--- | :--- | :--- |
470
470
|`approval`| a human decision | the approvals service (`POST /api/v1/approvals/requests/:id/approve\|reject`) — resumes down the matching `approve` / `reject` edge. **Decide through the approvals API**; the resume route above **refuses** an approval pause outright (see below). |
471
-
|`screen`| a user submits the form | the UI runner posting the collected `inputs`; a `paused` response carrying the next `screen` chains multi-step wizards under one stable `runId`|
471
+
|`screen`| a user submits the form | the UI runner posting the collected `inputs` — **validated server-side against the screen's declared `fields`** (see below); a `paused` response carrying the next `screen` chains multi-step wizards under one stable `runId`|
472
472
|`wait` (timer) | an ISO-8601 duration elapses |**automatically** — a one-shot job resumes the run; after a cold boot the engine re-arms pending timers from the durable store (overdue timers resume immediately) |
473
473
|`wait` (signal) | a named external event | any caller invoking `resume(runId)`|
474
474
@@ -513,6 +513,48 @@ key. A reserved name answers **400**, nothing is applied (not even legitimate
513
513
keys sent alongside it), and the run stays parked. Ordinary author variables are
514
514
unaffected, `$` mid-name (`price$`) included.
515
515
516
+
### A `screen` resume is checked against the declared fields
517
+
518
+
A screen node's `config.fields` is a **contract**, not just a rendering hint:
519
+
the author declares which keys are collected, which are `required`, and — via
520
+
`visibleWhen` — when a field is even asked for. `resume` enforces all of it
521
+
server-side, so skipping the dialog and posting to the route directly is not a
522
+
way around what the author declared:
523
+
524
+
```
525
+
POST /api/v1/automation/{flow}/runs/{runId}/resume
526
+
{ "inputs": { "kind": "escalate" } }
527
+
528
+
400 Invalid screen input: Screen field "escalation_reason" is required
529
+
— declared fields: 'kind', 'escalation_reason'
530
+
```
531
+
532
+
Two conditions are refused, both reported at once and both with
533
+
`code: 'INVALID_SCREEN_INPUT'`:
534
+
535
+
- a **`required` field the caller was actually asked for** is missing (an empty
536
+
or blank string counts as missing);
537
+
- a key the screen **never declared** was sent.
538
+
539
+
`visibleWhen` is evaluated against the **submitted values** first, so a hidden
540
+
field's `required` never fires — enforcing it would dead-end the run at a field
541
+
the user was never shown. A predicate that cannot be evaluated is treated as
542
+
hidden (and logged), because the client is the authority on what was rendered.
543
+
544
+
Like the `$`-namespace rule above, the refusal happens **before** the suspension
545
+
is consumed: nothing is applied, the run stays parked, and the corrected
546
+
submission still lands.
547
+
548
+
Three shapes declare no contract and so keep the pass-through — the same way an
549
+
action with no `params` is untouched:
550
+
551
+
- an **object-form** screen (`config.objectName`), whose flat `fields` list is
552
+
empty by construction; the client persists the record through the normal write
553
+
path, which enforces that object's own `required` fields;
554
+
- a **message-only** screen (`waitForInput: true` with no fields);
555
+
-`signal.output`, which is the node-*output* namespace of the approval-style
556
+
resume envelope rather than the screen's collected-values channel.
557
+
516
558
Registering a pausing node of your own? Declare `resumeAuthority: 'service'` on
517
559
its descriptor when the decision to continue belongs to your service rather
0 commit comments