tricu

A lazy functional language based on Tree Calculus
Log | Files | Refs | README | LICENSE

compiler-rebuilding.md (36077B)


Rebuilding the compiler

Compiler development uses an accepted stable compiler to build changed sources, then tests the resulting artifacts. At an agreed acceptance checkpoint, a fresh compiler builds itself again from the same frozen source. Independent audits verify both generations and their reproduced outputs.

For ordinary application authoring, use the included compiler and Build and run. Rebuilding uses either an explicit bootstrap kit containing the map-builder and standard IO adapter, or a completed generation from which those executables can be selected. The standalone application compiler has a different entrypoint.

Ordinary generations

Select these inputs explicitly:

Variable Selection
TRICU Nix-built Haskell executable.
PREVIOUS Completed, audited generation supporting current native headers.
NEXT New output directory in the working checkout or temporary storage.
ADAPTER Verified standard six-argument IO adapter.
SOURCE Source checkout to build.

An accepted-pair recovery transfer provides the completed second generation at recovery/seed and the adapter at recovery/tools/io-adapter.arboricx. Identify other inputs from their recorded provenance.

python3 test/bootstrap_generations.py "$PREVIOUS" "$NEXT" --tricu "$TRICU" \
  --source-root "$SOURCE" --backend stable --adapter "$ADAPTER" \
  --capabilities 8 --memory-mib 65536 --wall-seconds 7200
python3 test/bootstrap_store_check.py "$NEXT" --source-root "$SOURCE"

Add --expect-identical when rebuilding unchanged inputs. Omit it for source changes. The audit checks the complete selected dependency closure, including receipts and exported objects.

The runner uses the previous closure's compiled map-builder and precompiled adapter. tricu owns staging, compilation, graph policy, and receipts; Haskell executes IO, verifies transport, and packs bundles. Indexed compiler/adapter loading preserves sharing. Transport-object hashes and the compiler bundle identity used in receipts are recorded separately.

A full source_map generation produces:

Use --backend stable explicitly for new generations. The runner defaults to stable; historical records without a backend mean optimized. Generation selection uses sourceMapBuildStable or sourceMapBuild, while application selection uses the chosen application bundle. Both paths require the selected backend and entrypoint.

Execution-interface selection

The default --execution-host haskell uses the native CLI. Select --execution-host haskell-ffi --haskell-library /nix/store/.../lib/libtricu-haskell.so for the full Haskell embedding ABI. Both execute the same canonical reducer and IO driver. --tricu supplies authenticated input transport, independent store auditing and output packaging for either interface. --backend independently selects the lowering backend. Missing or incompatible libraries and exports fail preparation. Execution uses the selected interface exclusively.

Provenance binds the execution library, executor and Python binding separately from the transport executable. The executor rechecks the exact six arguments, IO version and captured bundle identities before effects. Supervision enforces publication, CPU, RSS/address-space and whole-job bounds. The shared library uses one GHC capability; --capabilities controls CLI execution and transport.

The shared-library executor accepts IO with OS process authority. Historical v1 jobs require a retained CLI runtime. Use OS isolation for path confinement. Bounded cold/warm, syntax, invalidation and corruption controls use:

nix build .#tricu-generation-ffi-test --no-link

These checks compare the CLI and embedding interfaces of the same reducer using complete receipts and object inventories. Ordinary pair acceptance and retention toolchain roles use CLI Haskell generations. Historical records retain their original runtime identities and use their frozen validation tools; current execution selection supports Haskell interfaces.

Explicit IO framing and transitions

New generation records bind execution_io_version, produced_io_version and produced_adapter_sha256. The first describes the selected running worker; the second describes the resulting compiler cohort. Callers declare both values explicitly. Old records without these fields describe the isolated historical v1 path. Partial metadata, unknown versions and Boolean/string JSON substitutes for integers are rejected.

Selection uses the kit's declared version or the previous generation's produced version. The previous produced adapter is authenticated and captured before execution; historical generations use their recorded input adapter. An explicit transition worker can override execution framing with --execution-io-version 1|2 and a matching --adapter. Conflicting kit flags are rejected. An execution failure terminates the selected run.

--produced-io-version 1|2 defaults to the execution version. Changing it requires an explicit --produced-adapter. For example, a retained v1 kit can compile a v2 source projection while selecting a previously prepared v2 adapter for its output:

