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

Composition — members and organisms in a coalition

audience: contributors

Architecture maps composite organisms and members onto one example coalition. Composite organisms specifies the composite-organism shape. Basic services specifies Atlas, Almanac, and Chronicle. This chapter shows the wiring: which public surface each composite organism subscribes to on each spanned member, how the subscription graph grows as composite organisms and modules are added, and what fails where when an upstream stalls.

The wiring is deliberately weak. No cross-Group atomicity, no shared state across members, no global scheduler. Members and organisms react to each other’s public commits via mosaik’s when() DSL and Collection / Stream subscriptions. This is the composition model at every rung.

Two keys: member id and clock event

Every commit in every block-building lattice is keyed by slot (see builder composition). Every commit in a standalone organism is keyed by that organism’s own clock event — a slot, a tick, a round, a sequence number. A coalition adds the member id as an outer key on every cross-member fact.

The member_id used as a key in cross-member commits is the first 20 bytes of the member’s blake3 stable-id fingerprint. The full 32-byte value lives in Atlas cards and in evidence pointers. The 20-byte truncation gives a compact key that matches the mental model readers bring from other ecosystems; collision resistance remains more than sufficient at this rung.

Concretely:

  • Intra-member commits continue to key on their own clock alone (no change from the member’s own book).
  • Composite-organism commits that fan-out to multiple target members key on (target_member_id, target_clock_event).
  • Composite-organism commits that fan-in from multiple origin members key on (origin_member_id, origin_clock_event).
  • An integrator joining commits across a composite organism and the members the composite organism touches keys on (member_id, clock_event) everywhere.
  • When a coalition ships an Almanac the composite organism has opted into, the composite organism may additionally carry almanac_tick as an alignment key for cross-member joins where the members’ native clocks are not commensurate.

The 20-byte member_id is stable — it never changes for a given member deployment. A member retirement yields a new stable id and is therefore a new member id for every coalition that adopts it.

The subscription graph

Each arrow is a subscription. The downstream component watches the upstream’s public collection or stream and reacts in its own state machine. No arrow represents atomic cross-Group commit. Dashed boxes are composite organisms; the oracle box on the left is a standalone organism.

   integrators
   (wallets,
    searchers)
        │
        ├──► zipnet[L1]:Submit ────► Broadcasts[L1] ──► UnsealedPool[L1] ─┐
        │                                                                  │
        ├──► zipnet[L2]:Submit ────► Broadcasts[L2] ──► UnsealedPool[L2] ─┤
        │                                                                  │
        └──► offer[L1]:Bid / offer[L2]:Bid                                 │
                                                                            │
   price-feed.eur ──► Price[tick]                                          │
                                                                            │
   almanac     ──► Ticks[K]                                                │
        │                                                                   │
        └──────────── referenced by composite organisms for alignment ──┐     │
                                                                    ▼     │
                                                       ┌────────────────── ┘
                                                       ▼
                                          ┌───────────────────────┐
                                          │ intents (composite)   │
                                          │ reads UnsealedPool[*] │
                                          │   + price-feed.eur    │
                                          │ commits RoutedIntents │
                                          └───────────────────────┘
                                                       │
                      RoutedIntents[L1,S] / RoutedIntents[L2,S]
                                                       ▼
                       offer[L1]:AuctionOutcome     offer[L2]:AuctionOutcome
                                 │                            │
                         atelier[L1]:Candidates       atelier[L2]:Candidates
                                 │                            │
                         relay[L1]:AcceptedHeaders    relay[L2]:AcceptedHeaders
                                 │                            │
                         tally[L1]:Refunds            tally[L2]:Refunds
                                 │                            │
                                 └────────────┬──────────────┘
                                              ▼
                                   ┌──────────────────────────┐
                                   │ ledger (composite)       │
                                   │ reads Refunds[*]         │
                                   │ commits SharedRefunds    │
                                   └──────────────────────────┘
                                              │
                                              ▼
                                         integrators

   atlas ──► MemberCards (consumed by dashboards, not coalition pipeline)
   chronicle ──► ChronicleEntries (consumed by auditors, not coalition pipeline)

