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 members — block-building lattices, standalone organisms (oracles, attestation fabrics, identity substrates, analytics pipelines, …), cross- member organisms (composite organisms), and up to five basic services (Atlas, Almanac, Chronicle, Compute, Randomness) 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 an opt-in composition. The substrate has no primitive for compulsion: every bond is opt-in at the TicketValidator layer, member identity is independent of coalition membership, and basic services never gate member operation. Every design choice in this chapter follows from this baseline.

The problem, at N members

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-member 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 members, 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 members.

  • 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-member observability. Operators running five members 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 members — every chain in a superchain stack, every oracle feed in a region, every deployment’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 members” to an in-consensus group collapses every member’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 members 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 members and organisms 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 + membership-by- reference + no cross-Group, cross-member, 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, composite organism, 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 members rather than re-deriving them, an integrator already bonded to some of the included members 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.

Membership-by-reference

A CoalitionConfig is a parent struct. Unlike a LatticeConfig (which nests six organism Configs and hashes them together), a CoalitionConfig references existing member fingerprints without re-deriving the members’ 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
                     (including any composite organisms)
  COA_TICKETS      = optional TicketIssuerConfig fingerprint (0 bytes if absent)
  COALITION_ROOT   = blake3(
                       COALITION
                       || COA_LATTICES
                       || COA_ORGANISMS
                       || 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 (or composite organism) j publishes in its own handshake. Composite organisms are referenced by their own fingerprint just like any other organism. 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 composite organism it references. A member referenced by three coalitions is one member; 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:

  • Members predate coalitions. The first production organism and first production lattice ship before the first coalition. An integrator already bonded to a specific member must not have to re-bond when an operator composes that member into a new coalition.

  • Members outlive coalitions. Coalition membership is a composition choice and changes; member identity is an operator-level commitment. Coalitions must not force churn.

  • Members belong to multiple coalitions. The shared- universe choice implies a member may be referenced from several coalitions simultaneously — one per operator grouping, one per analytics vendor, one per umbrella grouping. Re-derivation would force a cartesian product of IDs.

Member reference shape

A member 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 member’s ACL, TDX Measurements, and policy parameters and bumps with every operational rotation.

CoalitionConfig references members by stable id. Composite organisms 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 TDX Measurements rotations in its upstream members. A composite organism whose committee bonds into a TDX-gated surface pins the content hash and redeploys on rotation.

The split decouples TDX Measurements rotation churn from coalition- level identity. A coalition referencing ten members rotating TDX monthly sees its COALITION_ROOT change only on member retirement or coalition membership change, not on routine operational rotations.

Open for v0.3 decision. Whether content-hash- pinning is a per-component 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.

Composite organisms as a recurring pattern

Composite organisms 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 member fingerprints. A composite organism’s identity does not fold a coalition root:

  ORGANISM(cfg) = blake3(
                      "organism|"
                      || SCHEMA_VERSION_U8
                      || "|" || name
                      || ordered spanned lattice references
                      || ordered spanned organism references
                      || content_fingerprint
                      || acl_fingerprint
                    )

  ORGANISM_committee = ORGANISM(cfg).derive("committee")

A composite organism is therefore referenced by zero, one, or many coalitions exactly as a lattice or any other standalone organism is. Its committee, state machine, and log are shared across those references. A composite organism misconfigured against the wrong spanned members 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-member, or cross-coalition atomicity

Every rung below refuses cross-Group atomicity. The coalition rung refuses cross-member and cross-coalition atomicity by the same argument:

  • Cross-Group (one member, 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-member (one coalition, two members) — refused by this blueprint. Composite organisms read public surfaces of the members they span; their state machine commits its own facts. A composite organism never forces two members 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 composite organism whose content fingerprint folds the relevant members (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 OrganismConfig and OrganismRef structs. Every organism (including composite organisms) and every standalone-organism reference has these shapes.
  • The five 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.
  • Membership 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 composite organisms. Each arrives when a cross-member 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 (deferred-primitives space)

A family of primitives the blueprint refuses:

  • Enforceable policies over members — a CoalitionConfig declaring constraints members must satisfy (TDX Measurements requirements, bond minimums, code-audit attestations).
  • Taxation — a protocol-level mechanism for the coalition to collect fees from members’ commits.
  • Judiciary — an organism whose commits have protocol-level precedence over individual members’, resolving disputes.
  • Mandatory membership — any primitive whose absence renders a member’s commits invalid in the coalition’s view.
  • Legislation — a state machine in the coalition that members 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 opt-in participation. Any future deferred-primitives 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 “deferred-primitives space” when the topic arises so the 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;
pub const COALITION_ROOT_SEED:  UniqueId = /* fixed */;

#[non_exhaustive]
pub struct CoalitionConfig<'a> {
    /// Schema version byte. Folded as the first element of
    /// every top-level identity preimage.
    pub schema_version: u8,

    /// Coalition root domain-separation seed. Constant
    /// across the blueprint; carried explicitly so the
    /// fingerprint stays self-describing.
    pub coalition_seed: UniqueId,

    /// Short, stable, namespaced name chosen by the
    /// coalition operator (e.g. "ethereum.superchain",
    /// "signals.euro-fx", "searcher-α").
    pub instance_name: &'a str,

    /// Ordered set of lattice stable ids this coalition
    /// references. Held by stable id so the coalition does
    /// NOT re-derive any lattice's organism IDs; a lattice
    /// retains a single canonical identity across every
    /// coalition that references it.
    pub lattices: &'a [UniqueId],

    /// Ordered set of organism references this coalition
    /// composes — any mosaik organism with a Config
    /// fingerprint living directly on the universe,
    /// including composite organisms whose `Config` folds
    /// multiple member references. A composite organism's
    /// identity is derived independently of any coalition;
    /// the same composite organism may be referenced by
    /// many coalitions.
    pub organisms: &'a [OrganismRef<'a>],

    /// Basic services the coalition ships. Each field
    /// holds an `OrganismRef` when shipped, `None`
    /// otherwise. See [basic services](./basic-services.md).
    pub atlas:      Option<OrganismRef<'a>>,
    pub almanac:    Option<OrganismRef<'a>>,
    pub chronicle:  Option<OrganismRef<'a>>,
    pub compute:    Option<OrganismRef<'a>>,
    pub randomness: Option<OrganismRef<'a>>,

    /// Optional coalition-scoped ticket issuer.
    /// Non-authoritative: no component is required to
    /// accept tickets from this issuer. Organisms opt in
    /// via their own TicketValidator composition.
    pub ticket_issuer: Option<TicketIssuerConfig<'a>>,

    /// Per-coalition retirement policy. Default: explicit
    /// republication on every content change.
    pub retirement_policy: RetirementPolicy,
}

#[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, TDX Measurements, policy).
    /// Components that pin this redeploy on operational
    /// rotations.
    pub content_hash: Option<UniqueId>,
}