python3 test/bootstrap_generations.py "$FIRST" --bootstrap-kit "$V1_KIT" \
  --tricu "$TRICU" --source-root "$FROZEN_SOURCE" \
  --map "$FROZEN_SOURCE/bootstrap/compiler-source-map.v1" \
  --produced-io-version 2 --produced-adapter "$V2_ADAPTER" --backend stable --fresh

Acceptance of the selected cohort requires full pair verification and retention. The compiler source map selects compiler_io, the compiler storage facade, plus its v2 dependencies. Compiler source imports this facade explicitly; general application io is independent. The packaged compiler uses v2. Input limits and source-owned storage semantics are described in the compiler projection controls.

Reproduction requires the previous produced version and exact produced adapter. Pair verification binds generation one's produced version/adapter to generation two's execution and reproduced output. Each transition input adapter retains its recorded identity. It still requires the first rebuilt compiler, identical frozen inputs, complete manifest reproduction and all three identical compiler bundles. New records also check invocation authority flags. IO v2 execution uses OS process authority; invocation audits reject legacy --unsafe-io and read/write grants. Correctness smokes and retention probes select the recorded produced version explicitly.

An opt-in preparation/control builds a v2 map-builder and adapter using the retained compiler, writes a versioned kit, stages the full v2 map and builds only native_math. It compares the complete manifest with the authenticated packaged kit and rejects conflicting versions, missing transition adapters and unsupported hosts:

python3 test/io_generation.py "$TRICU" --directory /tmp/new-io-generation-probe

This probe covers preparation and one module. It also checks mixed native, unit and reexport maps: exact overlays and independently authenticated raw/overlay digests agree with the packaged compiler, including through the shared library when --haskell-library is selected. Full pair acceptance requires source freezing, complete reproduction, smokes and rolling retention. Accepted native pairs support recovery archives; seed-only v2 archives remain rejected. The native CLI and shared-library probes compare interfaces of the same Haskell implementation; independently expected semantic and IO controls remain separate. Application provenance capture/rebuild uses its own versioned descriptor and explicit trust gate.

Shared-library program observation

test/shared_library_smoke.py compiles a small import/recursion/invoice program with the packaged stable compiler, independently audits its workspace, and compares twelve complete results with both the CLI and Haskell C ABI:

nix build .#tricu-generation-ffi-test --no-link

The same opt-in gate runs cold/warm generation-runner jobs, source invalidation, corruption/interruption/restart and exact object/receipt comparisons, plus the scoped native_math map-builder controls. These are bounded application/library jobs. Full compiler checkpoint acceptance and rolling retention use the ordinary CLI procedures below. Existing historical cross-runtime evidence retains its original frozen tools and runtime identities.

Explicit bootstrap kit

The repository includes the accepted kit at bootstrap/rebuild/. It works from a source archive using the included compiler and adapter artifacts. Set KIT="$PWD/bootstrap/rebuild" when rebuilding that archive.

A kit is a caller-selected directory containing bootstrap-kit.json, rebuilt-compiler.arboricx, and io-adapter.arboricx. Select it as the complete bootstrap input:

python3 test/bootstrap_generations.py "$FIRST" --bootstrap-kit "$KIT" \
  --tricu "$TRICU" --source-root "$FROZEN_SOURCE" --backend stable --fresh \
  --capabilities 8 --memory-mib 65536 --wall-seconds 7200

The historical JSON manifest has schema tricu.bootstrap-kit.v1 (IO v1). New explicit kits use tricu.bootstrap-kit.v2 with required integer io_version (1 or 2). A v1 schema cannot declare v2 execution. Both have backend stable and an artifacts object with exactly two roles:

Role file export
compiler rebuilt-compiler.arboricx sourceMapBuildStable
adapter io-adapter.arboricx main

Each role also records its lowercase whole-file sha256. Select the manifest through a trusted release or verified artifact preparation. The runner verifies both files before creating output, captures their bytes, imports them indexed, and records the manifest digest alongside the actual executable identities. The kit supplies the complete compiler selection, independently of previous stores. A --validate-only run checks source/map staging inputs and ends after that check.

