Core-1 previewnormative preview; stable Core-1 inactive

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.

Projected fromspec/editions/core-1/flow-preview.md