Context & Groups

calimero-context + calimero-context-primitives + calimero-context-config

Purpose

ContextManager is an Actix actor that manages contexts (application instances), groups (governance units), and namespace governance DAGs. Every context belongs to a group within a namespace. The manager holds per-namespace DagStores (via HashMap<namespace_id, Arc<Mutex<DagStore>>>), handles 30+ message types, coordinates with a modular GroupStore for persistence, and runs lifecycle recovery and Prometheus metrics collection.

ContextManager actor contexts cache · namespace_dags HashMap GroupStore (modular) · namespace governance 30+ Handler<ContextMessage> variants lifecycle recovery · metrics · 30s heartbeat crates/context/src/lib.rs Server / RPC ContextClient NodeManager delta routing GroupStore persistence NetworkManager gossip publish

Module Structure

calimero-context

lib.rs
ContextManager actor — startup, fields, Actix lifecycle, namespace DAG management
lifecycle.rs
Startup recovery: recover_in_progress_upgrades(), start_namespace_heartbeat() (30s periodic)
metrics.rs
Prometheus metrics: execution count/duration, namespace retry/decode events, membership policy rejections
group_store/mod.rs
Authoritative persistence layer (refactored into modules) — apply_local_signed_group_op
group_store/namespace.rs
Namespace identity resolution: resolve_namespace(), get_or_create_namespace_identity_bundle()
group_store/namespace_governance.rs
Namespace governance DAG application, key unwrapping, skeleton storage
group_store/namespace_membership.rs
Namespace membership tracking and policy enforcement
group_store/membership.rs
Group membership management with parent chain walking (max depth 16)
group_store/group_keys.rs
Group encryption key management: generate, store, wrap/unwrap via ECDH
group_store/capabilities.rs
Per-member capability bitmask management
group_store/context_tree.rs
Context-group binding: register, detach, list contexts per group
group_store/tests.rs
Comprehensive group store tests including namespace identity, governance, key delivery
governance_dag.rs
DAG bridge — GroupGovernanceApplier + NamespaceGovernanceApplier
handlers/
30+ handler files, one per ContextMessage variant (create, join, delete, execute, namespace ops, etc.)

calimero-context-primitives

client/mod.rs
ContextClient typedef — thin async façade wrapping LazyRecipient<ContextMessage>
client/sync.rs
Sync-related client helpers (group delta request/response coordination)
client/context_api.rs
Context-level API methods (create, join, delete, execute, config queries)
client/crypto.rs
Cryptographic helpers (signing, key derivation, namespace identity)
local_governance/mod.rs
Wire types: SignedGroupOp, GroupOp, SignedNamespaceOp, NamespaceOp — Ed25519, borsh, schema v3
local_governance/tests.rs
Unit tests for governance wire types and signing
group.rs
Actix messages — group-level and namespace-level message types for actor communication
messages.rs
ContextMessage enum — top-level message envelope with 30+ variants (including namespace ops)

calimero-context-config

client_config.rs
Configuration for context client connections and transport settings
types.rs
Shared configuration types used across context crates

ContextManager Actor

The central actor for all context and group operations. Initialized on node start, it rebuilds in-memory state from persistent storage and starts background coordination tasks.

Key Fields

pub struct ContextManager {
    store: Arc<Store>,
    node_client: NodeClient,
    context_client: ContextClient,
    group_dags: HashMap<GroupId, DagStore>,
    contexts: HashMap<ContextId, ContextState>,
    upgrade_propagators: HashMap<GroupId, UpgradeProp>,
    // ... network, runtime refs
}

Startup Sequence

1

recover_in_progress_upgrades

Resume any application upgrades that were interrupted by a previous shutdown.

2

reload_group_dags

For each group in the store, read the full OpLog and rebuild the in-memory DagStore via restore_applied_delta.

3

start_group_heartbeat (30s)

Periodic timer publishes GroupStateHeartbeat with current dag_heads for every group, triggering catch-up on peers.

Actor::started() recover upgrades reload DAGs start heartbeat OpLog (storage) per-group sequence borsh(SignedGroupOp) DagStore (memory) restore_applied_delta rebuild heads + pending Heartbeat (30s) GroupStateHeartbeat broadcast dag_heads

Handler Map

All 20+ ContextMessage variants, grouped by function. Each variant is handled by a dedicated file in handlers/.

Context Lifecycle

CreateContext
Create a new context, bind to group, emit ContextRegistered ops
JoinGroupContext
Join a context within a group (requires group membership)
DeleteContext
Remove context and clean up all associated state
Execute
Run WASM method, produce state delta, broadcast
UpdateApplication
Upgrade WASM binary for a context

Group Governance

AddGroupMembers
Add one or more members to a group
RemoveGroupMembers
Remove members with cascade to all contexts
ApplySignedGroupOp
Ingest a signed governance operation into the DAG
SetMemberCapabilities
Grant or revoke per-member capability flags
UpdateMemberRole
Change member role (admin, member, etc.)
UpgradeGroup
Propagate application upgrade across group contexts

Membership & Access

JoinGroup
Join a group via invitation claim flow
JoinGroupContext
Join a specific context within a group
CreateGroupInvitation
Generate a SignedGroupOpenInvitation
SetDefaultCapabilities
Default capability flags for new group members

Configuration & Sync

SyncGroup
Trigger group state synchronization with peers
SetGroupAlias
Set human-readable alias for a group
SetMemberAlias
Set human-readable alias for a member
DeleteGroup
Delete group and cascade to all owned contexts
DetachContextFromGroup
Unbind a context from its group
Caller Server, NodeMgr, SyncManager ContextMessage enum dispatch 20+ variants Context Lifecycle (5) Group Governance (6) Membership & Access (7) Config & Sync (5) handlers/ one file per variant async fn handle(&mut self, ...)

