Branch feat/entity-gen-schema-port, commits e71860b7..d3c74242 (the delivery-half
rewrite: nine ad-hoc delivery mechanisms collapsed into one (S, T, P, M) edge algebra).
This document enumerates every net runtime behavior change versus the pre-rewrite
baseline, with justification. Spec + plan + deviation ledger live in
den-architecture/{specs,plans}/2026-06-12-delivery-edge-unification-*.
This is a behavior-preserving refactor with exactly one intended runtime change and one additive API. The headline proof: the reference host's full system closure is byte-identical — same derivation hash — built against den at the pre-rewrite baseline and at HEAD. Not "flake-source-only delta"; the same store path. Every existing configuration evaluates to exactly what it did before, with one exception (a latent-bug fix that no shipping config was hitting).
- 1 intended behavior change: a latent cross-host config-resolution bug, fixed.
- 1 additive API: the
deliverprimitive (new surface; nothing removed). - 0 regressions, 0 semantic flips to any existing configuration.
Verification: den CI 974/974; the delivery-contract fixture suite 14/14 byte-stable; reference-host toplevel byte-identical across the entire port.
Status: intended fix of a latent bug. The only change in evaluated output for any input that exercises it.
When one host's configuration depended on another host's fully-evaluated config — the
cross-host config-thunk path, used by fleet pipe.collect / collectAll that feed a
peer's config — the peer host's config was built over raw scope contexts: the
context slice before pipe values were assembled into it.
If that peer's aspect tree contained a pipe-consuming module — one that destructures a
pipe/quirk value out of its context, e.g. { feat, ... }: { ... } where feat is a
value produced by the pipe layer — the peer config was built with that value missing,
and evaluation threw:
error: attribute 'feat' missing
This diverged from the peer's real instantiate output (which builds over assembled
contexts and resolves feat correctly). It was a genuine latent bug: no shipping
configuration combined "cross-host config-thunk" with "pipe-consuming peer aspect", so it
never fired in practice — but it was a real correctness gap, surfaced by the census that
opened this effort.
The cross-host peer-config build now runs over an assembled context (an
assemblePipes pass with cross-host config resolution disabled). Pipeline-parametric pipe
values are injected, so a pipe-consuming peer aspect resolves correctly and the peer
config builds instead of throwing.
The naive fix — "just build peer configs over the already-assembled contexts" — is a
dependency cycle: assembling contexts triggers cross-host config resolution, which is the
very thing being built. The fix breaks the cycle by running a context-assembly pass with
cross-host resolution turned off: that pass is cycle-free yet still resolves all
pipeline-parametric pipe values (the feat case), which is what the peer aspect needs.
A peer aspect that defers on a value which is genuinely config-dependent — i.e. its value can only come from another peer's fully-built config, the exact thing mid-resolution — stays deferred. That is an unbreakable mutual-config recursion, not something any single pass can resolve; it is documented in code and covered by an asymmetric-topology test that isolates the fixed bug from the genuine recursion.
A regression witness (bprime-basedrain-crosshost) was added: a defect case
(cross-host thunk reading a pipe-derived config field) plus a control. Reverting the fix
makes the defect case throw feat missing while the control still passes — confirming the
test has teeth and the fix is load-bearing.
Status: new public surface. Nothing removed; no existing behavior altered.
A user-facing delivery primitive was added: deliver { from; to; at ? []; mode ? "merge"; }
— the direct expression of one delivery edge. from is either a class name (collect that
class's content) or { module = …; } (inject new content); mode ∈ merge | nest | verbatim.
route and provides are now thin permanent shims over deliver. Their signatures
are unchanged and they desugar to identical delivery descriptors (proven by descriptor
equality + the edge-trace oracle), so every existing route/provides/policy.provide
call behaves exactly as before. The shims carry a # TODO marker for a future
deprecation warning, but no warning is emitted today — they may live on permanently as
sugar.
Surface notes:
mode = "verbatim"is explicit (replaces the oldreinstantiate = trueroute flag at the new surface; the route shim still acceptsreinstantiateand maps it to verbatim, so existing reinstantiating routes are unaffected).appendToParentandcollectSubtreeare deliberately not on thedeliversurface — they remain route-shim-internal mechanisms. Consequently the nix-config microvm-guest delivery (which needs both: parent-scope targeting + subtree-source widening) stays on therouteshim permanently — there is no migration. (An earlier spec draft claimed a guests-to-delivermigration; that claim was found incorrect during implementation and struck —deliver'stonames a class, not a scope, so it cannot express the parent-scope redirect the guest edge requires.)
Everything else. Concretely:
- The reference host's full system closure is byte-identical — same derivation hash — built against den at the pre-rewrite baseline vs. HEAD. This subsumes, by closure membership, that agenix identity paths, the microvm guest config (+ its passthrough gating), impermanence, home-manager delivery, and every other delivery-fed surface are unchanged.
route/provides/policy.providesemantics — identical (shims desugar to the same descriptors).reinstantiateandappendToParentroute behavior — identical (still reachable via the route shim).- Isolation semantics (isolated entities are their own delivery roots) — identical; now expressed as "no default-fold edge into the parent" (edge-absence) rather than a mid-walk filter, but the resulting content is the same.
- Pipe / quirk data-flow — out of scope and untouched (only an internal stage-interpreter de-duplication, behavior-preserving).
- The instantiation-root selection for entity outputs — identical results; the old
name-matching heuristic (
findHostScopeId, matching scopes by name infix) was replaced by an exact link recorded at scope creation, but it resolves to the same scope for every input, including the multi-system same-name case.
For completeness — these changed how delivery works, with no change to what it produces:
- Nine delivery mechanisms (default class-fold, simple route, verbatim route, complex
forward, provides, host-aspects spawn projection, instantiates, + two pipe flows that
stayed out of scope) → one
(S, T, P, M)edge algebra: a single mechanism-free mode switch, one constructor per mechanism, a toposort for ordering, and an explicit root-context projection replacing three hand-threaded phase-fold re-entries. - The
findHostScopeIdname-infix heuristic was dissolved into an exact scope-creation link. - Write-only pipeline state, four duplicated subtree-walk implementations, three hand-rolled dedup loops, and a provenance-forked stage interpreter were unified.
- A read-only edge-trace oracle was added that renders delivery decisions as a normalized edge list and shares the same constructors production uses — so the oracle and the runtime cannot drift. A 14-fixture suite pins this as the delivery contract.
Line count rose ~+550 deliberately: the algebra is now explicit, typed, and oracle-verified where the old mechanisms were implicit and untested; all the duplication was removed.
| Gate | Result |
|---|---|
| den CI (full battery) | 974/974 |
| delivery-contract fixtures | 14/14 byte-stable |
deliver API suite |
11/11 |
| reference-host toplevel (baseline vs HEAD) | byte-identical (same store hash) |
| B′ fix witness | has teeth (fails on revert, passes with fix) |
The single intended behavior change is a fix; everything else is provably unchanged.