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
9a63b70ff4are withdrawn pending fresh replay. Stale binaries could produce false kills. This qualifies all such counts below; seedocs/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-
Durablelayouts, 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.