Core-1 preview extensionimplementation reference

Owned Dynamic snapshots, schema-bound replay, and native v4 checkpoint durability.

Owned Flow checkpoint payloads

Evidence correction: historical kill counts from the native Core-1 mutation runner before 9a63b70ff4 are withdrawn pending fresh replay. Stale binaries could produce false kills. This qualifies all such counts below; see docs/mutation-evidence.md. Test inventories are not execution evidence.

The opt-in native v4 profile records owned Dynamic snapshots in canonical Flow history. It extends the same reducer, independent compiled state machine and append-only file journal used by v2/v3. The ZLM2 2.27 / ZLA2 1.19 source extension exposes this boundary without embedding process authority. It does not promote a benchmark fixture or activate stable Core-1. The separate benchmark scenario adapter and pinned corpus promotion are described below; runtime support alone is not fixture evidence.

Typed Flow follow-up boundary (ZER5-230)

The 2026-09-09 audit leaves ZER5-230 in Icebox pending its named ZER5-227 first-class domain-result/host-boundary prerequisite. Durable scalar graphs, compensation and owned Dynamic snapshots remain delivered; they do not admit all typed activities, source continuations or ordinary owned Durable values. ZER5-27’s existing replay/crash evidence is not reopened or relabeled as coverage for unimplemented typed workflows.

Reproduced source limits

Public zlc check --edition=core-1 --preview=flow --diagnostics=json probes used clean archived compiler 3cf6d107e4b899500b0c79cd2461a87a866fa8b8, version 0.1.1-preview.4, Apple Clang 17.0.0 on arm64 macOS, LLVM disabled. Worktree cb81eed1bb differed only through subsequent documentation commits.

Probe Observed result
One scalar activity and its FlowEngine.start wrapper Accepted
Same graph with Bool or fieldless-enum activity input/result ZL-TYPE-0001: activity requires an Int64 -> Int64 signature
Conditional selection between two scalar activity nodes ZL-FLOW-0001: definition must be a checked acyclic durable graph
FlowOutcome<Int64, Int64> source result ZL-TYPE-0001: type does not resolve to a supported scalar or enum
Direct owned Dynamic -> Dynamic checkpoint Accepted
Int64 -> Int64 checkpoint ZL-TYPE-0001: checkpoint requires an owned Dynamic -> Dynamic signature
Checkpoint assigned to a Dynamic local before returning it ZL-FLOW-0001: boundary must return exactly one owned input checkpoint

The conditional probe has no user-authored cycle: its diagnostic identifies the current restricted graph grammar, not general cycle analysis. The first two probe runs used reserved flow/workflow module identifiers and are not typed-Flow evidence. The retained *-valid-module.zl probes use the existing native tests’ accepted module identifiers.

The existing Flow, source compensation, source checkpoint, checkpoint payload and runtime-observation groups passed 18/18 in 3.38 seconds with 90-second per-test deadlines and --no-tests=error. The runtime wrapper contains two Python tests. These are bounded-profile baseline checks, not a fresh-process crash campaign for new typed workflows. Complete logs and all probe attempts are retained under /private/tmp/zerglang-230-flow-audit.PKylWR/.

Representation and recovery constraints

src/core1_flow.c requires scalar activity signatures and the separate exact owned-Dynamic checkpoint wrapper. Public command inputs and activity/outcome values are Int64; the checked graph retains ordered nodes and input/node dependencies, not a typed continuation with arbitrary branch-local owners. The v4 Dynamic snapshot is a distinct history event containing canonical ZLD2 bytes. Recording it does not replace the current command input, advance a graph cursor or create a new typed result. Its recursive list/record tags are not nominal source type instances or executable continuations.

Consequently, adding a schema number or reusing a Dynamic tag cannot establish the missing typed owner/error contract. Nor can the old scalar value slot be reinterpreted as a pointer, enum tag or heap handle. New typed activity values, decisions and continuations need explicitly bounded source/native/history allocations coordinated with ZER5-227 and its owned-carrier prerequisites.

