Skip to content

Groups & Governance

Governance is layered: a namespace scopes an application instance; groups (and subgroups) within it hold members with roles and capabilities and own contexts. This guide covers the pieces you’ll touch most; the exhaustive list is in the admin API reference.

val ns = mero.admin.createNamespace(
CreateNamespaceRequest(
applicationId = applicationId,
upgradePolicy = UpgradePolicy.AUTOMATIC,
name = "production",
)
)
val namespaceId = ns.namespaceId
val all = mero.admin.listNamespaces() // List<Namespace>
val one = mero.admin.getNamespace(namespaceId)
val identity = mero.admin.getNamespaceIdentity(namespaceId)

Invite and join:

val invite = mero.admin.createNamespaceInvitation(namespaceId)
// CreateNamespaceInvitationResult — .Single(...) or .Recursive(...)
// on the joining node, with a SignedGroupOpenInvitation:
val joined = mero.admin.joinNamespace(
namespaceId, JoinNamespaceRequest(invitation = signed)
)
// joined.groupId, joined.memberIdentity

Joining is only half the story: the group’s contexts are not initialized on your node until each is joined and state-pulled, and cross-node sync is asynchronous. Until then a context sits on the all-ones (1111…) uninitialized hash and reads come back empty. Follow the join with syncGroupContexts, retrying while the peer catches up:

repeat(6) {
val contexts = runCatching { mero.admin.syncGroupContexts(joined.groupId) }
.getOrNull().orEmpty()
if (contexts.isNotEmpty()) return@repeat
delay(2_000)
}

If it stays empty, check getPeersCount() — 0 peers means this node has no one to sync from, which is a networking problem, not an app-id mismatch.

val info = mero.admin.getGroupInfo(groupId)
val members = mero.admin.listGroupMembers(groupId).members
val contexts = mero.admin.listGroupContexts(groupId)
mero.admin.addGroupMembers(
groupId, AddGroupMembersRequest(members = listOf(GroupMemberInput(identity = id, role = "member")))
)
mero.admin.updateMemberRole(groupId, identity = id, UpdateMemberRoleRequest(role = "admin"))
mero.admin.removeGroupMembers(groupId, RemoveGroupMembersRequest(members = listOf(id)))
val subgroups = mero.admin.listSubgroups(groupId)

A member’s permissions are a u32 bitmask, carried as a Long in Kotlin. Capabilities provides the bit constants and pure helpers so you never write raw bitwise operators.

import com.calimero.mero.Capabilities
// Read the current mask (MemberCapabilities.capabilities is an Int)
val current = mero.admin.getMemberCapabilities(groupId, identity = id).capabilities
val mask = current.toLong()
// Test / add / remove bits
val canInvite = Capabilities.hasCap(mask, Capabilities.CAN_INVITE_MEMBERS)
val next = Capabilities.withCap(mask, Capabilities.MANAGE_MEMBERS)
val revoked = Capabilities.withoutCap(next, Capabilities.MANAGE_APPLICATION)
// Persist (the request takes an Int)
mero.admin.setMemberCapabilities(
groupId, identity = id, SetMemberCapabilitiesRequest(capabilities = next.toInt())
)

The bits (mirroring core’s MemberCapabilities):

Constant Bit Grants
Capabilities.CAN_CREATE_CONTEXT 1 shl 0 create contexts in the group
Capabilities.CAN_INVITE_MEMBERS 1 shl 1 invite new members
Capabilities.CAN_JOIN_OPEN_SUBGROUPS 1 shl 2 join open subgroups
Capabilities.MANAGE_MEMBERS 1 shl 3 add/remove members, change roles
Capabilities.MANAGE_APPLICATION 1 shl 4 manage the group’s application
Capabilities.CAN_CREATE_SUBGROUP 1 shl 5 create subgroups
Capabilities.CAN_DELETE_SUBGROUP 1 shl 6 delete subgroups
Capabilities.CAN_MANAGE_VISIBILITY 1 shl 7 change subgroup visibility
Capabilities.CAN_MANAGE_METADATA 1 shl 8 edit group/member/context metadata

Group defaults and subgroup visibility:

mero.admin.setDefaultCapabilities(
groupId, SetDefaultCapabilitiesRequest(defaultCapabilities = Capabilities.CAN_CREATE_CONTEXT.toInt())
)
val defaults = mero.admin.getDefaultCapabilities(groupId) // Int
val visibility = mero.admin.getSubgroupVisibility(groupId) // String

Attach a small named key/value record to a group, member, or context:

mero.admin.setGroupMetadata(
groupId, SetGroupMetadataRequest(name = "Production", data = mapOf("region" to "eu", "tier" to "gold"))
)
val meta = mero.admin.getGroupMetadata(groupId) // MetadataRecord?
// meta?.name, meta?.data, meta?.updatedAt, meta?.updatedBy

The same pair exists for members (set/getMemberMetadata) and contexts (set/getContextMetadata). Setting a record replaces it wholesale (server limits: name ≤64 B, ≤64 entries, key ≤64 B, value ≤4096 B).

Restrict which nodes may join a group to those in an approved Trusted Execution Environment:

mero.admin.setTeeAdmissionPolicy(
groupId,
SetTeeAdmissionPolicyRequest(
allowedMrtd = listOf("<mrtd-hex>"),
allowedRtmr0 = emptyList(), allowedRtmr1 = emptyList(),
allowedRtmr2 = emptyList(), allowedRtmr3 = emptyList(),
allowedTcbStatuses = listOf("UpToDate"),
acceptMock = false, // true only for local/dev
)
)
val policy = mero.admin.getTeeAdmissionPolicy(groupId)

Nodes can attest and verify quotes directly:

val teeInfo = mero.admin.getTeeInfo()
val attest = mero.admin.teeAttest(TeeAttestRequest(nonce = nonce))
val verdict = mero.admin.teeVerifyQuote(
TeeVerifyQuoteRequest(quoteB64 = attest.quoteB64, nonce = nonce)
)
mero.admin.upgradeGroup(groupId, UpgradeGroupRequest(targetApplicationId = newAppId, cascade = true))
val status = mero.admin.getGroupUpgradeStatus(groupId)
val migration = mero.admin.getMigrationStatus(namespaceId)
// migration.rollup.allMigrated == true when done