Use generation one's completed output as PREVIOUS for generation two with --expect-identical. Kit selection cannot be combined with previous-generation, adapter, or transition-worker overrides. Prepare an explicit kit with the verified transition worker when the batch needs one. The fresh-pair acceptance, independent audits, and publication requirements below still apply.

Development batches and acceptance checkpoints

Agree on a coherent change and focused-check budget before implementation. Compile changed modules and affected consumers with the accepted stable compiler. Check complete results, diagnostics, demand, effects, integrity, and relevant workloads against independent expectations. Testing distinguishes these candidate checks from reference-artifact tests and acceptance.

Several reviewable candidate commits may precede a generation checkpoint. Record the candidate identities, checks, source freshness, and acceptance scope in an external acceptance record. Preserve accepted fixtures and the rollback toolchain through the publication checkpoint. Use focused checks between drafts and run full generations at the agreed checkpoint.

Verification failures block acceptance and further feature work. Diagnose and repair with bounded checks. A failed generation gate requires a passing fresh pair for acceptance, including complete independent audits. Cached equality and refreshed fixtures serve only as supporting evidence.

Mandatory compiler hygiene gate

For a compiler-affecting batch, freeze source at an exact Git commit. Syntax, semantics, lowering, dependencies, staging, and compiler transport all affect this boundary. Select FROZEN_SOURCE, new FIRST and SECOND directories, and a new ACCEPTANCE report path:

python3 test/bootstrap_generations.py "$PREVIOUS" "$FIRST" --tricu "$TRICU" \
  --source-root "$FROZEN_SOURCE" --backend stable --fresh --adapter "$ADAPTER" \
  --capabilities 8 --memory-mib 65536 --wall-seconds 7200
python3 test/bootstrap_generations.py "$FIRST" "$SECOND" --tricu "$TRICU" \
  --source-root "$FROZEN_SOURCE" --backend stable --fresh --expect-identical \
  --capabilities 8 --memory-mib 65536 --wall-seconds 7200
python3 test/bootstrap_acceptance.py "$FIRST" "$SECOND" --tricu "$TRICU" \
  --output "$ACCEPTANCE"

Both output directories must be nonexistent. Generation two must actually use generation one's rebuilt compiler on the same frozen inputs. A transition worker may be selected for generation one only. Acceptance requires independent closure/receipt audits, identical executable manifests, and exact reproduction of all three compiler/application bundles.

Run generation-two correctness smokes separately:

python3 test/bootstrap_smoke.py "$TRICU" --generation "$SECOND" \
  --acceptance "$ACCEPTANCE" --source-root "$FROZEN_SOURCE" \
  --directory "$SMOKE_DIRECTORY"

The smoke probe authenticates its compiler/runtime and mapped library inputs, builds a fresh workspace, audits sources/dependencies/exports, and checks native imports, patterns, top-level/local recursion, and a small invoice program. Its whole-job limit is five minutes with an 8 GiB sampled child guard. Self-reproduction and these smokes complement the batch's independent semantic tests.

Verified compiler checkpoint; publication deferred

An explicitly agreed checkpoint may promote the audited fresh pair after the smokes and rolling retention while deferring fixture publication, the broad downstream sweep, and recovery packaging. Record verified compiler checkpoint, publication deferred, with source commit, tool identities, coverage, and remaining obligations. Retain the previous accepted compiler for rollback. Checkpoint promotion covers the development compiler. The publication procedure accepts and distributes the support cohort and fixtures.

Publish the accepted batch

When replacing the shipped bootstrap/build-stable.arboricx, update its trusted whole-file SHA256 in src/PortableCompiler.hs (packagedCompilerSha256) alongside bootstrap/stable-compiler.json. The packaged frontend rejects mismatched bytes; its receipt identity is this bundle digest. Indexed transport objects have their own separately recorded hashes.

Publish genuinely rebuilt compiler and support fixtures using the procedures in Testing. Capture matching source snapshots, then check freshness and the fast gate:

python3 test/fixture_sources.py capture --source-root "$FROZEN_SOURCE"
python3 test/fixture_sources.py current --source-root "$FROZEN_SOURCE"
nix flake check

Reference snapshots authenticate the bytes actually used to compile an artifact. Publish freshly compiled artifacts with fingerprints of their actual inputs. Support-only .tri changes require candidate/publication checks; compiler generations are required for compiler-affecting changes.