Resume implementation with public failing typed workflows, then cover:

  • Exact input/result/error and owned-Durable layouts, including empty values, with static and decoded-artifact rejection of borrows, Txn, capabilities and address-dependent payloads. Preserve allocator/owner cleanup and impose explicit serialized size/depth limits.
  • Versioned graph decisions, reachable forward/undo implementation identities, code epochs and schema binding. Preserve cancellation, reverse compensation order and precisely the declared delivery guarantees; deduplicated durable acknowledgements do not imply exactly-once external effects.
  • Equal typed commands/outcomes/history under the independent reducer and compiled machine, then fresh-process journal recovery. Kill and inject failures at every new effect/history/checkpoint boundary; retained in-memory acknowledgements and uncertain persisted frames must remain distinguishable.
  • Source/artifact/package relocation, changed reachable code, schema drift, malformed typed history, generated replay/identity properties and behavior-mapped mutation evidence. Preserve ZFH/ZFJ and frozen source epochs; an explicit future migration cannot regenerate missing historical behavior.
  • Bounded canonical snapshot and visible ZDE representation/tests for new runtime state, extending ZER5-241’s delivered scalar/Dynamic Flow projection rather than treating it as coverage for unimplemented types.

This is a documentation-only dependency audit using the test-first/mutation exception. It changes no runtime, journal, source epoch, corpus, publication or activation state.

Source boundary

Source-language checkpoint syntax is a deliberately bounded owned-value boundary:

public interpreted algorithm message save(
    type: Class<Self>, input: Dynamic
) -> Dynamic effects { flow } {
    return FlowEngine.checkpoint(11, input, 9);
}

The arguments are a positive snapshot ID, the owned input, and a positive u32 payload schema. Source IDs range from 1 through 9,223,372,036,854,775,807; native and artifact snapshot IDs retain their full u64 allocation. The current source profile requires exactly one Dynamic input and an owned Dynamic result, an interpreted non-portable algorithm, and exactly the flow effect. Its body must directly return that input’s checkpoint. Borrowed values, Txn, device/engine capabilities, scalar substitutions, nested checkpoints, arbitrary computation, extra locals/effects and contracts are outside this boundary.

The optional third argument in FlowEngine.start(Work.definition, input, 9) selects a schema-bound v4 run. Omitting it preserves existing v2/v3 behavior. The existing start/attach host APIs create the run from checked source or a reloaded artifact; activities and compensation retain their scalar contracts. This is not an automatic graph-node snapshot or a new graph input type.

The host calls zl_flow_run_checkpoint_module_dynamic_v4 with that live run, checked module, public checkpoint selector and owned value. The adapter follows only transparent checked wrapper calls (including linked package helpers), verifies the declared schema against the live run, and allocates its owned return value before the durable append. Success returns an independent owned value, including on exact duplicate acknowledgement. Failure clears the output; a disk error retains the native engine’s recovery fence. Destroy the returned value with zl_dynamic_destroy_v2.

Declaring effects { flow } does not mint a FlowRun. Ordinary zl_module_invoke_dynamic_v2 rejects checkpoint execution with AUTHORITY_REQUIRED and ZL-FLOW-0002, even when supplied an issued effect capability. Artifact loading and reflection do not create runtime authority.

ZLM2 2.27 is selected only by a checkpoint or schema-bearing start carrier. It uses ZLA2 1.19 and semantic kind 35 (flow-engine-checkpoint) with the existing FlowEngine boundary operation 4. FlowIR rows are always 72 bytes in this profile, even without compensation; other outer record widths are unchanged. Canonical private carrier metadata binds the snapshot ID and schema. Artifact admission checks carrier spelling, ownership, signature, checked semantics and profile consistency. The additive zl_zlm2_wire_schema_v9 API and zlc-core zlm2-schema --minor=27 describe the allocation; old APIs retain their accepted ranges and byte-identical descriptors.