Governance Preflight & Signing Key Resolution

Every governance mutation handler calls governance_preflight() before signing and publishing. The common pattern is encapsulated in sign_and_publish_group_op() which handles the boilerplate.

governance_preflight Flow

1
Resolve requester identity — from explicit parameter, or fall back to node_namespace_identity() which walks the parent chain to the namespace root
2
Verify group existsload_group_meta() must return Some
3
Admin check (optional) — require_group_admin() if require_admin=true
4
Resolve signing key via ancestor walkresolve_group_signing_key() checks self, then walks parent chain up to MAX_NAMESPACE_DEPTH (16) levels. Returns the first signing key found for the requester identity.

Signing Key Hierarchy

When a child group is created via create_group_in_namespace, the signing key is stored only at the namespace root. Child groups resolve signing authority by walking up the parent chain:

// resolve_group_signing_key (signing_keys.rs)
for _ in 0..MAX_NAMESPACE_DEPTH {
    if let Some(sk) = get_group_signing_key(store, &current, public_key)? {
        return Ok(Some(sk));
    }
    current = get_parent_group(store, &current)?; // walk up
}
get_group_signing_key(store, &current, public_key) // final check at root

Key revocation at the namespace level automatically propagates — child groups lose access because the ancestor walk finds nothing. No stale copies to clean up.

sign_and_publish_group_op Helper

Encapsulates the governance handler boilerplate: preflight → clone datastore/node_client → build signer → sign, apply, publish via async actor response. Used by 7 handlers (set_group_alias, set_default_capabilities, set_default_visibility, update_group_settings, set_member_alias, set_member_capabilities, update_member_role).

GroupStore Deep-Dive

The authoritative persistence layer for all group and governance state. Handles signature verification, state-hash optimistic locking, nonce-based idempotency, DAG maintenance, and cascading side effects.

apply_local_signed_group_op Flow

1

Cap parent_op_hashes

Ensure parent hashes reference valid DAG heads (capped at 64 concurrent heads).

2

verify_signature

Ed25519 signature verification on the signable_bytes of the operation.

3

check state_hash

Optimistic lock — the op's state_hash must match the current computed group state hash.

4

check nonce (idempotent on duplicate)

Per-signer monotonic nonce. If the nonce was already seen, the op is silently treated as idempotent (no error, no re-apply).

5

dispatch GroupOp

Match on the GroupOp variant and apply the state mutation (add member, set visibility, cascade removal, etc.).

6

append OpLog

Serialize the SignedGroupOp via borsh and append to the persistent op log with an incrementing sequence number.

7

recompute dag_heads

Update the set of DAG head hashes — the new op becomes a head, its parents are removed from the head set.

8

set nonce

Record the signer's nonce for replay protection.

Key Storage Prefixes

All stored in Column::Group with typed key prefixes:

GroupMeta
Group metadata (name, created_at)
GroupMember
Member records per group
GroupOpLog
Persisted signed operations
GroupOpHead
Current DAG head hashes
GroupLocalGovNonce
Per-signer monotonic nonce
GroupContextIndex
Context → group mapping
ContextGroupRef
Reverse group → context ref
GroupMemberContext
Per-member context tracking
GroupCapability
Member capability grants
GroupAlias
Human-readable group alias
MemberAlias
Human-readable member alias
sign_apply_and_publish helper

Convenience function used by most handlers to emit governance ops. Combines three steps into one call:

async fn sign_apply_and_publish(
    &self,
    group_id: GroupId,
    op: GroupOp,
) -> Result<()> {
    // 1. Sign op with node's Ed25519 key
    // 2. apply_local_signed_group_op (persist + DAG)
    // 3. Publish via gossipsub on group topic
}
GroupOp incoming op verify + check sig · state_hash nonce · parents dispatch match GroupOp variant persist OpLog + heads + nonce DAG updated new heads computed

ContextClient

Thin async façade wrapping LazyRecipient<ContextMessage>. Used by Server (for RPC execution) and NodeManager (for delta routing) to invoke context-level operations without importing Actix directly.

pub struct ContextClient {
    recipient: LazyRecipient<ContextMessage>,
}

impl ContextClient {
    pub async fn send(&self, msg: ContextMessage) -> Result<ContextResponse>;
}

Context Operations

create_context
Create new context bound to group
delete_context
Remove context and clean up state
execute
Run WASM method, produce delta (auto-resolves executor identity)
get_context
Query context metadata

Group Operations

add_group_members
Add members to a group
remove_group_members
Remove members with cascade
apply_signed_group_op
Ingest signed op into group DAG
apply_signed_namespace_op
Ingest signed op into namespace DAG
set_member_capabilities
Set capability flags for member

Membership

join_group
Join group/namespace via invitation (generates namespace identity, unwraps group key)
create_group_invitation
Generate invitation token
list_namespaces
List namespaces with identity info
get_namespace_identity
Get this node's namespace identity public key

Configuration

sync_group
Trigger group sync with peers

Query

context_config
Get context configuration
get_context_application
Get WASM app metadata
get_context_member_page
Paginated member listing

Dependencies

How the three context crates relate to other Calimero crates. Arrows show compile-time dependencies.

calimero-context ContextManager actor + handlers calimero-context-primitives ContextClient + messages + wire types calimero-context-config client config + types store dag node-primitives network-primitives primitives actix tokio borsh ed25519-dalek serde
Context crates
Storage
Node
Network
Primitives
External crates