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.
Namespaces
Section titled “Namespaces”val ns = mero.admin.createNamespace( CreateNamespaceRequest(applicationId = applicationId, name = "production"))val namespaceId = ns.namespaceId
val all = mero.admin.listNamespaces() // List<Namespace>val one = mero.admin.getNamespace(namespaceId)
// Who this node is. `getNamespaceIdentity` is gone: core#3522 deleted the route// that asked a namespace — "ask the node who it is" — and it 404s.val identity = mero.admin.getNodeIdentity()upgradePolicy is not a parameter any more: core#3485 removed the concept, and
it always held "LazyOnAccess".
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.memberIdentityJoining 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.
Groups, members & roles
Section titled “Groups, members & roles”val info = mero.admin.getGroupInfo(groupId)val members = mero.admin.listGroupMembers(groupId).membersval 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)Capabilities
Section titled “Capabilities”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).capabilitiesval mask = current.toLong()
// Test / add / remove bitsval 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 |
Capabilities.CAN_AUTHOR_ON_BEHALF |
1 shl 9 |
relay members’ writes under their warrants (core rc.83; implied by nothing, not even admin) |
Group defaults and subgroup visibility:
mero.admin.setDefaultCapabilities( groupId, SetDefaultCapabilitiesRequest(defaultCapabilities = Capabilities.CAN_CREATE_CONTEXT.toInt()))val defaults = mero.admin.getDefaultCapabilities(groupId) // Intval visibility = mero.admin.getSubgroupVisibility(groupId) // StringMetadata
Section titled “Metadata”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?.updatedByThe 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).
TEE admission policy
Section titled “TEE admission policy”Restrict which nodes may join a group to those in an approved Trusted Execution Environment:
// Measurement form: MRTD and RTMR1–3 are all required (core rc.83), even with acceptMock.mero.admin.setTeeAdmissionPolicy( groupId, SetTeeAdmissionPolicyRequest.measurements( allowedMrtd = listOf("<mrtd-hex>"), allowedRtmr1 = listOf("<rtmr1-hex>"), allowedRtmr2 = listOf("<rtmr2-hex>"), allowedRtmr3 = listOf("<rtmr3-hex>"), allowedTcbStatuses = listOf("UpToDate"), ),)
// Or the signed-release form: admit any release the mero-tee workflow signed.mero.admin.setTeeAdmissionPolicy( groupId, SetTeeAdmissionPolicyRequest.signedRelease( allowedProfiles = listOf("locked-read-only"), mode = TeeAdmissionMode.RELAY, // admit as RelayTee (may author under warrants) ),)
val policy = mero.admin.getTeeAdmissionPolicy(groupId) // enabled, signedRelease, modeChanging the policy is a root-guarded owner op since core rc.83: run it on a
node holding the owner’s account root, or pass rootProof. See
root-guarded owner ops.
Nodes can attest directly; verify the quote client-side (the node’s
tee/verify-quote route is gone):
val teeInfo = mero.admin.getTeeInfo()val attest = mero.admin.teeAttest(TeeAttestRequest(nonce = nonce, includeCollateral = true))// attest.quote, attest.collateral → your DCAP verifierOwner operations
Section titled “Owner operations”Transferring a group, repointing a namespace’s admin and the owner-only delete need the owner account’s root key (core rc.83):
val info = mero.admin.getGroupInfo(groupId) // namespaceId + ownerOpCounter go in the proofmero.admin.transferOwnership(groupId, TransferOwnershipRequest(newOwner = adminAccount))mero.admin.ownerDeleteGroup(groupId) // group must hold no contextsOmit rootProof on a node that holds the root; it signs for itself. A 409
means the counter moved — re-read and re-sign.
Upgrades & migration
Section titled “Upgrades & migration”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