For the complete documentation index, see llms.txt. This page is also available as Markdown.

Events

Events serve two distinct purposes, and every DeRecEvent falls into one of them:

  • Notification. Most events tell the application what has happened as a result of driving a flow — a channel became Paired, a share was stored, a Secret was recovered, an inbound Secret sync arrived. The application reacts to these to update its own UI, its own state, and downstream systems. The protocol has already decided what to do; the event is the report.

  • Control point. One event variant — DeRecEvent::ActionRequired { channel_id, action: PendingAction } — pauses a flow and hands the decision back to the application (or to the human behind it). The application either calls accept(action) to let the flow proceed on protocol-correct terms, or reject(action, status, memo) to refuse. This is how the application (or its user) gates every inbound request that admits an application-level choice — accepting a pairing, storing a share, disclosing a share during recovery, and so on. ActionRequired is the mechanism by which policy — not just protocol — reaches the wire.

This chapter is the reference enumeration of every DeRecEvent the orchestrator emits and every PendingAction the application can be asked to resolve. Per-flow walkthroughs of when each fires live in Flows; SDK-specific naming (Rust enum variants, .NET subclass records, TypeScript discriminated unions) lives in the SDK chapters.

How events are surfaced

The DeRecProtocol orchestrator is event-driven by design. protocol.process(envelope_bytes) decodes inbound wire bytes and returns a Vec<DeRecEvent> describing what changed; an empty vector or a single NoOp means nothing actionable happened. protocol.start(flow) may also queue events that fire on a later process call (for example SharingComplete once every targeted Helper has responded or timed out).

DeRecEvent::ActionRequired — the control-point event described above — is what gives the application its say. Every inbound request that needs an application-level policy decision is funnelled through ActionRequired: the orchestrator has decoded the request, validated its shape, and is ready to send the protocol-correct response — but it will not do so until the application (or its user) has been consulted. accept(action) releases the response; reject(action, status, memo) sends back a refusal with the supplied status and human-readable reason.

DeRecEvent::NoOp is the explicit "nothing meaningful happened" signal — emitted for stray or replayed inbound responses that the library safely dropped (see Verification binding gate for one example). Applications can usually ignore NoOp; it exists so the event stream is total rather than carrying a None semantic.

The event surface is identical across SDKs — only the language-specific naming differs. The variant set, field set, and emission semantics are defined once by the orchestrator API; bindings just re-expose the same values under language-idiomatic names. See Cross-SDK naming at the bottom of this page.

Event catalogue

Events are grouped by the flow that produces them, matching the order in Flows. Each entry documents the field shape and what state transition triggers emission.

Pairing

PairingCompleted

The pair handshake closed successfully on this side. Helper/Owner channels reach Paired status immediately; replica channels stay Pending until both sides also complete fingerprint cross-confirmation.

Field
Type
Description

channel_id

ChannelId

The channel the handshake completed on.

kind

SenderKind

This node's role on the channel, persisted as Channel.role. Drives flow gating from here on.

peer_communication_info

HashMap<String, String>

Key-value identity metadata extracted from the peer's CommunicationInfo (e.g. "name", "email"). Empty if absent.

Triggers:

  • Owner side: arrival of a PairResponseMessage whose nonce and binding fields validate against the cached pairing state.

  • Helper / responder side: completion of accept on the corresponding PendingAction::Pairing.

See Pairing.

ReplicaPaired

Fires alongside PairingCompleted on replica-mode channels. Carries the peer's stable replica_id, which the application needs as a from_replica_id correlation handle for later Secret syncs.

Field
Type
Description

channel_id

ChannelId

The channel the pair handshake completed on.

peer_replica_id

u64

Peer's replica_id, extracted from the derec.replica_id entry in their CommunicationInfo.

Triggers:

  • Same as PairingCompleted, restricted to channels whose role is ReplicaSource or ReplicaDestination.

See Pairing.

ReplicaSecretReceived

A ReplicaSource peer pushed a Secret snapshot on a ReplicaDestination channel. The library has already auto-acked the inbound StoreShareRequest and decoded the ReplicaSecretPayload into typed fields. The destination can install secret directly, optionally using shares to verify or to take over recovery toward each helper.

Field
Type
Description

channel_id

ChannelId

The channel the Secret sync arrived on.