Full compiler publication also includes the browser/site assets. Commit the accepted compiler/support fixtures first (the site authenticates their full Git origin), then complete the site compiler-promotion refresh and commit its matching assets and validation changes. Update the site’s separately pinned compiler explicitly. Full publication remains incomplete while the site refresh is deferred. Finish rolling retention after this refresh; retain matching previous site/dependency outputs for rollback. Public deployment requires separate authorization after local preparation and validation.

Inputs, restart, and resource controls

Full jobs use eight GHC capabilities, at most 64 GiB, and a two-hour whole-job ceiling. Select --capabilities 8 --memory-mib 65536 --wall-seconds 7200 explicitly; CLI defaults are smaller. Use lower bounds for focused probes. Measure the effect of capability counts on the selected workload.

On an interactive workstation, run heavy jobs sequentially with low priority. If selecting CPU affinity, choose available CPUs with a caller-provided CPU_SET. Configure Nix daemon builders separately with Nix’s job/core settings or builder limits; caller affinity applies to the caller’s processes.

Option Meaning
--map, --source-root, --root Explicit map, source location, and closure root.
--validate-only Validate staging inputs and return the check result.
--memory-mib Sampled direct-child RSS guard and host heap ceiling; default 32768, maximum 65536 MiB.
--capabilities GHC capabilities; default one.
--initial-seconds, --extended-seconds Initial notice and final timeout for gaps between object publications.
--wall-seconds Whole-job budget including preparation, audits, and packing; default 3600, maximum 7200.

Native transport commands also have a 600-second timeout. RSS sampling covers the direct child; descendants require separate aggregate limits. Sampled peaks can miss short-lived allocations. Keep the enclosing process alive beyond the configured guards.

A native map row preserves source bytes and declared import/export policy. Its ordered dependencies must match local imports, re-exports, and build-only edges, deduplicated in first-occurrence order. Legacy unit and reexport rows translate checked headers. Compiler dependency names are map aliases; supply them through a staged workspace or immutable manifests.

Sources must remain unchanged throughout a generation. The runner checks them again before accepting outputs. Snapshot consistency relies on trusted, stable filesystem contents throughout the job. inputs.json, source-map.v1, invocation.json, logs, and status.json record input identities, limits, comparisons, and completion.

Rerunning a diagnostic output directory reuses verified checkpoints. Changed source/compiler identities invalidate keys, and an output lock prevents concurrent writers. Interrupted builds remain incomplete. Resumption helps diagnosis and recovery; the acceptance pair requires fresh stores. The map-builder checkpoints completed modules as whole units.

Preparing a source-mapped compiler kit

This route prepares a changed worker/adapter through source-mapped modules and uses the explicitly selected IO version throughout its source projection:

python3 test/prepare_compiler_kit.py "$TRICU" "$NEW_PREPARATION" \
  --compiler-bundle "$APPLICATION_COMPILER" --compiler-io-version 2 \
  --io-version 2 --map bootstrap/compiler-source-map.v1 \
  --source-root "$SOURCE" --backend stable
python3 test/io_generation.py "$TRICU" \
  --kit "$NEW_PREPARATION/kit" --directory "$NEW_SCOPED_CHECK"

The compiler's execution version and the produced kit's version are separate, explicit selections. An older v1 application compiler uses --compiler-io-version 1. The requested output version remains an independent selection. For v1 output, select its source map and --io-version 1. Callers declare each version explicitly.

Preparation captures the mapped source bytes and compiler bundle into a new workspace, retains namespaces and normal native module resolution, and compiles both the worker and its six-argument adapter. It records source/map/compiler/runtime identities and ordinary whole-process metrics. Limits default to 8 GiB and 1800 seconds; select explicit larger limits within the 64-GiB/two-hour guard if needed. Preparation uses the selected interpreter exclusively and produces a kit for subsequent generation checks. The scoped check exercises real generation boundaries and complete native_math output against a v1 control; full frozen-input pair acceptance, application smokes and retention remain separate requirements.

Transitioning older inputs

An older closure may lack the selected entrypoint, current native headers, or verified native tree transport. Prepare an explicit bridge using a retained portable application compiler and standard adapter:

python3 test/prepare_generation_worker.py "$TRICU" "$PREVIOUS" "$WORKER" \
  --compiler-bundle "$APPLICATION_COMPILER" --backend stable