impl CoalitionConfig<'_> {
    /// blake3("coalition|" || schema_version || "|"
    ///         || coalition_seed || "|"
    ///         || instance_name || ...)
    pub const fn stable_id(&self) -> UniqueId { /* ... */ }
}

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 member or organism handles they need:

use std::sync::Arc;
use mosaik::Network;
use builder::{LatticeConfig, UNIVERSE};
use coalition::{
    CoalitionConfig,
    SCHEMA_VERSION_U8, COALITION_ROOT_SEED,
    RetirementPolicy,
};

const ETH_SUPERCHAIN: CoalitionConfig<'static> =
    CoalitionConfig {
        schema_version:    SCHEMA_VERSION_U8,
        coalition_seed:    COALITION_ROOT_SEED,
        instance_name:     "ethereum.superchain",
        lattices: &[
            ETH_MAINNET.stable_id(),
            UNICHAIN_MAINNET.stable_id(),
            BASE_MAINNET.stable_id(),
        ],
        organisms: &[
            EUR_PRICE_FEED,
            INTENTS_ROUTER,
            SHARED_LEDGER,
        ],
        atlas:             Some(SUPERCHAIN_ATLAS_ORG),
        almanac:           None,
        chronicle:         None,
        compute:           None,
        randomness:        None,
        ticket_issuer:     None,
        retirement_policy: RetirementPolicy::default(),
    };

#[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.organisms[1]).await?;
    let atlas    = atlas::Atlas::<MemberCard>::read(&network, ETH_SUPERCHAIN.atlas().unwrap()).await?;
    // ...
    Ok(())
}

Every organism (composite, module, or standalone) 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.

The coalition crate’s convenience surface

The coalition crate re-exports a small convenience surface so that book-level examples, composite organisms, and integrator quickstarts can use the same vocabulary without each reaching into mosaik’s module layout directly. The surface is stable across the blueprint; downstream crates depend on it rather than on mosaik’s primitive modules where both are interchangeable.

