@protobase/cli 0.71.5 → 0.71.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/package.js +1 -1
- package/package.json +8 -8
- package/skills/protobase/references/features/workflows.md +2 -1
- package/skills/protobase/references/reference/layouts.md +1 -1
- package/skills/protobase/references/reference/mcp.md +1 -1
- package/skills/protobase/references/reference/workflow-plugins.md +7 -6
- package/skills/protobase/references/reference/workflows.md +12 -2
package/dist/cli/package.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/cli",
|
|
3
|
-
"version": "0.71.
|
|
3
|
+
"version": "0.71.6",
|
|
4
4
|
"description": "The protobase command: scaffold, doctor, dev, build and serve a Protobase project.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -43,13 +43,13 @@
|
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@better-auth/core": "^1.7.12",
|
|
45
45
|
"@hono/node-server": "^2.1.3",
|
|
46
|
-
"@protobase/config": "0.71.
|
|
47
|
-
"@protobase/layout": "0.71.
|
|
48
|
-
"@protobase/query": "0.71.
|
|
49
|
-
"@protobase/schema": "0.71.
|
|
50
|
-
"@protobase/server": "0.71.
|
|
51
|
-
"@protobase/ui": "0.71.
|
|
52
|
-
"@protobase/workflows": "0.71.
|
|
46
|
+
"@protobase/config": "0.71.6",
|
|
47
|
+
"@protobase/layout": "0.71.6",
|
|
48
|
+
"@protobase/query": "0.71.6",
|
|
49
|
+
"@protobase/schema": "0.71.6",
|
|
50
|
+
"@protobase/server": "0.71.6",
|
|
51
|
+
"@protobase/ui": "0.71.6",
|
|
52
|
+
"@protobase/workflows": "0.71.6",
|
|
53
53
|
"@react-email/render": "^2.1.0",
|
|
54
54
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
55
55
|
"@sentry/vite-plugin": "^11.10.0",
|
|
@@ -15,6 +15,7 @@ Graphs of steps the server runs along their edges, written in a language a model
|
|
|
15
15
|
- [Runs and step records](../reference/workflows.md#runs-and-their-steps) (beta): Each run started by hand through the API, from the app's code or by a trigger, with every step's input, output, logs, timing, tries and error recorded, secrets blanked out.
|
|
16
16
|
- [Run debugger](../reference/workflows.md#following-and-debugging-a-run) (beta): Each workflow lists its runs with their status, who started them, when and for how long; a run's page marks every step's status on the workflow's graph, live while it runs, with each step's input, output, logs, timing, tries and error, secrets blanked out.
|
|
17
17
|
- [Re-run from the failed step](../reference/workflows.md#re-running-a-failed-run) (beta): Once the workflow is fixed, a failed run runs again from the step that failed, keeping what the steps before it gave; a test run starts the workflow with a sample input from its page.
|
|
18
|
+
- [Replay a run](../reference/workflows.md#replaying-a-run) (beta): A run that ended runs again from the start on its version and input without sending its outside writes, such as mail, record writes and requests other than GET, which give what they gave then, while its reads come from the step cache or run again, so you can work on what formed a message without sending it twice.
|
|
18
19
|
- [Runs that survive restarts](../reference/workflows.md#how-a-run-is-run) (beta): A run takes up where it stood after a restart or a crashed worker, never running a saved step again, and every limit fails closed.
|
|
19
20
|
- [Who may run workflows](../reference/workflows.md#who-may-do-what) (beta): Making, changing and running workflows and reading their runs are each granted by name, within the tenant.
|
|
20
21
|
- [Workflows over MCP](../reference/mcp.md#workflows) (beta): AI clients find, write, validate, change by a small edit and test-run workflows as the signed-in person, and read each step of a run to fix the one that failed and re-run it from there.
|
|
@@ -25,5 +26,5 @@ Graphs of steps the server runs along their edges, written in a language a model
|
|
|
25
26
|
- [Guarded requests for plugins](../reference/workflow-plugins.md#sending-requests) (beta): A plugin's requests reach only public addresses, follow no redirect and read at most 10 MiB.
|
|
26
27
|
- [Webhook trigger](../reference/workflows.md#appwebhook) (beta): A workflow starts from a signed call to a URL of its own, limited in size and rate.
|
|
27
28
|
- [Polling triggers](../reference/workflow-plugins.md#polling-and-webhook) (beta): A workflow starts once for each new item a plugin finds on a schedule, or at each time of a cron with `app:schedule`.
|
|
28
|
-
- [Incoming Gmail mail](../reference/workflow-plugins.md#incoming-gmail-mail) (beta): A workflow starts once for each new mail on a label of a connected Gmail mailbox that passes optional sender, recipient and subject filters, read with read-only access within about a minute
|
|
29
|
+
- [Incoming Gmail mail](../reference/workflow-plugins.md#incoming-gmail-mail) (beta): A workflow starts once for each new mail on a label of a connected Gmail mailbox that passes optional sender, recipient and subject filters, read with read-only access within about a minute.
|
|
29
30
|
- [Connected accounts](../reference/workflows.md#connecting-an-account) (beta): People connect their own accounts at other services through OAuth from the account page, see which workflows use each one, and MCP clients list them to bind. A workflow's steps and triggers sign in with one only once its owner turned the workflow on, with tokens encrypted and refreshed. Disconnecting deletes the token and revokes it at a provider that has a revocation endpoint, trying again for a few hours when the provider does not take it, and says which it did.
|
|
@@ -86,7 +86,7 @@ Record pages take the same conditions in the `when` of `l.section`, `l.related`
|
|
|
86
86
|
| `a.ai(name, { label, prompt, writes, confirm?, icon? })` | Has the model write the field `writes` of the record from `prompt`, `{field}` replaced by the record's values, as the user, on the server; see [AI fields and actions](ai.md#ai-actions). |
|
|
87
87
|
| `a.action(name, { label, confirm?, icon?, bulk?, inputs? })` | Runs the handler the app registers under `name` in [`protobase.ui.tsx`](custom-components.md#action-handlers), after asking for its [`inputs`](ui-config.md#actions-that-ask-for-input) in a dialog when it has some. |
|
|
88
88
|
|
|
89
|
-
`icon` is one of `archive`, `arrow-right-left`, `ban`, `bath`, `bed`, `bell`, `bookmark`, `calendar`, `camera`, `copy`, `cpu`, `door-open`, `download`, `external-link`, `file-text`, `hard-drive`, `heart`, `house`, `key`, `land-plot`, `leaf`, `memory`, `network`, `play`, `plug`, `plus`, `power`, `restart`, `ruler`, `send`, `server`, `stop`, `table`, `tag`, `terminal`, `upload`, `wallet` or `wrench`; another name shows a lightning bolt. With `confirm` the button asks first. Every builder also takes `when`, `otherwise` and `reason`, which limit the action to the records it [applies to](ui-config.md#where-an-action-applies), and `variant: 'danger'`, which draws it in red. The button is disabled, with the reason as its tooltip, when the user may not update or delete the record (or, for an AI action, the field it writes), when an `a.action` has no handler, or when an update, delete or AI action has no record. After an action, everything shown of that resource reloads. The record page's action rail runs the same actions the same way; there an `a.link` is a real link that looks like the rail's buttons, which the browser can preview, copy or open in a new tab, while a disabled one is a disabled button with its reason. <gap>An `<Action>` block for an `a.link` cannot be opened in a new tab or copied as a link because drawing link actions as links is not implemented for the block.</gap>
|
|
89
|
+
`icon` is one of `archive`, `arrow-right-left`, `ban`, `bath`, `bed`, `bell`, `bookmark`, `calendar`, `camera`, `copy`, `cpu`, `door-open`, `download`, `external-link`, `file-text`, `hard-drive`, `heart`, `history`, `house`, `key`, `land-plot`, `leaf`, `memory`, `network`, `play`, `plug`, `plus`, `power`, `restart`, `ruler`, `send`, `server`, `stop`, `table`, `tag`, `terminal`, `upload`, `wallet` or `wrench`; another name shows a lightning bolt. With `confirm` the button asks first. Every builder also takes `when`, `otherwise` and `reason`, which limit the action to the records it [applies to](ui-config.md#where-an-action-applies), and `variant: 'danger'`, which draws it in red. The button is disabled, with the reason as its tooltip, when the user may not update or delete the record (or, for an AI action, the field it writes), when an `a.action` has no handler, or when an update, delete or AI action has no record. After an action, everything shown of that resource reloads. The record page's action rail runs the same actions the same way; there an `a.link` is a real link that looks like the rail's buttons, which the browser can preview, copy or open in a new tab, while a disabled one is a disabled button with its reason. <gap>An `<Action>` block for an `a.link` cannot be opened in a new tab or copied as a link because drawing link actions as links is not implemented for the block.</gap>
|
|
90
90
|
|
|
91
91
|
### Links to a record's URL
|
|
92
92
|
|
|
@@ -100,7 +100,7 @@ In an app with [workflows](workflows.md), the endpoint also offers tools to find
|
|
|
100
100
|
| `list_connections` | The person's own [connected accounts](workflows.md#connecting-an-account), by each connection the app's plugins declare: its key and label, and each account's `id`, scopes and `needs_sign_in`. A definition's `connections` binds an account by its `id`. It returns no token, and no one else's accounts. |
|
|
101
101
|
| `create_workflow` | Creates a workflow from its `name`, `description` and `definition`, and returns its `id`, `version` and ETag. |
|
|
102
102
|
| `update_workflow` | Changes a workflow's definition by an `edit` or whole, as `definition`, and its `name` or `description`, and returns its `id`, new `version` and ETag. With the `etag` that `get_workflow` gave, it fails when someone changed the workflow since; an edit without one is saved against the version it was applied to. |
|
|
103
|
-
| `run_workflow` | Starts a run of a workflow's current version with an `input`, and returns its `run_id` and `status`. With `rerun_of`, a failed run's id, it [re-runs](workflows.md#re-running-a-failed-run) that run from its failed step instead, on its input. |
|
|
103
|
+
| `run_workflow` | Starts a run of a workflow's current version with an `input`, and returns its `run_id` and `status`. With `rerun_of`, a failed run's id, it [re-runs](workflows.md#re-running-a-failed-run) that run from its failed step instead, on its input; with `replay: true` too, it [replays](workflows.md#replaying-a-run) a run that ended, sending none of its outside writes. |
|
|
104
104
|
| `enable_workflow` | Turns a workflow's trigger on (`enabled: true`) on the `version` it names, which must be the workflow's current version, or off, and returns its `enabled_version`. A workflow saved since that version is refused, so an approval never turns on a version the person did not see. One that uses a [connected account](workflows.md#connecting-an-account) is turned on only by the account's owner in the admin app: the tool answers `own-sign-in-only` and says so. |
|
|
105
105
|
| `new_webhook_url` | Issues a new URL and secret for a workflow that starts from a [webhook trigger](workflows.md#appwebhook), keeping a `secret` the sender makes when given one, and returns `{ url, secret }`, shown this once; the old URL stops. |
|
|
106
106
|
|
|
@@ -67,6 +67,7 @@ Two plugins of one name stop startup, naming both. The exception is an app's own
|
|
|
67
67
|
| `errorPrefix`, `errors` | The prefix of its error names, `Step` by default, and the errors it fails with by name, each with what it means. |
|
|
68
68
|
| `connection` | The connection it signs in with, by the name the plugin declares it under. |
|
|
69
69
|
| `cache` | Reuses the result of an identical earlier call: see [Caching results](#caching-results). Without it the engine looks up and keeps nothing for the type, unless a workflow forces it on. |
|
|
70
|
+
| `writes` | Whether a call changes the outside world, such as sending mail, writing a record or a request other than a read, so that [replaying a run](workflows.md#replaying-a-run) does not call it: `true`, `false`, or a function of the input that returns one. It must be accurate: a type that declares `false` and writes anyway writes in a replay too. Left out, or a function that throws or returns anything but `false`, it writes. |
|
|
70
71
|
| `run(context)` | Returns the step's result. It throws a `StepError` to fail with an error name of its own; any other error is `States.TaskFailed`. |
|
|
71
72
|
|
|
72
73
|
What `run` gets, and nothing more:
|
|
@@ -94,7 +95,7 @@ A plugin sends through `context.fetch`, never through the global `fetch`, Node's
|
|
|
94
95
|
### Caching results
|
|
95
96
|
|
|
96
97
|
:::caution[Beta]
|
|
97
|
-
|
|
98
|
+
A cached step that decides on access keeps its answer until its result expires, and without the app's secret a hit cannot say whether a secret changed since.
|
|
98
99
|
:::
|
|
99
100
|
|
|
100
101
|
A step type can reuse the result of an identical earlier call, from any run of any workflow, so a slow or costly lookup with a small result runs once. Caching is for such steps: a quick read, such as a local file, runs faster than a lookup in the cache, and only fills the app's database. A type without `cache` costs nothing: the engine looks up, keeps and tracks nothing for it.
|
|
@@ -118,11 +119,11 @@ lookup: {
|
|
|
118
119
|
| `key` | What a call's key covers. `reads` (default): only the input values `run` read, everything it serialised, listed or handed to `fetch` counting as read. `input`: the whole input. A function of the input: its value, as JSON; one that throws or gives nothing leaves the call uncached. Under `reads`, a value read only after `run` returned is not in the key, so a type that reads its input in a callback later uses `input`. |
|
|
119
120
|
| `actsAs` | `records`, as the person who saved the version, and `mailbox`, as the person who turned it on: the services a cached call may act through, each adding that person to the key. |
|
|
120
121
|
|
|
121
|
-
Two calls share a result only when their keys match and they act as the same people: the run's tenant always; the saver and the enabler where `actsAs` lists them; and the account the version binds to the type's connection, with the person who turned it on. The app reads these from the run, never from the step. A cached call of a type with a connection checks the connection before it looks up the cache, as its `accessToken()` does, so once the run may no longer use the account, the call
|
|
122
|
+
Two calls share a result only when their keys match and they act as the same people: the run's tenant always; the saver and the enabler where `actsAs` lists them; and the account the version binds to the type's connection, with the person who turned it on. The app reads these from the run, never from the step. A cached call of a type with a connection checks the connection before it looks up the cache, as its `accessToken()` does, so once the run may no longer use the account, the call throws its `not_connected` `ConnectionError` rather than return an earlier result, and the step fails with `States.TaskFailed`, the name a `Catch` matches. A call that used `records` or `mailbox` without listing them, or touched the run's files, which are the run's alone, runs as always and is not kept. Only a call that succeeded with a result is kept, at most 256 KiB of it as the run records it, so secrets are blanked out. Each type keeps at most 1,000 results per tenant, and the ones that expire soonest go first. A cache that cannot be read within two seconds or written leaves the step to run without it.
|
|
122
123
|
|
|
123
124
|
A workflow forces the cache on with `Cache: true` on its Task, even for a type without `cache` or with a hidden one. A type without `cache` then keys on its whole input and on every person a step can act as, and keeps a result for an hour; a type with `cache` keeps its own `ttl`, `key`, `use` and `actsAs`. A Task with `Cache: false` never uses it.
|
|
124
125
|
|
|
125
|
-
Secrets are never kept. A step always gets a secret's current value, and a key holds the secret's name where the input held its value, so changing a secret and running again calls the step with the new value whenever the old result no longer matches. A hit whose input named a secret that has changed since says so in the step's logs, and in `secrets_changed`, by a fingerprint of the value under the app's secret, `PROTOBASE_SECRET`. Without one, the app cannot tell. A cached step that decides on access, such as whether someone may do something, keeps its answer until its result expires. The results are kept in the workflows schema's `step_cache`.
|
|
126
|
+
Secrets are never kept. A step always gets a secret's current value, and a key holds the secret's name where the input held its value, so changing a secret and running again calls the step with the new value whenever the old result no longer matches. A hit whose input named a secret that has changed since says so in the step's logs, and in `secrets_changed`, by a fingerprint of the value under the app's secret, `PROTOBASE_SECRET`. Without one, the app cannot tell. A cached step that decides on access, such as whether someone may do something, keeps its answer until its result expires. The results are kept in the workflows schema's `step_cache`. A [replay](workflows.md#replaying-a-run) serves its reads from the cache the same way. <gap>A replay cannot hold back a plugin's step that writes while declaring `writes: false` because a check at run time of what a step does is not implemented.</gap>
|
|
126
127
|
|
|
127
128
|
## Trigger types
|
|
128
129
|
|
|
@@ -228,7 +229,7 @@ auth: {
|
|
|
228
229
|
},
|
|
229
230
|
```
|
|
230
231
|
|
|
231
|
-
A step or a trigger hook gets the connection its type declares as `connection`, and nothing else. It has no connection id, so a plugin cannot reach another workflow's account. `connection.accessToken()` gives a token that is valid now, refreshed first when it expired, and checks every time that the workflow may still use the account, as [Connecting an account](workflows.md#connecting-an-account) says. It throws a `ConnectionError`:
|
|
232
|
+
A step or a trigger hook gets the connection its type declares as `connection`, and nothing else. It has no connection id, so a plugin cannot reach another workflow's account. `connection.accessToken()` gives a token that is valid now, refreshed first when it expired, and checks every time that the workflow may still use the account, as [Connecting an account](workflows.md#connecting-an-account) says. `connection.accessToken({ refresh: true })` refreshes it even before it expires, for a token the provider refused, such as after an answer of `401`. It throws a `ConnectionError`:
|
|
232
233
|
|
|
233
234
|
- **`not_connected`:** the workflow may not use an account for the connection.
|
|
234
235
|
- **`link_expired`:** the provider refused the refresh, so the account's owner connects it again. A trigger that throws this waits for that sign-in.
|
|
@@ -244,7 +245,7 @@ Every app has these plugins besides `app`. A type that signs in is offered once
|
|
|
244
245
|
### Incoming Gmail mail
|
|
245
246
|
|
|
246
247
|
:::caution[Beta]
|
|
247
|
-
|
|
248
|
+
Mail starts its run within about a minute of arriving, not at once.
|
|
248
249
|
:::
|
|
249
250
|
|
|
250
251
|
`gmail:new_mail` starts one run for each new mail on a label of a connected Gmail mailbox. It reads the mailbox with read-only access, so a workflow it starts cannot send, change or delete mail through it.
|
|
@@ -290,4 +291,4 @@ A run's input is the mail:
|
|
|
290
291
|
|
|
291
292
|
## The type list
|
|
292
293
|
|
|
293
|
-
[`list_workflow_types`](mcp.md#workflows) over MCP lists every step and trigger type the app has, built-in and plugin ones alike, as their plugins declare them: id, label, description, input and output schemas, errors, connection
|
|
294
|
+
[`list_workflow_types`](mcp.md#workflows) over MCP lists every step and trigger type the app has, built-in and plugin ones alike, as their plugins declare them: id, label, description, input and output schemas, errors, connection, a step type's cache advice (`choice`, `default` and `disclaimer`) and whether it writes (`writes`: `true`, `false`, or `by_input` when that depends on its input), with `available: false` and a reason for a type the app cannot run. A save, and `validate_workflow`, check a definition against the same list.
|
|
@@ -24,7 +24,7 @@ It adds five resources, over a `workflows` schema that [`protobase migration`](c
|
|
|
24
24
|
| --- | --- |
|
|
25
25
|
| `workflows` | A name, a description and the definition. Each save that changes the definition is checked, and kept as the workflow's next version, saved by the caller. |
|
|
26
26
|
| `workflow_versions` | Every version of every definition as it was saved, with who saved it and when. Read-only. |
|
|
27
|
-
| `workflow_runs` | A run, started by creating it with `workflow_id` and an `input`, or with `rerun_of` to [re-run a failed run](#re-running-a-failed-run); it runs the version its workflow had then. Its status, output, error and timing are written by the runner. |
|
|
27
|
+
| `workflow_runs` | A run, started by creating it with `workflow_id` and an `input`, or with `rerun_of` to [re-run a failed run](#re-running-a-failed-run), and `replay: true` too to [replay a run](#replaying-a-run); it runs the version its workflow had then, a replay the replayed run's. Its status, output, error and timing are written by the runner. |
|
|
28
28
|
| `workflow_steps` | Every step of every run. Read-only. |
|
|
29
29
|
| `workflow_triggers` | The trigger of each enabled workflow that has one besides `manual`: its type, status and last error. Read-only. |
|
|
30
30
|
|
|
@@ -235,7 +235,7 @@ A workflow names the account each of its connections uses in its definition's `c
|
|
|
235
235
|
|
|
236
236
|
- **Saving:** anyone who may change the workflow saves it, an MCP client included. A save refuses a binding that no trigger or step of the workflow uses.
|
|
237
237
|
- **Turning it on is the consent.** A workflow that uses a connection is turned on only by the owner of every account it names, signed in as themselves. Someone else, staff signed in as the owner, and an outside app acting for them get a `403` problem (`own-sign-in-only`), the same for an account that is not there. Over MCP, `enable_workflow` answers that the owner turns it on in the admin app. A save that keeps such a workflow on is turned on again by the person saving it, so only the owner saves it while it is on; anyone else turns it off first.
|
|
238
|
-
- **At run time:** each time a step or trigger asks for a token, the app checks again. The version it runs must name the account, the person who turned that version on must own it, and that person must still be allowed to change the workflow in its tenant. Otherwise the step fails with `
|
|
238
|
+
- **At run time:** each time a step or trigger asks for a token, the app checks again. The version it runs must name the account, the person who turned that version on must own it, and that person must still be allowed to change the workflow in its tenant. Otherwise the token request throws a `not_connected` `ConnectionError`, and the step fails with `States.TaskFailed`. The same happens once the workflow is turned off, once the account is disconnected, and once that person lost the right to change workflows, even between two requests of one step. A run never records the token.
|
|
239
239
|
- **Signing in again:** when the provider refuses a refresh token (`invalid_grant`), the step fails with `link_expired`, and every trigger on that account waits as `needs_sign_in`. The Connections section marks the account. Connecting the same account again, without disconnecting it first, resumes the triggers from where they stopped. A refresh that fails otherwise, as when the provider is down, fails only that try, which a `Retry` or the next poll repeats, and the account stays connected.
|
|
240
240
|
- **Turning it off:** turning the workflow off, turning on a version with another trigger, or deleting the workflow ends a webhook trigger's subscription at the provider, also for a trigger type the app no longer saves, as after its connection's client was removed. The stop signs in through the account the subscription was made with, as the person who turned that version on, for that one call only; runs and steps still get `not_connected`. While that account is disconnected or no longer that person's, or when the provider refuses its sign-in, the trigger stays `stopping` in `workflow_triggers` with `not_connected`, as does one turned on before the app recorded who turned it on.
|
|
241
241
|
|
|
@@ -323,6 +323,16 @@ A failed run's page offers *Re-run from the failed step*. It opens the form for
|
|
|
323
323
|
|
|
324
324
|
A kept step's card on the run's page says it is from the earlier run, as it finished before the re-run started; the earlier run's page lists its re-runs.
|
|
325
325
|
|
|
326
|
+
### Replaying a run
|
|
327
|
+
|
|
328
|
+
A run that ended, succeeded or failed, offers *Replay* on its page. It opens the form for a new run with `workflow_id`, `rerun_of` and `replay` filled in; creating it, through the form or `POST /api/v1/workflow_runs` with `{ "workflow_id": "...", "rerun_of": "...", "replay": true }`, starts a run that runs from the start on the replayed run's version and input, so you can look into what formed a message, such as a mail sent with the wrong contents, without sending it again:
|
|
329
|
+
|
|
330
|
+
- a step whose type writes, such as `app:mail.send`, `app:records.create`, `app:records.update`, an `app:http` request with any method but `GET`, and a plugin's step that does not say it only reads, is not called: it gives what the replayed run's step of the same place gave, its output and variables, or a `null` output when that run has none there, and its logs say *Not sent: replay*;
|
|
331
|
+
- a step that only reads, such as `app:records.get`, `app:records.list`, an `app:http` `GET`, or a plugin's step that says so, is served from the [step cache](workflow-plugins.md#caching-results) when its type caches and an earlier result matches, saying so in its logs, with a line for each secret that changed since that result was kept; otherwise it runs again, as the same people a run of that version acts as;
|
|
332
|
+
- Pass, Choice, Map, Succeed and Fail states compute as always, and a Wait goes on at once.
|
|
333
|
+
|
|
334
|
+
A replay given an `input` of its own is refused with `rerun-input`, and one without `rerun_of` with `replay-without-run`. It is refused, and nothing starts, when the replayed run is not one of the workflow's runs in the person's tenant (409, as for a workflow that is not there), has not ended (`run-not-ended`), or when the person may not start runs of the workflow, read the run, or read all of its steps (404). A step holds back only as its type declares: a plugin's step that writes while it declares it only reads runs in a replay too, so a replay is no guard against a plugin that declares it wrongly.
|
|
335
|
+
|
|
326
336
|
### Test runs
|
|
327
337
|
|
|
328
338
|
A workflow's page offers *Test run*: the form for a new run of the workflow, whose `input` takes a sample input as JSON, which every state reads as `$states.context.Execution.Input`. Creating it opens the run's page, which follows it as it runs. A test run is a run like any other: its steps do what they do, as the workflow's last editor, with the version saved last.
|