Public reflection exposes flow_checkpoint (ID, schema, owned Dynamic, and FlowRun authority) and the start’s checkpoint_schema. Body identities bind these metadata; changing only ID/schema leaves the message contract identity unchanged. Canonical checked-AST output exposes the boundary as well. There is no separate UI-only runtime store.

Native boundary

Construct zl_flow_checkpoint_definition_v4, set compensated.base.abi_version = ZL_FLOW_ABI_VERSION_V4 and compensated.base.struct_size = sizeof(zl_flow_checkpoint_definition_v4), then pass &definition.compensated.base to the existing start/attach APIs. Set a positive checkpoint_schema_version. The nested v3 compensation table is optional and retains its ordinary ownership, retry and receipt rules.

zl_flow_run_checkpoint_dynamic_v4(run, id, value, error) records a snapshot at the current pending command. The nonzero snapshot ID is run-scoped and independent of forward/compensation node IDs. Recording does not advance the command, change its input, consume an attempt, or alter its outcome. Snapshots are allowed while waiting for an activity, timer or signal and during compensation, including compensation retries. New snapshots after a terminal outcome reject. An empty graph is already terminal and cannot record a snapshot.

An exact repeated ID and canonical value acknowledges without appending, even after later snapshots, completion or reload. Reusing an ID with a different value rejects with HISTORY_MISMATCH. Repeating an older snapshot does not make it the latest one. This is acknowledgement deduplication, not exactly-once execution of external effects.

zl_flow_run_read_checkpoint_dynamic_v4 returns an owned value and zl_flow_checkpoint_info_v4 identifying its ID and recorded schema. ID zero selects the latest snapshot; a positive ID selects that exact snapshot. CHECKPOINT_UNAVAILABLE means no matching snapshot was recorded. Reads remain available after completion, cancellation or an uncertain journal append. The caller destroys each returned value with zl_dynamic_destroy_v2.

Ownership and durability

Only the opaque owned Dynamic value boundary is accepted. Its closed tags are null, bool, Int64, Float64, UTF-8 text, recursively owned lists and records. Borrows, Txn, process capabilities, device/engine authority and address-bearing objects have no admissible tag. Rechecksumming an unsupported tag does not make it a durable value. The runtime clones the canonical representation; destroying inputs, aggregate children, returned values or local run handles does not alter retained snapshots or cancel a run.

The complete canonical ZLD2 value is bounded to 1,048,576 bytes, including its header. Empty/null values are valid. One byte over the bound rejects before an append. The payload schema is copied into engine-owned run metadata and bound to the definition identity. Changing the schema or code epoch on attachment rejects; no implicit migration, address serialization or schema conversion occurs.

A snapshot acknowledgement requires a complete journal append, flush and fsync. An uncertain append leaves the old in-memory history and snapshot visible and requires close/reopen before any further acknowledgement, including an exact duplicate. The unacknowledged complete snapshot may recover. The existing zl_flow_engine_checkpoint_v2 atomically publishes a copy of the committed journal, now including owned snapshot bytes. There is no separate payload file or process-local side store needed for recovery.

Canonical v4 allocation

ZFH4 uses the existing 40-byte history header, with the positive checkpoint schema in bytes 4–7. The existing event prefix remains 56 bytes: bytes 12–15 now contain the following payload length. A snapshot is event kind 8; it binds the current forward graph index and attempt, carries its unsigned snapshot ID in the existing 64-bit value field, and has a zero receipt. The complete ZLD2 value follows immediately. Other events have zero payload length and retain their existing meanings. Integers are little-endian, including all 64 bits of the snapshot ID. The event count counts snapshots as well as ordinary events.

ZFJ4 frames have a 160-byte fixed overhead plus the payload:

