Execution — the Third Primitive
Formal model: Execution — the Third Primitive
2026-07-06. Status: APPROVED FOR PROMOTION (nrd, 2026-07-07 — core
terminology and observation posture ratified; remaining §9 items are
tracked open questions, not blockers). Extends the algebra of
docs/models/publishing-stack-layers.md (olog / coalgebra / session
types); where this document and prior prose (ADR-0005, htc-sad) conflict,
this document is the correction — the reconciliation sweep amends the
older documents accordingly.
v0.2 (2026-07-07): incorporates all ten corrections from a zero-context adversarial review by an independent architect instance, which reached ADOPT-WITH-CORRECTIONS — having independently formulated the layer without the execution unification and then conceded to it on engineering economy, REAPI precedent, and the test-edit property. Sharpest corrections: the determinism claim restated relationally (§2.2), strata made policy-derived so hermetic tests cache (§2.3), signer-trust semantics for facts (§3.4), request-digest completeness (§2.1), the deposit-vs-bind law (§5.2), and the μ/ν and monoidal-category decorations struck (§4.4, §5.1).
v0.3 (2026-07-07): extensions from nrd's expansion — interfaces as the
typing of compositions (§1.4: packages / environments / systems made
explicit, the granularity derivations blur); the human escape hatch as a
third justification kind, Declared, with its epistemics kept honest
(§4.2–4.3); materialization/installation/transactional update as the value
layer's own operation, proving install needs zero new concepts (§6.4).
v0.4 (2026-07-07): the language-free purity thesis promoted to §0.5 (the model's framing result); the override algebra added as §5.3 — overlays are operations inside the model (typed edits over the binding graph, blast radius computed by the interface layer) rather than an external generator gap; §9.11 rewritten accordingly.
v0.5 (2026-07-07): the two strata of intent (§1.5) — executable intent (packages: elaboration requires execution) vs algebraic intent (environments, generators: elaboration is pure, terminating, self-verifying by recomputation) — mirroring the action/trial stratification at the intent layer; kinds open at schema level, strata closed at law level; the certificate identified as the environment's lockfile.
v0.6 (2026-07-07): §3.5 — promotion as reified monadic bind (the IFD answer: monadic builds as iterated applicative generations, signatures at the join points); mixed-atom law (pipelines cross strata, steps do not) and fact-conditioned formation added to §1.5; P6 (promotion coherence: identity, idempotence, confluence of independent promotions — the lock must merge).
v0.7 (2026-07-07): corrections from the SECOND zero-context adversarial
review (cross-formalism audit; verdict RATIFIABLE AFTER NAMED CORRECTIONS).
F1: the certified interval's quantification repaired (Dep indexed per view;
build-view sufficiency made relative — runtime-only deps are real, the
Nix containment coincidence is not inherited). F2: theory-transfer claim
split (Lean bounds transfer; Track A's overlap-safety needs a
witness-reconciliation rule — P4 extended, eos amendments queued). F3:
resolve-is-a-function demoted to obligation P7 (determinism +
materializer versioning + totality). F4: record_core defined; all
equality claims quantify over it. F5: ⊕ conflicts defined denotationally
(grafts in P1 scope). F6: algebraic stratum's law restated as pure in
(intent, fact-set). F8: flaky-verdict caveat on hermetic-test caching.
F9: BuildRecord.observed_read_set_digest added to the correction sweep.
F10: blast radius qualified by the interface proxy. F7 (lock inadequacy:
no merge op, single-valued owner, purge rules, Either-order gap) recorded
in §9.14 as the lock campaign's acceptance criteria.
v0.8 (2026-07-07): whole-system corrections from nrd — the model was overcorrecting for problems sibling layers already solve. The single-slot "first-witness" cache (imported in v0.2 from the centralized Nix/REAPI worldview, and the sole reason v0.7 demanded a witness-reconciliation rule) replaced by the multi-valued record store the trust design always implied: witnesses accumulate, cache hits serve any trust-acceptable one, selection at request formation is a recorded choice, multiplicity is reproducibility evidence. F2 shrinks to two wording amendments in eos docs. P6's concurrent-merge demand struck (write topology is phased by design); reorder-invariance laws kept. §9.14's owner-set patch superseded by consumer-side requires-edge generalization. New §7.3: the boundary discipline — what this model deliberately does not own.
v0.10 (2026-07-07): the substrate trichotomy made explicit in the
formalism (nrd's directive: the three founding primitives should be
primitives in the formalism, not only in the narrative). The composition
algebra — §0.5, the composition value of §1.1, §1.2, §1.4, §1.5, §5.3,
§6.4 — is re-homed to the Composition Model;
storage is axiomatized in the Storage Model. The
moved sections are retained below as stubs so section numbering and
inbound references stay stable. This document now consumes Composition
as an abstract type and owns the dynamic primitive alone. P1 moves with
the merge monoid; obligation numbering (P1–P11) is unique across the
three models.
Trigger: early substrate implementation work treated build as the
primitive and bolted observation into its materialization layer (a FUSE
read-set inside BuildResult). The substrate's founding analysis had
already named three primitives — storage (CAS), composition, and
EXECUTION — with execution never given its own formal definition. This model gives execution its missing formal
definition, from which build, test, fetch-recording, and closure capture all
derive as policy strata — rather than as separately engineered features.
0. The one-sentence model¶
Execution is a single operation,
execute : Request × World → Record, stratified by the request's policy — never by workload kind — into actions (all ambient channels discharged, hence cacheable) and trials (some channel open, hence signed witnesses); a build is an action, a test that needs the world is a trial, a hermetic test is an action and caches like one, and every other capability of the substrate — caching, fetch recording, runtime-closure capture, publication gating, no-rebuild-on-test-edit — is a consequence of this stratification rather than a feature.
0.5 The language-free purity thesis¶
Re-homed: Composition Model §1
— the thesis is the composition primitive's framing result. The clause
that is execution's own is restated where it binds: the executor never
interprets command; there is no interpreted language in the trusted
core (§2.1).
1. The semantic domain¶
1.1 Values¶
All values are content-addressed; identity is digest.
- Blob
b— bytes. IdentityH(b). - Tree
t— a Merkle tree of named blobs/trees (castore-shaped). Identity: root digest. - Composition
c— the second primitive's value: a signed finite map from conventional paths to content entries, with denotation⟦c⟧ : Path ⇀ Content(directory enumeration order included). Its full algebra — merge, interfaces, strata of intent, overrides, materialization — is owned by the Composition Model (§2 there); this document consumes the denotation and nothing else.
1.2 Composition merge is a partial commutative monoid¶
Re-homed: Composition Model §3,
together with P1. Execution uses exactly one fact from it: independent
subgraphs of the action DAG merge views by the partial commutative
idempotent monoid (Comp, ⊕, ∅) (§5.1).
1.3 The world and its channels¶
Everything a process can be influenced by that is not in its view is an ambient channel. The finite channel set (extensible, but honest — list what exists):
C = { net, clock, entropy, ids(uid/gid), locale/tz, cpu-features, ... }
A world w ∈ W is an assignment of concrete behavior to channels. The
world is the enemy of reproducibility; the whole design is a discipline for
discharging it.
Each channel, per execution, is in one of three states:
closed — the channel does not exist for the process (e.g. no network ns)
pinned — the channel exists but its content is a declared function of the
request (e.g. replay-proxy map; SOURCE_DATE_EPOCH; fixed uid map)
open — the channel passes through to the real world
A policy p ∈ P assigns a state to every channel. This is a
classification, not an order: no result below uses a policy ordering
(and the tempting monotonicity claim — "less access ⇒ same-or-fewer
records" — is vacuous anyway, since different policies produce different
request digests and hence different cache slots). The only structure the
model needs is the deterministic stratum:
Det = { p ∈ P | no channel is open under p }
1.4 Interfaces: the typing of compositions¶
Re-homed: Composition Model §4.
What execution borrows: satisfies/iface_digest as the checkable
contract proxy (htc-sad §6.1–6.2), and the package contract — not
per-environment patching — as the durable home of closure-fault
repairs (§4.4; the environment is the unit of linking and choice).
1.5 The two strata of intent¶
Re-homed: Composition Model §5. The strata mirror this document's action/trial stratification at the intent layer; the executable stratum is realized here (§2–§3), and cross-strata data flows only through CAS values, records, and promotion (§3.3).
2. Execution¶
2.1 The request¶
ExecutionRequest = {
view : CompositionRoot -- the entire visible universe of the process
command : { argv, env, cwd } -- opaque; the executor NEVER interprets it
outputs : [Path] -- declared scratch/output paths to ingest
policy : P -- channel states, per §1.3
}
req_digest = H(canonical serialization)
Three invariants inherited from the substrate's doctrine, now stated as type-level facts:
- No interpreted language.
commandis opaque argv. The executor is a universal machine runner, not an evaluator. (This is what died with the eval stage in ADR-0005 §6; the request type keeps it dead.) - The view is the whole world. Under any
p ∈ Det, the process's observable universe is exactly⟦view⟧plus pinned channel content. There is no second place inputs can come from. - Request completeness. Every pinned channel carries a payload (the
replay map's digest, the
SOURCE_DATE_EPOCHvalue, the uid/gid mapping) — and every pin payload MUST be inside the canonically serialized, digested request. Otherwise two executions differing in pin content share a cache slot: unsound. This is not hypothetical — Gate A's "seed toolchain provenance untracked" debt is a live instance of the defect class (the seed enters viaview, so it is digested, but the lesson generalizes: anything that can influence the record must be reachable fromreq_digest). Proof obligation P5: an injectivity/completeness audit of the canonical request serialization — every field the executor consults appears in the digest preimage.
2.2 The semantics¶
execute : ExecutionRequest × W → ExecutionRecord
execute is implemented by a sandbox; §6.3 states the obligations that make
the following true by construction rather than by trust.
The semantics is relational, not functional — deliberately. Even with
every ambient channel closed or pinned, residual nondeterminism remains
inside the sandbox: thread scheduling, ASLR, the interleavings of a
parallel make. So execute denotes, for each request, an admissible
record set:
⟦req⟧ ⊆ ExecutionRecord (non-empty when execution terminates; failure
is itself admissible — a record with exit ≠ 0)
Property (hermeticity / world-discharge). If req.policy ∈ Det, the
admissible set is a function of the request alone:
Adm(req, w) = Adm(req, w′) for all w, w′. The world argument is
discharged; what is not claimed is that the set is a singleton. The
discharge distributes over channels: for each channel, either the
namespace construction removes it (closed) or the pinning mechanism
determines its content from the request (pinned). §6.3's audit table is
the per-channel discharge — a conformance checklist for executor
implementations, not a metaphysical claim.
The record store is multi-valued; caching is witness selection, not
canonicalization. (v0.8 correction — nrd, from the whole-system view:
v0.2–v0.7 carried a single-slot "first witness wins" cache imported from
the centralized Nix/REAPI worldview, an invariant this system never
needed.) Records of ⟦req⟧ accumulate — signed facts in the atom's
metadata, possibly several distinct output digests from several trusted
builders ("a trusted key has seen this action produce these three
hashes"). A cache hit is: ∃ a witness acceptable under the consumer's
trust anchors (§3.4) — serve it. The executor neither knows nor cares
that other witnesses exist. Coherence needs no tie-break because
consumers bind to the concrete output digests of whichever witness they
consumed — every downstream branch is internally consistent by
content-addressing. The one genuine obligation: when a downstream
request is formed, the witness pick is a recorded choice over the
fact snapshot (the same fact-conditioned machinery as the fact-set discipline,
Composition Model §6), so P7's
determinism holds relative to (intent, fact-set, choice policy).
Multiplicity is not an anomaly: distinct witnesses of one request are
free reproducibility evidence (§2.3), surfaced, never reconciled away.
Reproducibility (§2.3) is then precisely the empirical claim that
⟦req⟧ is observed to be a singleton — which is why it can only ever be
attested, not proven.
2.3 The stratification: actions and trials¶
action = request with policy ∈ Det (world-independent)
trial = request with some channel open (world-dependent)
Stratum membership is a property of the request's policy — never of the workload kind. There is no "build stratum" and "test stratum"; there are only requests that discharge the world and requests that do not:
- An action's record is a fact: a signed witness of
⟦req⟧, accumulating alongside any others in the record store (§2.2); a cache hit serves any trust-acceptable witness.buildis an action. So is a hermetic test — a test that opens no channel is cacheable exactly like a build, and skipping its re-execution on cache hit is sound (this is Bazel's cached-test-results behavior, recovered here as a stratum consequence rather than a feature). One epistemic caveat (F8, second adversarial review): a test's exit code is its payload. For a hermetically flaky test (racy threads), first- witness caching converts "∃ run that passed" into "passes" — sound for build outputs (any witness is a valid build), weaker for verdicts that gates (§3.2) consume. Acceptance policy may therefore demand n core-equal samples before a cached verdict feeds a gate — a policy hook, not a structural change. - A trial's record is an attestation: a signed witness that "at time τ, under executor E, with world summary σ, this outcome occurred." It is evidence, never a cache value. Whether existing evidence is accepted in lieu of re-running is an explicit acceptance policy (staleness, required count, required signers) — a policy decision, not an identity judgment. A networked test is a trial. So is record-mode fetch (§3.3).
Hermetic ≠ reproducible — two predicates, deliberately separated:
- Hermetic (§2.2): input surface fully determined by the request. Auditable per channel; holds by construction for actions.
- Reproducible: two executions of the same action yield byte-identical output trees. This is empirical, not provable — a toolchain may consume a pinned-but-present channel (a clock we haven't virtualized, ASLR interacting with a buggy linker) in ways we don't model. Reproducibility is therefore an accumulating attestation on an action (n independent executions, k distinct executors, all record-equal — exactly Gate A's two-run check, formalized). Nix conflates these two predicates; we will not. Cache correctness needs only hermeticity; trust policies (who may substitute a binary) may demand reproducibility attestations on top.
2.4 Identity discipline (three identities, never conflated)¶
action_id = H(atom_czd_closure_root, toolchain_composition_root, params)
-- identity of INTENT (signed); the scheduler/user-facing key
req_digest = H(ExecutionRequest)
-- identity of the concrete execution; the executor-level key
record czd = signed content digest of the record/attestation object
-- identity of the FACT/WITNESS as durable data
Resolution and materialization map intent to request:
resolve : action → ExecutionRequest. That resolve is a function is an
OBLIGATION (P7), not an established fact (correction F3, second
adversarial review — v0.6 asserted it while §9.14 admitted it open; worse,
ion's [resolution-deterministic] governs a different arrow entirely,
manifest→lock, and resolve is not even total from lock data today:
the toolchain composition has no lock entry type yet, an already-
registered ADR-0005 open item). P7's content: (a) determinism of the full
action→request map — closure materialization layout, command assembly,
pin/policy assembly; (b) the materializer's layout algorithm must be
versioned inside the identity (as an action_params component or an
explicit term) — otherwise a layout change silently serves stale
action-level cache entries against a changed request; (c) totality,
gated on the toolchain-pin lock entry. Granted P7, caching at either
level is coherent and the two-level scheme buys something real:
Corollary (cross-intent deduplication). Two distinct atoms whose
resolution produces the same ExecutionRequest share one executor-level
cache entry. Dedup happens at the request layer even when intent differs.
Corollary (early cutoff). Records address outputs by content. If a re-executed action yields an output tree with an unchanged digest, every downstream request (whose views embed that digest) is byte-identical to its cached form — the rebuild cascade stops. This is the "constructive traces" rebuilder of Build Systems à la Carte, obtained structurally. (À la carte decomposes build systems as scheduler × rebuilder; ours is eos × CAS-traces, and this model adds the axis their taxonomy lacks: observation policy.)
3. Facts, witnesses, and promotion¶
3.1 The record¶
ExecutionRecord = {
req_digest, exit_code,
outputs : [tree_digest], -- ingested from declared output paths
stdio : blob digests,
observed : ObservationDigest?, -- iff policy.observe = Trace (§4)
context : { executor id, time, world summary σ } -- trials only
signature : executor identity
}
record_core(r) = (req_digest, exit_code, outputs)
All record-equality claims in this model quantify over record_core,
never the full record — the signature and context differ across
executors by construction, and stdio is not bit-stable even for
reproducible builds (timestamps, parallel interleaving). Reproducibility
(§2.3), k-record-equal attestation counting (§3.4), and golden-request
conformance (§6.1) all mean core-equality. (Correction F4, second
adversarial review: v0.6 let three inequivalent equalities drift across
those sections.)
Note what is absent: BuildResult carrying a read-set (Gate 0's design)
is mis-homed under this model. Observation is an execution-policy output,
attached to the record of whichever execution was traced — typically a
trial, never mandatory for builds.
3.2 Publication gating¶
A build fact is advertised (published into the fact channel — appended
atom metadata per htc-sad §6.10) according to a gating policy: e.g.
"advertise BuildRecord iff a passing test attestation for req_digest
exists." The build's fact-hood is never conditional on the test — the cache
entry exists the moment the action completes (nrd's requirement: a failed
test never forces a rebuild) — only its advertisement is gated. Gating is
configuration on the fact channel, not scheduler structure.
3.3 Promotion: attestation → intent (the cargo-update law)¶
Record-mode fetch is a trial (net: open through the recording proxy).
Its attestation contains the discovered map request → blob digest. The
tool then promotes that attestation into intent: fetch-set entries
written into the lock (plugin-typed, per ADR-0005 §7). Promotion is the
unique arrow from the trial stratum back into the deterministic one:
trial (record-mode fetch) ──attestation──▶ promotion ──▶ lock entries
(intent)
⇒ all subsequent builds run with net: pinned(replay map) ∈ Det
This is the same shape as cargo update writing Cargo.lock, and the same
epistemics as a Nix FOD hash bump — formalized as the only sanctioned way
world-dependence enters the deterministic stratum: through signed,
reviewed intent, never at execution time.
Adopted lockfiles: promotion's door is not always needed (v0.9, nrd).
For ecosystems that already ship a pinned, checksummed lockfile
(Cargo.lock, package-lock.json, go.sum), the net channel's pin payload
can be adopted rather than discovered: the language lockfile is
already reviewed, pinned intent — and it lives inside the atom's
sources, hence inside the closure, hence inside action_id, so request
completeness (§2.1) holds with zero extra machinery. A tiny
per-ecosystem proxy adapter enumerates the lockfile's (url, digest)
pairs as the replay map — the build fetches exactly what the language
lockfile declares and nothing else. No record trial, no promotion, no
redundant re-declaration in the atom lock. This reduces the *2nix
translators' entire job to its essence: not IR translation into build
instructions, just fetch-set enumeration through a small compatibility
interface. Record-mode → promotion remains the door for lockfile-less
ecosystems; both doors end at the same place — signed, digested intent
pinning the channel.
3.4 Whose facts are facts: signer trust¶
This is a decentralized stack: records arrive signed by executors you do
not control. Hermeticity is a property of honest execution — it does
nothing to make a remote builder honest. So fact-hood is relative to a
trust anchor set: a record is accepted as a fact (cache-usable,
substitution-grade) only when its signer lies within the consumer's
configured anchors; outside them, the very same record is epistemically an
attestation — evidence from a foreign party, subject to acceptance policy
like any trial's witness. Reproducibility attestations (§2.3) are the
hardening mechanism: k record-equal executions from independent signers
upgrade confidence without requiring local rebuild. This is the classical
substituter problem (Nix's binary-cache trust), placed where it belongs:
the action/trial stratification governs what a record can be; signer
trust governs what it is, to you. The concrete anchor format and
policy language ride on atom's existing signing machinery (Coz identities)
and are deliberately left to the trust-model spec, not fixed here.
3.5 Promotion is reified bind (the IFD answer)¶
Moggi's value/computation distinction is this model's strata: an
ExecutionRequest is a reified effect description — a pure,
content-addressed value describing a worldly computation without
performing it. The algebraic stratum constructs and composes descriptions
purely; the executor is the boundary that runs them; results re-enter the
pure universe as data. The system state is always pure (one Merkle
universe holding sources, requests, records, compositions, certificates
alike); impurity exists only in the transition execute, never in the
state — effects are edges, nodes are values. The quantitative core of
this — both endpoints of every execution edge are fully-determined
content-addressed points, while the edge itself stays a witnessed
relation — is stated once as the endpoint-completeness corollary
(composition model §1.1).
In Build Systems à la Carte terms, dynamic dependency — computing the
next build from a previous build's result — is monadic bind, and
Nix's import-from-derivation is bind hidden inside an evaluator that
claims purity: evaluation blocks on builds, analyzability and caching
collapse, and the pain is structural. This model has bind too:
Promotion is bind, reified and priced at a signature. The continuation runs only after the result is a first-class fact promoted into signed intent; monadic builds are realized as an iteration of applicative generations with signatures at the join points. Each generation remains fully static and analyzable; the dynamic step is explicit, audited, resumable.
The frame is adopted for its laws, not its vocabulary (stated as obligations, P6, not as assumed structure): left identity — promoting an already-known fact is equivalent to having declared it as intent; idempotence — re-promoting without change is a no-op; confluence of independent promotions — concurrent promotions touching disjoint intent (two record-mode fetches of different deps; a fetch promotion racing a closure-fault promotion) MUST merge without conflict. The third is a genuine engineering requirement surfaced by the frame: the lock must be confluent under independent promotion, conflicting only on genuine overlap. And the frame yields a standing rejection rule: any future proposal that computes intent mid-elaboration without a signature is a hidden bind — recognizable and refusable on sight.
4. The two closures¶
4.1 The true dependency set is not computable¶
For an artifact a (an output tree), the dependency notion must be
indexed by view — v0.6 carried one symbol for two quantifications,
which was a genuine formal error (caught by the second adversarial
review, F1):
Dep_v(a) = { p ∈ Path | some execution of a within view v reads p }
Dep(a) = ⋃_v Dep_v(a) (the liberal union, over all views)
Dep(a) is undecidable (Rice's theorem; the classic witness is
dlopen-by-computed-string — htc-sad §6.3's motivating case). No component of this system ever claims to compute it.
Everything below is about maintaining certified bounds — and the two
bounds quantify over different dependency sets, which is why the index
matters.
4.2 The certified interval¶
R_static ∪ R_observed ∪ … ⊆ Dep(a) (evidence: liberal union)
Dep_v(a) ⊆ dom(⟦v⟧) (structure: per view)
- Upper bound — structural, free, and per-view. The namespace bounds
the observable universe: a process cannot read what does not exist in
its view, so
Dep_v(a) ⊆ dom(⟦v⟧)holds by construction, for each view separately — no tracing, no checking, no FUSE required. What does not hold isDep(a) ⊆ dom(⟦v⟧)for any particularv: an execution in a larger view can read paths this view lacks. That is not a defect; it is the definition of a closure fault (§4.4), and the reason runtime-only dependencies (lazily-loaded plugins, CA certificates, theawka script shells out to — things no build-time execution ever touches) are real in this model. Nix never had to say this because hash-scanning made runtime references a subset of build-time presence by construction; this model deliberately does not inherit that coincidence, and the package-contract repair discipline of Composition Model §4 exists precisely because of it. - Lower bound — evidential.
R_static: name references extracted structurally (ELFDT_NEEDED, shebangs, import syntax — the Debian/RPM lineage, htc-sad §6.2), each resolved binding carrying a satisfaction proof.R_observed: reads captured by traced executions (trials withobserve: Trace), each an unforgeable member ofDep(a)— observation is sound (every observed read really happened) and incomplete (unexercised branches), the exact dual of static analysis (complete over syntax, blind to computed loads). The two compose because they under-approximate from independent directions.
There is a third justification kind, and it is not evidence:
Declared— the human escape hatch. BecauseDep(a)is uncomputable, detection will miss; a human may declare a runtime dependency directly (signed intent, like any lock entry). Epistemics kept honest: a declaration asserts nothing aboutDep(a)— the human may be wrong in the fat direction, and that is safe (an unused binding is closure bloat inside the sound upper bound, prunable later by evidence). Declarations therefore participate inJ's generating set (§4.3) but never in the certified lower bound — the interval above remains evidence-only. A declaration must resolve within the candidate set (containment is preserved); it is the runtime analogue of a lock entry, and it rides the same signed-intent discipline (§3.3).
4.3 The justified closure¶
The runtime composition is computed as the least fixpoint (htc-sad §6.4) over the evidence:
J = μX. bind(R_static(a) ∪ R_observed(a) ∪ R_declared(a) ∪ requires(X))
Proof obligation P2 (real, small, Lean-able): J is monotone in its
evidence and terminates (Knaster–Tarski on the finite powerset of the
candidate set); refinement by new observations only grows J toward
Dep(a), never oscillates. Every entry of J carries a justification
object — a binding proof or an observation record — so the closure is not
merely computed but explainable, entry by entry (contrast: Nix's closure
is "every path whose hash appears in the output bytes," unexplainable).
4.4 Graceful degradation and the refinement loop¶
Property (build-view sufficiency — relative, not absolute). v0.6
claimed the full build composition is "always a sound runtime
composition"; that is false as an absolute (runtime-only dependencies,
§4.2, are the counterexample — and the model's own repair story
contemplates exactly them). The true statement: dom(⟦build view⟧) is
sound with respect to every behavior witnessable within it — shipping
fat covers everything build-time evidence could ever justify, and the
residue (runtime-only deps) is a completeness gap, never a
soundness gap: minimization only prunes entries with no
justification, so nothing evidence supports is ever lost. The residue is
handled by the two mechanisms built for it — Declared bindings (§4.2)
and fail-closed closure faults feeding refinement (below). An artifact
with no trials run is still shippable fat; it simply carries the honest
caveat that its runtime-only residue is undiscovered. The core-vs-hook
resolution stands: the capability is core, its exercise optional per
artifact.
Property (fail-closed refinement). Running under J in production, a
read outside J is a closure fault: denied, logged with the name that
missed. The fault is itself an observation — a new justification candidate
— and by P2's monotonicity, incorporating it strictly grows J toward
Dep(a). The runtime closure is thus refined over the artifact's
operational life: never assumed complete, always improvable, always
inside the sound upper bound. Formally this is one mechanism, applied
twice: a least fixpoint — over declared lock structure for the build
closure, over a monotonically accumulating evidence set (a Kleene chain in
the evidence parameter) for the runtime closure. The two closures differ
in where their generating sets come from (declaration vs. observation),
not in their fixpoint mathematics. (v0 dressed this as an algebra/
coalgebra μ/ν duality; the adversarial review correctly struck that — no
coinduction is constructed anywhere, and the operational content needs
none.)
5. Compositional structure¶
5.1 The action DAG over the merge monoid¶
Deterministic executions compose: action r₂ may embed r₁'s output
digests in its view (via ⊕ and binding). The structure actually used is:
- The action DAG — nodes are requests, edges are output-digest flows into downstream views. Sequential dependency is edge order.
- Independent subgraphs merge views by
⊕(the partial monoid of Composition Model §3) — and eos's parallel dispatch is exactly the evaluation of independent subgraphs.
Theory transfer, split honestly (correction F2, second adversarial review — v0.6 overclaimed this as "unchanged"):
- The Lean bounds (Theorems 1–7 + Main) are genuinely node-agnostic and transfer to requests-as-nodes without amendment.
- Track A's overlap-safety argument transfers after a wording repair, not a new mechanism. (v0.8 — nrd's correction dissolved v0.7's demand for a "witness-reconciliation rule," which only existed because of the single-slot cache invariant this model wrongly imported.) The eos scope note justifies concurrent-completion safety via "identical content at identical output-tree digests" — a determinism premise §2.2 denies. The correct justification needs no determinism: witnesses accumulate (set-insert, commutative), each downstream branch coheres by binding the concrete digests it consumed, and witness selection at request formation is a recorded choice (§2.2). Queued doc amendments only: the eos scope note's justification, and eos-sad §6.6's "exactly one BuildRecord per action" → one per build event (accumulating). P4's scope (§8) is correspondingly modest: check that set-accumulation under concurrent completion preserves the dispatch state machine's invariants — expected to be near-trivial.
(v0 claimed a symmetric monoidal category here; the adversarial review
correctly struck it — morphism composition was not well-defined as stated
(binding data is not carried by the morphism), ⊕'s partiality breaks the
monoidal laws without machinery this model neither invokes nor needs, and
no result below used the categorical structure. The partial monoid and the
DAG carry everything.)
5.2 Tests are not build nodes; trials deposit but never bind¶
Tests (of either stratum) do not appear in the build DAG: no build request's view embeds a test's output. They decorate the DAG — attached to the nodes whose outputs they exercise.
The deposit/bind law. A trial MAY deposit values into the CAS (record- mode fetch deposits every response body as a blob — the draft's own flagship trial does this, so "trials produce no outputs" would be false). What a trial MUST NOT do is bind those values into any view: binding requires promotion through signed intent (§3.3 — lock entries, written by the tool, reviewed as intent). CAS deposit is inert — an unbound blob influences nothing; binding is the only door into the deterministic stratum, and it is guarded by signatures. This law is also the answer to v0's open question 5: benchmark artifacts and coverage reports are CAS blobs referenced by the attestation, never view-entering outputs.
Theorem (no-rebuild-on-test-edit). Test parameters occur only in test
requests (of either stratum). action_id and every build req_digest
are computed from objects that contain no test parameters. Hence editing
test configuration changes no build identity, invalidates no build cache
entry, and triggers no rebuild — structurally, not by scheduler courtesy;
a hermetic test's own cached result is invalidated (its req_digest
changed), which is exactly the desired scope of re-execution. (Manifest
support: test/check params must live in a manifest section that feeds test
requests only — an atom-API obligation this model imposes on L1/L4.)
The nixpkgs pathology this kills, named precisely: coupling build and test into one derivation makes the pair the unit of caching, so a sandbox- induced test failure poisons a perfectly good build. Here the build fact stands alone; the trial's failure gates advertisement (§3.2), and its fix (test-flag edit, network policy change) re-runs only the trial.
5.3 The override algebra: generators live inside the model¶
Re-homed: Composition Model §7.
Execution's stake is the rebuild frontier: consumers whose interface
satisfaction fails under a subst need intent edits (new action_ids),
and early cutoff (§2.4) prunes every descendant whose rebuilt output is
digest-identical.
6. The executor¶
6.1 The coalgebra and its bisimulation¶
F_exec(X) = ExecutionRequest → X × Result<ExecutionRecord>
Two executors are equivalent iff:
- on actions: every record either produces lies in
⟦req⟧— i.e. their admissible sets coincide (consistent with §2.2's relational semantics: conforming executors may return different witnesses of a non-singleton set, and the cache's first-witness fiat absorbs that). The testable form: on requests empirically known reproducible (singleton⟦req⟧attested per §2.3), conforming executors MUST produce digest-identical records — golden-request conformance uses exactly these; - on trials: they produce valid attestations for identical requests (agreement up to the policy's licensed nondeterminism — a test may pass on one run and fail on another only through open channels or residual nondeterminism).
This is the deployment-interchangeability story (local runner ≅ remote
worker ≅ adapter-bridged worker), and — more practically — the
conformance criterion for any executor implementation: bisimilarity on
the action stratum is directly testable (golden request → expected record
digest). The existing BuildEngine bisimulation (publishing-stack-layers
§2.4) is this criterion restricted to builds.
6.2 The session type¶
ExecuteSession (client → Executor):
!ExecutionRequest .
& { Known: ?ExecutionRecord . end -- action: cache hit
-- trial: accepted attestation
, Scheduled: ?ExecutionRecord . end -- executed now
, Refused: ?PolicyError . end } -- policy unsatisfiable here
BuildSession(existing, ion→eos) is the restriction ofExecuteSessionto actions derived from atoms:Known = BuildPlan::Cached,Scheduled = NeedsBuild-then-apply. The existing findingCacheSession ≅ BuildSessionis the action-stratum instance of a general law: theKnownbranch is inhabited by identity on actions and by acceptance-policy on trials — same protocol shape, different epistemic license.TestSession=BuildSession ; ExecuteSession(trial)— a composite: the trial's view is assembled from the build's output ∪ build closure.- Progress/duality for
ExecuteSessionis proof obligation P3 (cheap, mechanical — same treatment BuildSession got).
6.3 Enforcement obligations (the per-channel audit)¶
What makes §2.2's theorem true. For each channel, the executor MUST implement the policy state by the named mechanism — this table is the conformance checklist, and it is honest about today's gaps:
| Channel | closed | pinned | status today |
| filesystem | mount ns: view is the root | — (view IS the pin) | exists (bind/OCI); composefs pending privilege story |
| net | empty netns | replay proxy over bound socket (ProxyOnly) |
exists (Gate 0) |
| ids | userns fixed mapping | fixed uid/gid in spec | exists |
| clock | — (cannot close) | SOURCE_DATE_EPOCH env; not virtualized |
gap, honest: env-pinning is convention, not enforcement; full pinning needs time-ns/seccomp — decide if we care (Nix doesn't enforce it either) |
| entropy | — | seeded/deterministic /dev/urandom? |
gap, honest: unpinned; same posture as every mainstream build system |
| cpu-features | — | pinned -march/target in params |
convention via action params |
| nproc / CPU count | — | pinned via cgroup cpuset or params | unpinned today: make -j$(nproc) reads it (Gate A/B literally did); affects parallelism (usually not bytes, but usually is not a proof) |
| readdir order | — | pinned by ⟦view⟧'s sorted denotation (§1.1) | obligation on every mount mechanism |
| /proc contents | partially maskable | — | partially open: OCI masks some paths; a build reading /proc/meminfo sees the world — audit and extend masking |
Two further absolute obligations: the executor materializes exactly
⟦view⟧ (no more — the containment bound depends on it) and never
interprets command. Everything else (which mount tech, which trace
tech, fork daemon vs native binary) is implementation freedom below the
bisimulation line.
Observation instruments and their honesty conditions. The model
requires only soundness of observed reads (every logged read really
happened); instruments differ in coverage, and coverage is an empirical
property that MUST be named per instrument: ptrace+seccomp (~25% measured
overhead at zlib scale) misses reads issued through syscall surfaces the
filter doesn't trap — io_uring is the known live hazard, direct syscalls
a second — so an observed read-set from ptrace is a lower bound relative
to its trapped surface. FUSE had the complementary profile: structurally
complete over the mount (every read flows through the daemon) at ~5.9×
cost. Missed reads are survivable by construction — they surface later as
fail-closed closure faults (§4.4) and refine J — but the coverage gap
must be recorded with the observation, not assumed away. eBPF is a future
instrument with its own privilege/coverage trade.
6.4 Materialization, installation, and transactional update¶
Re-homed: Composition Model §8.
The executor-side residue is already stated in §6.3: the executor
materializes exactly ⟦view⟧. Installation and transactional update
involve no execute at all — they are the static primitives' own
operations.
7. Boundary mappings¶
7.1 REAPI: adapter, not core¶
REAPI (build.bazel.remote.execution.v2) validates this factorization at
industrial scale — its Action = (Command, input-root Merkle digest,
platform) → ActionResult is our request/record restricted to the action
stratum, and Bazel already runs both builds and tests over it. We adopt the
shape natively and treat literal REAPI as a lossy adapter functor
at the boundary: their Directory protos ↔ castore trees (re-hash), their
stringly platform properties ↔ our policy classification (lossy — the policy
semantics is precisely our novelty), their unsigned actions ↔ our signed
intent (lossy). Lossy in both load-bearing dimensions ⇒ edge tier (like
Export materialization), never the core contract. What the adapter buys,
when wanted: off-the-shelf worker fleets (BuildBarn/NativeLink-class) as
dumb action-stratum capacity, and foreign clients.
7.2 What this model corrects in the existing corpus¶
| Where | Today | Under this model |
| ADR-0005 §11 three "materialization tiers" | Observe/Fast/Export as mount tiers | Materialization and observation are orthogonal axes; "Observe" as a mount tier dissolves; Fast (composefs) is the default mount, tracing is a policy |
Fork: BuildResult.read_set, FUSE recording (Gate 0) |
observation welded into build materialization | mis-homed: observation is execution-policy output; build records carry no read-set. FUSE is demoted from mechanism to instrument — removal from the fork is sequenced on the ptrace observer proving coverage at openssl scale (io_uring included, §6.3), not decreed by this model. This reverses an earlier design preference for FUSE-based observation (chosen for needing zero ptrace/seccomp machinery) on measured cost evidence — ratified by nrd 2026-07-07: ptrace is the runtime-closure capture instrument. |
htc-sad §2.3 BuildRecord.observed_read_set_digest |
read-set as a standing BuildRecord field | same defect class as the row above, at the doctrinal home (F9, second review — v0.6's sweep missed it): becomes optional/absent for untraced executions; populated only when policy.observe was on |
htc-sad §3.5 executor trait build(...) |
build-shaped | specializes execute(...); the trait generalizes, build becomes the Det instance |
| eos DAG | build nodes | nodes are requests; trials attach as non-morphism decorations; gating policies on the fact channel |
| Fork trajectory | castore + build daemon | once a native execution runtime exists, OCIBuildService is subsumed; fork shrinks toward castore — tightening the GPL seam |
7.3 What this model deliberately does not own¶
Added v0.8, after two corrections in one review cycle traced to the same root: reviewers (and the formalizer) importing obligations the system already discharges at sibling layers. The standing boundary, for every future reader and reviewer of this document:
- Acceptance is the trust layer's job. Which witnesses count, whose signatures suffice, how many record-equal executions upgrade confidence — §3.4 hooks, atom's signing machinery, never new invariants here. In particular: the record store is multi-valued and needs no canonical winner; do not re-import a centralized single-slot cache.
- Write concurrency is dissolved by topology, not solved by algebra. Lock writes are phased within one atom's lifecycle and land in per-atom files; environment-level intent lands in certificates. Do not demand merge operators for races the design never runs.
- Sharing relations live consumer-side.
requires-style edges express many-to-many naturally; do not patch provider-side back-pointers into sets. - Human-facing concurrency (two people editing one atom) is VCS territory, same as every lockfile ecosystem today — out of scope.
8. Proof obligations — and deliberate non-obligations¶
Worth proving (small, load-bearing, mechanical):
- P1 (re-homed v0.10) — owned by the
Composition Model §10:
⊕partial-monoid laws + exact denotational conflict detection. - P2 — justified-closure fixpoint: monotone, terminating, refinement- monotone (Lean; disciplines the closure computer — this is the algorithm the whole minimal-composition story rests on).
- P3 —
ExecuteSessionprogress/duality (mechanical; same as BuildSession's treatment). - P4 (re-scoped in v0.2) — a small state-machine specification of the relational semantics and cache confluence (§2.2): channels as nondeterministic inputs, the sandbox as a filter, residual nondeterminism as internal choice; show (a) under Det the admissible record set is invariant across channel valuations, and (b) first-witness caching preserves DAG coherence (downstream always builds against the witness that won). TLA⁺-able at modest scope. Valuable as the specification of the executor, not as mathematical news.
- P5 (added in v0.2) — request-serialization completeness (§2.1): every input the executor consults is reachable from the digest preimage; pin payloads included. An audit obligation with a checkable inventory more than a theorem.
- P6 (added in v0.6; re-scoped in v0.8 per nrd) — promotion
coherence laws (§3.5): left identity, idempotence, and
reorder-invariance of independent sequential promotions (running
two independent promotions in either order yields the same lock).
v0.6–v0.7 additionally demanded a concurrent lock-merge operator —
an overcorrection: the system's write topology is phased by design
(resolution pre-build; fetch pins once at trial end; runtime-dep
discovery post-build; repairs land as declared contract facts and
re-formed certificates, not package locks), so no two writers race
one file. Whole-file atomic
write stands. Testable directly against the lock tooling; the one
genuine residual spec gap is
[lock-dep-ordering]'s undefinedEither<AtomId, Name>sort order. - P7 (added in v0.7, from F3; extended v0.8) —
resolveis a function and total: determinism of the action→request map (layout, command, pin assembly, and witness selection over the fact snapshot per §2.2), materializer-version inclusion in the identity, totality gated on the toolchain-pin lock entry. §2.4's two-level cache coherence and cross-intent dedup are conditional on P7 until discharged. - P4 (extended v0.7; shrunk v0.8) — covers concurrent completion as set-accumulation: check that commutative witness insertion preserves the dispatch state machine's invariants. No reconciliation rule (v0.8 struck it); the eos amendments were wording-level and have landed (§5.1).
Deliberately NOT proof targets, with reasons:
- Bit-reproducibility — empirical by nature (§2.3); the artifact is an accumulating attestation, and pretending to prove it would re-conflate hermetic/reproducible.
- Completeness of
Dep(a)— undecidable (Rice); the design is structured around never needing it (certified interval + fail-closed refinement). - The scheduler — already proven; transfers unchanged.
9. Open questions for nrd¶
Naming— RATIFIED (nrd, 2026-07-07):action/trialfor the two strata;record/attestationfor their outputs. Canonical.- Clock/entropy posture. Accept the honest gap (Nix-parity, convention-pinned) or commit to enforcement (time-ns, seccomp on clock_gettime, deterministic urandom) as a differentiator? Cost is real; my lean: record the gap, defer enforcement, revisit when reproducibility attestations exist to measure whether it matters in practice.
- Trial acceptance policy. (Narrowed in v0.2: hermetic tests cache as actions — no policy needed there.) For genuine trials (networked tests): what makes an attestation "fresh enough" to skip re-running? (Time-bound, executor-set-bound, per-atom policy?) Needs a decision before the scheduler can gate on trial evidence.
- Executor binary placement (deliberately unconstrained by the model): extend the fork's daemon near-term vs. a fresh axios-native executor crate. The §7.2 trajectory (fork shrinks toward castore) suggests where it ends; the question is the path.
Trial output scope— resolved in v0.2 by the deposit/bind law (§5.2): trials deposit CAS values referenced by their attestations; binding into views happens only via promotion through signed intent.Observation-posture reversal— RATIFIED (nrd, 2026-07-07): ptrace is the runtime-closure capture instrument, superseding the earlier FUSE-observation preference on measured cost evidence (5.9× vs ~25%). The §7.2 sequencing rule stands: FUSE machinery is removed from the fork only after ptrace proves coverage at openssl scale.- Trust-anchor policy language (added in v0.2, from §3.4; scope updated 2026-07-12): fact-hood is signer-relative; the anchor set format and the acceptance policy for foreign records (reproducibility-attestation thresholds included) need their own spec, riding on atom's Coz identities. The ATOM-SIDE half is now specified: the declared reproducibility mode, its violation semantics, and the promotion/demotion law live in atom-model.md §6; the policy-language spec inherits them. The same work package owns split-view (equivocation) detection: per-consumer linearizability plus chain monotonicity cannot detect a backend serving different consumers permanently divergent timelines — detection needs gossip or witness-cosigning (atom-backend-contract.md, Open Questions).
- The ambient base (added in v0.3, from §1.4): an environment's
coherence certificate must state its residual
Req— the declared base it stands on (kernel ABI, dynamic loader, VDSO). What is the canonical ambient set, and who owns its definition? (This is the substrate's version of "what does glibc get to assume.") - Coherence choice function defaults (added in v0.3): "supported shared versions, user-selectable with sane defaults" — the default policy for choosing one provider per (ns, name) at environment formation needs an owner and a spec (prefer-same-atom, then lock order, is ion's existing determinism rule; does it lift unchanged?).
Environments as atoms— RATIFIED (nrd, 2026-07-12): yes — every composite stratum (package, environment, system) MAY publish as a signed atom; publication is uniform across strata (atom-model.md §3, §7). The system kind stands with it (composition model §4's boundary-declaration content). (Original framing, for the record: publishing an environment as a signed atom gives versioning/resolution over environments with zero new machinery, at the cost of making environments publishable intent.)- The override operator set (rewritten in v0.4 — v0.3 misfiled
this as a "generator gap"): overlays are not a missing language
feature; they are operations inside the algebra (Composition Model §7 —
subst, extend, prune, choice-function override), with blast radius computed by the interface layer instead of assumed total. What remains open is only the surface: the canonical operator set, whether overlays are first-classed as signed content-addressed objects, and the CLI verbs (eka) that apply them. Pure-build-flag variants ("everything with clang") still route through intent edits (action params) and enjoy only early-cutoff pruning, not interface pruning — worth saying honestly in any paper: the LEGO win is at the binding layer; intent-layer changes still rebuild what they touch, merely with a tighter bound than input-addressing gives. - Env vars as an interface namespace (added in v0.4, from the
generator audit): dev shells and some runtimes need
JAVA_HOME-style bindings the FHS view doesn't eliminate. Candidate answer with zero core changes:ns = "env"as a namespace plugin — an env-var requirement is an interface fact, satisfied at environment formation, values rendered at materialization. Decide when the dev-environment surface is designed. - Atom kind schema policy (added in v0.5, from §1.5; narrowed same
day): strata are closed and law-bearing; kinds are open and
schema-level. The mixed-atom question is answered in the model —
lawful, with the system atom as canonical case, governed by
pipelines-cross/steps-don't and the promotion door. What remains is
only schema: the kind discriminator's shape in the manifest
(
[package]/[environment]/[generator]tables? akindfield?) and whether hygiene wants mixed atoms discouraged despite their legality. (2026-07-12: the kind family's plane placement is now stated in atom-model.md §7 — granularity strata vs intent strata separated, generator atoms placed as transitions outside the composite genus; the schema shape remains open here.) - The lock format (added v0.6; re-assessed v0.8 after nrd's
whole-system corrections): the lock is binding-algebra instantiation
#1 and the promotion target (§3.3); its formal treatment is an
operational TOML spec, and some elaboration is owed — but less
than the second review claimed. Dissolved by the phased write
topology (P6 re-scope): the demands for a concurrent merge operator
and field-level merge granularity. Superseded by a better design
direction (nrd): rather than pluralizing the single-valued
ownerback-pointer on fetch entries, generalize consumer-siderequires-style edges to cover fetch entries — shared ownership becomes many-to-many for free, GC becomes reference-counting over edges,ownerbecomes derived or deleted. Still genuinely owed: theEither<AtomId, Name>sort-order definition; a tool-authored-entry liveness class (the reconcile/sanitization purge rules must not eat promoted fetch entries — pre-existing P4 flag); whether lock and certificate share one formal treatment (Composition Model §5's symmetry); and P7's resolution-determinism elaboration. - Constraint authoring for declared runtime dependencies (added
2026-07-12, from atom-model.md §2): when a closure fault names a
missing runtime dependency, the
Declaredrepair carries a version/interface constraint the fault cannot supply — a human or tool must author it. The system can assist (candidate proposal from interface satisfaction, prefer-same-atom ordering), but the assist is UX, not formalism. Directed at the ion UX design pass that precedes ion implementation (Phase 2); the declared dep is injected into the observation environment so tracing exercises it (atom-model.md §2).