Document Classification: Architecture Decision Record Audience: Architects, Core Developers


Context

ADR-0005 established the Hermetic Transactional Composition substrate and deleted the evaluation stage from the primary path, but it left two things unfinished. First, it treated build as the substrate's primitive operation, with observation (read-set capture) welded into the build's materialization mechanism (a FUSE tier) — early implementation work measured that welding at ~5.9× wall-clock overhead on a real openssl build versus bare metal, against ~25% for ptrace+seccomp syscall-level observation performing the equivalent capture. Second, it allowed an "optional legacy passthrough-snix executor" as a Nix-interop on-ramp, keeping evaluator-shaped doctrine (a third BuildPlan variant, a [compose] lock section, an entire executor spec) alive in the corpus.

Meanwhile the formal work surfaced a deeper factorization: build, test, fetch-discovery, and runtime-closure capture are not four mechanisms but one operation under four policies — and the differences between them (cacheable vs. not, gating vs. gated, rebuild-triggering vs. not) are consequences of that stratification, not features to engineer separately.

Decision

1. Execution is the primitive; build and test are policy strata [exec-primitive]

Precisely: execution is the dynamic primitive of the substrate's founding trichotomy — content-addressed storage (identity), composition (structure), execution (dynamics) — each of which has its own formal model. "The primitive" below is asserted against build/test/fetch/trace as competing dynamic operations, not against the two static primitives.

The substrate's dynamic layer has a single operation, execute(request, world) → record, where a request is (view: composition, command: opaque argv, outputs, policy) and the policy assigns each ambient channel (network, clock, entropy, …) one of closed | pinned | open. Requests stratify by policy, never by workload kind:

  • an action (no channel open) is world-independent; its record is a cacheable fact. A build is an action. A hermetic test is an action and caches like one.
  • a trial (some channel open) is world-dependent; its record is an attestation — signed evidence, never a cache value. A networked test is a trial. Record-mode fetch discovery is a trial.

Canonical terms: action / trial, record / attestation. The executor never interprets command — there is no interpreted language in the trusted core. Full semantics, laws, identity discipline (action_id / req_digest / record czd), and proof obligations P1–P7: the Execution Model.

Consequences that are theorems of the stratification rather than features: test parameters occur only in test requests, so editing test configuration can never trigger a rebuild; a failed trial gates a build fact's advertisement, never its existence, so a sandbox-hostile test never forces a rebuild of a good build; fetch discovery re-enters the deterministic stratum only through promotion into signed intent (lock entries) — or is bypassed entirely for ecosystems whose own lockfiles (Cargo.lock et al.) ship inside the atom's sources and are adopted directly as the pinned fetch-set via a per-ecosystem proxy adapter.

2. Materialization and observation are orthogonal axes, not tiers [exec-axes-not-tiers]

ADR-0005 §11's Observe/Fast/Export "materialization tiers" conflated two independent choices: how a view is mounted (bind mounts, composefs/ EROFS+fs-verity, export copies) and whether file access is observed. "Observe" as a mount tier dissolves. Observation is an execution-policy axis (observe: none | trace), applied to whichever materialization is in use.

ptrace(+seccomp filtering) is the ratified observation instrument for runtime-closure capture — measured at ~25% overhead with zero privilege requirements inside the production rootless sandbox, versus FUSE-based observation's measured 5.9×. This reverses an earlier design preference for FUSE observation (chosen for requiring "zero ptrace/seccomp machinery") on cost evidence. Sequencing rule: the FUSE read-set machinery is removed from the snix fork only after the ptrace observer proves read-coverage at openssl scale (io_uring and direct-syscall surfaces included); observation-instrument coverage is an empirical property recorded with every observation.

3. The evaluator is dead — entirely [exec-no-evaluator]

The "optional legacy passthrough-snix executor" is removed from the design. It was never implemented; the system has no users; maintaining doctrine, schemas, and wire surface for a superseded model inflates implementation burden in exchange for nothing. This project asserts, without a compatibility hedge, that the evaluator is unnecessary:

  • The passthrough executor's spec (eos-snix-backend.md) is deleted.
  • The third BuildPlan variant (NeedsEvaluation) and the Plan = Derivation binding disappear from the engine's model; the two-variant coproduct (Cached | NeedsBuild) stands alone.
  • The lock's [compose] section (composer selection, NixTrivial, [compose.args]) is removed — a composer has no meaning without an evaluator. The successor intent schema (action params, test params, intent kinds) is the manifest/lock redesign, deliberately not performed by this ADR (see Consequences).
  • The eos-snix crate is slated for removal as implementation cleanup; asserting the direction is this ADR's act, deleting code is not.
  • ADR-0002's residual tiers lose their referent and the ADR is superseded wholesale; ADR-0003's deployment modes (embedded / daemon / distributed) are unaffected — they describe eos generally, not the dead executor.

4. Records accumulate; there is no canonical witness [exec-witness-accumulation]

Build records are signed facts accumulating in atom metadata — several distinct output digests for one action, from several builders, is a legitimate and useful state ("a trusted key has seen this action produce these three hashes"). A cache hit is ∃ a witness acceptable under the consumer's trust anchors; the executor neither knows nor cares that other witnesses exist. Downstream coherence requires no tie-break: consumers bind the concrete output digests of whichever witness they consumed, and witness selection at request formation is a recorded choice over the fact snapshot. Witness multiplicity is surfaced as reproducibility evidence, never reconciled away. (This corrects the "concurrent identical builds produce identical content" justification previously given in the eos scheduling model's scope note; the dispatch design itself is unchanged.)

5. Runtime-only dependencies are real [exec-runtime-only-deps]

The runtime composition is not a subset of the build closure. Nix's runtime ⊆ build containment was an accident of its discovery mechanism (hash-scanning output bytes can only find paths present at build time) — an accident that forces rebuilds to change a CA bundle and makes the GPU driver an impurity hack. This substrate deliberately does not inherit it: containment is per-view and structural (a process cannot read outside its own view), while runtime-only dependencies (CA certificates, drivers, computed plugin loads) are supplied by the runtime composition, justified by declared bindings, and grown by fail-closed closure faults feeding monotone refinement.

Consequences

Positive

  • One executor and one policy surface replace four bespoke mechanisms (build sandbox, test harness, fetch recorder, closure tracer).
  • Hermetic tests become cacheable facts; networked tests stop poisoning builds; test-config edits stop triggering rebuilds — each structurally.
  • The evaluator's removal deletes an entire spec, a wire surface's design burden, a lock section, and a standing three-variant special case from the formal models.
  • Build-time observation overhead drops out of the default path entirely; observation is paid only by traced executions.

Negative / burden

  • The snix fork obligation is unchanged (castore + build daemon — neither is the evaluator), but the fork's FUSE read-set machinery becomes scheduled-for-removal debt, gated on ptrace coverage evidence.
  • The lock and manifest lose [compose] with no successor in this ADR: the manifest/lock redesign is now the substrate's most important open design surface — action params, test/check params, the intent kind discriminator (package / environment / generator), fetch-entry generalization (consumer-side requires edges), and adopted-lockfile entries all need a home. Tracked as follow-up design work, not reconciliation.
  • The execution model's proof obligations (P1–P7) stand as the verification queue; P7 (resolve determinism + materializer versioning
    • totality) gates the two-level cache-coherence claims.

Neutral

  • The scheduler's verified theory is unaffected: the Lean bounds are node-agnostic and transfer; the scheduling-model scope note's justification wording is amended (witnesses accumulate), not its design.