from_replica_id

u64

Peer's replica_id (from Channel.replica_id, populated at pair time).

secret_id

u64

Echoed from the inbound StoreShareRequest.

version

u32

Echoed from the inbound StoreShareRequest.

secret

Secret

Decoded full Secret: helpers, secrets, replicas, owner_replica_id. See the Secret payload section in Messages.

shares

Vec<ChannelShare>

Per-helper VSS share map. Each entry pairs a helper's channel_id with the serialized CommittedDeRecShare bytes that helper holds.

Triggers:

  • Inbound StoreShareRequest on a ReplicaDestination channel whose payload decodes as a ReplicaSecretPayload.

See Pairing and Sharing.

ReplicaSecretAcked

A replica peer's StoreShareResponse to a Secret sync this node sent earlier. Fires on the ReplicaSource side and mirrors ShareConfirmed / ShareRejected on the helper side.

Field
Type
Description

channel_id

ChannelId

The channel the response arrived on.

from_replica_id

u64

Peer's replica_id (from Channel.replica_id).

secret_id

u64

Echoed from the response.

version

u32

Echoed from the response.

status

i32

StatusEnum value from StoreShareResponseMessage.result.

memo

String

Human-readable reason from the peer (empty on Ok).

Triggers:

  • Inbound StoreShareResponse on a ReplicaSource channel correlating to an outbound Secret-sync request.

Sharing

ShareStored

A share was accepted and stored locally on the helper side after the application's accept resolved the matching PendingAction::StoreShare.

Field
Type
Description

channel_id

ChannelId

Channel from the inbound request.

version

u32

Version stored.

replica_id

Option<u64>

Stable per-device identifier of the writer, copied from StoreShareRequestMessage.replica_id. None when the writer was a non-replica Owner.

Triggers:

  • protocol.accept on a PendingAction::StoreShare after the share has been persisted via DeRecShareStore::save.

See Sharing.

ShareConfirmed

A helper confirmed it stored an owner-side share. { channel_id: ChannelId, version: u32 }.

Triggers:

  • Inbound StoreShareResponseMessage with result.status == Ok for a known outbound share.

See Sharing.

ShareRejected

A helper rejected or failed to store a share. The protocol absorbs the failure and surfaces it as an event so the application can render per-participant progress.

Field
Type
Description

channel_id

ChannelId

Channel of the rejecting helper.

version

u32

Version that was being distributed.

status

i32

StatusEnum value from the helper's response.

memo

String

Human-readable reason from the helper, or "timeout" (synthetic).

Triggers:

  • Inbound StoreShareResponseMessage with non-Ok status.

  • Local timeout firing on an outstanding outbound store-share request.

See Sharing.

SharingComplete

Aggregate result for one DeRecFlow::ProtectSecret round. Emitted exactly once per round after every targeted helper has either confirmed, rejected, or timed out.

Field
Type
Description

version

u32

The version this round distributed.

confirmed_count

usize

Number of helpers that confirmed with Ok.

failed_count

usize

Number of helpers that rejected or timed out.

threshold_met

bool

true when confirmed_count >= threshold.

Triggers:

  • Resolution of the last outstanding store-share request in the round.

See Sharing.

Verification

ShareVerified

A helper's VerifyShareResponse passed both the (nonce, secret_id, version) binding gate and the SHA-384 hash check. { channel_id: ChannelId, version: u32 }.

Triggers:

  • Inbound VerifyShareResponseMessage that binds to an outstanding VerifyShareRequest and whose proof hash matches the locally-recomputed SHA-384(share || nonce).

See Verification.

Discovery

SecretsDiscovered

A helper returned its catalogue of (secret_id, version, replica_id) tuples it currently holds for this Owner identity. Application persists this and uses it to drive DeRecFlow::RecoverSecret and to detect concurrent-write conflicts between sibling Replicas.

Field
Type
Description

channel_id

ChannelId

Channel of the responding helper.

secrets

Vec<SecretVersionEntry>

One entry per known secret_id, each carrying a Vec<VersionEntry> of (version, description, replica_id).

Each VersionEntry carries a replica_id field that lets the application detect concurrent writes — two distinct replica_ids with the same version for the same secret_id signal a conflict the application must reconcile before driving recovery.

