Composition — the Second Primitive
Formal model: Composition — the Second Primitive
2026-07-07. Status: v0.1 — the composition primitive given its own
formal home, completing the substrate trichotomy (nrd's directive: the
three founding primitives — storage, composition, execution — should be
primitives in the formalism, not only in the narrative). §§1–5, 7–8
are re-homed from the Execution Model v0.9 (its
§0.5, §1.1 in part, §1.2, §1.4, §1.5, §5.3, §6.4), where they had
accumulated as request-formation machinery; that content passed two
zero-context adversarial reviews inside the execution model and is moved,
not re-litigated — cross-references remapped, substance unchanged. New
material: §0 (the trichotomy and the promotion cycle), §6 (the fact-set),
§9–§10 (boundary and obligations P8–P9). The execution model consumes
Composition as an abstract type whose semantics this document owns.
0. The substrate trichotomy¶
The substrate's founding analysis named three primitives. Each answers a different question, and each has its own formal model:
| Primitive | Question | Model |
| storage | identity — what content is | Storage Model |
| composition | structure — how content is arranged into a world | this document |
| execution | dynamics — what happens when a process runs in that world | Execution Model |
store : Bytes → Digest (static: identity)
compose : Intent × FactSet → Composition (static: structure; pure, §6)
execute : Request × World → Record (dynamic; policy-stratified)
The cycle, and the one arrow that closes it. The three primitives are not a stack but a loop:
compose ──▶ execute ──▶ record ──promotion──▶ fact-set ──▶ compose ...
Composition forms views; execution runs opaque commands in them and emits records; promotion (execution model §3.3, §3.5) — reified monadic bind, priced at a signature — is the unique arrow by which execution's outputs re-enter composition's inputs as signed intent or accumulated facts. Every dynamic behavior of the substrate (iterated builds, fetch-discovery, closure refinement) is a traversal of this cycle; "pipelines cross strata, steps do not" (§5) is the law that each traversal step lives in exactly one primitive.
The evaluator-exclusion law. The composition algebra is deliberately sub-Turing: elaboration is a terminating computation over declared data (P8), never general recursion. Anything Turing-complete is execution — a generator is not a composition-level program but an action whose outputs re-enter composition only through promotion. This is the structural guarantee behind ADR-0006 §3: the evaluator cannot regrow in the trusted core, because the only place a program can run is the executor, and the executor's results cross back only through signed intent.
Deliberate non-primitives. Two things are conspicuously absent from the trichotomy, both on purpose:
- Naming and trust (atom identity,
publish_czd, signatures, trust anchors) are the protocol plane above the substrate. The primitives answer "what is computed"; trust answers "who believes what" (execution model §3.4). The omission is a decision, not an oversight. - The fact-set is not a fourth primitive but the substrate's only state — the shared knowledge both static primitives read and execution writes. It is defined once, in §6, and both sibling models state their obligations against that definition.
1. The language-free purity thesis¶
Nix's purity is folklorically attributed to its purely functional language — and at the binding level the attribution is correct: the language's semantics is what guarantees that package composition combines purely (referential transparency of the generator ⇒ coherence of the generated graph). Removing the language therefore creates a real obligation, and this model is how it is discharged:
Purity moves from a property of the generator to a property of the artifact. Nix guarantees "this graph was produced by a pure function" — a provenance claim, which evaporates the moment an object crosses a trust boundary (a foreign drv must be re-evaluated or trusted). This model guarantees "this object satisfies checkable laws, whoever produced it" — digest identity for referential transparency (a composition root is its meaning; substitution of interface-equals is the β-rule, §4), the merge monoid for combination (§3), certificates for linking coherence (§4), signatures for authorship (execution model §3.4). Validity is carried by the object, which is the only kind of purity that survives decentralization.
In embedding terms: Nix embeds composition shallowly in a host language and inherits purity from the metalanguage; this model embeds it deeply — compositions are first-order data with algebraic laws — and trades the language's expressiveness for analyzability (you cannot ask a Nix closure why it contains a path; it is the residue of an arbitrary Turing-complete evaluation. You can ask a certificate). In Build Systems à la Carte terms: the model deliberately occupies the applicative fragment — dependency structure is static data, never the output of arbitrary computation — and the monadic escape (Nix's import-from- derivation) is deleted with the eval stage, its legitimate use-cases re-entering through exactly one audited door: trial → promotion → signed intent (execution model §3.3). Where Nix evaluation may diverge and resists analysis, environment formation is a terminating fixpoint (P8) over declared data.
The generating language thereby becomes optional, untrusted sugar: anything may emit compositions — a CLI, a script, someday a DSL — because soundness is enforced at the object level, not the generator level. The system is "implicitly functional": it has functional programming's combinatorial guarantees with no functional language in the trusted core.
1.1 Corollary: endpoint completeness¶
(Ratified, nrd 2026-07-08.) The purity thesis has a quantitative core, scattered until now across three documents and stated here once. Every execution edge connects two fully-determined points in content-addressed space:
- The intent endpoint is complete. An atom's snapshot pins manifest,
sources, and lock under one snapshot identity; lock exhaustiveness
(lock spec
[lock-sufficiency],[lock-groundness],[lock-closure-completeness]) guarantees no worldly discovery remains at build time, and[lock-action-totality]makes the passage from intent to action identity a total function: the lock determines, by pure elaboration, all three inputs ofaction_id = H(atom_czd_closure_root, toolchain_composition_root, params)(execution model §2.4), discharging P7's totality gate. Pinned intent determines action identity; nothing else enters. - The artifact endpoint is complete. Canonical-serialization injectivity (storage model P10, axioms A1–A3) makes a composition root determine its denotation up to byte identity: the root is the environment (§2).
The two endpoints live in different content-addressed spaces — coz- signed atom identity on the intent side, the blake3 artifact store on the other; the argument needs completeness of each space, not a shared namespace. The two stores share the content-addressing idea, never an identifier type (storage model §5).
Purity is therefore formal, not folkloric: what was asked and what
was produced are both unambiguous mathematical points, so substitution
(§4), caching, and blast-radius arguments reduce to equational reasoning
over digests. What this corollary deliberately does not claim: that
the arrow between the endpoints is a function. execute remains a
witnessed relation (execution model §2–§3) — records accumulate,
hermetic ≠ reproducible, determinism is attested per witness, never
assumed. The purity lives in the endpoints, not the arrow; strengthening
this to "source root ↦ output root is a pure function" would silently
re-import the reproducibility assumption this substrate refuses. The
arrow carries its own faithfulness obligations — request-preimage
completeness (P5) and resolve determinism (P7) guarantee the concrete
request the executor sees carries exactly the pinned intent — but those
are properties of the arrow's formation, not of the endpoints.
Dependency list (the tripwire). Endpoint legs: [lock-sufficiency],
[lock-groundness], [lock-closure-completeness],
[lock-action-totality] (intent side); P10 (artifact side). Arrow
faithfulness: P5, P7. If any of these weakens, this corollary names
exactly what breaks with it.
2. The composition domain¶
All values are content-addressed; identity is digest (storage model A2). Blobs and Merkle trees are the storage primitive's values; composition adds one value of its own:
Composition
c— a finite map from conventional paths to content entries, signed. Its denotation is a partial function:⟦c⟧ : Path ⇀ Content (Content = blob | tree-node | symlink)A composition is a value that denotes an environment. This is the precise sense in which the system is "purely functional": a process under this model runs against an environment that is itself a first-class, content-addressed, signed value — not against an ambient mutable world.
The denotation includes directory enumeration order (castore trees are name-sorted, storage model A3; executors MUST present that order). Without this, two conforming executors backed by different mount technologies could return different
readdirorders for one view, and a build sensitive to enumeration order would break action-stratum bisimulation (execution model §6.1).
3. Composition merge is a partial commutative monoid¶
Define c₁ ⊕ c₂ (merge) as the union of the two maps, defined only when
they agree on the intersection of their domains (byte-identical entries).
Disagreement is a conflict — merge is undefined, surfaced as an explicit
error at compose time (never resolved silently — the collision point is
exactly where ABI reality lives).
Laws (where defined):
c ⊕ ∅ = c (identity: the empty composition)
c₁ ⊕ c₂ = c₂ ⊕ c₁ (commutative)
(c₁ ⊕ c₂) ⊕ c₃ = c₁ ⊕ (c₂ ⊕ c₃) (associative)
c ⊕ c = c (idempotent)
(Comp, ⊕, ∅) is a partial commutative idempotent monoid. This tiny
structure carries real weight: it is what makes "compose the toolchain with
the dep trees with the fetch blobs" order-independent and deterministic, and
it is the merge over which independent subgraphs compose in the action DAG
(execution model §5.1).
Conflict is defined over denotations, not keys (correction F5, second
adversarial review): entries include subtree grafts (Dir{tree}), so
{/usr ↦ Dir t} and {/usr/lib/x ↦ File b} have disjoint keys but
overlapping denotations — key-level conflict detection would either miss
the collision or silently shadow. Two compositions conflict iff their
denotations disagree on any path in the intersection of their
denotation domains (equivalently: mandate a prefix-free normal form and
check keys there). Proof obligation P1 (cheap, Alloy-able): the concrete
merge implementation satisfies the laws AND detects exactly the
denotational conflicts — the graft case is in P1's scope explicitly.
4. Interfaces: the typing of compositions¶
The merge monoid (§3) says when compositions can coexist (path disjointness). It says nothing about whether the result works. That is the job of the interface layer — and it is the seam the whole substitution story hangs on, so it enters the formal core rather than remaining prose.
The judgment. Interface manifests (htc-sad §2.2) assign each tree its
provides/requires per namespace, with iface_digest = H(canonical interface description). For a composition c, lift pointwise:
Prov(c) = ⋃ provides of member trees
Req(c) = ⋃ requires of member trees NOT satisfied within c
(the composition's free variables — its unbound obligations)
A binding is a justified edge required ↦ provider with
satisfies(needs, provides) holding (htc-sad §6.1's relation). The
iface_digest is the substrate's chosen identity proxy for "same
contract" — normalized, hashed, unforgeable, decidable by
construction where semantic ABI equality is not. (Honesty is inherited
from htc-sad §6.2's precision statement: symbol-level satisfaction is
necessary, not sufficient; strict mode remains the guarantee floor. The
proxy is load-bearing precisely because it is a proxy we can check.)
This is a module system. Compositions are modules; interface
manifests are their signatures; binding is linking. Substitution
soundness — the LEGO thesis, "swap the OpenSSL blob, nothing rebuilds" —
is exactly signature-preserving relinking, and iface_digest is what
makes "signature-preserving" checkable. In this precise sense the
runtime is more than an optimized runtime: the executor and materializer
are the interpreters of the value algebra, and the interface layer is its
type discipline — the support contract under every claim the model
makes about pure computation.
The granularity strata (explicit, where the derivation blurred them):
package an atom's output tree + its interface manifest
(typed but not linked: Req ≠ ∅ is normal and healthy)
environment a composition equipped with a COHERENCE CERTIFICATE:
every internal require bound, at most one provider chosen
per (ns, name) per scope (no diamonds), the choice function
recorded (defaults ∪ user overrides), and the residual
Req(c) — the declared ambient base (kernel ABI, loader) —
stated, not silent. An environment is a LINKED module.
Environments are recursive, and the third stratum is a declaration
of boundaries. (Amended twice, nrd 2026-07-08: the original "system =
a composition of environments" was vacuous once scopes nest and was
briefly dissolved; it is reinstated with sharper content.) The
containment law: packages contain only packages; environments compose
environments and packages. A system is then not a bigger
environment but a different artifact: where an environment's
certificate proves the internal coherence of ONE scope, a system
declares a family of scopes — the namespace partitioned into
disjoint domains, each pinned to its own composition root (the base
environment at /usr and /lib; a config layer at /etc, swappable
without touching the base; a user layer at /home/<user>; quirk scopes
for individually-patched programs) — and its certificate proves
boundary coherence: every layer's residual requires discharged by a
sibling layer or the declared ambient base. Domains are disjoint by
construction, so layers compose by ordinary ⊕ — no ordered shadowing,
no OCI-style "later wins" — and each layer swaps transactionally on its
own cadence. Under the flat-is-normal norm, the system kind is exactly
the declarative record of the scope boundaries that earned their
existence, and why. Per-composition scoping (ADR-0005's store-path role decomposition:
conflict-free co-installation) is what permits two nested environments
to bind DIFFERENT versions of one provider: ⊕'s conflict rule forces
explicit prefix/namespace separation, so co-installation is a theorem of
the merge monoid, not a convention.
Reconciliation: merging environments is re-formation. Composing two
certified environments into ONE scope must produce a single choice per
shared (ns, name): reconciliation finds a provider c* with
satisfies(needs, provides(c*)) for every consumer across BOTH
environments, applies subst[name ↦ c*] to whichever side pinned
differently, and re-runs the formation fixpoint — so the merged
certificate may legitimately pin different members than either
constituent did. Reconciliation is resolution-shaped — candidates from
the pinned fact snapshot, interface satisfaction as the constraint
relation, version order used only as preference — and deterministic in
(intent, fact-set snapshot, choice policy) like all formation (P8).
Costs are the algebra's usual: surviving consumers rebind free;
failures form the bounded frontier; and when NO shared pick exists, the
lawful alternatives are exactly two — edit intent (bump or backtrack),
or refuse to merge the scope and declare the boundary instead (a
system layer, above). Formation is the degenerate case: forming an
environment from n packages is reconciling n trivial environments —
one operation, not two.
Flat is the normal form; a scope boundary must earn its existence.
The default composition is a single scope: everything merged by ⊕, one
provider chosen per (ns, name), coherence certified across the whole.
Scope boundaries — layers — are introduced only when meaning demands
them: a genuine conflict (one member requiring a divergent provider —
the co-installation escape hatch above), or a genuine intent boundary
(an OS scope vs. a project dev shell whose choice function is a
recorded delta over its parent's, inheriting every choice it does not
override; vs. a single package's quirk view inside that). Because a
scope is itself just a composition with its own choice function, a
layer is expressible inside the algebra — never an external
packaging artifact — which is the precise difference from OCI layering:
there a layer is an accident of build-script ordering; here it is a
certified linking boundary with a reason. Pragmatics align with the
semantics: every scope boundary buys indirection overhead, so the
preference for flatness is a performance norm and a coherence norm at
once.
Environment formation IS resolution. The coherence certificate is the recorded output of a fixpoint + choice function over interface bindings — ion's version-resolution algebra, one layer down. Bindings all the way down, again: this is the third instantiation (lock, composition, and now environment linkage) of the same name→signed-pointer discipline.
Repair happens at the package contract, not the environment. The
environment is the unit of linking and choice — assembling packages,
selecting providers — never a dumping ground for fixes. A closure fault
(execution model §4.4) — a missing runtime dep that every detector
missed — is evidence about the package: its requires-set was
incomplete. The durable repair is therefore a declared require in the
package's interface contract (execution model §4.2's Declared kind —
an author- or third-party-asserted fact, keyed by declarer exactly as
analyzer facts are keyed by analyzer, so it works even for atoms the
repairer does not own). Every environment containing the package then
picks up the binding automatically at re-formation: new composition
root, zero rebuilds, the package's content untouched. Patching a
single environment instead would strand the repair in one place while
every other environment with the same package stays broken — and the
formalism agrees: the evidence sets of the justified closure
(R_declared(a), execution model §4.3) are indexed by the artifact,
not by any environment.
Versioning. An environment's identity is its composition root; its contract is its interface (Prov/residual Req and their digests). A semantic version is neither — it is human-facing naming, and it applies exactly when an environment is published as an atom (signed intent whose content is the composition), at which point ion's existing version machinery covers it with nothing new invented. Whether environments routinely publish as atoms is an open question (execution model §9.10), but the model makes it a choice, not a gap. Composition of environments into an OCI image (one layer per environment, each with its certificate) is an Export-tier mapping that gives layering meaning — the layer boundary is a certified linking boundary, not an accident of Dockerfile ordering.
5. The two strata of intent¶
Atoms are the unit of publishable, versioned, signed intent. The question "should package-atoms and environment/generator-atoms be distinct types?" cuts at the right joint only when asked about laws, not schemas — and there the answer is yes, there are exactly two strata of intent, mirroring the action/trial stratification of the execution primitive:
executable intent elaboration REQUIRES execution: a package atom
denotes actions; realizing it crosses into the
execution model's world (sandbox, cache, records,
signer trust — execution model §2, §3.4).
Verifiable only by building.
algebraic intent elaboration is pure computation in the value
algebra — precisely: PURE IN (intent, fact-set)
(correction F6, second adversarial review: v0.6's
"from the intent alone" was an overclaim, since
interface manifests are analyzer OUTPUTS — i.e.
executions — so even base-case formation is
fact-conditioned, and version resolution pins a
worldly discovery snapshot just as the lock does).
An environment atom denotes a composition +
certificate via the formation fixpoint (P8); a
generator atom denotes a published, signed,
versioned choice-function delta — a generator in
intension, acting on elaborations generally:
package resolution and environment formation alike
(§7). Given the pinned fact snapshot,
elaboration needs no executor, no sandbox, no cache,
and is SELF-VERIFYING BY RECOMPUTATION: a registry
can check an environment's coherence RELATIVE TO ITS
CLAIMED MANIFESTS at publish time (the manifests'
own truth rides signer trust or re-analysis).
No registry can refuse a package that won't build
without building it.
Why this helps rather than decorates: several results already proven quietly depend on "the composition side never touches the execution layer" — repair-without-rebuild (§4), the rebind stratum of overrides (§7), install-without-execute (§8), transactional update. The strata give that recurring precondition a name and a single home. They also settle the earlier versioning question with symmetry rather than fiat:
package atom manifest ──resolution──▶ lock (pinned intent)
environment atom manifest ──formation───▶ certificate (pinned intent)
The certificate is the environment's lockfile — the same intent→pinned-elaboration shape, fourth instantiation of the binding algebra. Both strata version and resolve through ion's existing machinery; environment members ARE references (set/label/version constraints) — what environments lack is not dependency declaration but build-input semantics (no toolchain, no fetch set, no build/runtime dep split).
Kinds are open; strata are closed. Package, environment, generator — and future kinds (test intent is already a manifest section per the execution model §5.2) — form an open, schema-level family. Each kind belongs to exactly one stratum, and the stratum determines its laws (execution-realized and trust-mediated, vs. purely elaborated and recomputation-verified). A mixed atom (one publishing intent in both strata) breaks no law; whether the schema permits or discourages it is a convention call (execution model §9.13).
Pipelines cross strata; steps do not. The strata classify elaboration steps; an atom denotes an elaboration pipeline, and pipelines may interleave strata freely — a system atom is the canonical case: members composed (algebraic) → config intent rendered by actions (execution) → rendered blobs bound (algebraic) → certificate. Boot and running services are executions of the finished artifact at use time, outside elaboration entirely (the atom ships the activation program as content). The prohibited object is a mixed step — a computation partly pure, partly world: Nix's import-from-derivation is precisely that violation, and promotion (execution model §3.3) is its lawful replacement. Cross-strata data flows only through CAS values, records, and promotion.
Fact-conditioned formation stays algebraic. Formation may consume
execution records and attestations as inputs ("admit P only if its
tests pass", "members must carry k reproducibility attestations") and
remain in the algebraic stratum, provided the certificate pins the fact
snapshot consumed: formation is pure in (intent, fact-set),
recomputable by anyone holding both. Publication gating (execution model
§3.2) is the degenerate case; staged rollouts and policy-gated
environments are the general one.
6. The fact-set: the substrate's only state¶
Everything in the substrate is an immutable value except one thing: what
is known. The fact-set is that knowledge — the accumulating store
of signed execution records and attestations (execution model §3.1),
indexed by action_id and req_digest — and it is deliberately not a
fourth primitive but the shared state at the seam: execution writes
it, composition reads it, and promotion is the only arrow that turns its
contents back into intent.
FactSet = finite set of signed records/attestations
(indexed: action_id ↦ Set<record>, req_digest ↦ Set<record>)
Laws:
- Monotone accumulation. The fact-set only grows; insertion is commutative and idempotent (set-union shape). This is the witness-accumulation semantics of the execution model §2.2 stated as the state's own law: several distinct output digests for one action from several signers is a legitimate state, and no reconciliation, tie-break, or canonical winner exists at the state level.
- Read-side selection is a recorded choice. Any consumer that acts
on the fact-set (cache lookup, witness pick at request formation,
fact-conditioned formation §5) records which snapshot and which
choice it consumed. Determinism of every algebraic elaboration is
relative to
(intent, fact-set snapshot, choice policy)— never to a global state the system nowhere maintains. - Snapshot pinning. A certificate (or lock) that consumed facts pins
the snapshot: formation is pure in
(intent, fact-set)and recomputable by anyone holding both (§5). The pin is what keeps fact-conditioned formation inside the algebraic stratum. - Fact-hood is signer-relative at read time. Which records count is the trust layer's judgment (execution model §3.4), applied on read; the state itself stores evidence from any signer.
The concrete realization is atom metadata (appended records, htc-sad §6.10) — decentralized, per-atom, no global store. That the substrate's only state is an append-only, commutative, signer-attributed set is what makes the whole design tractable under decentralization: replicas converge by union, and nothing anyone writes can invalidate what another party already consumed (they pinned their snapshot).
7. The override algebra: generators live inside the model¶
What Nix does with an overlay — an untyped function over the package universe ("give everything Python 3.4") — this model does with a typed edit over the binding graph, and the difference is the model's central economic claim. Define the substitution operator:
subst[n ↦ p′] : Comp → Comp -- rebind every binding of name n to p′
Its validity is checked, per consumer: for each require previously bound
to the old provider, satisfies(needs, provides(p′)) must hold (§4;
under Strict policy only digest-equality passes; under Compat, an
interface-satisfying swap passes with a recorded proof — the
SubstitutionRecord htc-sad §2.1/§6.2 already anticipated). This one
operator splits every override into its two true strata:
- Rebinding (runtime stratum, cheap). Consumers whose interface contracts survive the swap are relinked: composition edit, recertify, re-sign. Zero rebuilds. This is the security-patch case — the Guix-grafts use case with proofs instead of binary patching.
- Rebuild frontier (intent stratum, bounded). Consumers where
satisfaction fails form the frontier
F— they genuinely need their intent edited (build against the new provider ⇒ newaction_ids), or they stay on the old provider in their own scope (co-installation, §4, is the lawful answer to the diamond). Downstream ofF, early cutoff (execution model §2.4) prunes every descendant whose rebuilt output is digest-identical.
Theorem (bounded blast radius — relative to the interface proxy). An
override's rebuild set is exactly F plus its non-cutoff descendants,
where F is computed against iface_digest satisfaction — and per
htc-sad §6.2's precision statement that proxy is necessary-not-sufficient,
so real breakage escaping it (same-symbol struct-layout changes) makes
F an under-approximation; strict mode remains the guarantee floor
(correction F10, second adversarial review). Contrast the system
being replaced: Nix rebuilds the entire reverse-dependency closure of
the overridden node, unconditionally — input-addressed identity with no
interface layer means it cannot certify that anything survived. The
interface certificate is precisely the instrument that turns "rebuild the
world below the change" into "rebuild what the change (checkably) broke."
Ratified posture (nrd, 2026-07-08): Compat is the default constraint
strength for patch-level provider swaps. Evidence: the interface-proxy
operating-characteristics study (tools/iface-proxy-study/ — Debian
libssl3 3.0.15→3.0.16 against a 100-consumer sample: 97/97 clearable
under both symbol-level and ABI-level analysis with an empty
disagreement set, both classifiers negative-controlled; 0/8 false clears
on crypto-exercising execution tests). Scope is deliberate: the evidence
covers patch-level updates of an ABI-disciplined provider; swaps beyond
patch level default to strict until their gradient is measured, and
per-edge strictness declarations override the default in either
direction. ABI-level analysis (the abicompat/abidiff class) is the
evidence grade the study exercised; whether symbol-level alone may ever
authorize Compat clearance remains open — the study found no divergence
between the two, so the floor question did not force itself.
An overlay is then a reusable, named sequence of algebra operations
(subst, ⊕-extend, prune, choice-function override) — data, so it can
itself be content-addressed and signed like everything else. No language
in the trusted core; the expressive power Nix gets from functions, this
model gets from operators whose blast radius the type layer computes.
Overrides are choice-function deltas — generators in intension.
(Ratified, nrd 2026-07-08.) An override declaration denotes a delta
Δ on the choice function: elaboration consumes c = defaults ∪ Δ —
the recorded slot §4 always carried. Since elaboration is pure in
(intent, snapshot, choice policy) (P8), applying Δ IS a
transformation of the result: a generator, presented intensionally
(a rule perturbing choice) rather than extensionally (an edit set) —
and the generator normal form below is the compiler between the two
presentations. Generators therefore act on elaborations generally,
package resolution and environment formation alike, not on environments
only. Two keying classes, distinguished by quantifier:
- target-keyed (pointwise): finite-support deltas that name their choice points — necessarily manual.
- role-keyed (uniform): deltas quantified over a declared role
(∀ atoms declaring role
cc: preferp) — automatic within bounds: each atom's own declared constraints bound the unification, and failures take reconciliation's lawful exits (above). A forced delta binds outside the bounds — aDeclared-class human assertion (execution model §4.2): explicit, signed, marked in the certificate as an unproven binding; never silent, never a default, lintable as impermanent.
Law (delta composition). Deltas compose by union, defined only
where they agree on the intersection of their domains: two deltas
assigning different providers to one choice point conflict explicitly,
surfaced at compose time, never resolved by ordering. Choice deltas
thus form a partial commutative idempotent monoid — the merge
monoid's shape (§3) recurring one level up: ⊕ for content, delta
union for choices. Override application is order-independent by
construction; the overlay-ordering folklore of function-based override
systems is structurally unexpressible here. Proof obligation P11
(§10): the delta monoid laws and compilation soundness.
Property (generator normal form). Environments are freely generated
from packages by ⊕ and binding; therefore every generator's effect —
whatever its surface abstraction — has a normal form as a package-level
edit set: (remove R, add A, rebind B, re-certify). A Nix overlay is an
opaque function whose effect is learned by evaluating the world twice and
diffing; a generator here is statically diffable as a certificate
delta before it is ever applied. The package is the primitive of the
generator/environment relation; environments are states; generators are
transitions compiled to the operator algebra.
8. Materialization, installation, and transactional update¶
The runtime has a second operation beside execute — the interpreter of
the value layer itself:
materialize : CompositionRoot → View
Obligation: the view presents exactly ⟦c⟧ (§2, enumeration order
included), tamper-evident where the mechanism allows (composefs +
fs-verity: the kernel refuses corrupted content at read time — a
guarantee Nix does not have; its store verifies at substitution and
executes tampered bytes happily). Materialization is the seam arrow from
the static primitives into execution's world: the executor's conformance
obligation to present exactly ⟦view⟧ (execution model §6.3) is this
obligation, restated from the consuming side.
Installation is the dogfood test, and the model passes it with zero new concepts. Installing the system (bootstrap binary included) is:
1. obtain the signed composition → trust anchors verify the
SIGNER (execution model §3.4);
nothing else is trusted
2. fetch blobs by digest, any channel → the substitution principle
(storage model §3): the
transport is untrusted BY
CONSTRUCTION — mirrors, CDNs,
peers are all equally fine
3. materialize, persistently → the composefs layer
Note what installation is not: it is not an execute — no sandbox, no
policy stratum, no record. Fetching during install needs no trial
machinery because nothing here has identity to corrupt: a blob either
matches its digest or is rejected. (This sharpens the deposit/bind law,
execution model §5.2: fetch-by-digest is substitution into an existing
binding, the third and most trivial way content moves — deposit needs
promotion to bind; substitution needs nothing because the binding,
signed, already exists.) The bootstrap binary itself ships as a tree
pinned by a composition: the system installs itself with the two
static primitives alone — storage and composition; no execution
occurs — and the only bootstrap-specific artifact is the initial
trust-anchor set.
Update is a root swap; transactionality is structural. A new system
state is a new composition root; switching a mount (or a symlink to a
mounted image) is atomic. The old root remains valid — rollback is
keeping it; GC is dropping unreferenced blobs (storage model §4).
"Combining and cheaply updating pieces of a layer, transactionally" is
then: recompose (edit bindings, ⊕ in a new environment), re-certify
(§4), re-sign, swap. No package rebuilds anywhere in the loop unless
intent changed — the separation of the action DAG (rebuild world) from
the composition algebra (rebind world) is the entire point of the
trichotomy, exercised end to end by the system's own installation.
9. What this model deliberately does not own¶
The same boundary discipline as the execution model's §7.3, from the composition side:
- Execution semantics — policy strata, records, witness accumulation,
the sandbox conformance table: the execution model. This document
consumes
RecordandFactSetentries as opaque signed values. - Acceptance and trust — which signers count, how many record-equal attestations upgrade confidence: the trust layer (execution model §3.4), never new invariants here.
- Naming and version resolution — atom identity and ion's resolution algebra sit above; §4's "environment formation IS resolution" borrows the algebra's shape, not its ownership.
- The intent schema — the manifest/lock redesign (ADR-0006's flagged
open surface: action params, test params, the kind discriminator,
requires-edge generalization, adopted-lockfile entries) is design work
this model grounds but does not perform.
Intenthere is the abstract input type ofcompose; its concrete schema is that redesign's deliverable. - Composition-side open questions remain queued in the execution model §9 (its items 8–13: ambient base, choice-function defaults, environments-as-atoms, override surface, env-var namespace, kind schema) — one queue, deliberately not split across documents.
10. Proof obligations¶
Owned here (continuing the substrate-wide P-numbering; P1–P7 are homed in the execution model, P10 in the storage model):
P1 (re-homed from the execution model) —
⊕partial-monoid laws- exact denotational conflict detection, the graft case in scope (Alloy; disciplines the composer implementation).
P8 — the formation fixpoint: environment formation terminates (Knaster–Tarski over the finite candidate set — the same mathematics as P2's justified closure, applied to declared bindings rather than observed evidence), is deterministic in
(intent, fact-set snapshot, choice policy), and its certificate is recomputable: any holder of intent + snapshot re-derives a byte-identical certificate (self-verification by recomputation, §5). Reconciliation (§4) is in P8's scope: merging certified environments is re-formation over the union of constraints and owes the same determinism.P9 — override soundness:
subst[n ↦ p′]preserves certificate coherence for every consumer whose satisfaction survives the swap; the rebuild frontierFis exactly the satisfaction-failure set relative to theiface_digestproxy; recertification after rebind yields a valid certificate (§7). Testable against the composer implementation.P11 — the delta monoid and its compiler: choice-delta union satisfies the partial-monoid laws with exact same-point conflict detection (the §3 mirror at the choice level), and normal-form compilation is sound: elaborating under
defaults ∪ Δyields the same state as applying Δ's compiled edit set(remove, add, rebind, re-certify)to the default elaboration (§7).
Seam obligations shared with the execution model, homed there: P6
(promotion coherence — the arrow of §0's cycle), P7 (resolve
determinism and totality — the arrow from intent into requests).