Publishing Stack Layer Boundaries
Formal model: Publishing Stack Layer Boundaries
Domain Classification¶
Problem Statement: The decentralized publishing stack is organized as three independent workspaces (atom, eos, ion) communicating through trait interfaces. Before implementation, the trait boundaries need formal validation to confirm: (1) the interfaces are behaviorally correct — implementations can be swapped without downstream breakage; (2) the protocols between layers enforce correct operation ordering; (3) the layered composition preserves properties end-to-end.
Domain Characteristics:
- Coalgebraic observation — traits define observers over abstract state (AtomSource, BuildEngine), not constructors. Identity is behavioral, not structural.
- Functorial resource flow — atoms transfer between stores with different capabilities while preserving identity (atom-id stable through ingest).
- Protocol sequencing — operations must occur in defined order: claim before publish, resolve before plan before apply.
- Layer discipline — strict downward dependency (ion → eos → atom).
Model Scope — what is and is not captured:
This model operates at the logical trait boundary level. It captures the behavioral contracts between workspaces as if callers invoke trait methods directly. This is sufficient for validating interface design, protocol ordering, and implementation interchangeability.
| In Scope | Out of Scope |
| Trait behavioral contracts (coalgebras) | Transport mechanism (IPC, network, gRPC) |
| Protocol ordering (session types) | Serialization / wire format |
| Store relationships (ingest homomorphism) | Authentication / authorization |
| Concurrency model (parallel builds) | Git object internals (atom-git impl) |
| Error recovery asymmetry (plan vs apply) | Nix/snix evaluation internals (removed with the evaluator, ADR-0006) |
| Static ontology (olog) | Dependency resolution (SAT solver, locks) |
| Scheduling correctness (bisimulation) | Manifest format (atom.toml parsing) |
| Network topology (distributed eos, peers) |
The model's coalgebras treat trait invocations as abstract observations regardless of whether the call is local, crosses a process boundary via IPC, or traverses a network. Bisimulation invariance guarantees this abstraction is safe: if two implementations produce the same observations, they are interchangeable — whether the observation arrives via a function return or a deserialized RPC response. Transport, serialization, and authentication are orthogonal concerns that layer on top of the trait boundaries modeled here, not within them.
Formalism Selection¶
| Aspect | Detail |
| Primary Formalism | Coalgebra (behavior endofunctors, bisimulation) |
| Supporting Tools | Session types (protocol ordering), Olog (domain ontology) |
| Decision Matrix Row | State + observation → Coalgebra; Protocol → Session types |
| Rationale | Traits-as-observers is the dominant pattern; bisimulation is the |
| ... | correct equivalence for implementation interchangeability. |
| ... | Session types capture the protocol ordering that coalgebra |
| ... | alone cannot express. Olog grounds the static ontology. |
Alternatives Considered:
- Pure functorial migration: Too narrow — trait boundaries involve behavioral contracts, not just data restructuring.
- Linear logic as primary: Atoms are freely duplicable and discoverable; no linear resource discipline on data flow.
- Hyperdoctrine: No quantified predicates over type families needed — associated types are simple type-level programming.
Formalism Provenance:
The formalisms above were selected by applying the Decision Matrix to the domain (hidden state → coalgebra, sequenced protocols → session types, static ontology → olog) independently of the trait signatures in ADR-0001. The ADR traits and the coalgebras converge because Rust traits-as-interfaces and coalgebras-as-observers are the same concept in code and mathematics: both define observations on opaque state. This is structural correspondence, not a circular derivation.
Operations with command-like character (apply, store, ingest)
are modeled as observations because the model captures inter-layer
boundaries — what the caller sees, not what happens internally. For
internal eos verification (cache coherence, store invariants), a future
refinement to state-transformer coalgebras (c: X → F(X) × X')
may be warranted. The boundary coalgebras embed into state-transformer
coalgebras via projection, so the current model remains valid.
Model¶
1. Olog — Domain Ontology¶
Objects:
| Object | Description | Layer |
| Atom | Fundamental unit of publishing | L1 |
| Atom-id | Protocol-level identity pair: (anchor, label) | L1 |
| Label | Human-readable name | L1 |
| Anchor | Cryptographic commitment establishing atom-set identity | L1 |
| Owner | Opaque identity digest (e.g., Coz tmb, Cyphr PR) | L1 |
| Atom-set | Collection of atoms sharing a common anchor | L1 |
| Version | Abstract version (via VersionScheme — ecosystem-agnostic) | L1 |
| Revision | A specific commit in source history | L1 |
| Manifest | Metadata: label + version (minimal trait surface) | L1 |
| Plan | Engine-specific build recipe (associated type) | L2 |
| Output | Engine-specific build result (associated type) | L2 |
| Artifact | Content-addressed blob in artifact store | L2 |
| Digest | Content-addressed hash | L2 |
Substrate objects (ADR-0005 / htc-sad §1.5, §2 — extension): the
layer tags below use the substrate's own designation (HTC, ADR-0005
§9) rather than this table's pre-existing L1/L2 numbers, since
realigning those numbers stack-wide is explicit follow-up work outside
this ADR's (and this node's) file scope — see htc-sad §1.5's "five
nouns, one function."
| Object | Description | Layer |
| Composition | Signed name→digest binding — the closure object (htc-sad §2.1) | HTC |
| Interface Manifest | Derived, static (provides/requires) fact about one tree; binding-free by construction (htc-sad §2.2) | HTC |
| Action | One invocation of build, identified by action_id; for the primary executor, Plan binds to Action (htc-sad §1.5, §6.5) |
HTC |
| View | A composition mounted at runtime — not persisted (htc-sad §1.5) | HTC |
| Build Record | SLSA-shaped provenance for one action (htc-sad §2.3) | HTC |
Key morphisms:
has_id: Atom → Atom-id (each atom has exactly one id)
has_label: Atom → Label (each atom has exactly one label)
belongs_to: Atom → Atom-set (each atom belongs to one set)
identified_by: Atom-set → Anchor (each set has exactly one anchor)
computed_from: Atom-id → (Anchor × Label)
claimed_by: Atom → Owner (ownership via signed claim)
pins: Revision → Version (each revision pins one version)
described_by: Atom → Manifest (at a given version)
derives: Revision → Plan (given a build engine)
produces: Plan → Set(Output) (a plan produces outputs)
stores_as: Output → Artifact (each output stored as artifact)
addressed_by: Artifact → Digest (content-addressed)
Substrate morphisms (extension):
binds: Composition → (Name × Digest) -- per entry: a bound name's
-- content digest (htc-sad
-- §2.1's Composition.entries)
describes: InterfaceManifest → Tree -- the subject tree these
-- provides/requires facts
-- are about (htc-sad §2.2)
satisfies: Tree × InterfaceManifest → Bool -- a tree's provided facts
-- satisfy a manifest's
-- required facts, witnessed
-- by a proof recorded in
-- Composition.provenance
identifies: Action → Digest -- action_id (htc-sad §6.5)
records: BuildRecord → Action -- one BuildRecord per action
mounts: View → Composition -- a view materializes one
-- composition at runtime
Manifests are binding-free, by construction [htc-manifest-binding-free]
(ADR-0005 §4): no morphism out of Interface Manifest names a foreign
artifact hash. describes gives only the subject tree; provides and
requires name (ns, name, iface_digest) / (ns, name, needs) pairs —
never a specific candidate composition or tree. satisfies is the only
place a tree and a manifest meet, and that meeting is witnessed by a
proof, not a hardcoded reference. This is an extension to the olog (new
objects/morphisms); it does not alter or re-derive any of the
commutative diagrams below, which predate and are independent of the
substrate objects.
Commutative diagrams:
Cryptographic chain:
stores_as ∘ produces ∘ derivesfrom a revision yields an artifact whose digest is deterministic. Same revision → same plan → same output → same digest. Formal statement of reproducibility.Identity stability:
computed_from ∘ (identified_by ∘ belongs_to, has_label) = has_idcommutes. Atom-id is computed from (anchor, label), neither of which changes across versions. Formal statement that publishing new versions does not alter atom identity.Ownership independence:
claimed_byis independent ofhas_id. Ownership can change (claim revocation + reclaim) without altering the atom-id. Formal statement that identity and ownership are separate concerns.Verification chain: Given a publish CozMessage referencing a claim czd,
claim.anchor × claim.label → Atom-idandpublish.dig → Atom-commit → Treecommute with verification. Formal statement that local verification is sufficient.
2. Coalgebras — Behavioral Observation¶
Each trait defines a coalgebra c: X → F(X) where X is the state space (any implementation) and F is the behavior endofunctor.
2.1. AtomSource (atom-core, L1)¶
F_source(S) = (AtomId → Option<AtomMeta>) -- resolve (metadata, not entry)
× (Query → Set<AtomId>) -- discover
Bisimulation: s₁ ~ s₂ iff resolve and discover agree pointwise. Two implementations containing the same atoms are interchangeable.
Implementation note: The implementation wraps both observers in
Result<_, Error> to distinguish "not found" (Ok(None)) from backend
failure. The coalgebra models the ideal behavior; error handling is
orthogonal.
2.1a. AtomContent (atom-core, L1)¶
F_content(C) = F_source(C) -- inherits AtomSource
× (AtomId × Dig → Option<Tree>) -- content
Where Tree = Seq<ContentEntry> is an ordered sequence of path/data
pairs representing the atom's content tree at the given version digest.
Entries are ordered children-before-parents (leaves-to-root).
ContentEntry = Regular { path, data, executable }
| Symlink { path, target }
| Directory { path }
Bisimulation: c₁ ~ c₂ iff F_source bisimulation holds AND content agrees pointwise — same (id, dig) pair yields the same tree.
AtomContent extends AtomSource as a coalgebra morphism: the
forgetful map F_content → F_source drops the content observer.
This is the content recovery functor — it recovers the tree data
that F_source (the forgetful functor) deliberately omits.
Design rationale: F_source remains minimal (identity +
metadata) for consumers that need only observation (e.g., dependency
resolution, version queries). F_content extends it for consumers
that need tree data (store ingestion, build engine content transfer).
The separation avoids burdening lightweight consumers with a content
access obligation.
2.2. AtomRegistry (atom-core, L1)¶
F_registry(R) = F_source(R) -- inherits AtomSource
× (ClaimReq → Result<Czd>) -- claim (AtomId is pre-computed;
-- returns claim czd)
× (PublishReq → Result<()>) -- publish
Trait inheritance (AtomRegistry: AtomSource) is a coalgebra morphism —
the forgetful map dropping claim/publish observers.
2.3. AtomStore (atom-core, L1)¶
F_store(W) = F_content(W) -- inherits AtomContent
× (dyn AtomContent → Result<()>) -- ingest
× (AtomId → bool) -- contains
Note: F_store now inherits F_content (not F_source directly),
and ingest takes dyn AtomContent (not dyn AtomSource). This
makes the content dependency explicit — ingestion requires content
access, which F_source alone cannot provide.
Critical morphism — ingest as coalgebra homomorphism:
∀ source: AtomContent, ∀ id:
after store.ingest(source):
resolve(store, id) ⊇ resolve(source, id) -- metadata preserved
content(store, id, dig) = content(source, id, dig) -- content preserved
The ⊇ (superset) condition holds for metadata — the store accumulates atoms from multiple sources. Content preservation is exact (=) because content is immutable and content-addressed: the same (id, dig) pair always yields the same tree. Atom-id is stable through ingest.
2.4. BuildEngine (eos-core, L2)¶
F_engine(E) = (AtomRef → Result<BuildPlan<P>>) -- plan
× (BuildPlan<P> → Result<Vec<O>>) -- apply
where P = E::Plan, O = E::Output
BuildPlan<P> is a coproduct (sum type) introducing session-type
branching. Per ADR-0005 (htc-sad §1.5, §6.5), the primary executor's
cache ladder collapses to two rungs — the substrate has no evaluation
stage, only action dispatch:
BuildPlan<P> = Cached { outputs: Vec<ArtifactRef> }
| NeedsBuild { plan: P }
For the primary executor, P = E::Plan binds to Action
(atom_czd_closure_root + toolchain_composition_root + action_params,
htc-sad §6.5), and action_id (E::Digest) is the cache key. (A
third rung, NeedsEvaluation, formerly survived inside an optional
passthrough-snix executor; that executor was removed — ADR-0006 §3 —
and the two-variant coproduct stands alone.)
Bisimulation: e₁ ~ e₂ iff plans agree (variant-matching + content equality) and apply produces digest-equivalent outputs for equivalent plans. Formal justification for embedded/daemon/remote deployment modes.
2.5. ArtifactStore (eos-store, L2)¶
F_artifact(A) = (Digest → Option<Blob>) -- fetch
× (Blob → Digest) -- store
× (Digest → bool) -- exists
× (Vec<Digest> → Vec<bool>) -- check_substitute
Invariant: ∀ blob: fetch(store(blob)) = Some(blob) (round-trip).
2.6. Inter-Layer Morphisms¶
| Morphism | Type | Direction | Mechanism |
| atom → eos | Forget: F_store → F_content | Downward | Eos reads via AtomContent (not full |
| AtomStore — no ingest/contains) | |||
| ion → atom | Full: F_store | Downward | Ion exercises ingest, contains |
| ion → eos | Full: F_engine | Downward | Ion dispatches plan/apply |
The atom → eos morphism forgets mutation observers (ingest, contains) but retains content access. Eos needs content to transfer atom trees into the build store; it does not need to mutate the atom store.
Composition law:
ion ──populate──→ AtomStore ──forget──→ AtomContent ──read──→ eos
↑ |
Manifest, VersionScheme BuildEngine
(atom-core abstractions (eos-core
implemented by ion) abstraction)
The forgetful functor preserves bisimulation by construction: F_content is a component of F_store (which inherits F_content).
3. Session Types — Protocol Ordering¶
Convention: !T = send, ?T = receive, ⊕ = internal choice
(sender selects), & = external choice (receiver handles all),
end = termination.
3.1. PublishSession (client → AtomRegistry)¶
!ClaimReq . ?Result<Czd> . !PublishReq . ?Result<()> . end
AtomId is the abstract pair (anchor, label) itself — never a
digest of it (atom-sad §6.1, [identity-content-addressed]); no step
in this protocol depends on a precomputed identity hash.
ClaimReq carries (anchor, label, owner, key). On success, returns
the claim's czd. PublishReq carries (anchor, label, claim_czd,
dig, src, path, version). Claim MUST succeed before publish —
the client cannot send PublishReq without first receiving the
claim czd to embed in the publish payload.
3.2. BuildSession (ion → BuildEngine)¶
!AtomRef .
?BuildPlan<P> .
& {
Cached: end,
NeedsBuild(P): !P . ?Vec<Output> . end
}
The BuildPlan enum is a session type branching point. Each variant
determines the remainder of the interaction: Cached ends immediately,
NeedsBuild requires one apply round-trip (action dispatch to the
executor, keyed by action_id). (A third branch formerly belonged to
the removed passthrough executor, ADR-0006 §3.)
Finding: CacheSession ≅ BuildSession — the two cache-skipping levels at the primary executor (artifact exists, action not yet built) are isomorphic to the BuildPlan variants (Cached, NeedsBuild). This confirms that the build protocol precisely captures the cache-skipping decision tree at the executor's granularity.
3.3. PopulateSession (ion → AtomStore)¶
⊕ {
from_registry: !&dyn AtomSource . ?Result<()> . end,
from_local: !&dyn AtomSource . ?Result<()> . end
}
Ion selects which population method to use. Both branches use ingest
with different source implementations (remote registry vs local path).
4. Concurrency, Scheduling, and Error Recovery¶
4.1. Parallel Build Composition¶
Ion submits a batch of atoms. Each gets its own BuildSession running in parallel:
BatchBuild(refs) =
BuildSession(ref₁) | BuildSession(ref₂) | ... | BuildSession(refₙ)
Non-interference property:
∀ ref₁ ≠ ref₂:
plan(e, ref₁) | plan(e, ref₂) ≡ plan(e, ref₁) ; plan(e, ref₂)
Concurrent planning must equal sequential planning. Guaranteed by the
coalgebraic structure: plan is an observer (no shared mutable state).
Concurrent apply is safe because ArtifactStore writes are idempotent
(content-addressing: same blob → same digest).
4.2. Session Delegation (Work-Stealing)¶
Delegation transfers an in-progress session from one worker to another:
DelegateSession<S> =
!S . -- source sends session continuation
?Ack . -- target acknowledges
end
If a worker completes plan and receives NeedsBuild(P), its
remaining session is !P . ?Vec<Output> . end. Delegation transfers
this typed continuation to another worker. The target resumes the
session exactly where the source left off — no steps skipped or
reordered.
Scheduler coalgebra (eos-internal):
F_scheduler(Sch) = (Set<AtomRef> → Vec<Assignment>) -- schedule
× (WorkerId → Set<InProgressSession>) -- worker_load
× (WorkerId → WorkerId → Result<()>) -- delegate
× (() → SchedulerMetrics) -- observe
Bisimulation: two schedulers are bisimilar iff they produce the same final outputs for the same input set, regardless of strategy. Scheduling is an optimization; correctness is invariant.
The scheduler is eos-internal. Ion sees only BuildEngine. The
existing ion → eos morphism (F_engine) is unchanged. Scheduling
introduces no new inter-layer coupling.
4.3. Error Recovery as Protocol¶
Errors are protocol structure, not just data. The session type must express recovery options after failure.
Extended BuildSession:
BuildSession =
!AtomRef .
& {
PlanOk:
?BuildPlan<P> .
& {
Cached: end,
NeedsBuild(P):
!P .
& {
ApplyOk: ?Vec<Output> . end,
ApplyFail:
?Error .
⊕ { -- scheduler selects recovery:
retry: recurse, -- same worker retries
delegate: !Continuation . end, -- work-steal
abort: end -- report error
}
}
},
PlanFail:
?Error .
⊕ { retry | abort } -- no continuation to delegate
}
(A removed NeedsEvaluation branch formerly nested one more
round-trip ahead of the same recovery structure — ADR-0006 §3; its shape,
for the historical record: !AtomRef . & { EvalOk: ?P . !P . { ApplyOk | ApplyFail: ⊕{retry|delegate|abort} }, EvalFail: ?Error . ⊕{retry|delegate|abort} } — under PlanOk, alongside
Cached/NeedsBuild above. The recovery algebra is identical; only
the branching depth differs. It is omitted from the primary session
above as scoped to that legacy path.
Key insight — plan failure ≠ apply failure:
- Plan failure has no continuation to delegate. The plan hasn't been produced. Only retry or abort.
- Apply failure HAS a transferable continuation — the plan exists, another worker can execute it. Delegation is available.
The scheduler makes recovery decisions (⊕ internal choice), not ion.
This preserves the architectural boundary: ion submits work, eos handles
recovery.
4.4. Async Extension Property¶
The plan mandates sync-first traits (KD-14). The model validates that the trait surface extends to async without structural changes:
Coalgebras are async-agnostic. c: X → F(X) specifies observations, not execution semantics.
fn plan(...)andasync fn plan(...)produce the same observations. Bisimulation is defined on results, not timing.Session types carry natively to async. Session-typed channels are message-passing constructs — they map to async channels directly. Parallel composition (
|) maps totokio::join!. Delegation maps to channel ownership transfer.
Conclusion: KD-14 is formally validated. The sync-first design is not a limitation — the model extends to async without restructuring.
Validation¶
| Check | Result | Detail |
| Olog commutativity | PASS | Crypto chain and identity stability diagrams commute |
| Coalgebra structure | PASS | All coalgebras follow canonical c: X → F(X) form |
| Bisimulation closure | PASS | Ingest ⊇ + content = conditions correct; all well-defined |
| Content factoring | PASS | F_content × F_source product preserves bisimulation; |
| forgetful F_content → F_source drops content observer only | ||
| Session type duality | PASS | All duals well-formed; ⊕/& inversion checks out |
| Formalism coverage | PARTIAL | Manifest/VersionScheme are algebraic (constructors), not |
| coalgebraic (observers) — correctly omitted from coalgebras | ||
| Minimality | PASS | No unused formalisms; each captures a distinct concern |
| External adequacy | PASS | Concurrency, errors, async modeled; SPEC §4–9 pending |
Complexity Analysis¶
Abstract complexity of each modeled operation, parameterized by domain-relevant quantities. Implementation-specific constants (hash function cost, network latency) are elided.
Coalgebra Observers¶
| Observer | Complexity | Parameters | Notes |
| AtomSource.resolve | O(1) | — | Hash-based lookup by atom-id |
| AtomSource.discover | O(n) | n = atoms in store | Scan; O(k) with index (k = result count) |
| AtomContent.content | O(|T|) | |T| = tree entry count | Walks content tree; I/O-bound for remote |
| AtomRegistry.claim | O(1) | — | czd computation + Ed25519 sign |
| AtomRegistry.publish | O(1) | — | Sign version transaction |
| AtomStore.ingest | O(|S|) | |S| = atoms in source | Iterates source; O(|S∖W|) with dedup check |
| AtomStore.contains | O(1) | — | Hash-based membership test |
| BuildEngine.plan (primary executor) | O(1)–O(|lock|) | Lock size (atom + toolchain refs) | Cached: O(1). NeedsBuild: O(|lock|) — action_id hashes already-resolved lock data (htc-sad §6.5), not an evaluation |
| BuildEngine.apply | O(build) | Plan-specific | Dominated by actual build execution |
| ArtifactStore.fetch | O(1) | — | Content-addressed lookup; +latency if remote |
| ArtifactStore.store | O(|blob|) | |blob| = artifact size | Must hash entire blob |
| ArtifactStore.exists | O(1) | — | Digest lookup |
| ArtifactStore.check_substitute | O(k) | k = number of digests | Batch existence check |
| Scheduler.schedule | O(n log n) | n = atoms in batch | Priority-based; O(n) for round-robin |
| Scheduler.delegate | O(1) | — | Channel transfer |
Session Costs (End-to-End)¶
| Session | Best Case | Typical Case | Worst Case |
| PublishSession | O(1) | O(1) | O(1) — bounded by crypto ops |
| BuildSession | O(1) (Cached) | O(build) (NeedsBuild) | O(build) (NeedsBuild) — executor has only two variants, so worst case equals typical case. |
| BatchBuild | O(1) (all hit) | O(max(build_i)) wall-clock | O(Σ build_i) total work |
| PopulateSession | O(1) (one atom) | O(|S|) (full ingest) | O(|S|) — linear in source size |
| Delegation | O(1) | O(1) | O(1) — channel transfer |
Performance Implications¶
The cache cliff is real, and narrower at the primary executor. BuildPlan's two variants there correspond to two complexity classes: Cached = O(1), NeedsBuild = O(build) — action dispatch, keyed by
action_id. There is no evaluation stage to fall through to (the O(eval) class was removed with the evaluator, ADR-0006). Cache hit rate (action-id cache hits, plus CAS sharing across identical action_ids) remains the dominant performance lever.Ingest is the scaling bottleneck for store population. O(|S|) means ingesting a large registry is expensive. Incremental or lazy ingestion (only ingest atoms actually needed for the current build) should be a priority optimization. The dedup check (
contains) reduces this to O(|S∖W|) — atoms not already present.Parallelism is the dominant scheduling lever. BatchBuild wall-clock is O(max(build_i)) with sufficient workers, vs O(Σ build_i) sequential. The non-interference property guarantees this parallelism is correct. Scheduling strategy affects constant factors, not asymptotic behavior.
Delegation is free. O(1) channel transfer means work-stealing has negligible overhead. The decision to delegate should be driven by load balancing, not by delegation cost.
Plan is O(|lock|) at the primary executor — no longer the variance hotspot.
action_idis a hash over already-resolved lock data (atom_czd_closure_root + toolchain_composition_root + action_params, htc-sad §6.5), not an evaluation: O(1)–O(|lock|). The O(∞) Turing-complete variance was removed with the evaluator (ADR-0006). Optimization effort at the eos layer should focus on maximizing action-id cache hits and CAS sharing across identical action_ids, not on minimizing evaluation cost — there is none on the primary path. Apply remains expensive but predictable; on the primary path, plan no longer is the wild card.
Implications¶
Architecture Validation¶
The trait hierarchy is structurally sound.
AtomSourceas a supertrait forAtomRegistryandAtomStoreis a forgetful functor dropping role-specific observers. Preserves bisimulation by construction.BuildPlan is protocol structure. CacheSession ≅ BuildSession: the two variants precisely encode the cache-skipping decision tree. Any change to caching must be reflected in
BuildPlanand vice versa.Deployment modes are formally interchangeable. BuildEngine bisimulation proves embedded, daemon, and remote engines are equivalent if they produce the same plans and outputs.
Ingest validates store unification. The ⊇ preservation condition proves atoms retain identity after transfer. Published, local, and cross-store atoms are indistinguishable once ingested.
Scheduling is eos-internal and correctness-invariant. Different scheduling strategies (round-robin, work-stealing) are bisimilar — ion's code is genuinely generic over scheduling.
Plan failure ≠ apply failure. Plan failure has no delegatable continuation; apply failure does. Eos's error handling must reflect this asymmetry.
KD-14 (sync-first) is validated. The model's coalgebras and session types extend to async without structural changes.
Implementation Guidance¶
Test bisimulation, not structure. Property-based tests should verify equivalent observations across implementations — not identical internal structure.
Session types as API contracts. PublishSession: "claim before publish." BuildSession: "handle all BuildPlan variants." These are testable invariants.
The forgetful functor is a dependency firewall. Eos sees
AtomContent(notAtomStore) — it can observe identity, metadata, and content, but cannot mutate the atom store. The firewall dropsingestandcontains, not content access. Enforced structurally.Design error handling around the plan/apply asymmetry. Apply failures should support delegation; plan failures should not.
Remaining Gaps¶
- Protocol specification: When Atom SPEC §4–9 mature, a companion session type model should formalize the full interaction protocol.