Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

  1. Network topology. Does a coalition live on its own NetworkId, or share a universe with every other mosaik service?
  2. 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; CoalitionConfig may 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 CoalitionConfig struct and derivation rules. Every coalition has this shape; every coalition’s identity is computed this way.
  • The ConfluenceConfig and OrganismRef structs. 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 Config signature, 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 CoalitionConfig bumps, 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 CoalitionConfig declaring 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, &ETH_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, &ETH_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 CoalitionConfig struct (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 CoalitionConfig updates 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 TicketValidator composition is unchanged; each confluence has its own TicketValidator. 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.

  1. Identifier derivation from the organism’s Config fingerprint. Inherited from zipnet.

  2. 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.

  3. Fingerprint, not registry. Inherited from zipnet; reaffirmed by builder and here.

  4. Citizenship-by-reference, module-by-derivation. New for the coalition rung. A CoalitionConfig folds 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 folds COALITION_ROOT.

What the pattern buys

  • Collapsed integrator model: one Network, one CoalitionConfig per 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 CoalitionConfig omitting it — or, if the confluence itself is going away, by a retirement marker on its own stream. Integrators see ConnectTimeout, 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

  1. 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.

  2. 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.

  3. Pick the confluence name. Folded into the confluence’s own derivation; no coalition root is involved.

  4. Define the Config fingerprint inputs. Content parameters affecting the state-machine signature, upstream public surfaces subscribed to, ACL composition.

  5. Write typed constructors. Confluence::<D>::verb(&network, &Config). Never export raw StreamId/StoreId/GroupId across the crate boundary.

  6. Specify TicketValidator composition 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.

  7. 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.

  8. Document which citizens’ public surfaces are read. This is the composition contract; changes to it touch Composition.

  9. Declare dependency on any modules. If the confluence aligns to Almanac ticks or observes Chronicle entries, state so in the crate documentation.

  10. 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.

  11. 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.