ReadonlyciphersuiteReadonlypeelerSnapshot of the active immutable pass, exposed for scheduler diagnostics.
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.
The full-fork history tree: every group state observed — the canonical branch and every fork — keyed by MLS confirmation tag. Read-only structural access; the engine grows it as commits and proposals arrive.
The group's lifecycle state (group-state.md). A new local commit may only
be prepared while Stable; the commit flow moves through PendingPublish
(commit prepared, publish unconfirmed) and Merging (publish acked, staged
commit applying) and back to Stable.
Number of undecryptable events currently held in the ingestion pool.
Whether the group's canonical GroupContext still classifies as the
current account identity proof profile (D-11). A derived read, computed
fresh from this.#state on every access — never cached — so it always
reflects the latest adopted state. Never throws.
Number of envelopes retained but not yet admitted to a convergence pass.
Canonical terminal evidence retained until the client persists its tombstone.
Opens or refreshes the current collection pass from the monotonic clock.
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.
Current durable irreversible request, hydrated before this promise resolves.
Releases engine resources — currently the pending settle-check timer. Called on group teardown (destroy/unload) so no timer outlives the group.
Admits retained input into a later pass once lifecycle and the prior fixed deadline permit it. Each call is a deterministic one-shot scheduler edge.
Atomically enables lifecycle-v1 for a legacy group when every leaf supports it.
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.
Optionaloptions: { maxRetries?: number }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.
Flushes restart-critical terminal candidate/pass evidence.
Reverts lifecycle when a staged commit publish fails or is abandoned.
Covers both commit-producing seams (CR-09/WR-17): a selfUpdate now also
transitions to PendingPublish, so a failed publish must roll it back or
the engine would be stuck unable to prepare any further commit.
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.
Persist irreversible intent, then prepare its exact candidate against this epoch.
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.
Executes a local send intent and returns the wrapped transport envelope.
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-enginesplit acrossmessage_processor/{ingest,send,store},fork_recovery, andepoch_manager.