Offset Width Meaning
0 4 ZFJ4
4 4 Complete frame length
8 8 Run ID
16 8 Definition ID
24 8 Code epoch
32 8 Forward node count
40 4 Event kind
44 4 Forward graph index
48 4 Attempt
52 4 Checkpoint schema
56 8 Event value or snapshot ID
64 32 Receipt, zero for snapshots
96 32 SHA-256 header checksum over bytes 0–95
128 variable ZLD2 snapshot value, absent for other events
final 32 bytes 32 SHA-256 over every preceding frame byte

Recovery verifies the header checksum before using a declared payload length to classify a write as incomplete. Inflating the length of a complete frame cannot turn it into a repairable torn payload. Lengths are bounded before allocation. Complete bad headers, checksums, Dynamic encodings, duplicate IDs, mixed per-run schemas/versions and impossible event orderings reject. Graph attachment also checks the pending cursor/attempt and forbids post-terminal snapshots. Failed attachment never installs candidate metadata.

Only the writable file provider removes an incomplete tail. Both read-only seed providers reject it without modifying the seed. A complete damaged frame is not repaired and does not trigger fallback to an older checkpoint. A process can recover from the published checkpoint even when the live journal is absent.

The v4 start receipt is SHA-256 of ZFDEF4\0\0, the positive schema as u32, four zero bytes, and the same ordered 80-byte forward/compensation rows as v3. All other definition fields and compensation rules retain their meanings. v2 and v3 wire bytes remain unchanged; different versions may share one journal as independent runs, but a run cannot change versions. Older readers reject v4. There is no automatic journal upgrade.

Verification

The initial behavioral test is commit 8fd1342f23: public v4 admission fails with expected status 0, got 1 before implementation. A second behavioral-red test caught a corrupted length being accepted as a torn tail; that failure motivated the separate header checksum.

Public tests cover nested values containing embedded NUL text, schema and snapshot-ID boundaries, exact maximum payload size, conflicting duplicates, mixed v2/v3/v4 journals, checksum-valid payload/metadata forgeries, independent replay and checkpoint-only recovery after destroying all live state. Generated tests vary signed nested values and incomplete snapshot prefixes. The shared parent-driven SIGKILL harness adds 45 frame windows and 24 checkpoint publication windows, including large snapshots, unwind and retry. Filesystem failure tests separately verify fsync-before-acknowledgement and the recovery fence; reaching a durability hook is not the sole evidence.

For the native profile commit bfbc63851d, the portable CTest run passed 275/275 tests. The selected Flow suites pass 65/65 in both the LLVM and LLVM+ASan/UBSan builds. The two generated properties each run 60 examples in each of the three builds (120 per build). All 24/24 new checkpoint mutation controls are killed, as are the affected legacy gates: history 27/27, compensation 20/20, durability 14/14 and sequence 18/18 (103 total, no invalid or surviving mutants). The site passes 327 tests across 27 files and typechecking; the guide is included in its public content projection. This docs-only site change does not modify UI runtime code, so no additional UI mutation campaign is required.

ctest --test-dir out/build/core2-portable -R 'flow_checkpoint|flow_commit' --output-on-failure
ZERGLANG_FLOW_CHECKPOINT_EXECUTABLE=out/build/core2-portable/zl_test_core1_flow_checkpoint_payload python -m pytest tests/properties/test_flow_checkpoint_payload.py
python tools/run_core1_mutation_checks.py --suite flow-checkpoint --report out/verification/flow-checkpoint-mutants.json

The source test-first commit b2afb12805 rejected FlowEngine.checkpoint with ZL-FLOW-0001 (expected status 0, got 1). Additional behavioral failures caught the optional start schema, 72-byte row allocation without compensation, and schema/snapshot identity binding before their implementations were corrected. Public source tests cover native and source-created runs, full-state destruction and artifact reload, linked wrappers, maximum schemas/IDs, borrowed-value rejection, authority rejection, duplicate conflicts and terminal behavior. Source-backed disk-failure tests verify the same no-acknowledgement recovery fence. The SIGKILL harness repeats all 69 payload windows through source checkpoint calls. Three generated properties exercise 60 source replay cases, 30 descriptor key-order permutations and 40 invalid allocation cases per run.