Read top to bottom:

  1. Wallets submit to each lattice’s zipnet::Submit; searchers submit to each lattice’s offer::Bid. Neither changes from builder.
  2. Each lattice’s zipnet and unseal do their usual work independently. The oracle organism price-feed.eur publishes ticks on its own clock. The Almanac (if shipped) publishes its own tick stream.
  3. The intents composite organism subscribes to every spanned member’s relevant public surface — lattices’ UnsealedPools and, in this example, price-feed.eur’s Price stream. It may additionally subscribe to the Almanac for alignment keys. It observes cross-member intents and commits routed-intents entries per target lattice.
  4. Each lattice’s offer subscribes to the composite organism’s RoutedIntents collection filtered for its own member id. Routed intents are an additional input to the auction, along with the lattice’s own UnsealedPool.
  5. Each lattice’s atelier and relay proceed as in builder.
  6. Each lattice’s tally commits independently.
  7. The ledger composite organism subscribes to every spanned lattice’s tally::Refunds and commits the aggregated SharedRefunds collection.
  8. Integrators read SharedRefunds and per-member surfaces depending on their use case. Dashboards read the Atlas. Auditors read the Chronicle.

Atlas and Chronicle are not on any composite organism’s critical path in this example; they are consumed by dashboards and auditors directly. Modules serve observability and provenance, not the coalition’s data flow.

Subscription code shape

A contributor implementing a composite organism writes a role driver in that composite organism’s crate. The driver is a mosaik event loop multiplexing subscriptions across members and calling group.execute(...) on its own Group. The pattern is identical to any organism driver, fanned out across more member kinds.

// Inside intents::node::roles::committee_member.
//
// `lattices` and `organisms` are the ordered sets of
// member references this composite organism spans,
// sourced from self.config.lattices and
// self.config.organisms.
loop {
    tokio::select! {
        // One subscription per spanned lattice's UnsealedPool.
        ev = pool_a.when().appended() => {
            let round = pool_a.get(ev).expect("appended implies present");
            let intents = extract_cross_chain_intents(&round);
            self.group.execute(IntentsCmd::ObservePool {
                member_id: lattice_a_id,
                slot: round.slot,
                intents,
            }).await?;
        }
        ev = pool_b.when().appended() => {
            // ... same shape
        }
        // One subscription per spanned standalone organism.
        ev = price_feed_eur.when().appended() => {
            let tick = price_feed_eur.get(ev).unwrap();
            self.group.execute(IntentsCmd::ObservePrice {
                member_id: eur_feed_id,
                tick: tick.seq,
                price: tick.value,
            }).await?;
        }
        // Optional: align to a coalition's Almanac.
        ev = almanac.when().appended() => {
            let t = almanac.get(ev).unwrap();
            self.group.execute(IntentsCmd::AdvanceAlmanacTick(t.seq)).await?;
        }
        _ = self.apply_clock.tick() => {
            // Per-slot deadline: seal the routing decision.
            self.group.execute(IntentsCmd::SealSlot).await?;
        }
    }
}

Every observed event carries enough evidence ((member_id, clock_event, commit_pointer)) that the state machine can validate it against the member’s public surface during replay.

Apply order across the graph

Within one organism’s Group, mosaik’s Raft variant guarantees every committee member applies commands in the same order. Across composite organisms, and across members, no such guarantee exists. The coalition layer relies on the same two properties the builder layer does, scaled up:

  1. Monotonicity by key. Within any component, commits for key (C, K+1) are not applied before (C, K). The component’s state machine enforces this per- member in its apply handler.
  2. Eventual consistency by subscription. A downstream component’s driver observes every upstream commit eventually, because mosaik collections are append-only and readers converge.

Together these are enough to reconstruct a consistent per-member per-clock-event view across the whole coalition without global atomicity. An integrator wanting “the canonical decision for member C, clock event K, across members and composite organisms” reads each component’s per-(C, K) commit independently and joins on the key.

When an upstream stalls

Failure propagation is a small matrix. Rows are the failing component; columns are downstream components’ observable behaviour.