Triggers:

  • Inbound GetSecretIdsVersionsResponseMessage correlating to an outstanding DeRecFlow::Discovery request.

See Discovery.

Recovery

RecoveryShareReceived

A GetShareResponse was accumulated into the recovery context but the threshold of valid shares has not yet been collected.

Field
Type
Description

channel_id

ChannelId

Helper that sent this share response.

shares_received

usize

Total share responses collected so far for the current (secret_id, version) context.

Triggers:

  • Inbound GetShareResponseMessage whose share decoded cleanly but did not yet complete reconstruction.

See Recovery.

RecoveryShareError

A GetShareResponse was received but reconstruction failed for a reason other than insufficient shares (corrupted share, version mismatch, decode error, Merkle-root inconsistency).

Field
Type
Description

channel_id

ChannelId

Helper that sent the offending share response.

shares_received

usize

Total share responses collected so far.

error

String

Description of the failure cause.

Triggers:

  • Inbound GetShareResponseMessage whose share could not be reconciled with the accumulating recovery context.

See Recovery.

SecretRecovered

Reconstruction succeeded. The reconstructed Secret is returned exactly once.

Field
Type
Description

secret

Vec<u8>

The reconstructed Secret bytes. Decode as a Secret message to access helpers, secrets, replicas, and owner_replica_id.

Triggers:

  • Threshold of valid, mutually-consistent shares accumulated for (secret_id, version).

See Recovery.

UpdateChannelInfo

ChannelInfoUpdated

The stored Channel record was updated with new communication_info and/or transport_protocol. Surfaces on both sides of the flow: on the responder side from accept, on the initiator side from process when the peer's Ok response arrives. { channel_id: ChannelId }.

The post-update values are already on the local Channel by the time the event fires; applications that care about the new state read it from the channel store directly.

Triggers:

  • Responder: protocol.accept on a PendingAction::UpdateChannelInfo writes the new fields to the Channel record.

  • Initiator: inbound UpdateChannelInfoResponseMessage with Ok status.

See UpdateChannelInfo.

ChannelInfoUpdateRejected

The peer answered an outbound UpdateChannelInfo with a non-Ok status. The peer's stored state is unchanged; the initiator's local state is also unaffected.

Field
Type
Description

channel_id

ChannelId

Channel the update was targeting.

status

i32

StatusEnum value from the peer's response.

memo

String

Human-readable reason from the peer.

Triggers:

  • Inbound UpdateChannelInfoResponseMessage with non-Ok status.

Unpair

Unpaired

The local channel/share/secret state for channel_id has been dropped as the result of an unpair flow. { channel_id: ChannelId }.

Surfaces on both sides of the flow:

  • Initiator: emitted when (a) UnpairAck::NotRequired and the request has just been sent, (b) UnpairAck::Required and the peer acknowledged with Ok, or (c) UnpairAck::Required and the configured timeout elapsed without a response.

  • Responder: emitted by accept after the channel/share/secret state for the requesting peer has been removed.

Triggers:

  • See per-side conditions above. Self-initiated, peer-initiated, and timeout-induced tear-downs all surface through the same event variant; distinguishing them is the application's responsibility (typically by tracking whether start(Unpair) was called locally).

See Unpair.

UnpairRejected

The peer answered an outbound unpair request with a non-Ok status. The initiator's local state is not dropped — the application decides whether to retry, escalate, or force-delete locally.

Field
Type
Description

channel_id

ChannelId

Channel the unpair was targeting.

status

i32

StatusEnum value from the peer's response.

memo

String

Human-readable reason from the peer.

Triggers:

  • Inbound UnpairResponseMessage with non-Ok status.

See Unpair.

Pairing — PrePair tail

PrePairRejected

The contact creator answered the scanner's PrePairRequest with a non-Ok status (the HashedKeys contact-delivery sub-flow). The scanner cannot proceed to a normal PairRequest because the public keys were never published.

Field
Type
Description

channel_id

ChannelId

Channel the pre-pair was targeting.

status

i32

StatusEnum value from the contact creator's response.

memo

String

Human-readable reason from the contact creator.

Distinct from PairingError::PrePairHashMismatch, which fires when keys were published but failed the binding-hash check — that surfaces as a thrown error from process(), not an event.

