Local Group Governance
Signed gossip operations, causal DAG, capability-based authorization
Architecture
Core Concept: Groups Own Contexts
Member Roles
Admin
Full control. Can perform all governance operations, manage members, configure the group, and write state in all contexts.
Member
Standard participant. Can read and write state in joined contexts. Specific governance operations require capability bits (e.g. CAN_INVITE_MEMBERS, MANAGE_MEMBERS).
ReadOnly
Observer. Can join contexts and read state, but state mutations are rejected by the platform. Enforced at both the local node (execute handler discards changes) and remote nodes (state deltas from ReadOnly authors are rejected on ingestion).
Subgroups
Groups can form a tree hierarchy. A child group links to a parent via GroupParentRef. Membership and admin authority inherit downward:
Membership Inheritance
Membership checks walk up the ancestor chain (max depth 16). A member of the parent group is automatically a member of all descendant groups. Direct membership in the child takes priority over inherited membership.
Admin Authority
Parent admins inherit structural governance over descendant groups: add/remove members, detach contexts, delete subgroups, set target application. However, parent admins do not inherit access control over Restricted contexts. Only direct group admins (or the context creator) can manage allowlists and change context visibility.
Access Control for Restricted Contexts
Joining
Admins and members alike must be on the allowlist. No admin bypass. An admin can add themselves to the allowlist (if they are a direct admin of the group), then join.
Allowlist Management
Only direct group admins or the context creator. Inherited parent admins cannot modify allowlists in child groups — this is a privacy boundary.
Visibility Changes
Only direct group admins or the context creator can change a context between Open and Restricted. Parent admins cannot flip Restricted to Open as a backdoor.
Create a subgroup with --parent-group-id on meroctl group create or via the parentGroupId field in the API. The SubgroupCreated governance op is published on the parent group's gossip topic.
When joining a parent group with auto_join, the node subscribes to gossip topics and contexts in all descendant subgroups. MemberRemoved cascades to descendant subgroup contexts (skipping child groups where the member has direct membership).
Governance DAG Structure
Each group has a DAG of SignedGroupOp operations. Operations reference their parents by content hash, forming a causal chain that supports offline ops, concurrent admins, and merge.
SignedGroupOp Schema (v3)
Signable Payload
GroupOp Variants
Operation Flows
Create Context Flow
Admin calls create_context
API receives group_id (or auto-creates group). Uses node's group identity keypair if no explicit secret provided.
Handler signs GroupOps
Emits a sequence: ContextRegistered → ContextVisibilitySet → optional ContextAliasSet. Each is a SignedGroupOp applied locally and published via gossip.
Local apply + OpLog
group_store::apply_local_signed_group_op verifies signature, checks state_hash, updates KV rows, appends to op log, recomputes DAG heads.
Gossip propagation
Published on group/<hex> topic. Remote nodes receive via SignedGroupOpV1 broadcast, verify, and apply the same ops.
Join Group via Invitation
Admin creates invitation
create_group_invitation generates a SignedGroupOpenInvitation with optional expiration timestamp. Shared out-of-band to the joiner.
Joiner submits invitation
join_group builds GroupRevealPayloadData, signs it, submits a JoinWithInvitationClaim GroupOp.
Op applied + member added
Group store validates the claim, adds the new member, updates op log. Gossip ensures all peers converge.
Join Context (within Group)
Member calls join_group_context
Must already be a group member. Handler checks context visibility (open vs restricted) and member capabilities (CAN_JOIN_OPEN_CONTEXTS).
Allowlist check (if restricted)
For restricted-visibility contexts, the member's public key must be on the allowlist.
Context identity created
Adds local context membership. Context membership is implicit from group membership — no separate governance op is required.
Member Removal Cascade
Sync & Catch-up Protocol
Startup Recovery
reload_group_dags
On node start, for each group in the store, reads the full OpLog and rebuilds the in-memory DagStore via restore_applied_delta. Reconstructs pending parent resolution state.
start_group_heartbeat
Starts a 30-second periodic timer. Each tick publishes GroupStateHeartbeat with current dag_heads for every group.
Peers respond with deltas
Peers compare heads against their own. For any ops the restarted node is missing, they serve them via GroupDeltaRequest/Response streams.
Known Limitations
Multi-admin convergence
Concurrent ops from multiple admins with the same state_hash: first applied wins per node. Different nodes may pick different winners for conflicting ops. Merge Noop resolves heads but op ordering may differ. Single-admin groups have no ambiguity.
Governance epoch
State deltas carry a governance_epoch field but it's not yet used to reject stale application-level deltas from removed members.
Context-level capability enforcement
Context-level capabilities (ManageApplication, ManageMembers) are stored and can be granted/revoked via governance ops, but are not yet checked during WASM execution. ReadOnly role enforcement is platform-level (execute handler + delta ingestion), not capability-based.
Gossip publish failures
If gossip publish fails, it's currently silent. Should surface errors to callers or retry.