P1 → Open Workflow DSL, v1
Normative. Derived from RUNBOOK.md §5. Every rule here has a worked example in
spec/v1/examples/, and every example is validated against the published schema —
vendored at spec/vendor/open-workflow-1.0.3.yaml — by
packages/convert/src/ows.test.ts.
Without this mapping the catalog is a format island (§5). The mapping is what makes "implementable on any engine" checkable rather than rhetorical.
Which DSL, exactly
The specification RUNBOOK.md §5 calls "Open/Serverless Workflow DSL 1.0" is published
today as Open Workflow DSL, version 1.0.3, under the Open Workflow Specification.
This document targets 1.0.3 and every example declares dsl: 1.0.3, because a mapping
that names a version nobody publishes is not checkable.
The mapping
P1 kind |
DSL | Example |
|---|---|---|
check |
run (or call) then switch on the result |
check.workflow.yaml |
action |
run, preceded by listen when approval is required |
action.workflow.yaml |
decision |
switch |
decision.workflow.yaml |
wait |
wait |
wait.workflow.yaml |
escalate |
emit then raise |
escalate.workflow.yaml |
check
expect becomes the when of the passing branch; on_fail becomes the branch taken
when it does not hold. Invariant 4 guarantees both exist, so the switch always has
somewhere to go and never falls through.
Where a step carries a machine-checkable assert (Q12), that is what the condition is
built from. A step with only prose expect cannot be converted faithfully: the mapping
emits the switch with the prose in a comment and the condition left for a human, and the
conversion report says so rather than inventing a predicate.
action
The approval gate is a task, not a flag. requires_approval: true becomes a
listen immediately before the action, waiting for an approval event that names the
step.
That shape is chosen deliberately: a gate expressed as a property of the action is
invisible in a trace, while a gate expressed as a task means a runner that skipped it
has visibly skipped a task. The forced-approval rule of §12 applies before conversion —
a destructive or irreversible action is converted with the gate whatever the source
document said.
decision
Branches come from branches. The default branch is the one with no when, which the
DSL takes as the fallthrough. Invariant 3 requires it to exist.
wait
A P1 wait is always bounded — a duration, or a condition with a timeout — so the DSL
form carries a bound too. Neither format can express an unbounded wait, which is the
point: an unbounded wait is a hang with better manners.
escalate
emit the escalation event, then raise a fault. Escalation is an exit from the
workflow rather than a step inside it, so the workflow ends faulted: something outside
now owns the problem, and a run that ended in escalation must not be reported as success.
What is dropped
Conversion loses everything the catalog adds and the runner does not need: provenance,
trust, licence, publisher, content_hash, x-layout, facets. The conversion report
lists what went, because a silent drop is how someone later discovers their attestation
did not travel with the workflow.
The reverse direction does not exist
Not "is not implemented" — must not exist. The DSL is more expressive than P1, so an arbitrary workflow does not fold back into it, and a converter that appeared to do so would silently discard the parts it could not represent. If one is written, people will rely on it.