Triggers:

  • Inbound PrePairResponseMessage with non-Ok status on a HashedKeys flow.

See Pairing — HashedKeys sub-flow.

Lifecycle and housekeeping

ActionRequired

An inbound request needs application confirmation before the library responds.

Field
Type
Description

channel_id

ChannelId

Channel the inbound request arrived on.

action

PendingAction

Opaque token round-tripped to protocol.accept or protocol.reject. See the PendingAction catalogue.

Triggers:

  • Any inbound request message that admits an application-level policy decision.

NoOp

Explicit "nothing meaningful happened" — emitted for stray or replayed inbound responses the library safely dropped. Carries no fields. Applications can usually ignore.

Triggers:

  • Inbound response that fails the per-flow binding gate (e.g. a VerifyShareResponse whose nonce does not match any outstanding request — see Verification binding gate).

  • Replay of an already-processed response.

PendingAction catalogue

PendingAction is the opaque token carried by DeRecEvent::ActionRequired. The application must call protocol.accept(action) to drive the protocol-correct response, or protocol.reject(action, status, memo) to refuse. Each variant carries the inbound request payload plus the trace id, shared key, and any other state the orchestrator needs at resolution time.

Pairing

PendingAction::Pairing

The peer sent a PairRequest. Emitted on the responder side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

PairRequestMessage