python3 test/bootstrap_generations.py "$PREVIOUS" "$NEXT" --tricu "$TRICU" \
  --source-root "$SOURCE" --backend stable --adapter "$ADAPTER" \
  --worker-bundle "$WORKER/worker.arboricx" \
  --capabilities 8 --memory-mib 65536 --wall-seconds 7200

This legacy flat bridge requires v1 retained support and produces v1 actions; it rejects a v2 support generation before writing output. Its application compiler may be explicitly selected with --compiler-io-version 2; the bridge still produces v1 actions. Use the source-mapped kit route above for v2 output.

Worker preparation selects only the requested backend and its reachable local definitions. It audits every transported support export. Inspect --plan-only before preparing a retained flat compiler; its source compiler export can exceed the normal 64 MiB flat limit. Select an explicit --support-flat-bytes BYTES for that verified support transport. Ordinary flat imports retain their default 64 MiB limit. Worker compilation has a 900-second guard within the selected whole-job limit.

For a pre-consolidation header parser, explicitly add --legacy-compiler-header to worker preparation. It changes only the bridge wrapper’s header; native language semantics and failed-compilation behavior remain unchanged. Subsequent generations use the rebuilt closure directly. Both routes require the standard six-argument adapter. Test wrappers also compile through retained portable tools.

Rolling development retention

Retain the latest accepted second-generation store and the preceding accepted second-generation store: two compiler revisions for development and rollback. Keep complete CAS/cache/staging inputs, frozen source/maps, receipts, bundles, adapters, acceptance reports, and the exact Haskell runtime closure protected from Nix GC. Also retain current support preparation and committed fixtures.

Before pruning an older store:

  1. Audit both retained closures and source snapshots.
  2. Hide older stores. Exercise imports, compilation, execution, diagnostics, integrity controls, and a fresh scoped map-builder job with each retained compiler and adapter.
  3. Authenticate current support preparation and reproduce its complete manifests with the old stores hidden, within the selected audit budget.
  4. Preserve compact sources, bundles, receipts/manifests, and reports. Record the exact removed object directories and mark their parents incomplete. Unlink obsolete paths, preserving the bytes of shared hardlinked objects.
  5. Re-audit retained stores and smoke-test both compilers after deletion.

Check for sibling-worktree users before pruning. Removing generation one means its original full-pair audit cannot be rerun in place; preserve that distinction in the retained evidence. Off-site recovery packages remain outside routine scratch cleanup.

Checkpoint cleanup command

Checkpoint completion requires pair acceptance, smokes, and rolling retention. Run test/bootstrap_retention.py after acceptance and smokes, before declaring completion or starting the next feature batch. This opt-in Linux/Haskell workflow requires Python, Nix and bwrap. It validates retained toolchains and prunes selected scratch objects; full compiler generation and Nix GC are separate operations. An existing verified checkpoint can satisfy its outstanding retention obligation using its retained accepted pair.

Create an explicit local retention registry, for example $SCRATCH/retained-toolchains.json. Replace the example paths and runtime hashes with recorded provenance as the basis for compiler selection. current and rollback are required unique roles. Additional uniquely named toolchains or protected scratch directories are allowed for active consumers and release evidence. Prefer an independently useful prior compiler revision for executable rollback. Two builds with identical bundles retain the same executable revision.

{
  "schema": "tricu.bootstrap-retention.v1",
  "protected": ["current-cohort", "previous-cohort"],
  "sibling_users_checked": true,
  "uncoordinated_jobs_stopped": true,
  "toolchains": [
    {
      "role": "current",
      "generation": "current-cohort/generation2",
      "source": "current-cohort/source",
      "acceptance": "current-cohort/accepted-pair.json",
      "runtime": "/nix/store/CURRENT-RUNTIME/bin/tricu"
    },
    {
      "role": "rollback",
      "generation": "previous-cohort/generation2",
      "source": "previous-cohort/source",
      "acceptance": "previous-cohort/accepted-pair.json",
      "runtime": "/nix/store/PREVIOUS-RUNTIME/bin/tricu"
    }
  ],
  "support": {
    "toolchain": "current",
    "preparation": "current-cohort/support",
    "relocate_patterns": true
  }
}

