Marmot-TS
    Preparing search index...

    Class MarmotGroupEngine<TEnvelope>

    Transport-agnostic MLS group state machine: ingest, send intents, fork recovery, and publish-before-apply lifecycle for local commits.

    This class is a coordinator. The heavy concerns live in focused modules it composes: retained history (RetainedHistoryStore), convergence fork recovery (ForkRecovery), and the inbound pipeline (ingestEnvelopes). The engine owns only the live state and lifecycle, the send path, and the wiring between those modules — mirroring darkmatter's cgka-engine split across message_processor/{ingest,send,store}, fork_recovery, and epoch_manager.

    Type Parameters

    • TEnvelope
    Index
    ciphersuite: CiphersuiteImpl
    • get convergenceStatus(): ConvergenceStatus

      The derived convergence status (group-state.md §Convergence status, B5): Syncing while the quiescence window since the last convergence-relevant input has not elapsed, then Resolving / Blocked / Settled per the last pass. Recomputed on every read against the injected clock, so it advances to Settled as wall-clock time passes even with no new input.

      Returns ConvergenceStatus

    • Applies staged state after publish confirmation (publish-before-apply).

      CR-09: selfUpdate takes the identical path to commit — it is a commit in every sense that matters here (it advances the epoch and produces a new confirmation tag), so it must be recorded into retained history and the fork tree. Recording it only via #setState left RetainedHistoryStore with no stateAt(newEpoch) (so resolveFork could never rebuild across a selfUpdate) and the tree with no node for the new tip (so the next GroupRegistry.#loadHistory discarded the entire persisted fork history). Since refs/marmot/protocol-core/joining.md tells clients to selfUpdate immediately after joining from a Welcome, the normal join path destroyed its own convergence persistence.

      Parameters

      Returns StateNotification[]

    • Ingests transport envelopes and applies MLS messages to group state.

      WR-04 — terminal facts after an envelope-free rewind: most results name their triggering envelope, but a rewind driven entirely by pool replay or by the persisted history tree has none. Such a rewind reports itself as appliedNotifications results, which now carry selectedTerminal and removedFromGroup so a consumer building its own transport can see a disband selection or its own removal without reaching into engine state.

      A rewind that produced NO notifications yields no result at all, so those two facts have nothing to ride on. Consumers that must not miss them — rather than merely observe them — should re-read selectedDisbandEvidence and state.groupActiveState after fully draining this generator. The client layer (MarmotGroup, GroupSession) already does exactly that.

      Parameters

      • envelopes: TEnvelope[]
      • Optionaloptions: { maxRetries?: number }

      Returns AsyncGenerator<DispositionedIngestResult<TEnvelope>>

      DispositionedIngestResult - processing result plus inbound Disposition.

    • The undecryptable events currently held in the ingestion pool, oldest-first: received transport envelopes that have not yet decrypted/processed into the history tree (e.g. a newer-epoch message awaiting its commit, or a fork message awaiting its branch). They are retried as the tree grows; an entry that never clears is a received event the unlocking state never arrived for.

      Returns TEnvelope[]

    • Drives one tree-fed re-convergence pass to completion, switching to the canonical branch if the persisted history now favors a competing fork. Public entry for the load path (after the engine hydrates from the tree) and any caller wanting an explicit re-evaluation. Witness-free — the structural keys decide; witnesses refine on the next live ingest/sweep.

      Returns every result the pass produced, dispositioned and audited exactly as ingest does, so a caller can route them through the identical handler.

      CR-06: these results MUST reach the caller. #reconvergeFromTree is the only site that can yield the stateInvalidated withdrawal proving a rewind superseded the commit that removed us, and that withdrawal is what clears the persisted removed-inactive marker (CONV-03, D-12). While this method drained into void _, a client that was removed on a losing fork, restarted, and re-converged onto a branch where it is still a member ended up with canonical membership restored AND a stale marker still set — silently suppressing the next genuine removal.

      Returns Promise<DispositionedIngestResult<TEnvelope>[]>

    • The retained canonical states within the rollback horizon, newest epoch first. Used for cross-epoch encrypted-media decryption: media is keyed by its source-epoch exporter secret, which is not carried on the wire, so a receiver tries each still-retained epoch's key. States older than max_rewind_commits are pruned (retained-history.md); media from a pruned epoch can no longer be decrypted.

      Returns ClientState[]