README.md (32054B)
Zig runtime and C ABI
The Zig runtime evaluates tree-calculus programs and provides IO through an owned-handle C ABI. Build and validate it with Nix:
nix build .#tricu-zig .#tricu-zig-tests --no-link
nix build .#tricu-js-tests .#tricu-php-tests --no-link
The executable and shared library build in ReleaseSafe, with Zig runtime safety checks enabled. Reducer, collector and libuv scheduler-root tests run in both Debug and ReleaseSafe.
Cross-runtime compiler reproduction compares complete compiler generations executed by Haskell and Zig. The workflow includes independent object audits, program smokes and retention checks. Haskell supplies common artifact transport and packaging.
The WASM adapter reuses this reducer without native IO. The demo site selects Zig or Haskell for native browser execution, with IO implemented by the owning session. The JS reference graph supports pure probes. Browser work uses unbounded reducer/Unicode/ preview entry points. Structured Stop waits for IO settlement; emergency disposal terminates the Worker. Finite-fuel native entry points retain their semantics.
Basic performance smoke (Linux)
nix build .#tricu-zig-performance-smoke --no-link
PERF=$(nix path-info .#tricu-zig-performance-smoke)
cat "$PERF/report.json"
This target checks complete results for string identity, append of two independently constructed inputs, deep constant-selection chains, distribution chains, and repeated calls in one context. It records three fresh-process samples per case, phase timings, fixture hashes, CPU information and memory observations. The C driver is optimized and the linked runtime is ReleaseSafe. Each sample records the runtime optimization mode; mixed-mode samples fail validation.
Construction, WHNF reduction, complete observation and explicit collection are
timed separately. Lazy work may occur during observation, and automatic GC time
is included in whichever phase triggers it. Explicit collection runs every 16
iterations, at the midpoint, and at the end. Peak process RSS comes from Linux
VmHWM; midpoint/end RSS comes from /proc/self/statm after collection.
Each child applies a 512 MiB address-space limit, 8 MiB stack limit and 60-second CPU limit before initializing the runtime; the harness enforces a 65-second wall limit. A wrong result, crash, exhausted limit or timeout fails the smoke. Timings are recorded for comparison across runs.
Nix may reuse the stored measurement report. For fresh measurements with the same built runner:
nix develop -c python3 ext/zig/tests/performance_smoke.py \
"$PERF/bin/performance-smoke" test/fixtures /tmp/tricu-zig-performance-smoke.json
Small compiler probe
test/cross_host_generation.py runs a two-module native map-builder job through
the shared generation runner on both Haskell and Zig, using the authenticated
bootstrap/rebuild kit. Haskell authenticates the captured input bundles. Each
execution host applies the retained compiler and adapter and drives IO.
Publication passes an independent store audit, and the resulting program returns
41 on Haskell. A second invocation checks warm reuse in a fresh runtime context.
nix build .#tricu-zig .# --no-link
ZIG=$(nix path-info .#tricu-zig)
HASKELL=$(nix path-info .#)
nix develop -c python3 test/cross_host_generation.py \
--library "$ZIG/lib/libarboricx.so" --tricu "$HASKELL/bin/tricu" \
--directory /tmp/zig-compiler-probe-new
Choose a new output directory. Limits are 1 GiB per child and 120 seconds per generation invocation, with an 8 MiB stack for Zig. Cold and warm runs must publish identical complete receipt and object sets across hosts. The output records inputs, runtime hashes, logs and completion status. Unsupported IO actions produce an error result identifying the action tag.
Add --compatibility for reexports, multiple exports, direct and local recursion,
patterns, sequencing, lazy discard, UTF-8 comments, dependency changes, warm
reuse, compiler error tags, corrupt checkpoints/executables, guarded interruption
and restart, and missing-input controls. The supervisor checks BOMs and headers
before execution. Runtime Debug/ReleaseSafe tests cover source/indexed IO
permission denial and malformed inputs. The execution child records string
diagnostics or a bounded numeric prefix for structured diagnostics.
Evaluator allocation headroom
The default collector runs after max(1048576, last_live_nodes) allocations
since the last collection. It recomputes this threshold after each collection,
so a transient live-heap peak does not permanently delay later collections.
Construction still never collects automatically. These thresholds count nodes,
not bytes, and are not RSS limits.
C ABI clients can choose the memory/speed tradeoff per context:
/* More allocation headroom for a memory-rich compiler job. Check the status. */
arb_status_t status = arb_set_gc_policy(ctx, 4194304, 4);
The two positive uint32_t arguments are the minimum allocation interval and
live-node multiplier. The threshold is their maximum after multiplying the
most recently measured live-node count (with saturating arithmetic). Changing
policy does not allocate, collect, or reset the allocation counter. The next
reducer safepoint checks the new threshold. Zero arguments return
ARB_INVALID_DATA without changing the policy. arb_collect still requests an
explicit collection regardless of the threshold. Older ABI-v2 libraries may
lack this additive entrypoint; callers requiring it must select a library that
exports it, not silently fall back.
The generation runner accepts the same explicit policy and records it in input provenance, status, invocation and the execution report. For a bounded native math compilation, using the runtime paths from the small compiler probe:
nix develop -c python3 test/bootstrap_generations.py /tmp/zig-math-headroom-new \
--bootstrap-kit bootstrap/rebuild --tricu "$HASKELL/bin/tricu" \
--execution-host zig --zig-library "$ZIG/lib/libarboricx.so" \
--root native_math --fresh --memory-mib 4096 --wall-seconds 180 \
--zig-gc-policy 4194304 4
Use a fresh directory for each sample. Omit --zig-gc-policy to measure the
library default; it is also accepted by test/cross_host_generation.py for
cold/warm parity and compatibility checks. The existing isolated 1 GiB probe
continues to use the default. Larger headroom may need several GiB, even on a
single module; increasing it further need not improve execution time. Keep
external resource guards and measure both time and peak RSS on the actual job.
The collector retains backing capacity, including its mark/work buffers, and
the reducer retains continuation-buffer capacity. Collection trims dead node
array tails without moving live IDs, but does not promise to return memory to
the OS.
For comparisons, alternate original/candidate order on one allowed CPU after
builds finish. Compare execution.json execution times and status.json child
peak RSS, not just supervisor wall time. Require independent audits and identical
complete cas/objects inventories. A native-math measurement is not a full
compiler-generation measurement; use the cross-runtime reproduction workflow
for compiler fixed-point acceptance.
Indexed tree transport
The indexed-tree v1 facility supplies authenticated read/hash/write/verify
operations under IO protocol 2. The domain remains arboricx.indexed-tree.v1;
facility revisions and content domains are not IO protocol versions.
The codec uses iterative traversal, memoizes physical identities and interns constructor/child-ID keys. Equal finite trees produce the same canonical bytes regardless of physical sharing. Reads authenticate before decoding and preserve DAG sharing. Native facilities use OS process authority.
Source-owned storage composes expected-digest checks, no-replace publication, exact winner verification and synchronization. Publication is not rolled back on a later failure. Serialized buffers and decoded trees can occupy substantial memory; use external process limits. See the storage contract.
Source-text IO
The utf8 facility encodes/decodes strict Unicode scalar lists and byte chunks.
Source-owned whole-file readers preserve raw octets and decoded text, reject
malformed UTF-8 and an initial BOM, and do not normalize newlines or Unicode.
Compiler policy supplies the source-size budget. Ordinary text encoding permits
U+FEFF; source-file reading owns initial-BOM rejection.
ABI v3 ownership
include/arboricx.h defines ABI v3. arb_abi_version() returns 3; the JS, PHP and
Python test bindings check this version before using handles.
- A handle is an opaque 64-bit integer identifying an owned root. Each successful handle output owns one root. Constructors borrow their inputs.
arb_retainreturns a different, independently owned handle to the same node.arb_releaseinvalidates that handle. Context destruction releases all remaining handles and allocations.- Inspection returns independently owned child handles. Child handles remain valid after releasing the parent. Handle equality compares handle identities; tree equality requires structural comparison.
- Stale, zero, and foreign-context handles return
ARB_INVALID_HANDLE. Slot reuse has a fresh process-wide generation; generation exhaustion returnsARB_HANDLE_EXHAUSTED. Handles fit in a positive signed 64-bit integer; JavaScript bindings expose them asbigint. - Operations return
ARB_OKor an explicit status. Failure leaves output parameters unchanged. Multi-handle output publication is all-or-nothing. - Reduction updates the shared graph in place. Aliases observe evaluation progress, including partial progress after fuel or allocation failure. The returned result handle and the input handle are independently owned.
- Contexts require single-threaded, non-reentrant use. Pointers must be valid, and output parameters must be distinct. Callers are responsible for native memory safety; handle validation detects stale and foreign references.
- Decoded byte/string buffers have separate ownership: free them with
arboricx_free_bufbefore destroying their context.arboricx_initreturns null on allocation failure; other tree operations use status/out parameters.
For example (check every status in application code):
arb_ctx_t *ctx = arboricx_init();
if (!ctx) return 1;
arb_handle_t leaf, application, result;
if (arb_leaf(ctx, &leaf) != ARB_OK ||
arb_app(ctx, leaf, leaf, &application) != ARB_OK ||
arb_release(ctx, leaf) != ARB_OK ||
arb_reduce(ctx, application, 1000, &result) != ARB_OK ||
arb_release(ctx, application) != ARB_OK ||
arb_collect(ctx) != ARB_OK) {
arboricx_free(ctx);
return 1;
}
/* result owns Stem Leaf, including the child released above. */
int is_stem;
if (arb_is_stem(ctx, result, &is_stem) != ARB_OK || !is_stem) {
arboricx_free(ctx);
return 1;
}
if (arb_release(ctx, result) != ARB_OK) {
arboricx_free(ctx);
return 1;
}
arboricx_free(ctx);
Reduction and collection contract
The reducer is a heap-framed, in-place, call-by-need WHNF machine. It evaluates terms on demand. Constructor demands dispatch directly; only suspended function, discriminator, argument and update demands use heap frames. Already-normal observations avoid borrowing continuation storage unless collection is due. Distribution and fork-argument triage also fuse immediately demanded constructor/constant applications, without allocating their private intermediate thunks. These are kernel-rule reductions, not source-name intrinsics: they do not demand discarded arguments, and charge the same application-entry fuel as the unfused rules. Low-fuel cases fall back to the unfused path. Tests compare single-invocation fuel boundaries and resumed results against a literal unfused evaluator.
The arena uses non-moving mark/sweep and reuses dead slots. Collection occurs at reducer loop safepoints, with the current term and continuation redexes rooted. The ABI supplies live handles and its kernel root; the CLI supplies the invocation root. The IO scheduler snapshots all tasks' current terms, environments, state, results and continuation frames for each turn. Async callbacks allocate and link nodes. Root scopes unwind on errors.
Construction allocates and links nodes; collection runs during reduction or an
explicit arb_collect call. Construction-only clients should call arb_collect
periodically. Graphs unreachable from the remaining roots become eligible
for reclamation at the next collection. Evaluation and IO operate directly on the shared graph. Internally,
keep raw node IDs within their registered root lifetime and reacquire pointers
into the growable node array after allocations.
Resource limits
- Arena capacity and collector work buffers retain their high-water allocations. Retaining handles retains their reachable graphs. Root-table rebuilding scans handle slots at ABI observation/evaluation entry. Apply an external memory limit to bound process usage.
- Fuel counts application entries for one reducer invocation. Codec observations use separate budgets. Apply an external deadline to bound a complete IO program.
- Codecs, structural comparisons, parsers and renderers may use the native stack. Complete observations require finite results; observing infinite results can diverge.
Tests cover all kernel rules, independent finite-reference comparisons,
undemanded divergent terms, shared updates, deep evaluation, allocation-failure
cleanup, GC slot reuse, scheduler roots, stale/foreign handles, independent
ownership, and C/Python/JS/PHP fixture programs. tests/helpers.h supplies checked
constructors for fixture tests.
Paired Haskell/Zig runtime measurements
nix build .#reducerComparison --no-link
nix run .#reducerComparison -- /tmp/reducer-comparison.json
Run measurements after builds finish, with other benchmark/build workloads idle.
This Linux harness compares the Haskell reducer (GHC -O2, one RTS capability)
with Zig ReleaseSafe. Both use the same retained identity/append bundles,
canonical byte inputs, and constant-selection/distribution workloads. Every
iteration checks its complete result. Each case has five fresh-process samples
per host, alternating host order, pinned to one allowed CPU. Results include
binary/fixture hashes, compiler version, raw samples, timing ranges and process
peak RSS. Package checks require deliberately wrong workers to fail result
validation.
The primary timing combines WHNF reduction and full output decoding and checking, including codecs and automatic GC. Build and explicit-GC phases are recorded separately and as part of the combined total. Haskell input normal forms are forced before application timing; opaque suspension boxes prepare unevaluated applications. Harness-only anti-sharing flags and varying input bytes give each round fresh work. Production Haskell runtime modules compile normally. Zig's construction includes owned-handle C ABI calls; Haskell's includes input forcing and suspension preparation.
Haskell may collect during construction; Zig collects automatically at reduction safepoints. Compare both evaluation/observation and combined totals. Initialization contributes to process RSS and has its own timing. Common guards are 512 MiB sampled RSS, 60 CPU seconds, 65 wall seconds and an 8 MiB native stack per child. Zig also has a 512 MiB address-space cap; Haskell has a 512 MiB managed-heap cap and an 8 MiB RTS stack cap.
Isolated mapped-library probe
test/bootstrap_cross_host.py --probe runs the frozen native_math closure on
both hosts, with identical kits and a 1 GiB/120-second budget per job. It requires
Linux bwrap and util-linux unshare with working user/PID/mount namespaces,
and a Nix Python environment:
nix develop -c python3 test/bootstrap_cross_host.py --probe \
--source-commit COMMITTED_SOURCE --directory /tmp/cross-host-probe-new \
--tricu "$HASKELL/bin/tricu" --zig-library "$ZIG/lib/libarboricx.so"
Each sandbox mounts the read-only source snapshot and kit, Nix dependencies,
an initially empty writable work directory, and ephemeral system directories.
The generation supervisor runs inside the sandbox, where its RSS monitor observes
the execution child. The probe compares complete independently audited object
inventories. Its experiment definition records the source commit/archive,
mapped-input hashes, runtimes, Python, tooling, limits and comparison boundaries.
Snapshot extraction excludes the tracked build-output symlink ext/zig/result.
The launcher creates a private PID namespace with a matching procfs mount,
then starts the filesystem sandbox. Both isolation tool identities are recorded.
For a full experiment stopped before a job creates its generation directory,
--continue-experiment reverifies frozen sources, runtimes and completed
generations, preserves the failed launch and original tooling, and starts the
pending job fresh. A partially executed generation requires a new experiment.
IO protocol boundary
The native CLI validates canonical tricuIO version 2 before demanding its action.
Historical v1 execution requires a retained runtime. V2 implements the structured core and the
compiler/file profile: regular files, path/directory mutations and sync, stages,
SHA-256, strict UTF-8 and authenticated indexed transport. IO.capabilities
reports exactly those operations and their restrictions; IO.inspect classifies
one header without demanding its arguments. Standard streams, directory cursors,
networking, processes, clocks and context are not implemented in this Zig profile.
Unknown named facilities/revisions return ordinary unsupported Results.
V2 uses OS process authority and rejects --unsafe-io before action demand. Unknown
envelope versions fail without interpreter fallback. Terminal failures/cancellation
fail the CLI without rendering opaque user payloads. Each native run gets a unique,
nonwrapping identity. Synchronous filesystem calls make no prompt-cancellation or
deadline promise. Explicit text:VALUE arguments encode Unicode scalars (including
Unicode paths); historical unprefixed argument forms retain byte-string semantics.
Owned-handle C ABI version3 remains a separate contract, not an IO interpreter.
Pure evaluation is independent of IO-envelope execution, and the freestanding WASM
ABI has no native IO driver; the site's adapter supplies the shared structured v2 core/task/terminal profile. See the validated program/backend matrix
before selecting an execution host or attempting a compiler/default transition.
src/io_v2_protocol.zig is the host-independent foundation for the separate v2
port: bounded canonical core/header decoding, with temporary GC rooting and opaque
payloads. It does not execute requests, resolve facilities, or enable v2 in the CLI,
C ABI or browser. Its Debug/ReleaseSafe controls run with tricu-zig-tests, including
computed headers, excess-element non-demand, divergent payloads, and allocator/GC
failure unwinding. Reducer fuel is explicitly per demand, not a request deadline.
The separate v2 resource/task state machines and private pure-core probe are also validated through these test targets. The native CLI uses these state machines through the separate v2 boundary. Resource admission, active leases and asynchronous close claims have separate lifetimes; cancellation does not detach outstanding host callbacks. Task results survive only an unconsumed ticket or a claimed wait, not a completion archive. Lexical scopes share their task identity for join-cycle detection.
For actual source/handler controls on both Haskell and the private Zig core:
nix build .# .#tricu-zig-tests --no-link
ZIG=$(nix path-info .#tricu-zig)
HASKELL=$(nix path-info .#)
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_zig_core.py "$HASKELL/bin/tricu" \
"$ZIG/bin/io-v2-core-probe" --directory /tmp/zig-v2-core-new
Use a fresh output directory. The probe supports the structured core only; all named facilities return ordinary unsupported Results. It rejects v1 rather than retrying it and never renders terminal diagnostic/reason payloads. It is not a compiler execution host, native filesystem port or browser adapter. The source controls include fresh forced 4096-cell payloads in repeated joined/discarded tasks; the 128-MiB Zig process guard covers those workloads, not arbitrary programs or future native IO. Unit tests additionally sweep allocation failure and force GC.
A separate private io-v2-file-probe exercises synchronous regular-file IO
through that core, including the compiler storage transports described below.
It uses the same facilities/capability catalogue as the native CLI, with extra
private raw-tree argument support for controls. Path opens reject known nonregular types,
use nonblocking/no-controlling-terminal opens, verify the opened descriptor before
truncation, and follow ordinary host symlinks. These checks are backend type
support, not confinement: raced device opens can have effects. Standard streams
are separate and are not implemented by this probe. Synchronous regular-file
calls may delay all tasks and cancellation; they have no hard deadline.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_zig_files.py "$HASKELL/bin/tricu" \
"$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-files-new
Controls compare exact file bytes and result trees, including borrowed/transferred handles, implicit joins, cancellation without rollback, positioned/update/append operations and malformed/unsupported requests. Native unit controls inject a path substitution after preflight and close failures after descriptor release, and sweep allocation failures through real acquisition and core unwind. Host cleanup error codes have fixed-size storage even when allocator/reducer failure prevents a tree reply. This synchronous host interface cannot be used for asynchronous browser input or detached native work: those need an explicit settlement pump.
The private file probe also implements path/stat, lstat, remove-file, rename
and directory/create, remove, sync. Stat/lstat are observations, not pinned
claims about future paths. Rename uses the OS namespace operation, without copy
fallback or rollback. Directory synchronization opens/checks a directory descriptor
with nonblocking and close-on-exec flags, preserves primary failures, and attempts
close exactly once. Directory enumeration is not implemented by this subset.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_zig_paths.py "$HASKELL/bin/tricu" \
"$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-paths-new
The private host implements scoped digest/open, update, finish for SHA-256.
It retains fixed-size hash state, not prior chunks; finish consumes the active
state. Algorithm rejection preserves at most128 Unicode scalars of diagnostic
text. Typed backend tags keep digest handles distinct from file/stream operations.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_zig_digest.py "$HASKELL/bin/tricu" \
"$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-digest-new
Private stage/open, seal, publish supports scope-owned private writers,
same-filesystem cross-directory link/rename publication, no-replace exists, and
explicit replacement. Seal synchronizes and closes the file before publication;
publication synchronizes destination then distinct source directories. Syscall
failure poisons the stage; uncertain publication is never retried or copied across
filesystems. Later failure/cancellation/OOM cleans private names, never rolls back
a visible destination. exists does not prove identity or durability: caller code
must authenticate the winner. Source artifact policy is not in the native host.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_zig_publication.py "$HASKELL/bin/tricu" \
"$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-publication-new
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_zig_stages.py "$HASKELL/bin/tricu" \
"$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-stages-new
The latter control requires a writable /dev/shm on a different filesystem for
actual EXDEV validation; absence is a failed/incomplete control, not a silent skip.
Fault-injected state-machine tests cover every publication step; allocator sweeps
cover acquisition and reply encoding before/after visibility with real private
files. These remain private integration tools, not release/compiler acceptance.
Private utf8/encode and utf8/decode implement bounded stateless strict
transport (1024 scalars / 4096 bytes). Nonfinal decode returns at most three carry
bytes; invalid available prefixes fail immediately. No replacement, normalization
or BOM stripping. Source code retains text/source policy and streaming carry.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_text.py "$HASKELL/bin/tricu" \
--zig-probe "$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-text-new
The shared independent controls cover binary NUL, Unicode, carry boundaries,
source BOM rejection, partial writes and excess-list non-demand. Private probes
accept tree-file:PATH arguments containing exact raw 0/1/2 constructor trees
(maximum 10 MiB per argument), parsed iteratively with trailing roots rejected.
These private transport arguments are not production CLI options.
The indexed codec also offers a graph-proportional canonical Plan, with repeatable
emit calls using at most 4096-byte borrowed chunks and no retained encoded payload.
The existing whole-value encode interface wraps that emitter; its bytes remain
unchanged. Preparation still needs a resident DAG and graph-proportional tables,
not a heap/CPU quota. Sink failure stops emission without retry. This foundation
alone does not implement the private host's authenticated indexed-tree facilities.
The private file host implements indexed-tree/hash, write, and verify with
that plan. Writes preflight size and digest before sending bytes, including to
stages; verification compares exact canonical bytes and consumes only one frame,
without an EOF probe. Resource kind/lease checks precede tree preparation. Caller
code still owns winner authentication, EOF policy and publication. Authenticated
reads use the snapshot transport described below.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_indexed_write.py "$HASKELL/bin/tricu" \
--zig-writer-probe "$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-indexed-new
That explicitly writer-only subset does not run or claim authenticated round-trip controls. Omitting the option retains the complete Haskell suite. Controls include independent wire bytes, deep linear trees, corrupt/excess winners, preflight preservation, exact frame tails and wrong-kind non-demand; native allocation sweeps cover lease cleanup and failure after a completed write without rollback.
Private indexed-tree/read now authenticates an exact frame into a scope-registered
anonymous file before decoding. The private name is unlinked before input is
consumed; disk usage is proportional to frame length, transfers/cache are <=4096
bytes, metadata is limited to 64 sections / 65536 manifest bytes, and node tables
are graph-proportional. No original-input reread, following-byte EOF probe, encoded
payload buffer, implicit publication or durable snapshot promise. Normal Results
and terminal allocation failures retire the internal descriptor; release errors
cannot replace a primary failure. These synchronous calls have no deadline.
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_indexed.py "$HASKELL/bin/tricu" \
--zig-probe "$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-snapshots-new
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_indexed_write.py "$HASKELL/bin/tricu" \
--zig-probe "$ZIG/bin/io-v2-file-probe" --directory /tmp/zig-v2-roundtrips-new
The complete private mode includes authenticated shared-DAG round trips. Read
controls include exact cursor boundaries, authentication before malformed-format
rejection, failed preflight without consumption, and opaque transport growth to
64 MiB under a sampled RSS guard. This does not establish large-DAG capacity or
constant aggregate memory. Format diagnostics currently use invalid indexed frame rather than Haskell's more specific text; category/progress remain explicit.
Public native/browser v2 entrypoints and release/compiler acceptance are unchanged.
Actual compiler callers can be exercised without enabling the production driver:
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_compiler_store.py "$HASKELL/bin/tricu" \
--zig-probe "$ZIG/bin/io-v2-file-probe" --compiler-smoke \
--directory /tmp/zig-v2-compiler-new
This builds a current source-mapped candidate on Haskell, runs its v2 compiler worker on private Zig for cold/warm dependency builds, authenticates the returned receipt, loads/executes the resulting store on Haskell, and compares the complete application bundle. Invalid source controls compare ordinary failure trees across hosts. Source-object, corrupt-winner, concurrent alias and cleanup controls remain included; pure helper checks run on Haskell and are distinguished in the report. This is a compiler-workflow smoke, not generation-two acceptance, browser support, production driver enablement, or support-catalogue publication.
Production native boundary and compiler smoke controls:
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_native_cli.py "$HASKELL/bin/tricu" \
"$ZIG/bin/tricu-zig" --directory /tmp/zig-v2-native-new \
--compiler-bundle .bootstrap-run/io-v2-cohort/generation2/build-stable.arboricx
The compiler bundle is an explicit v2 sourceBuildGraph worker, not inferred from
its filename. The optional compiler smoke selects that export into a separate
bundle, executes cold/warm dependency builds through the real CLI, and authenticates/
executes the resulting store on Haskell. It neither builds a new compiler generation
nor changes packaged compiler, browser, catalogue or registry defaults.
Native owned-ABI clients select arb_run_io_v2 for IO protocol 2. It uses the
CLI interpreter/profile, takes no grants, preserves ordinary Results and leaves
the output handle untouched on error. Native IO is rejected in WASM; the browser
adapter uses the shared structured scheduler instead.
Owned-handle C ABI 3 removes arb_run_io and the legacy permission struct.
IO protocol 2 is independent of that ABI version. Explicit historical
execution requires a retained ABI-2 library; current clients never fall back.
The generation executor selects the symbol from captured execution-version metadata, validates it against generation inputs, and uses Unicode-scalar trees for v2 arguments. Historical records lacking version metadata select v1 explicitly. The bounded comparison is reproducible with an explicit authenticated v2 kit:
PYTHONDONTWRITEBYTECODE=1 python3 test/io_v2_generation.py "$HASKELL/bin/tricu" \
--kit /path/to/v2-kit --zig-library "$ZIG/lib/libarboricx.so" \
--directory /tmp/zig-v2-generation-boundary-new
This exercises actual mapped-library builds and complete object inventories on
both hosts, plus version/identity-selection controls. It is not a full compiler
pair. Use the isolated cross-host workflow
with explicit --io-version 2 --bootstrap-kit /path/to/v2-kit for full
H1/H2/H3/Z3 reproduction. Its pair, inventory, program-smoke, corruption and
retention gates are independent of the bounded comparison.