All scratch paths are normalized, root-relative, and protected by explicit real directories. The current command reruns full-pair acceptance, so both retained first generations must also be protected and present throughout retention. Acceptance must select the actual retained second-generation path; for a relocated pair, create relocated evidence with bootstrap_acceptance.py and its explicit source override. Preserve the original acceptance records. The selected standard adapters remain inside the protected generations.

The support preparations must collectively contain the complete selected candidate cohort. List additional directories in support.additional_preparations, in publisher order, and include each in protected. The command reproduces current support bundles, metadata, receipts and full native manifests through the existing publisher, then checks source freshness. By default this means the working checkout. An optional support.source registry path selects a protected frozen reference source tree as the source selection. It must match all current fixture source pins and compiler-map membership, and the publisher reproduces all current support artifacts, receipts and full manifests exactly. Reports distinguish frozen-reference matching from working-tree freshness and list working-source differences requiring publication checks.

Materialize authenticated fixture snapshots into a reference source tree:

python3 test/fixture_sources.py materialize --source-root "$SCRATCH/support/source"

This authenticates recorded snapshots before writing a new directory and reconstructs compiler-map membership/dependencies from those snapshots. Its provenance is the fixture source receipt; generation acceptance uses the fresh-pair procedure. Include that directory in protected and set support.source to its scratch-relative path. Source drift, symlink escapes and a mismatched selection block the cleanup gate.

Published fixtures remain unchanged. relocate_patterns selects the current V3 publication procedure. Protect any other preparation or store used by active work before planning. The operator must check external users and stop uncoordinated jobs before setting the two confirmation fields.

SCRATCH="$PWD/.bootstrap-run"
CHECKPOINT="$SCRATCH/retention-checkpoint-UNIQUE"
python3 test/bootstrap_retention.py plan --root "$SCRATCH" \
  --config "$SCRATCH/retained-toolchains.json" --directory "$CHECKPOINT"

# Review plan.json: protected inputs, exact objects/ paths, compact evidence,
# and allocated-byte reclaim estimate. Use the SHA256 printed by plan.
python3 test/bootstrap_retention.py apply --root "$SCRATCH" \
  --directory "$CHECKPOINT" --approve-plan REVIEWED_PLAN_SHA256

# Completion gate: success requires completed checks and matching evidence/inputs.
python3 test/bootstrap_retention.py check --root "$SCRATCH" \
  --directory "$CHECKPOINT"

Planning registers indirect Nix GC roots for each exact accepted Haskell runtime. It inventories allocated blocks by inode, excludes hardlinks still referenced by retained or external files from reclaim estimates, and preserves compact header-bearing CAS documents with whole-file hashes before any deletion. Only selected objects/ directories inside this .bootstrap-run are candidates. Sources, portable bundles, original reports and archives outside that scope are preserved. Each removed CAS gets a PRUNED-CAS.json marker; its old completion records serve as historical evidence, and its store is marked incomplete.

Application holds an exclusive scratch-root lock. Generation runners take shared locks on previous/output scratch roots, so they refuse concurrent cleanup. Existing generation output locks are checked too. Stop other tools and external users separately: lock coordination covers the generation runners and retention command. Use trusted local filesystems and keep adversarial writers or mounts outside the workflow.

Before deletion, bwrap hides the entire scratch root, exposing only registered protected directories and the writable checkpoint evidence directory. The sandbox sets its temporary-directory variables to its fresh /tmp. Each selected compiler passes independent pair audits, CLI compilation/execution, diagnostics and integrity controls, correctness smokes, and a fresh scoped native_math map-builder/adapter job with complete-manifest comparison. Current IO compilers use the current CLI oracle; retained IO v1 compilers use the test/portable_cli.py oracle in their protected frozen source. Reports record the selected oracle and its hash. Selection precedes execution. Support reproduction must pass in that isolation too. The same checks run after deletion; only then can the checkpoint's plan.json become complete: true and the completion gate succeed. Verification logs, reports, registry and harness snapshots remain beside the plan. Keep those evidence directories and their runtime roots.

Deletion is journaled and resumable. A failed precheck deletes nothing. A failed postcheck leaves retention incomplete even if unlinking finished. For resumption, review the updated plan and approve its current SHA256; changed protected inputs, candidates, compact evidence or deletion harness block resumption. The completion checker can read archived harness identities after tooling evolves, but an in-flight delete must use its reviewed harness. Report an incomplete checkpoint with its blockers and outstanding bytes. Perform rotations at each checkpoint, while sufficient disk space remains.