SymbolProvenance / role
coalition::Stream<T>Alias for mosaik::streams::Producer<T> on the write side, mosaik::streams::Consumer<T> on the read side.
coalition::Collection<K, V>Alias for mosaik::collections::Map<K, V>.
coalition::Network (re-export)mosaik::Network verbatim.
coalition::Network::subscribe_of::<T>(id)Extension trait wrapping network.streams().consumer::<T>().with_stream_id(id).build() for ergonomic organism reads.
coalition::TicketValidator (re-export)mosaik::TicketValidator verbatim.
coalition::Tdx (re-export)mosaik::tee::Tdx; carries .require_mrtd(...) and .verify_peer(...).
coalition::acl::ReputationFloorTicket composer binding admission to a reputation card floor. Consumes (UniqueId, f32).
coalition::acl::MarketSettlementTicket composer binding admission to a market settlement pointer.
coalition::when!Declarative macro for reactive conditions over Streams; compiles to .when() method chains on mosaik::streams.
coalition::unique_id! / const_blake3!Compile-time blake3 of byte literals; convenience wrapper around mosaik::primitives::unique_id!.
coalition::zipnet::SealClientThin wrapper over zipnet::Zipnet::<D>::submit(&network, &Config) that carries the committed digest back to the caller.
coalition::RetirementPolicyPer-coalition retirement policy; default is explicit republication on every content change.

The convenience surface is the book’s one central declaration of these symbols. Every chapter that uses Stream, Collection, ReputationFloor, when!, or SealClient imports them from coalition::*, not from mosaik/zipnet directly. Downstream crates that want the raw primitives are free to bypass the convenience surface, but the book’s prose does not.

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 TDX Measurements for every organism (composite, module, or standalone) whose admission is TDX-attested. Per-member TDX Measurements are published by the per-member 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 member fingerprint ordering, or in any organism’s parameters 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 member’s owner. A coalition references a member; per-member 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 members. Every member and organism 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 members. Each member’s TicketValidator composition is unchanged; each organism 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, organism categories, or composite organisms. Real members and composite organisms ship when a concrete problem forces them.

Four conventions (three inherited, one added)

Every organism the blueprint references — whether standalone, composite, or a module — 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 composite organism, and every module.

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

  4. Membership-by-reference, module-by-derivation. New for the coalition rung. A CoalitionConfig folds member fingerprints as content without re-deriving their organism IDs; composite organisms 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 member, composite organism, and consumed module.

  • Operator pacing. A coalition starts as one operator’s set of members, adds a second operator’s member without touching the first’s, and adds a composite organism or module without forcing either per-member operator to change anything.

  • Clean retirement. A commissioned composite organism retires by the coalition’s next CoalitionConfig omitting it — or, if the organism 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-member services as organisms, not protocols. A shared-ledger composite organism, 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, organism categories, and composite organisms 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.

Member upgrades churn coalition-scoped identity

A member-level change bumping its stable id — a retirement — changes the corresponding entry in COA_LATTICES or COA_ORGANISMS for every coalition that references the member, 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: TDX Measurements rotations do not change the stable id and therefore do not cascade. A retirement does; retirement is rare and announced.

Commissioned composite organisms do not derive under COALITION_ROOT, so retirements ripple through modules only. A composite organism re-derives only when the members it itself spans retire or when its own content changes.

Wide composite organisms are heavy

A composite organism reading public surfaces from ten members 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 composite organism over interesting member pairs, composed by an integrator, is often cheaper and more debuggable than one wide composite organism.

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-member operators to discover which coalitions reference their member. A per-member operator objecting to being referenced has no technical recourse — anyone with a list of member stable ids can publish a CoalitionConfig.

This is intentional. The only cryptographic check is whether each referenced member admits the composite organisms’ committee peers via its own TicketValidator. Per-member and coalition operators negotiate references out of band.

Checklist for commissioning a new composite organism

  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 members it spans. Record as ordered sequences of member references in the composite organism’s Config — lattices, organisms, or both. Order matters; changing it changes the fingerprint.

  3. Pick the composite organism’s name. Folded into the organism’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. Organism::<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 composite organism wants to recognise any coalition’s ticket issuer, its validator composition includes that issuance root directly; the composite organism chooses, the coalition does not impose.

  7. Decide per-member-failure behaviour. If member A is down, does the composite organism keep processing remaining members’ inputs or stall? Document as part of the composition contract.

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

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

  10. Answer: does this composite organism meaningfully aggregate across members, or is it an integrator in disguise? If an ordinary integrator holding the same N member handshakes could do the same work, skip the composite organism. The 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 members bumps its fingerprint?” is not defined, the design is incomplete.

Cross-references