Upstream failsSame-member downstreamsComposite organisms that read this memberOther members
lattice organismsee builder compositiondegraded — partial evidence per specunaffected
standalone organismsee that organism’s own bookdegraded — partial evidence per specunaffected
member stable id bump (retirement)in-member consumers re-pincomposite organism must redeploy against new stable idunaffected
member content hash bumpin-member consumers re-pin if they pinnedcomposite organisms pinning content redeploy; stable-id-only pinners are unaffectedunaffected
composite organism stallsno effect (members don’t depend on composite organisms for their own pipeline)downstream composite-organism consumers see no new commitsunaffected
module stallsno effect (members don’t depend on modules)composite organisms aligned to this module see delayed/degraded commitsunaffected
composite-organism committee crosses thresholdno effectbad commits possible (integrity lost); on-chain settlement is the final arbiterunaffected
coalition operator retires coalitionno technical effect (members continue)modules under retired coalition emit retirement markers, then stopunaffected

Two properties fall out, mirroring builder’s:

  • Upstream failures degrade downstream outputs; they do not corrupt them. A missing UnsealedPool[L1, S] narrows the intents composite organism’s commit for that slot; the commit itself remains well-formed.
  • The pipeline is drainable per member. Failures on one member do not block the coalition’s other members. Each member’s pipeline is local; each composite organism applies what it observes.

What the coalition composition contract guarantees

Given the full commit logs of every member and every composite organism the coalition references:

  • Deterministic replay per (member_id, clock_event). Anyone can recompute each component’s per-(C, K) decision and cross-check composite-organism commits against the member inputs they folded in.
  • Independent auditability. A composite organism’s commit carries evidence pointers to the member commits it depends on. A consumer that trusts the members can verify a composite organism’s commit without trusting the composite organism’s committee, as long as the evidence pointers resolve.
  • No silent corruption across coalitions. A member referenced from multiple coalitions is still one member; its commit log is one log. Different coalitions reading the same member see the same facts. A composite organism referenced from multiple coalitions is still one composite organism; its commit log is one log.

What the contract does not guarantee

  • Atomic all-or-nothing commits across members. Already refused. An integrator reading a composite organism’s commit and the spanned members’ commits must tolerate differing commit wall-clock times.
  • Cross-coalition coordination. Coalitions coexist, they do not coordinate. If a cross-coalition commit is genuinely needed, the answer is a composite organism that spans the relevant members directly — which both coalitions may reference independently.
  • Bounded end-to-end latency. As in builder: if a composite organism stalls, downstream consumers never see its next commit. No composite-organism-level timeout triggers a dummy commit; operators requiring bounded latency add per-slot deadlines at the composite-organism level.

Contributors implementing a new composite organism

Wiring checklist for a composite organism:

  1. Identify every spanned member’s public surface you subscribe to. Record as ordered slices of member references in your Configlattices, organisms, or both.
  2. Decide whether your commits fan-in, fan-out, or both. Fan-in composite organisms aggregate per-origin-member facts; fan-out composite organisms distribute one fact to multiple target members; some composite organisms do both.
  3. Key every commit by (member_id, clock_event). Never by one alone. Use the 20-byte truncated member id. If your state machine has a natural sub-clock- event cadence, commit at that cadence but carry the owning (member_id, clock_event) pair. If the composite organism opts into a coalition’s Almanac and needs cross-member alignment, also carry almanac_tick.
  4. Write one role driver per Group member role. Keep it as a tokio::select! over the upstream subscriptions (one per member) and your local timers.
  5. Validate evidence on replay. The upstream-event pointers carried in your Observe* commands must be checkable against the member’s public surface during replay; a replay that sees an unresolved evidence pointer must reject the command.
  6. Document your per-member-failure policy. What happens when one spanned member stalls, reorders, or bumps its stable id or content hash — all are operational realities.
  7. Declare dependency on modules. If you require an Almanac for alignment, or consume Chronicle entries, document it.
  8. Document the composition contract in your composite organism’s contributors/composition-hooks.md (per-organism documentation). Update architecture and this page’s subscription graph to include the new composite organism if the example coalition is the right place for it.

Cross-references