If an enclosing PID namespace exposes a mismatched procfs, namespace setup can fail before a validation process starts. Record that outcome as a launch failure. Preserve isolation and run the same reviewed apply command inside a matching PID/proc namespace, using unshare --user --map-current-user --mount --pid --fork --kill-child --mount-proc -- python3 ..., and recheck completion. Run as a non-root user: the CLI oracle checks OS permission denial with that user's process authority. Preserve failed launch evidence; only the completed resumed postchecks satisfy retention.

The fast Nix check exercises the retention safety controls directly:

nix build .#checks.x86_64-linux.bootstrapRetention --no-link

Recovery archives

Select a completed generation, its matching frozen commit, standard adapter, Haskell executable, and a new transfer directory:

python3 test/bootstrap_archive.py \
  --generation "$PREVIOUS" --seed-commit "$MATCHING_COMMIT" \
  --adapter "$ADAPTER" --tricu "$TRICU" --output "$TRANSFER_DIRECTORY"

For a verified pair, also supply --first-generation "$FIRST" and --acceptance "$PAIR_ACCEPTANCE", with --generation selecting generation two. The selected seed's source must match its recorded inputs exactly. Fixture bundles and their reference source snapshots are authenticated separately; accepted-pair transfers record working-source differences separately from authenticated fixture coverage. Seed-only transfers still require current compiler fixture sources because they construct tools from that fixture.

IO recovery requires an independently accepted pair. The selected adapter must be the second generation's authenticated produced adapter. tricu.bootstrap-recovery.v2 records compiler IO version 2; its application probes explicitly select that version and run with OS process authority through the selected interpreter. The selected generation source map is retained in tools/source-map.v1; use that exact map when rebuilding. V1 transfers retain their historical runtime default.

The transfer includes full generation stores and provenance, matching source, current tracked working files, reachable Git history/refs, a tracked-change patch, fixtures, tools, and the Nix runtime closure. Accepted-pair transfers preserve actual reproduced tools; seed-only transfers pack current fixture entrypoints. Untracked scratch files are excluded. The historical Zig build-output symlink is omitted from source snapshots and retained only in Git history.

Creation verifies packed tools, extracts independently, checks all checksums, repeats relocated audits, verifies Git history, reimports the runtime closure, and probes native imports and stable recursion in fresh stores. Allow space for uncompressed stores and temporary validation. An interrupted transfer is incomplete until its final checksum and validation sidecars exist.

RECOVERY.json records inputs and limits; RESTORE-VALIDATION.json records actual checks. Follow the transfer's own README when restoring. Verify TRANSFER-SHA256SUMS before extraction and internal SHA256SUMS afterward. Existing transfers remain immutable when documentation changes.

Archive creation packages and validates existing accepted evidence. Runtime reimport is checked on the same host; empty-Nix-store and cross-platform recovery require separate validation. The retained Nix closure supports running the host; an offline Haskell rebuild requires its build dependencies as well. Archived SCP is retained solely as historical material.

Copy the whole transfer directory, including checksum and validation sidecars, to independent off-machine storage, and verify restoration before relying on it. Maintain an independently stored backup and retain the original recovery transfers.

Focused validation

python3 test/bootstrap_generation_cli.py "$PREVIOUS" --backend stable --adapter "$ADAPTER"
python3 test/io_indexed.py "$TRICU" --directory "$PROBE_DIRECTORY/read"
python3 test/io_indexed_write.py "$TRICU" --directory "$PROBE_DIRECTORY/write"
python3 test/io_compiler_store.py "$TRICU" --directory "$PROBE_DIRECTORY/compiler-store"

Use new writable diagnostic directories. The CLI probe checks cold/warm/change, interruption, corruption, backend selection, and receipt options. An older closure can use an explicit --worker-bundle. The v2 transport probes use independent canonical-byte, corruption, bounded transfer and compiler-store checks. The older v1 source-splicing probes are retired and reject before writes; their historical results remain historical. These focused checks support diagnosis; ordinary generation completion requires the generation runner and acceptance workflow above. See Storage for the transport contract.