The decoded inbound request (peer's public keys, senderKind, communicationInfo, ...).

pairing_secret

PairingSecretKeyMaterial

The local ephemeral ECIES / ML-KEM material the responder will use to derive the shared key.

kind

SenderKind

The peer's declared role on this channel.

peer_communication_info

HashMap<String, String>

Identity metadata copied from the peer's CommunicationInfo.

trace_id

u64

Trace id read from the inbound envelope, echoed verbatim on the response.

accept derives the shared key, persists the channel, and sends back a PairResponseMessage with Ok. reject sends a non-Ok response and discards the pairing state.

See Pairing.

PendingAction::PrePair

The peer scanned a HashedKeys-mode ContactMessage and is asking for the real pairing public keys via a plaintext PrePairRequest. Emitted on the contact-creator side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

PrePairRequestMessage

The decoded inbound PrePairRequest.

trace_id

u64

Trace id read from the inbound envelope, echoed on the response.

accept loads PairingSecret from the secret store and replies with the actual mlkemEncapsulationKey / eciesPublicKey; the scanner then validates the published keys against the contact's contactBindingHash and proceeds to a normal PairRequest. reject sends back a non-Ok PrePairResponse and keeps no state.

See Pairing — HashedKeys sub-flow.

Sharing

PendingAction::StoreShare

The peer sent a StoreShareRequest. Emitted on the helper side (and on ReplicaDestination channels carrying a ReplicaSecretPayload, where the orchestrator auto-accepts internally before surfacing ReplicaSecretReceived).

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

StoreShareRequestMessage

The decoded inbound request.

shared_key

SharedKey

The post-pairing symmetric key for this channel.

trace_id

u64

Trace id read from the inbound envelope, echoed on the response.

accept persists the share via DeRecShareStore::save and sends back Ok. reject returns a non-Ok and stores nothing.

See Sharing.

Verification

PendingAction::VerifyShare

The peer sent a VerifyShareRequest. Emitted on the helper side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

VerifyShareRequestMessage

The decoded inbound request (carries secret_id, version, nonce).

shared_key

SharedKey

Channel symmetric key.

trace_id

u64

Trace id, echoed verbatim on the response.

accept loads the stored share for (secret_id, version), computes SHA-384(share || nonce), and returns the proof. reject returns a non-Ok and discloses nothing.

See Verification.

Discovery

PendingAction::Discovery

The peer is asking the helper to enumerate the (secret_id, version) tuples it holds for them. Emitted on the helper side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

GetSecretIdsVersionsRequestMessage

The decoded inbound request.

shared_key

SharedKey

Channel symmetric key.

trace_id

u64

Trace id, echoed verbatim on the response.

accept builds the catalogue from DeRecShareStore::load_all and replies. reject returns a non-Ok and discloses no catalogue content.

See Discovery.

Recovery

PendingAction::GetShare

The peer sent a GetShareRequest. Emitted on the helper side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

GetShareRequestMessage

The decoded inbound request (carries secret_id, version).

shared_key

SharedKey

Channel symmetric key.

trace_id

u64

Trace id, echoed verbatim on the response.

accept returns the stored committed share for (secret_id, version). reject returns a non-Ok and discloses nothing.

See Recovery.

UpdateChannelInfo

PendingAction::UpdateChannelInfo

The peer announced an update to their communication_info and/or transport endpoint. Emitted on the receiver side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

UpdateChannelInfoRequestMessage

The decoded inbound request (carries the new fields).

shared_key

SharedKey

Channel symmetric key.

trace_id

u64

Trace id, echoed verbatim on the response.

accept writes the new fields onto the stored Channel record automatically — the application does not call set_communication_info / set_own_transport here. When transport_protocol is part of the update, the response is routed to the new endpoint, so subsequent outbound traffic on this channel already targets the new address. See UpdateChannelInfo for the endpoint-changeover discipline. reject sends a non-Ok response and leaves the stored channel state unchanged.

Unpair

PendingAction::Unpair

The peer is asking this node to drop its state for the channel. Emitted on the receiver side.

Field
Type
Description

channel_id

ChannelId

Channel the request arrived on.

request

UnpairRequestMessage

The decoded inbound request (carries memo).

shared_key

SharedKey

Channel symmetric key.

trace_id

u64

Trace id, echoed verbatim on the response.

accept deletes the local channel/share/secret state for this peer and sends back Ok (also emits Unpaired). reject sends a non-Ok response and keeps the state.

See Unpair.

Cross-SDK naming

Variant names differ slightly per SDK but the semantics are identical. The table below maps a representative subset; remaining variants follow the same convention (Rust DeRecEvent::Foo → .NET FooEvent → TypeScript { type: "Foo", ... }).

Events

Rust
.NET
TypeScript

DeRecEvent::PairingCompleted

PairingCompletedEvent

{ type: "PairingCompleted", ... }

DeRecEvent::ActionRequired

ActionRequiredEvent

{ type: "ActionRequired", ... }

DeRecEvent::ShareStored

ShareStoredEvent

{ type: "ShareStored", ... }

DeRecEvent::SharingComplete

SharingCompleteEvent

{ type: "SharingComplete", ... }

DeRecEvent::ShareVerified

ShareVerifiedEvent

{ type: "ShareVerified", ... }

DeRecEvent::SecretsDiscovered

SecretsDiscoveredEvent

{ type: "SecretsDiscovered", ... }

DeRecEvent::SecretRecovered

SecretRecoveredEvent

{ type: "SecretRecovered", ... }

DeRecEvent::ChannelInfoUpdated

ChannelInfoUpdatedEvent

{ type: "ChannelInfoUpdated", ... }

DeRecEvent::Unpaired

UnpairedEvent

{ type: "Unpaired", ... }

DeRecEvent::NoOp

(filtered by DeRecEventConverter)

{ type: "NoOp" }

Actions

PendingAction does not surface as a top-level discriminator in the bindings — both .NET and TypeScript carry it as the action payload of ActionRequired, with a side-channel discriminator (Kind / action_kind) the application switches on.

Rust
.NET
TypeScript

PendingAction::Pairing

ActionRequiredEvent with Kind == Pairing

ActionRequired event with action_kind: "pairing"

PendingAction::PrePair

ActionRequiredEvent with Kind == PrePair

ActionRequired event with action_kind: "pre_pair"

PendingAction::StoreShare

ActionRequiredEvent with Kind == StoreShare

ActionRequired event with action_kind: "store_share"

PendingAction::VerifyShare

ActionRequiredEvent with Kind == VerifyShare

ActionRequired event with action_kind: "verify_share"

PendingAction::Discovery

ActionRequiredEvent with Kind == Discovery

ActionRequired event with action_kind: "discovery"

PendingAction::GetShare

ActionRequiredEvent with Kind == GetShare

ActionRequired event with action_kind: "get_share"

PendingAction::UpdateChannelInfo

ActionRequiredEvent with Kind == UpdateChannelInfo

ActionRequired event with action_kind: "update_channel_info"

PendingAction::Unpair

ActionRequiredEvent with Kind == Unpair

ActionRequired event with action_kind: "unpair"

Refer to the SDK chapters for the exact field shapes and decoding helpers each binding ships.

Last updated