Groups & Governance
Governance in Calimero 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 method list is in the admin API reference.
Namespaces
Section titled “Namespaces”A namespace groups contexts and members under a shared target application.
const { namespaceId } = await sdk.admin.createNamespace({ applicationId, name: 'production',});
const { namespaces } = await sdk.admin.listNamespaces();const ns = await sdk.admin.getNamespace(namespaceId);const identity = await sdk.admin.getNodeIdentity();Invite and join:
const invitation = await sdk.admin.createNamespaceInvitation(namespaceId);// on the joining node:// `memberAccount` is what the member-addressing calls below take;// `memberIdentity` is the key it signs with.const { groupId, memberIdentity, memberAccount } = await sdk.admin.joinNamespace( namespaceId, { invitation },);Following an invitation
Section titled “Following an invitation”When a person follows an invitation link, call redeemInvitation, not
joinNamespace. A join waits for a member of the namespace to come online, and
that can outlast any proxy in front of the node. The request then fails even
though the join lands. redeemInvitation sends the join once, then checks
listNamespaces. A failed request whose namespace is listed is reported as
already-member, which is a success.
import { shouldRetain } from '@calimero-network/mero-js';
const outcome = await sdk.admin.redeemInvitation(namespaceId, invitation, { groupName: 'Design',});
switch (outcome.status) { case 'joined': case 'already-member': openNamespace(outcome.namespaceId); break; case 'failed': // `reason`: 'expired' | 'invalid' | 'refused' | 'signed-out' // | 'no-one-online' | 'node-unreachable' | 'unknown' showError(outcome.reason, outcome.message); if (!shouldRetain(outcome)) discardInvitation(); break;}It never throws for an ordinary failure. The reason comes from the node’s
status on core 0.11.0-rc.56 and later, and from its message on older nodes.
retryable (and shouldRetain) say whether to keep the invitation for another
attempt. An expired, invalid or refused one is final. One that failed because
no member was online, or because the node could not be reached, is worth
keeping.
A join that is more than one call, such as a namespace followed by a context
inside it, can use the standalone redeemInvitation(parsed, redeemer). Pass your
own join, plus a memberships that lists namespace ids.
Groups, members & roles
Section titled “Groups, members & roles”const info = await sdk.admin.getGroupInfo(groupId);const { members } = await sdk.admin.listGroupMembers(groupId);const { contexts } = await sdk.admin.listGroupContexts(groupId);
await sdk.admin.addGroupMembers(groupId, { /* members */ });await sdk.admin.updateMemberRole(groupId, identity, { role: 'admin' });await sdk.admin.removeGroupMembers(groupId, { /* identities */ });Subgroups nest under a parent group:
const subgroups = await sdk.admin.listSubgroups(groupId);await sdk.admin.createGroupInNamespace(namespaceId, { /* … */ });Capabilities
Section titled “Capabilities”A member’s permissions are a u32 bitmask. The SDK ships the bit constants
and pure helpers to read and edit a mask — no bitwise operators at your call
sites.
import { CAPABILITIES, hasCap, withCap, withoutCap } from '@calimero-network/mero-js';
// Read the current maskconst { capabilities } = await sdk.admin.getMemberCapabilities(groupId, identity);
// Test / add / remove bitsconst canInvite = hasCap(capabilities, CAPABILITIES.CAN_INVITE_MEMBERS);const next = withCap(capabilities, CAPABILITIES.MANAGE_MEMBERS);const revoked = withoutCap(next, CAPABILITIES.MANAGE_APPLICATION);
// Persistawait sdk.admin.setMemberCapabilities(groupId, identity, { capabilities: next });The bits:
| Constant | Value | Grants |
|---|---|---|
CAN_CREATE_CONTEXT |
1 << 0 |
create contexts in the group |
CAN_INVITE_MEMBERS |
1 << 1 |
invite new members |
CAN_JOIN_OPEN_SUBGROUPS |
1 << 2 |
join open subgroups |
MANAGE_MEMBERS |
1 << 3 |
add/remove members, change roles |
MANAGE_APPLICATION |
1 << 4 |
manage the group’s application |
CAN_CREATE_SUBGROUP |
1 << 5 |
create subgroups |
CAN_DELETE_SUBGROUP |
1 << 6 |
delete subgroups |
CAN_MANAGE_VISIBILITY |
1 << 7 |
change subgroup visibility |
CAN_MANAGE_METADATA |
1 << 8 |
edit group/member/context metadata |
Set the default mask new members receive, or read/change subgroup visibility:
await sdk.admin.setDefaultCapabilities(groupId, { defaultCapabilities: withCap(0, CAPABILITIES.CAN_CREATE_CONTEXT),});const defaults = await sdk.admin.getDefaultCapabilities(groupId); // numberconst visibility = await sdk.admin.getSubgroupVisibility(groupId); // stringMetadata
Section titled “Metadata”Attach a small named key/value record to a group, member, or context:
await sdk.admin.setGroupMetadata(groupId, { name: 'Production', data: { region: 'eu', tier: 'gold' },});const meta = await sdk.admin.getGroupMetadata(groupId); // MetadataRecord | null// meta.name, meta.data, meta.updatedAt, meta.updatedByThe same pair exists for members (set/getMemberMetadata) and contexts
(set/getContextMetadata).
TEE admission policy
Section titled “TEE admission policy”A group can restrict which nodes may join to those running in an approved Trusted Execution Environment. The policy lists the allowed measurements.
await sdk.admin.setTeeAdmissionPolicy(groupId, { allowedMrtd: ['<mrtd-hex>'], allowedRtmr0: [], allowedRtmr1: [], allowedRtmr2: [], allowedRtmr3: [], allowedTcbStatuses: ['UpToDate'], acceptMock: false, // true only for local/dev});
const policy = await sdk.admin.getTeeAdmissionPolicy(groupId);TEE authoring policy
Section titled “TEE authoring policy”Admission decides which TEEs may join; the authoring policy decides which of the
admitted ones may write as the namespace’s TEE authority (TeeOnly storage and
#[app::tee] methods). It is namespace-scoped, so set it on the namespace root.
await sdk.admin.setTeeAuthoringPolicy(namespaceId, { allowedMrtd: ['<mrtd-hex>'] });
// Turn TEE authorship off. Admitted TEEs stay members; none can author.await sdk.admin.disableTeeAuthoringPolicy(namespaceId);// Equivalent: an empty allowlist applies the same op.await sdk.admin.setTeeAuthoringPolicy(namespaceId, { allowedMrtd: [] });The node has no read-back route for this policy.
Nodes can also attest directly. Verify a quote in the client with
createQuoteVerifier (see sealed transport),
not by asking a node:
const info = await sdk.admin.getTeeInfo(); // { cloudProvider, osImage, mrtd }const attestation = await sdk.admin.teeAttest({ nonce }); // { quoteB64, quote }For cloud-managed TEE redundancy, see high availability.
Upgrades & migration
Section titled “Upgrades & migration”When an application ships a new version, drive the group upgrade and watch its progress:
await sdk.admin.upgradeGroup(groupId, { targetApplicationId, cascade: true });const status = await sdk.admin.getGroupUpgradeStatus(groupId);const migration = await sdk.admin.getMigrationStatus(namespaceId);// migration.rollup.allMigrated === true when done