Designing coalitions on mosaik
audience: contributors
This chapter extends the design intros of zipnet and builder from one organism → one lattice → one coalition: a named composition of coexisting mosaik citizens — block-building lattices, standalone organisms (oracles, attestation fabrics, identity substrates, analytics pipelines, …), cross- citizen organisms (confluences), and up to four basic services (Atlas, Almanac, Chronicle, Compute) plus an optional coalition-scoped ticket issuer.
The reader is assumed familiar with the mosaik book, the zipnet book, the builder book, and the two design intros cited above. This chapter does not re-derive content + intent addressing, the narrow-public-surface discipline, the shared-universe model, or within-lattice derivation; it uses them.
Stance
A coalition is a voluntary grouping. It offers services;
no component is required to accept them. The protocol
has no primitive for compulsion: every relationship is
opt-in at the TicketValidator layer, citizen identity is
independent of coalition membership, and basic services
never gate citizen operation. Every design choice in this
chapter follows from this baseline.
The problem, at N citizens
Builder composes six organisms under one LatticeConfig
for one EVM chain, solving “one end-to-end pipeline for
one chain” cleanly. It does not solve:
-
Cross-citizen coordination at scale. An agent operating across ten block-building lattices holds ten
LatticeConfigs; one additionally correlating across an oracle and an attestation organism holds twelve handshakes. Driver code pairs facts across citizens, reconciles partial observations, and recovers outcome correlators. The same correlator is re-implemented per agent. The builder book’s Shape 1 is correct for two or three lattices and an unbounded implementation burden at ten-plus heterogeneous citizens. -
Shared attribution. MEV captured on a block of lattice A originating from a searcher bundle submitted on lattice B — or a trade attributed by an analytics organism also reported by an oracle organism — is invisible to each service without explicit wiring. Ad hoc repetition is the same burden, one level deeper, across more boundary kinds.
-
Consistent cross-citizen observability. Operators running five citizens want one dashboard; analytics integrators want one API. Both want a named composition with a fingerprint that moves in lockstep with what they subscribe to. The Atlas basic service addresses this.
-
Operator coordination. Teams coordinating a set of citizens — every chain in a superchain stack, every oracle feed in a region, every ecosystem’s bonded organisms — need a naming unit for that coordination that does not itself become a consensus unit.
-
Durable provenance. Decisions outlive any one
CoalitionConfig. An operator grouping needs a tamper-evident record of its own publications, rotations, and retirements — the Chronicle basic service addresses this.
Promoting “a set of citizens” to an in-consensus group collapses every citizen’s trust boundary into one committee. Builder rejected that shape at the organism level; promoting it one rung higher inherits the same objection at higher cost.
The correct rung is a fingerprint above the citizens that names them for discovery, basic services, and module derivation — without consensus, state, or admission authority. That fingerprint is a coalition.
Two axes of choice
Same two axes zipnet picked and builder reaffirmed. Each rung of the ladder inherits the zipnet conclusion; the coalition rung does too.
- Network topology. Does a coalition live on its own
NetworkId, or share a universe with every other mosaik service? - Composition. How do citizens and confluences reference each other without creating cross-Group atomicity mosaik does not support — and, now, without creating cross-coalition atomicity either?
The blueprint chooses shared universe + citizenship-by- reference + no cross-Group, cross-citizen, or cross- coalition atomicity. The three choices are independent; each has a narrow rationale.
Shared universe (unchanged)
builder::UNIVERSE = unique_id!("mosaik.universe") — the
same constant zipnet and builder use. Every coalition,
lattice, standalone organism, confluence, and module
lives on it. Different coalitions coexist as overlays of
Groups, Streams, and Collections distinguished by
their content + intent addressed IDs. An integrator
caring about three coalitions holds one Network handle
and three CoalitionConfigs. Because coalitions
reference citizens rather than re-deriving them, an
integrator already bonded to some of the included citizens
observes no duplicate identity.
The alternative — one NetworkId per coalition, mirrored
all the way up — is rejected on the same grounds the
zipnet book rejects Shape A: operators already pay for one
mosaik endpoint, services compose, and one additional
endpoint per coalition would turn the coalition into a
routing device, which is not the rung’s function.
Citizenship-by-reference
A CoalitionConfig is a parent struct. Unlike a
LatticeConfig (which nests six organism Configs and
hashes them together), a CoalitionConfig references
existing citizen fingerprints without re-deriving the
citizens’ organism IDs. This is the key decision of the
coalition rung:
COALITION = blake3("coalition|" || SCHEMA_VERSION_U8
|| "|" || coalition_name)
COA_LATTICES = ordered stable ids of referenced lattices
COA_ORGANISMS = ordered stable ids of referenced organisms
COA_CONFS = ordered fingerprints of referenced confluences
COA_TICKETS = optional TicketIssuerConfig fingerprint (0 bytes if absent)
COALITION_ROOT = blake3(
COALITION
|| COA_LATTICES
|| COA_ORGANISMS
|| COA_CONFS
|| COA_TICKETS
)
Here COA_LATTICES[i] is the stable id of lattice i —
not a re-derivation. Likewise COA_ORGANISMS[j] is the
stable id the standalone organism j publishes in its
own handshake. Confluences are referenced by their own
fingerprint. Builder’s lattice identity is:
LATTICE(i) = blake3("builder|" || instance_name || "|chain=" || chain_id)
+ every organism's Config fingerprint inside that lattice
A coalition neither modifies LATTICE(i) nor the organism
roots under it, nor the Config of any standalone
organism or confluence it references. A citizen
referenced by three coalitions is one citizen; its
operators issue tickets once, its integrators compile it
in once, and its GroupIds are stable across coalition
memberships.
Rationale for by-reference over by-containment:
-
Citizens predate coalitions. The first production organism and first production lattice ship before the first coalition. An integrator already bonded to a specific citizen must not have to re-bond when an operator composes that citizen into a new coalition.
-
Citizens outlive coalitions. Coalition membership is a composition choice and changes; citizen identity is an operator-level commitment. Coalitions must not force churn.
-
Citizens belong to multiple coalitions. The shared- universe choice implies a citizen may be referenced from several coalitions simultaneously — one per operator grouping, one per analytics vendor, one per ecosystem grouping. Re-derivation would force a cartesian product of IDs.
Citizen reference shape
A citizen is referenced by two values: its stable id
— the (instance_name, network_id) blake3 hash — which
changes only on retirement; and its content hash,
which folds the citizen’s ACL, MR_TDs, and policy
parameters and bumps with every operational rotation.
CoalitionConfig references citizens by stable id.
Confluences and modules choose per-component whether they
also pin the content hash. A read-only correlator that
does not bond into TDX-gated surfaces pins only the stable
id and survives MR_TD rotations in its upstream citizens.
A confluence whose committee bonds into a TDX-gated
surface pins the content hash and redeploys on rotation.
The split decouples MR_TD rotation churn from coalition-
level identity. A coalition referencing ten citizens
rotating TDX monthly sees its COALITION_ROOT change only
on citizen retirement or coalition membership change, not
on routine operational rotations.
Open for v0.3 decision. Whether content-hash- pinning is a per-confluence choice or a coalition-wide policy is unresolved. Leaving it per-component for v0.2;
CoalitionConfigmay gain a policy flag later.
Builder’s LatticeConfig derivation already distinguishes
the two in practice; this rung surfaces the distinction
in the coalition-layer spec. Both LatticeConfig and
OrganismRef expose a stable_id() accessor separate
from their full fingerprint accessor.
Confluences as their own rung
Confluences are organisms in the zipnet sense: one
Config fingerprint, a narrow public surface, a ticket-
gated ACL. What distinguishes them is that their content
folds two or more citizen fingerprints. A confluence’s
identity does not fold a coalition root:
CONFLUENCE(cfg) = blake3(
"confluence|"
|| SCHEMA_VERSION_U8
|| "|" || name
|| ordered spanned lattice references
|| ordered spanned organism references
|| content_fingerprint
|| acl_fingerprint
)
CONFLUENCE_committee = CONFLUENCE(cfg).derive("committee")
A confluence is therefore referenced by zero, one, or
many coalitions exactly as a lattice or a standalone
organism is. Its committee, state machine, and log are
shared across those references. A confluence misconfigured
against the wrong spanned citizens derives a disjoint
GroupId and surfaces as ConnectTimeout — the ladder’s
standard debuggable failure mode.
Modules (Atlas, Almanac, Chronicle) are the only
coalition-scoped components. Their identity derives under
COALITION_ROOT.derive("module").derive(name) where
name is "atlas", "almanac", or "chronicle". An
optional ticket issuer derives under
COALITION_ROOT.derive("tickets").
No cross-Group, cross-citizen, or cross-coalition atomicity
Every rung below refuses cross-Group atomicity. The coalition rung refuses cross-citizen and cross-coalition atomicity by the same argument:
- Cross-Group (one citizen, two organisms) — refused by builder and by every organism’s own design. Organisms subscribe to each other’s public surfaces; there is no atomic multi-organism commit.
- Cross-citizen (one coalition, two citizens) — refused by this blueprint. Confluences read public surfaces of the citizens they span; their state machine commits its own facts. A confluence never forces two citizens to commit atomically.
- Cross-coalition (two coalitions) — refused by this blueprint. Coalitions do not coordinate in consensus. A use case for cross-coalition coordination resolves either to an integrator spanning coalitions or to a new confluence whose content fingerprint folds the relevant citizens (which are already referenced by both coalitions anyway).
Fixed shapes, open catalogs
The blueprint’s central discipline; restated because every downstream choice flows from it.
Specified
- The
CoalitionConfigstruct and derivation rules. Every coalition has this shape; every coalition’s identity is computed this way. - The
ConfluenceConfigandOrganismRefstructs. Every confluence and every standalone-organism reference has these shapes. - The four basic-service shapes (Atlas, Almanac,
Chronicle, Compute). Every instance of “Atlas” in any
coalition has the same
Configsignature, public surface, and derivation. Same for the other three. Specification in basic services. - The optional ticket-issuer shape. Specification in ticket issuance.
- Citizenship discipline — by-reference, multi- coalition-compatible, no re-derivation; stable-id and content-hash split.
Left open
- Whether any given coalition ships any basic services. A coalition with zero services is valid.
- Whether any given coalition ships a ticket issuer.
- The catalog of commissioned confluences. Each arrives when a cross-citizen problem forces it.
- The catalog of standalone-organism categories and lattice classes. New categories arrive with their own books. Block-building lattices are the first specified class; attestation fabrics, oracle grids, DA shards, and other categories follow their own proposals.
- Inter-coalition coordination. Coalitions coexist on the shared universe; a concrete need for coordination earns its way in through a fresh proposal.
- Governance of the coalition itself. Who approves
CoalitionConfigbumps, how operator groupings form and dissolve, what contracts multi-operator coalitions sign — all out of band.
Deliberately deferred (heavy-coalition)
A family of primitives the blueprint refuses:
- Enforceable policies over citizens — a
CoalitionConfigdeclaring constraints citizens must satisfy (MR_TD requirements, bond minimums, code-audit attestations). - Taxation — a protocol-level mechanism for the coalition to collect fees from citizens’ commits.
- Judiciary — a confluence whose commits have protocol-level precedence over individual citizens’, resolving disputes.
- Mandatory citizenship — any primitive whose absence renders a citizen’s commits invalid in the coalition’s view.
- Legislation — a state machine in the coalition that citizens must query and conform to before acting.
Each is a coherent design space, and adopting any one forces the blueprint to rewrite the trust composition and rebuild every passage that assumes voluntary participation. Any future heavy-coalition proposal lives on its own branch and earns the transition; the current blueprint neither anticipates nor precludes one.
Flag in-source and in-docs as “heavy-coalition” when the topic arises so the word’s current scope is unambiguous.
Coalition identity
A coalition is identified by a CoalitionConfig folding
every root input into one deterministic fingerprint.
Operators publish the CoalitionConfig as lattice
operators publish a LatticeConfig and organism operators
publish their own Config; integrators compile it in.
pub const SCHEMA_VERSION_U8: u8 = 1;
#[non_exhaustive]
pub struct CoalitionConfig<'a> {
/// Short, stable, namespaced name chosen by the
/// coalition operator (e.g. "ethereum.superchain",
/// "signals.euro-fx").
pub name: &'a str,
/// Ordered set of block-building lattices this
/// coalition composes. Referenced by stable id;
/// this struct does NOT re-derive any lattice's
/// organism IDs.
pub lattices: &'a [LatticeConfig<'a>],
/// Ordered set of standalone organisms this coalition
/// composes — any mosaik organism with a Config
/// fingerprint living directly on the universe. Held
/// by reference; the concrete Config type lives in
/// the organism's own crate.
pub organisms: &'a [OrganismRef<'a>],
/// Ordered set of confluences referenced by this
/// coalition. A confluence's identity is derived
/// independently of any coalition; the same
/// confluence may be referenced by many coalitions.
pub confluences: &'a [ConfluenceConfig<'a>],
/// Optional coalition-scoped ticket issuer.
/// Non-authoritative: no component is required to
/// accept tickets from this issuer. Organisms and
/// confluences opt in via their own TicketValidator
/// composition.
pub ticket_issuer: Option<TicketIssuerConfig<'a>>,
}
#[non_exhaustive]
pub struct OrganismRef<'a> {
pub role: &'a str,
/// blake3((instance_name, network_id)); changes only
/// on retirement.
pub stable_id: UniqueId,
/// Optional content hash (ACL, MR_TDs, policy).
/// Components that pin this redeploy on operational
/// rotations.
pub content_hash: Option<UniqueId>,
}
impl CoalitionConfig<'_> {
/// blake3("coalition|" || SCHEMA_VERSION_U8 || "|"
/// || name || ...)
pub const fn coalition_id(&self) -> UniqueId { /* ... */ }
/// Module lookup helpers. Return Some if the
/// coalition ships that module, None otherwise.
pub const fn atlas (&self) -> Option<&ConfluenceConfig<'_>> { /* ... */ }
pub const fn almanac (&self) -> Option<&ConfluenceConfig<'_>> { /* ... */ }
pub const fn chronicle(&self) -> Option<&ConfluenceConfig<'_>> { /* ... */ }
}
CoalitionConfig is lifetime-parameterised so runtime
construction is a first-class path. All existing const
instances continue to compile under 'static inference.
An integrator binds to the coalition by passing the
CoalitionConfig into whichever citizen or confluence
handles they need:
use std::sync::Arc;
use mosaik::Network;
use builder::{LatticeConfig, UNIVERSE};
use coalition::CoalitionConfig;
const ETH_SUPERCHAIN: CoalitionConfig<'static> = CoalitionConfig {
name: "ethereum.superchain",
lattices: &[ETH_MAINNET, UNICHAIN_MAINNET, BASE_MAINNET],
organisms: &[EUR_PRICE_FEED],
confluences: &[SUPERCHAIN_ATLAS, INTENTS_ROUTER, SHARED_LEDGER],
ticket_issuer: None,
};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let network = Arc::new(Network::new(UNIVERSE).await?);
let eth_bid = offer::Offer::<Bundle>::bid(&network, Ð_SUPERCHAIN.lattices[0].offer).await?;
let eur_feed = oracle::Feed::<Price>::read(&network, ETH_SUPERCHAIN.organisms[0].config()).await?;
let intents = intents::Router::<Intent>::publish(&network, Ð_SUPERCHAIN.confluences[1]).await?;
let atlas = atlas::Atlas::<CitizenCard>::read(&network, ETH_SUPERCHAIN.atlas().unwrap()).await?;
// ...
Ok(())
}
Each confluence, module, and standalone organism exposes
typed free-function constructors in the shape zipnet ships
and builder reproduces
(Organism::<D>::verb(&network, &Config)). Raw mosaik IDs
never cross a crate boundary.
Fingerprint convention, not a registry
Same discipline as zipnet and builder:
- The coalition operator publishes the
CoalitionConfigstruct (or a serialised fingerprint) as the handshake. - Consumers compile it in.
- If TDX-gated, the operator publishes the committee MR_TDs for every confluence, module, and commissioned standalone organism whose admission is TDX-attested. Per-citizen MR_TDs are published by the per-citizen operator; the coalition operator re-publishes them for convenience, not as authority.
- There is no on-network coalition registry. A directory collection of known coalitions may exist as a devops convenience; it is never part of the binding path.
Typos in the coalition instance name, in citizen
fingerprint ordering, or in any confluence parameter
surface as Error::ConnectTimeout, not “coalition not
found”. The library cannot distinguish “nobody runs this”
from “operator is not up yet” without a registry, and
adding one would misrepresent the error.
What a coalition is not
-
A citizen’s owner. A coalition references a citizen; per-citizen operators control admission, rotate keys, retire instances. The coalition’s
CoalitionConfigupdates independently. -
A consensus unit. There is no coalition-level Raft group, no coalition state machine, no coalition log. The coalition layer adds naming, modules, and module derivation — no consensus.
-
A routing device. Coalitions do not carry traffic between citizens. Every citizen and confluence discovers peers via mosaik’s usual gossip on the shared universe. The coalition fingerprint is a compile-time token, not a runtime hub.
-
An ACL root. A coalition does not issue tickets on behalf of referenced citizens. Each citizen’s
TicketValidatorcomposition is unchanged; each confluence has its ownTicketValidator. The optional ticket issuer is opt-in per component: components that want to recognise its tickets include its issuance root in their validator composition; others ignore it. -
A closed catalog. Unlike builder’s six organisms, this blueprint does not enumerate a canonical set of lattice classes, standalone-organism categories, or confluences. Real citizens and confluences ship when a concrete problem forces them.
Four conventions (three inherited, one added)
Every confluence, module, and standalone organism the blueprint references reproduces the three zipnet conventions verbatim; the coalition adds one.
-
Identifier derivation from the organism’s Config fingerprint. Inherited from zipnet.
-
Typed free-function constructors.
Organism::<D>::verb(&network, &Config)returning typed handles; raw IDs never leak across crate boundaries. Inherited from zipnet; mirrored by every organism in every lattice, every confluence, and every module. -
Fingerprint, not registry. Inherited from zipnet; reaffirmed by builder and here.
-
Citizenship-by-reference, module-by-derivation. New for the coalition rung. A
CoalitionConfigfolds citizen fingerprints as content without re-deriving their organism IDs; confluences are likewise referenced, not derived. Modules (Atlas, Almanac, Chronicle) and an optional ticket issuer are the components whose identity foldsCOALITION_ROOT.
What the pattern buys
-
Collapsed integrator model: one
Network, oneCoalitionConfigper coalition, typed handles on each referenced citizen, confluence, and consumed module. -
Operator pacing. A coalition starts as one operator’s set of citizens, adds a second operator’s citizen without touching the first’s, and adds a confluence or module without forcing either per-citizen operator to change anything.
-
Clean retirement. A commissioned confluence retires by the coalition’s next
CoalitionConfigomitting it — or, if the confluence itself is going away, by a retirement marker on its own stream. Integrators seeConnectTimeout, the ladder’s standard failure mode, or a clean upgrade pointer. -
Cross-citizen services as organisms, not protocols. A shared-ledger confluence, cross-feed correlator, cross-chain intent router, Atlas, or Almanac is a mosaik organism following the zipnet pattern. Its author reuses every primitive, ticket validator, and replication guarantee.
-
Open catalog. New lattice classes, standalone- organism categories, and confluences arrive on their own schedules and fold into the existing handshake shape without forcing the coalition layer to change.
Where the pattern strains
Three limitations a contributor extending this must be explicit about.
Citizen upgrades churn coalition-scoped identity
A citizen-level change bumping its stable id — a retirement
— changes the corresponding entry in COA_LATTICES or
COA_ORGANISMS for every coalition that references the
citizen, which in turn changes COALITION_ROOT and every
module derived under it.
The stable-id / content-hash split keeps routine
operational rotations out of COALITION_ROOT: MR_TD
rotations do not change the stable id and therefore do not
cascade. A retirement does; retirement is rare and
announced.
Commissioned confluences do not derive under
COALITION_ROOT, so retirements ripple through modules
only. A confluence re-derives only when the citizens it
itself spans retire or when its own content changes.
Wide confluences are heavy
A confluence reading public surfaces from ten citizens simultaneously runs ten subscriptions, holds peer entries for ten ticket-issuance roots, and is sensitive to the liveness of all ten. The coalition blueprint does not make this easier; it makes it legible.
Commissioning contributors should consider sharding: a per-pair confluence over interesting citizen pairs, composed by an integrator, is often cheaper and more debuggable than one wide confluence.
The coalition is not cryptographically authoritative
A CoalitionConfig fingerprint is a naming convenience.
It does not sign, commit on-chain, or expose a channel for
per-citizen operators to discover which coalitions
reference their citizen. A per-citizen operator objecting
to being referenced has no technical recourse — anyone
with a list of citizen stable ids can publish a
CoalitionConfig.
This is intentional. The only cryptographic check is
whether each referenced citizen admits the confluences’
committee peers via its own TicketValidator. Per-
citizen and coalition operators negotiate references out
of band.
Checklist for commissioning a new confluence
-
Identify the one or two public primitives. If the surface is not a small, named, finite set of streams or collections, the interface is not yet designed.
-
Identify the citizens it spans. Record as ordered sequences of citizen references in the confluence’s
Config— lattices, organisms, or both. Order matters; changing it changes the fingerprint. -
Pick the confluence name. Folded into the confluence’s own derivation; no coalition root is involved.
-
Define the
Configfingerprint inputs. Content parameters affecting the state-machine signature, upstream public surfaces subscribed to, ACL composition. -
Write typed constructors.
Confluence::<D>::verb(&network, &Config). Never export rawStreamId/StoreId/GroupIdacross the crate boundary. -
Specify
TicketValidatorcomposition on the public primitives. ACL lives there. If the confluence wants to recognise any coalition’s ticket issuer, its validator composition includes that issuance root directly; the confluence chooses, the coalition does not impose. -
Decide per-citizen-failure behaviour. If citizen A is down, does the confluence keep processing remaining citizens’ inputs or stall? Document as part of the composition contract.
-
Document which citizens’ public surfaces are read. This is the composition contract; changes to it touch Composition.
-
Declare dependency on any modules. If the confluence aligns to Almanac ticks or observes Chronicle entries, state so in the crate documentation.
-
Answer: does this confluence meaningfully aggregate across citizens, or is it an integrator in disguise? If an ordinary integrator holding the same N citizen handshakes could do the same work, skip the confluence. The confluence pattern earns its keep when the aggregated fact is itself a commit with its own consumers.
-
State the versioning story. If the answer to “what happens when one of the referenced citizens bumps its fingerprint?” is not defined, the design is incomplete.
Cross-references
- Anatomy of a coalition — concrete instantiation of the pattern for one example coalition.
- Basic services — Atlas, Almanac, Chronicle specified.
- Ticket issuance — the optional coalition-scoped issuance root.
- Confluences — shape and discipline of cross-citizen organisms.
- Composition — how citizens and confluences wire together without cross-Group atomicity.
- Atomicity boundary — what the coalition layer inherits and adds.
- Threat model — compositional trust.
- Roadmap — versioning, heavy-coalition as a deferred space, first commissioned confluences.