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
`xmd workflow start [--id] [--props-*] <definition>` and
`xmd workflow resume <run-id>` run a document as a retained workflow run:
one implicit logical Workspace and one journal in a database that outlives
the process, so an interrupted procedure continues from its journal frontier
instead of from the beginning.
`start` names a document and `resume` names a run, and that asymmetry is the
lifecycle rule — a path locates a definition and never selects a previous run,
so two starts without `--id` are two runs. What executes is the *committed*
document: `start` resolves HEAD once and stores that commit as the run's
identity, so uncommitted edits do not change what a run is a run of, and a
resume loads the same object through retained, credential-free retrieval
metadata rather than the current HEAD or a same-named working-tree file.
Identity and outcome go to standard error as two stable lines, leaving stdout
to the document. Only a completed run exits zero; failed, suspended, cancelled
and interrupted are distinguishable so automation cannot mistake an incomplete
workflow for a finished one.
The capability lives on one host and the grammar on all of them: the Deno
entrypoints own the local run store, and Node and Bun refuse before creating or
executing anything. The shared CLI module imports no SQLite, no DOFS and no
runtime detection — it asks a host adapter to open storage and attach the run's
Workspace.
Until #367 supplies durable ownership, a run left running because its host
disappeared is closed as an orphaned interrupted execution by the next resume.
Nothing here claims concurrent resume is safe.
Copy file name to clipboardExpand all lines: architecture.md
+80-19Lines changed: 80 additions & 19 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -149,9 +149,8 @@ The `@executablemd/workflow` package owns `WorkflowRun`, `useWorkflow()`,
149
149
`getWorkflowRun()` and the Git capability. It depends on `@executablemd/core`,
150
150
`@executablemd/durable-streams` and `@executablemd/runtime`, whose contextual
151
151
`exec()` and `cwd()` the Git provider invokes; core never imports workflow or
152
-
Git. The future CLI lifecycle is `xmd workflow start` and `xmd workflow resume`;
153
-
there is no workflow CLI execution branch yet. The durable lookup that resume
154
-
will require is the run storage below. Ordinary `xmd run` remains unchanged.
152
+
Git. `xmd workflow start` and `xmd workflow resume` are the CLI lifecycle, and
153
+
they resume through the run storage below. Ordinary `xmd run` remains unchanged.
155
154
156
155
## Workflow run storage
157
156
@@ -347,6 +346,68 @@ describe what failed without repeating retained props or journal payloads —
347
346
including their member *names*, which can carry a credential as readily as a
348
347
member value can.
349
348
349
+
## The workflow lifecycle
350
+
351
+
`xmd workflow start [--id=<run-id>] [--props-*=…] <definition>` and
352
+
`xmd workflow resume <run-id>` are the two commands. Both run in the foreground,
353
+
stream the document's own output to standard output, and report identity and
354
+
outcome on standard error as two stable lines — `workflow run: <run-id>` once
355
+
the run has been created or found, and `workflow status: <status>` once the
356
+
execution settles. Only a completed run exits zero: failed exits 1, suspended 2,
357
+
cancelled 3 and interrupted 130, so shell automation cannot mistake an
358
+
incomplete workflow for a finished one. A request the command refuses — bad
359
+
grammar, a missing run, an incompatible reuse, damaged storage, an unsupported
360
+
host — exits 1.
361
+
362
+
`start` establishes an immutable definition from Git rather than identifying a
363
+
working-tree file. It locates the repository containing the supplied path,
364
+
resolves `HEAD^{commit}` once because the command has no base option, reads the
365
+
repository's object format, and stores version 1 of the descriptor with that
366
+
format, the lowercase commit ID and the normalized repository-relative POSIX
367
+
path. **The bytes that execute are the ones that commit holds**, so a working
368
+
tree with uncommitted edits runs the committed document. Where the repository is
369
+
checked out is retrieval metadata: replaceable, credential-free, excluded from
370
+
identity, and reauthorized before it is used again. `resume` loads exactly the
371
+
retained object through that locator and never substitutes the current `HEAD` or
372
+
a same-named working-tree file. A missing object, missing or unreadable
373
+
retrieval metadata, a path outside the repository, or a root that is not
374
+
Markdown fails explicitly; none of them creates a replacement run or an empty
375
+
definition. A workflow definition is one immutable object, so the component
376
+
search path is empty and a repository component fails to resolve rather than
377
+
resolving to content beside the definition in a mutable checkout.
378
+
379
+
Every actual execution opens or creates the run's database, begins a
380
+
document-execution record, installs the exact retained WorkflowRun, installs the
381
+
service denial before the root is imported, and — for a live or partial
382
+
execution — installs the logical working directory `/`, the transaction-bound
383
+
Files provider and that database's Workspace effect coordinator. It executes
384
+
against the database's own journal with the retained props and secret detection,
385
+
then finishes the execution record and publishes the run's status. A completed
386
+
run still replays, so its retained output and result are emitted, but it
387
+
attaches no Workspace provider or coordinator and performs no filesystem
388
+
mutation.
389
+
390
+
A failure retains `failed` and uses a journal stop reason when a retained event
391
+
identifies it, and a categorical host code otherwise; no exception text is
392
+
retained beside the journal that filtered it. Graceful foreground interruption
393
+
finishes the execution `interrupted` and exits 130. `suspended` is resumable;
394
+
`failed` and `cancelled` are refused by `resume`, and `completed` replays under
395
+
either command.
396
+
397
+
The initial host is Deno-local. The Deno entrypoints — source and the compiled
398
+
binary — install the local run store, beneath `~/.xmd/runs` unless
399
+
`XMD_WORKFLOW_RUNS` names another absolute directory. Node and Bun expose the
400
+
same grammar and refuse before creating or executing anything. The shared CLI
401
+
module imports no SQLite, no DOFS and no runtime detection: it asks a host
402
+
adapter to open storage and to attach a run's Workspace, and the entrypoints
403
+
decide which adapter exists.
404
+
405
+
Durable ownership and concurrent-executor enforcement belong to #367. Until it
406
+
lands, a run left `running` because its host disappeared is treated as an
407
+
orphaned interrupted execution by the next resume, which closes that unfinished
408
+
execution record as `interrupted` before beginning its own. Nothing here claims
409
+
concurrent resume is safe.
410
+
350
411
## Workflow Workspace
351
412
352
413
The command selects the environment; the document describes the procedure.
@@ -475,8 +536,8 @@ commit form one boundary. The Workspace filesystem uses the pinned synchronous D
475
536
entry points for its string and byte-array surface, so cancellation leaves no
476
537
eager promise or stream pull able to reach the connection after transaction
477
538
authority ends. The transaction-bound Files provider selects that operation for
478
-
every document filesystem read, write and search; workflow lifecycle commands do
479
-
not select it yet.
539
+
every document filesystem read, write and search, and the workflow lifecycle
540
+
commands install that provider.
480
541
481
542
An external provider cannot join that transaction. Prompt, Git push and pull
482
543
request effects derive a stable identity from the run and expansion, ask the
@@ -552,8 +613,8 @@ collection is not in the production closure and is never invoked. The provider
552
613
exposes no public history selection or fork operation at this layer. Its
553
614
coordinator combines one mutation, immutable-root publication and one filtered
554
615
journal result atomically, and the transaction-bound Files provider is what
555
-
routes a document's `<File>` and `<Glob>` to it. Workflow start and resume do
556
-
not reach it yet.
616
+
routes a document's `<File>` and `<Glob>` to it. `xmd workflow start` and
617
+
`xmd workflow resume` install that provider around each execution.
557
618
558
619
The coordinator treats only errors produced through its private filesystem
559
620
adapter's documented path and mutation refusals as journalable operation
@@ -1017,12 +1078,12 @@ consumed. If collision handling terminates immediately,
1017
1078
receives no terminal event; restoring the compatible definition can still
1018
1079
replay it.
1019
1080
1020
-
#390 provides and tests the non-delegating`useWorkflowServiceDenial()` provider.
1021
-
#366 will install it in the future `xmd workflow start` and `xmd workflow resume`
1022
-
scopes. No workflow CLI execution branch exists yet. The provider prevents a
1023
-
workflow from reaching an inherited host adapter, because a run-owned durable
1024
-
service requires stable identity and reconciliation rather than an
1025
-
execution-owned live process.
1081
+
`xmd workflow start`and `xmd workflow resume` install the non-delegating
1082
+
`useWorkflowServiceDenial()` provider inside each execution scope, before the
1083
+
root document is imported — in the same place `xmd run` installs its host
1084
+
service adapter. The provider prevents a workflow from reaching an inherited
1085
+
host adapter, because a run-owned durable service requires stable identity and
1086
+
reconciliation rather than an execution-owned live process.
1026
1087
1027
1088
## State ownership
1028
1089
@@ -1115,24 +1176,24 @@ Status is measured against main.
1115
1176
|`useWorkflow()` / `getWorkflowRun()`| associates one document execution with a workflow run | built on main |
1116
1177
|`useRetainedWorkflow()`| associates one document execution with a run storage already created, requiring exact journal agreement | built on the #366 stack |
1117
1178
|`Git.revParse()`| verifies and resolves one Git revision expression contextually | built on main |
1118
-
| workflow run storage | creates or compatibly finds one run by public run ID, retains its identity, state, document executions and filtered journal, and validates immutable Workspace roots through one provider-owned connection entry | built on the #365 stack; public workflow execution is unbuilt|
1179
+
| workflow run storage | creates or compatibly finds one run by public run ID, retains its identity, state, document executions and filtered journal, and validates immutable Workspace roots through one provider-owned connection entry | built on the #365 stack; the CLI lifecycle reaches it on the #366 stack|
1119
1180
| caller-owned storage transaction | publishes several changes, including journal events, in one transaction nothing else enlists in | built on main |
1120
1181
| live durable-operation coordinator | explicitly coordinates structured live execution with existing Yield publication while leaving replay and callback effects unchanged | built on the #365 stack |
1121
1182
| Workspace coordination API | fails closed by default; replaceable context routes only a one-use provider selection, while the selected provider directly invokes an execution-owned credentialed capability for execution, publication and failure activation | built on the #365 stack; the Deno provider installs an adapter-private atomic handler |
1122
1183
| explicit WorkflowRun journal route | binds one already-filtered publication to one exact active transaction and otherwise uses ordinary serialized journal storage | built on the #365 stack |
1123
1184
|`API.Service` / `startService()`| creates an authenticated, supervised loopback service attachment through a provider-neutral operation | built on main |
1124
1185
|`API.Files`| routes every document filesystem operation to the installed provider, with no host default and structural failure data | built on the #227 stack |
1125
1186
| host Files provider / `useHostFiles()`| resolves document paths in the caller's filesystem, containing them while the host namespace is stable; installed by all four CLI entrypoints | built on the #227 stack |
1126
-
| transaction-bound Files provider | resolves document paths in the run-owned logical Workspace inside the caller-owned transaction | built on the #366 stack; CLI reachability remains unbuilt|
1187
+
| transaction-bound Files provider | resolves document paths in the run-owned logical Workspace inside the caller-owned transaction | built on the #366 stack |
1127
1188
|`service=<binding>`| publishes the attachment's endpoint into the live binding overlay for its invocation | built on main |
1128
1189
|`ephemeral eval`| reconstructs live middleware and bindings without a journal entry | built on main |
1129
-
|`useWorkflowServiceDenial()`| provides and tests a non-delegating workflow service denial provider; #366 will install it in future start and resume scopes | built on main; no workflow CLI execution branch exists yet|
1130
-
|`xmd workflow start` / `xmd workflow resume`| starts or resumes a workflow run from the CLI| defined in `specs/workflow-workspace-spec.md`, unbuilt; the lookup it resumes through is built|
1131
-
| implicit workflow Workspace | retains provider-neutral filesystem, repository and attachment state by run ID |defined in `specs/workflow-workspace-spec.md`, unbuilt (#218) |
1190
+
|`useWorkflowServiceDenial()`| provides a non-delegating workflow service denial provider, installed inside every start and resume execution scope | built on the #366 stack|
1191
+
|`xmd workflow start` / `xmd workflow resume`| starts or resumes a workflow run from the CLI, under the Deno entrypoints only | built on the #366 stack; status, list, history, cancel, fork and delete are unbuilt|
1192
+
| implicit workflow Workspace | retains provider-neutral filesystem, repository and attachment state by run ID |document filesystem built on the #366 stack; repository, process and attachment capabilities unbuilt (#218) |
1132
1193
| Repository / Worktree / transactional Git effects | compose named checkouts and publish local mutations with their journal result | defined in `specs/workflow-workspace-spec.md`, unbuilt |
1133
1194
| workflow inspection and history fork | reads status/history without advancing a run and creates a new run from a checkpoint | defined in `specs/workflow-workspace-spec.md`, unbuilt |
1134
1195
| read-only workflow Agent / generated XMD | lets an Agent inspect a derived view and propose constrained executable changes | defined in `specs/workflow-workspace-spec.md`, unbuilt |
1135
-
| Deno-local DOFS provider | owns one authoritative SQLite/DOFS connection per run path, captures arbitrary canonical retained roots, privately restores them, and atomically coordinates one Workspace mutation with its filtered Yield | built on the #365 stack; public document filesystem effects route to it on the #366 stack, and workflow lifecycle reachability is unbuilt|
1196
+
| Deno-local DOFS provider | owns one authoritative SQLite/DOFS connection per run path, captures arbitrary canonical retained roots, privately restores them, and atomically coordinates one Workspace mutation with its filtered Yield | built on the #365 stack; public document filesystem effects and the CLI lifecycle route to it on the #366 stack |
1136
1197
| scoped Worker Shell | executes `just-bash` through the Workspace adapter inside a Deno Worker | containment and effect-transaction POCs complete (#351, #357); production integration unbuilt |
1137
1198
|`<Retry max timeout>`| retry a region until it completes | defined, unbuilt |
0 commit comments