Source verification passes all seven public runtime cases, CLI/artifact checks, and descriptor drift checks on the portable, LLVM and LLVM+ASan/UBSan builds. The broader LLVM selection passes 93 tests. The sanitizer selection passes the same 93 checks after rerunning four legacy CLI tests updated to reject minor 28 instead of the newly supported 27. All 130 source property examples pass on each build. Mutation controls are 70/70 killed: source checkpoints 24, source compensation 25, source replay 7 and code epochs 14, with no invalid or surviving controls. The public site passes 327 tests and typechecking; only documentation projection changes, so no additional UI-runtime mutation campaign is required.

The earlier macOS executable-startup stall no longer reproduces. The subsequent portable regression, including the new owned-scenario adapter, passes 284 of 285 checks in its full run; the remaining native-synthesis test passes when rerun in isolation. That test’s deliberate two-second timeout hit before its expected repair phase under concurrent test load. Thus all 285 checks have a passing result, but this is not represented as one clean 285-test invocation. The aggregate benchmark framework completes successfully in the full run.

ctest --test-dir out/build/core2-portable -R 'flow_source_checkpoint|flow_checkpoint|flow_commit' --output-on-failure
python -m pytest tests/properties/test_flow_source_checkpoint.py
python tools/run_core1_mutation_checks.py --suite flow-source-checkpoint --report out/verification/flow-source-checkpoint-mutants.json

Benchmark scenarios

zerglang.benchmark-task/8 and native Exec/11 add a source-owned snapshot action alongside the existing atomic journal checkpoint. Each snapshot names the full checked public selector and a typed owned value; the source body supplies its ID/schema and the host supplies live-run authority. Scenarios have a 16-action, 512-byte total canonical payload limit so complete observations fit the existing 64 KiB response budget. The native checkpoint API retains its larger bound.

The new flow-durable-trace includes the latest independently owned checkpoint read. The host verifies it against canonical ZFH4 history, independently checks both ZFJ4 checksums, and compares both fresh memory replay engines with the file journal before and after each action. Typed null is distinct from no checkpoint, and text/integers and exact float bits remain distinct in oracle scoring. See the versioned scenario contract.

The native scenario executor also exposes parent-controlled crash boundaries. --flow-journal-pause emits a private paused marker and waits for an explicit parent release or process termination; a marker is never a success receipt. --flow-journal-recover opens an existing bound run without applying the next action, reads its owned checkpoint in fresh state, and can restore a published checkpoint when the live journal is absent. Only the writable provider repairs an incomplete tail. Empty-journal recovery reports an absent run, not a new start. See the host crash boundary contract.

zerglang.benchmark-task/9 wires those controls into a typed crash-recovery matrix. Its flow-crash-trace requires actual parent kills, real fsync-failure rejection, torn-tail/corruption, and invocation or pinned-source binding checks as declared by each case. Every valid recovery compares both memory engines, then performs real continuation to the same final bytes and owned state. The independent typed oracle remains mandatory; a pause marker or fault receipt is not measured success. See the typed matrix contract.

Source and native checkpoint values are observable through canonical history, owned snapshot reads, checked semantics and public reflection. Broader Durable type admission remains separate work.

Pinned corpus promotion

The separate offline ZL256 0.9.0 revision promotes the remaining 19 Flow scaffolds using Task-8/9 source-bound scenarios. Its 88 cases include 42 public and 46 held-out cases, each with source and artifact forms. Twelve fixtures run on all three providers; seven require actual crash/fault probes and fresh memory-provider replay. Independently wrong oracles must fail even after successful recovery.

The earlier five executable Flow fixtures retain their pins and unclaimed profile status. Earlier catalogs, default publication and stable Core-1 activation are unchanged. This is bounded owned Dynamic checkpoint evidence, not a new generic Durable type or schema-migration claim.

Projected fromdocs/flow-checkpoints.md