Durable definitions, activities, canonical history, and replay.
Core-1 Flow Preview
Supersession notice: C1-FLOW-002 → C1-FLOW-006, C1-FLOW-003 → C1-FLOW-007, C1-FLOW-004 → C1-FLOW-008, C1-FLOW-005 → C1-FLOW-009.
Earlier bodies are retained verbatim for historical references; the named
replacement clauses govern the current profile (see the supersession register).
Status: normative preview; reducer, state machine, durable journal and source/native compensation implemented
C1-FLOW-001 — Surface. The canonical types are unforgeable Flow<Self>,
FlowDefinition<Signature>, Activity<Signature>, FlowRun<Output,Error>,
FlowOutcome<Output,Error>, and FlowEngine.
C1-FLOW-002 — Canonical history. History records runs, nodes, activity attempts, decisions, timers, signals, retries, cancellation, compensation, schemas, code epochs, and external-effect receipts. The contract is durable and replay-safe even when the first provider stores history in memory.
C1-FLOW-006 — Canonical history. History records runs, nodes, activity attempts, decisions, timers, signals, retries, cancellation, compensation, schemas, code epochs, and external-effect receipts. The contract is durable and replay-safe even when the first provider stores history in memory.
A valid checksum is necessary but not sufficient for journal admission. Frames
must use canonical fields and contain one start per run. Before recovery installs
a definition, its history must follow that graph’s node kinds, ordered cursors,
consecutive activity attempts and retry limits. Completion is terminal. Without
registered compensation, retry exhaustion and cancellation are also terminal;
with compensation they begin the bounded unwind phase described below. Events
after a terminal outcome reject. An impossible history reports
HISTORY_MISMATCH, not a workflow failure, and repeated failed attachments cannot
make it admissible. Valid failed or cancelled runs retain their outcome and exact
history bytes on recovery; zero-filled activity receipts remain valid receipts.
Supersedes C1-FLOW-002 for the current profile. The retained earlier body
records its original meaning; this allocation governs the additions and
replacement behavior above.
C1-FLOW-003 — Delivery and checkpoints. Activities are at-least-once unless
checked deduplication or transactionally coupled authority strengthens them.
Only owned Durable values cross checkpoints. Borrows, Txn, process
capabilities, and address-dependent values reject. Dropping a local FlowRun
handle does not cancel its run.
C1-FLOW-007 — Delivery and checkpoints. Activities are at-least-once unless
checked deduplication or transactionally coupled authority strengthens them.
Only owned Durable values cross checkpoints. Borrows, Txn, process
capabilities, and address-dependent values reject. Dropping a local FlowRun
handle does not cancel its run.
The native explicit-delivery boundary identifies the durable run, node and
attempt. A deduplicated activity acknowledges an exact repeated outcome, value
and non-null receipt without appending history, including after retry,
completion, cancellation or reload. Conflicting or future deliveries and stale
non-deduplicated deliveries reject with HISTORY_MISMATCH. Receipt bytes are
scoped to that delivery, not globally unique. This deduplicates acknowledgements,
not arbitrary external effects. The unlabelled completion boundary always
targets the current command and cannot safely acknowledge stale deliveries.
An engine requiring recovery after an uncertain append rejects even previously
committed duplicates until it has closed and reopened.
The native engine owns stable run state until it closes. Starting other runs does not invalidate live handles or detach them from updates through another handle. Reattachment compares definition identity, epoch, ordered node fields, and inputs, not padding in the host C representation. After journal recovery, the acknowledged start digest must match those same semantic fields.
Supersedes C1-FLOW-003 for the current profile. The retained earlier body
records its original meaning; this allocation governs the additions and
replacement behavior above.
C1-FLOW-004 — Replay test. The simulator must serialize history, destroy all live process state, reload, and produce identical normalized next commands and outcomes.
C1-FLOW-008 — Replay test. The simulator must serialize history, destroy all live process state, reload, and produce identical normalized next commands and outcomes.
The native reducer and compiled-machine providers accept an optional existing
ZFJ2 journal as a read-only replay seed. A null path starts an empty in-memory
engine. Opening a seed loads complete checksummed frames and closes the file;
attachment validates the definition/input identity and canonical event sequence
before the selected executor resumes. Interleaved run IDs retain independent
histories, and fresh run IDs follow those already recorded. Further events, new
runs and checkpoint calls do not modify the seed or publish checkpoint files.
A fresh engine sees the original seed, not a previous in-memory continuation.
Missing seeds report IO; incomplete or malformed seeds report
HISTORY_MISMATCH without repair or checkpoint fallback. Only the writable
file-journal provider may repair an incomplete tail during recovery.
Supersedes C1-FLOW-004 for the current profile. The retained earlier body
records its original meaning; this allocation governs the additions and
replacement behavior above.
C1-FLOW-005 — Executors. Preview conformance requires a pure history reducer and independent compiled resumable state machine. Stable Core-1 additionally requires an append-only file journal with framed checksummed records, durable commit before acknowledgement, atomic checkpoints, and kill/restart tests at every history/effect boundary.
The implemented source profile retains straight-line checked integer activity
expressions as scalar signal regions and lowers Flow.activity definitions to
a dedicated durable flow-history row. FlowEngine.start is a distinct checked
algorithm boundary and requires live engine authority. Its reference reducer
and compiled-machine evaluator return activity results to the orchestration
boundary. The bounded recorded-activity adapter executes a polled scalar
algorithm, state, or optimize source target whose domain and arity match the
history command, then records its success or failure and any mandatory
deduplication receipt through FlowEngine before returning. Tensor compute
activities retain their explicit device boundary. Same-domain scalar calls
with up to two arguments execute from local or package-linked regions under
the common call-depth bound. Other unsupported source bodies remain
unmaterialized.
The implemented source definition profile contains an acyclic ordered durable
graph. Flow.activity records typed external work with an explicit durable
node ID, schema version, maximum attempts, and deduplication policy.
Flow.timer(id, duration) records a timer whose duration is a run input or a
prior node result. Flow.signal(id, schema) records a typed signal wait; its
received value becomes the node result. Nesting any value-producing durable
node supplies its result as the input of the enclosing activity or timer; a
direct parameter read remains a run input. Duplicate IDs, ordinary expressions
between durable nodes, forward dependencies, and cycles reject. Both in-memory
executors and the append-only file journal consume the same checked definition.
ZLM2 round trips preserve kinds and dependencies, package closures relocate
typed activity targets, and restart attachment verifies the definition/input
digest recorded by the acknowledged start event before replay continues.
The file-journal provider exposes a non-reentrant durability hook for provider
and conformance testing. Tests terminate child processes before, during, and
after frame writes, after flush and fsync, and across checkpoint file
fsync, rename, and parent-directory fsync. Recovery accepts only the last
complete checksummed frame and never acknowledges an event before the journal
fsync boundary.
C1-FLOW-009 — Executors. Preview conformance requires a pure history reducer and independent compiled resumable state machine. Stable Core-1 additionally requires an append-only file journal with framed checksummed records, durable commit before acknowledgement, atomic checkpoints, and kill/restart tests at every history/effect boundary.
The implemented source profile retains straight-line checked integer activity
expressions as scalar signal regions and lowers Flow.activity definitions to
a dedicated durable flow-history row. FlowEngine.start is a distinct checked
algorithm boundary and requires live engine authority. Its reference reducer
and compiled-machine evaluator return activity results to the orchestration
boundary. The bounded recorded-activity adapter executes a polled scalar
algorithm, state, or optimize source target whose domain and arity match the
history command, then records its success or failure and any mandatory
deduplication receipt through FlowEngine before returning. Tensor compute
activities retain their explicit device boundary. Same-domain scalar calls
with up to two arguments execute from local or package-linked regions under
the common call-depth bound. Other unsupported source bodies remain
unmaterialized.
The implemented source definition profile contains an acyclic ordered durable
graph. Flow.activity records typed external work with an explicit durable
node ID, schema version, maximum attempts, and deduplication policy.
Flow.timer(id, duration) records a timer whose duration is a run input or a
prior node result. Flow.signal(id, schema) records a typed signal wait; its
received value becomes the node result. Nesting any value-producing durable
node supplies its result as the input of the enclosing activity or timer; a
direct parameter read remains a run input. Duplicate IDs, ordinary expressions
between durable nodes, forward dependencies, and cycles reject. Both in-memory
executors and the append-only file journal consume the same checked definition.
ZLM2 round trips preserve kinds and dependencies, package closures relocate
typed activity targets, and restart attachment verifies the definition/input
digest recorded by the acknowledged start event before replay continues.
Source-backed runs additionally bind the complete reachable checked activity
implementation closure, including imported private helpers. The source code
epoch is the low 64 bits of the domain-separated
zerglang.flow-source-code-epoch.v1 identity: definition contract/body IDs,
reachable-function count, and the byte-sorted bound function IDs. Each function
ID uses zerglang.flow-source-call-bindings.v1 over its checked implementation
ID and the resolved callee contract IDs in instruction order. Binding the
ordered targets prevents declaration reordering plus call retargeting from
aliasing an old epoch with the same relocated slots and implementation set.
The visited call graph is finite even when activities contain recursive calls.
Live definition-activity execution and restart attachment both require this
epoch. Changing a reachable body or modality rejects with HISTORY_MISMATCH
before another event is recorded. An unchanged checked closure survives ZLM2
round trips, package input-file permutation and changes confined to existing
unreachable bodies; execution provider and host addresses do not participate.
This replaces the earlier source allocation that used only the definition’s body ID and therefore did not bind activity implementations. Such earlier source journals fail closed on source attachment, even with the original source: their recorded epoch cannot establish which activity code ran. There is no implicit migration or rewriting of those journals. Native caller-owned definition epochs, ZFH2/ZFJ2 field layouts, source syntax and ZLM2 allocations are unchanged. Frozen benchmark epoch bytes remain in their original catalogs; the separate ZL256 0.8.0 compatibility revision pins the stronger allocation.
The file-journal provider exposes a non-reentrant durability hook for provider
and conformance testing. Tests terminate child processes before, during, and
after frame writes, after flush and fsync, and across checkpoint file
fsync, rename, and parent-directory fsync. Recovery accepts only the last
complete checksummed frame and never acknowledges an event before the journal
fsync boundary.
An append write, flush or fsync failure makes the live file-journal engine
require recovery. It cannot acknowledge another append, new run or checkpoint,
even if the filesystem becomes writable again. The caller closes and reopens
the engine, then attaches the definition to resolve the complete persisted
prefix; an unacknowledged complete event may have survived. Read-only history
and outcome inspection still describe the old in-memory prefix until close.
A checkpoint-only I/O failure leaves the committed journal usable and may be
retried after the dependency recovers.
The additive native v3 definition profile registers at most one compensation
activity per forward activity. Registrations bind the forward node ID and a
distinct compensation node ID, domain, schema, retry bound and deduplication
policy. Only successfully completed forward activities compensate, in reverse
graph order; their recorded successful results supply compensation inputs.
Registration-table order does not affect identity or replay. A cancelled run or
an exhausted forward retry remains RUNNING while COMPENSATION commands are
pending. Successful unwind restores the initiating CANCELLED/0 or
FAILED/value. Exhausted compensation retries instead stop with FAILED and
the last compensation failure value; remaining compensation is not executed.
Successful forward completion never compensates. A second cancellation during
unwind rejects, and releasing a local handle never cancels either phase.
Native v3 runs use ZFH3 histories and ZFJ3 journal frames. Their existing
field widths are retained, with compensation-success/failure event kinds 6/7
and a start digest binding the registration metadata in forward-node order.
Every frame of a run must use the same version. v2 definitions and wire bytes
remain unchanged and v2 frames cannot carry compensation events. The complete
allocation and native API usage are specified in
docs/flow-compensation.md.
Source Flow.compensate(id, target, activity_result, schema, attempts, deduplicated)
registers a typed scalar undo callback and returns the owning forward activity’s
result without executing that callback. It requires a compiled non-portable
Flow message with the flow effect. Parameters, timers, signals and duplicate
registrations cannot be owners; all IDs are unique across both phases. The
explicit ZLM2 2.26 / ZLA2 1.18 extension retains callback ownership and policy,
re-lowers checked semantics at artifact admission, and exposes registrations in
public reflection. Source execution selects the native v3 definition and the
current phase’s callback. Undo implementations, including imported helpers,
participate in the same reachable code epoch used for forward work.
Broader Durable type admission, benchmark fixture promotions and schema
migration remain separate work.
The additive native v4 checkpoint profile records bounded owned Dynamic
snapshots at the current pending command without advancing its cursor or
attempt. Snapshot IDs are run-scoped; exact ID/value repeats acknowledge without
appending, while conflicting reuse rejects. Only new running-state snapshots
are admitted, including during compensation. The schema is copied and bound to
run identity. Owned reads select an exact ID or the latest snapshot and remain
available after termination. ZFH4 histories contain canonical value bytes;
ZFJ4 verifies a length-bearing header checksum before admitting a variable
payload and a second checksum over the complete frame. The maximum canonical
value is 1,048,576 bytes. v2/v3 bytes remain unchanged. Allocation, native APIs
and ownership/recovery evidence are documented in
docs/flow-checkpoints.md.
The additive ZLM2 2.27 / ZLA2 1.19 source profile admits
FlowEngine.checkpoint(id, input, schema) as a direct owned Dynamic boundary
in an interpreted algorithm with exactly the flow effect. A schema-bearing
FlowEngine.start(target, input, schema) selects v4; the two-argument form is
unchanged. The host checkpoint adapter requires a live schema-matching FlowRun
and follows only transparent checked wrappers, including package imports.
Ordinary Dynamic invocation rejects even with an issued effect capability.
Snapshot ID/schema metadata is retained in checked artifacts, body identity and
public reflection. It is not an automatic graph-node snapshot or schema migration.
The native conformance suite uses parent-driven SIGKILL at 35 frame windows
(start, retry, success, timer, signal, cancellation and retry exhaustion) and
21 checkpoint windows. It destroys the oracle executors before spawning the
journal process, then compares recovered commands, outcomes and canonical
bytes with both independent executors. Checkpoint tests also remove the live
journal to distinguish the previous published checkpoint from an unpublished
temporary file. Filesystem-boundary failures separately prove that journal,
checkpoint-file and parent-directory fsync calls are required for successful
acknowledgement; observing a durability hook alone is not that proof.
The native compensation extension adds 30 frame-kill and 18 checkpoint-kill windows, including unwind initiation, compensation retry, successful unwind and compensation retry exhaustion. Both oracle providers are destroyed before the journal process starts, and recovered commands, outcomes and bytes must match.
Supersedes C1-FLOW-005 for the current profile. The retained earlier body
records its original meaning; this allocation governs the additions and
replacement behavior above.