Roles: User Guide

Status: Active Audience: Application developers and operators provisioning role-based authority on Entity Core peers. Assumes familiarity with capabilities (GUIDE-CAPABILITIES.md). Familiarity with identity (GUIDE-IDENTITY.md) is helpful for the identity-coherence sections but not required. Spec reference: EXTENSION-ROLE.md (current draft), ENTITY-CORE-PROTOCOL.md §5 (capability system), §5.6 (the is_attenuated helper). Related guides: GUIDE-CAPABILITIES.md, GUIDE-IDENTITY.md, GUIDE-MULTISIG.md, GUIDE-GROUP.md.


1. What roles are (and what they aren't)

The core protocol gives you per-peer authorization: each peer is a root authority, each peer signs caps, every cap chain bottoms out at a peer-identity entity. That's enough to grant Bob get access to one specific path. It is not enough to express "Bob is a manager on team-alpha," "Carol is a moderator across all forums," or "every member of this team gets the same five permissions." You can hand-issue identical cap bundles to every member and then revoke them by hand when someone leaves — but that's a management problem the protocol shouldn't make you solve manually.

The role extension fills that gap. It defines named permission templates (role definitions), a way to bind a peer to a role within a context (assignment), and a way to deny a peer all role-derived access within a context (exclusion). The capability system stays the enforcement mechanism; roles are the management layer on top.

Roles are templates, not authority. A role definition by itself authorizes nothing. Assignment is what derives the actual cap tokens, and the assigner's authority is what those tokens ultimately chain to. If the assigner can't already do everything the role would mint, the assignment is rejected (the assigner-coverage check, §8).

A role lives in a context. Contexts scope roles independently — admin in group/team-alpha is different from admin in service/media-server. Cross-context interaction is convention, not protocol.

Two parallel mechanisms (just like identity). Caps are the chain-validated permission objects; role assignments and exclusions are entities that the role handler validates at the entity level. They don't mix — the role handler emits caps but never checks a cap with role logic.

What the role extension does:

What the role extension does NOT do:

If you're using the core protocol + identity and want "Bob can read Alice's photos," you don't need the role extension — issue a single cap. You need this extension when you have multiple peers who should hold the same set of grants OR when you want structural eviction within a scope (denial that revokes every role-derived cap and blocks future derivations; per the three-layer model in §7, this catches every role-mediated path but does not touch caps the peer holds via other routes unless those handlers opt in).


2. The model in one diagram

           ┌──────────────────────────────────────────────────────────┐
           │ Role definition (template)                                │
           │   system/role/{context}/{role_name}                       │
           │   { name, grants: [grant_entry, ...], metadata }          │
           │                                                           │
           │   - Tree data; not load-bearing on its own.               │
           │   - May contain template variables {context}, {peer_id}. │
           │   - Mutated via system/role:define (default).             │
           └──────────────────────┬────────────────────────────────────┘
                                  │
                                  │ assignment references the role by name
                                  ▼
           ┌──────────────────────────────────────────────────────────┐
           │ Role assignment                                           │
           │   system/role/{context}/assignment/{peer_id}/{role_name}  │
           │   { role, assigned_by, assigned_at }                      │
           │                                                           │
           │   - Created via system/role:assign.                       │
           │   - Triggers cap derivation at assign time.               │
           │   - Multi-role per (peer, context) — multiple entries     │
           │     under {peer_id}/ for the same peer.                   │
           └──────────────────────┬────────────────────────────────────┘
                                  │
                                  │ derived caps land at:
                                  ▼
           ┌──────────────────────────────────────────────────────────┐
           │ Role-derived caps                                         │
           │   system/capability/grants/role-derived/                  │
           │     {context}/{peer_id}/{token_hash}                      │
           │                                                           │
           │   - Standard caps. Granter = the runtime peer that signed │
           │     them. Parent = the role handler's own grant.          │
           │   - Pinned storage path so revocation can find them.      │
           └───────────────────────────────────────────────────────────┘

           ┌──────────────────────────────────────────────────────────┐
           │ Exclusion (orthogonal to assignment)                      │
           │   system/role/{context}/excluded/{peer_id}                │
           │   { peer_id, excluded_by, excluded_at, reason }           │
           │                                                           │
           │   - Created via system/role:exclude.                      │
           │   - Three-layer enforcement (§7).                         │
           └───────────────────────────────────────────────────────────┘

Three things to internalize:

  1. A role definition produces no authority. Authority comes from assignment plus the assigner's caller cap.
  2. The derived cap is signed by the assigner's runtime peer. The core protocol's rule that root capabilities must be granted by the local peer is unchanged — the role extension does not invent a new chain root.
  3. Exclusion is a parallel control surface. It deletes derived caps (layer 1), blocks future derivations (layer 2), and optionally surfaces at handler-level checks (layer 3). It does NOT touch caps that weren't role-derived.

3. Defining a role

A role definition is a named grant bundle. The minimum ceremony is a single call to system/role:define carrying the proposed grant set.

3.1 The shape of a role

system/role/{context}/{role_name} := {
  name:     "manager",
  grants:   [
    { handlers:   {include: ["system/tree"]},
      resources:  {include: ["shared/{context}/*"]},
      operations: {include: ["get", "put", "list"]} },
    { handlers:   {include: ["system/group"]},
      resources:  {include: ["system/group/{context}"]},
      operations: {include: ["status"]} }
  ],
  metadata: { ttl: 86400000 }   ; optional; ms — handler default lifetime for derived tokens
}

grants is an array of grant entries — exactly the same five-dimensional shape used in capability tokens (handlers, operations, resources, peers, constraints). The role bundles them under a single name.

3.2 Template variables

Two template variables are recognized in path values within grants:

VariableSubstituted with at issuanceExample
{context}The role's context path (e.g., group/team-alpha)shared/{context}/*shared/group/team-alpha/*
{peer_id}The assignee's peer-identity hash (canonical Base58)users/{peer_id}/*users/Bob_Base58/*

Templates resolve at issuance time (when assign runs), not at definition time. The literal string lives in the role entity; the assigner's runtime peer applies the substitution.

3.3 Reserved names

The role-name segments assignment and excluded are reserved — they collide with the dedicated subtree paths used by the role handler. Any other name is permitted; conventional choices are member, admin, manager, engineer, auditor, friend, follower, reader. Use whatever makes the deployment legible.

3.4 Who can define a role

system/role:define is the default path for creating or mutating a role definition. The handler runs the assigner-coverage check at definition-write time — the caller's capability MUST cover the proposed grant set — and triggers re-derive cascade on mutation (§5).

In practice, role-definition write authority is admin-tier: a peer that can mutate system/role/{context}/{role_name} can silently change downstream grants for every assignee. Bind the define op to the deployment's admin set, not to the general assigner population.

Implementations MAY alternatively keep raw tree:put legal for role definitions and trigger re-derive via a tree-mutation observer when the deployment already gates the underlying tree path tightly. Both paths converge on the same property: only authorized actors mutate definitions, and mutation always cascades through re-derive.


4. Assigning a role

Assignment is what turns a template into actual caps. The mechanics are simple; the authority model is what makes it interesting.

4.1 The typical flow (identity-aware deployment)

In a deployment running the identity extension, role assignments are typically authorized by Alice's operator key (the day-to-day key the identity extension calls "Op") via the local peer→Op cap installed by system/identity:configure. Concretely:

alice_runtime_peer.execute(
  "system/role", "assign",
  resource: "system/role/group/team-alpha/assignment/{Bob_id}/manager",
  params: { role: "manager" },
  capability: <local peer→Op cap on alice_runtime_peer>
)

What happens inside the role handler:

  1. Reads ctx.resource.targets[0] and parses out (context: "group/team-alpha", assignee: Bob_id).
  2. Reads the role definition at system/role/group/team-alpha/manager. (404 role_not_found if absent.)
  3. Resolves templates: {context}group/team-alpha, {peer_id}Bob_id.
  4. Exclusion check. If system/role/group/team-alpha/excluded/{Bob_id} exists, returns 403 assignee_excluded. This fires before the authority check — excluded peers don't even get to the authority check.
  5. Assigner-coverage check (the key invariant). The handler asks: "are the role's grants a (non-strict) attenuation of the caller's authority?" using the core protocol's is_attenuated helper. If the caller's authority does NOT cover everything the role would mint, the handler returns 403 assigner_authority_insufficient. Fail-closed — partial tokens are NOT minted. Either the assigner has full authority or the assignment doesn't happen.
  6. The assignment entity is written at system/role/group/team-alpha/assignment/{Bob_id}/manager.
  7. Derived caps are minted, signed by alice_runtime_peer, and persisted at system/capability/grants/role-derived/group/team-alpha/{Bob_id}/{token_hash}.
  8. The caps are delivered to Bob (out-of-band; the role extension does not specify the delivery mechanism — see §5.3 for the parallel re-derive case where delivery IS pinned).

4.2 Who's the granter on the derived cap

This is where most confusion happens. Be explicit:

The operator key never appears in the cap chain. The operator authorizes the assign (via the local peer→Op cap; that cap is the caller capability the assigner-coverage check looks at); the runtime peer signs the derived cap. Since the cap is a root cap, there is no chain link above it.

Read the chain bottom-up: Bob's derived cap → terminates (root). The operator key is nowhere in the chain. Side benefit: §10.2 op-key rotation invariance becomes structural — there is no link in the cap chain that could possibly reference the operator key.

4.3 Core-protocol-only deployments

In deployments that don't install the identity extension (one peer = one keypair = one identity), role assignments are caused by the peer-owner directly — either via the bootstrap path (§9) or via a dispatched EXECUTE under whatever cap the peer-owner has issued to the assigning identity. The role extension's mechanics don't change. The assigner-coverage check still runs against ctx.caller_capability.

4.4 What scope the assigner needs

The check says: "the assigner's authority MUST cover everything the role would mint." Practically this means:

Common configuration: the operator holds a near-wildcard cap (the operator is the deployment's admin); roles are narrower; the check passes by construction.

4.5 Multi-role per (peer, context)

A peer MAY hold multiple role assignments under the same context. The path is assignment/{peer_id}/{role_name} — the trailing {role_name} segment makes each (peer, role) pair its own assignment entity. Bob can be both manager and auditor in group/team-alpha; the two assignments coexist; tokens accumulate.

Token presentation. When Bob makes an EXECUTE that the verifier checks, Bob presents one token covering the requested operation. The verifier validates that one token; there is no protocol-level token-union step. If a token is later revoked (via re-derive or unassign), Bob's other live tokens covering the operation continue to authorize.


5. Mutating a role definition — the re-derive lifecycle

A deployment evolves. The manager role gets a new permission. The engineer role drops one. Re-issuance to existing assignees is what re-derive does.

5.1 The trigger

system/role:define (the default write path) automatically triggers re-derive cascade on definition mutation. Implementations using the tree-mutation-observer alternative fire the cascade on observation. Either way, mutation MUST cascade.

The cascade is also explicitly invocable: system/role:re-derive against the role-definition path re-issues tokens for all current assignees.

5.2 The lifecycle

For each assignee, the handler does issue T_new before revoking T_old (default). The ordering is load-bearing:

Step outcomeResultSafety
Issue T_new persists, revoke T_old failsAssignee briefly holds both tokensSafe — over-authorization bounded by grace window
Issue T_new fails (no revoke yet)Assignee keeps T_oldSafe — no stranding
Revoke-first (rejected default): revoke T_old persists, issue T_new failsAssignee stranded with no tokenUnsafe — rejected as default

Implementations MAY offer revoke-first as a deployment-configurable mode for security-critical contexts where stranding is preferable to overlap; the default MUST be issue-first. No tree-level transactional-bundle primitive is required — per-entity content-addressing makes each leg recoverable on retry.

5.3 Grace window and delivery

5.4 Multi-Op concurrency

In identity-aware deployments where the identity has multiple operator keys live concurrently (the multi-Op pattern from the identity extension), each operator may invoke re-derive independently. Each runs its own ordered-write flow. Tree CAS resolves at the assignment-entity level. Resulting tokens are independent — both may be issued; the assignee receives whichever delivers first; both pass the assigner-coverage check if either operator's authority covers the role definition. Implementations MUST NOT serialize concurrent re-derives across operators — that would block multi-Op operational concurrency.


6. Member-to-member delegation

A member who holds a role wants to delegate their role-authority to another peer — vacation coverage, role handoff, ad-hoc temporary authorization. The delegate op composes this through role's lifecycle rather than punching out to raw caps.

6.1 The flow

bob_runtime_peer.execute(
  "system/role", "delegate",
  resource: "system/capability/grants/role-derived/group/team-alpha/{Carol_id}/{token_hash}",
  params: {
    delegator: Bob_id,
    delegate:  Carol_id,
    context:   "group/team-alpha",
    role:      "manager",
    scope:     [<grant-entry subset>],     ; MAY attenuate from manager's grants
    expires_at: 1741800000000              ; optional
  },
  capability: <Bob's role-derived manager cap on team-alpha>
)

What happens:

  1. The handler verifies Bob holds the named role (system/role/group/team-alpha/assignment/{Bob_id}/manager exists).
  2. The assigner-coverage check applies to Bob's authority, not the operator's. scope MUST be an attenuation of the role's grants as held by Bob. A member can only delegate authorities they hold; they MAY narrow them.
  3. Templates resolve ({peer_id}Carol_id).
  4. The delegation cap is issued: granter = Bob's runtime peer, grantee = Carol, parent = Bob's role-derived cap. Signed by Bob's runtime peer.
  5. Stored at system/capability/grants/role-derived/group/team-alpha/{Carol_id}/{token_hash} — the role-derived path so the role extension's machinery (the layer-1 sweep, unassign revocation, re-derive) reaches it.

6.2 What revocation does to delegation caps

Delegation caps chain from the delegator's role-derived cap. The interactions:

6.3 Why this composes through role rather than raw caps

Doing it via raw caps would bypass role's lifecycle. With delegate, exclusion / re-derive / unassign all flow naturally — the delegation cap is in the role-derived storage path, so the layer-1 sweep finds it, the cascade reaches it, the deployment's audit trail records it.


7. Exclusion — the three-layer model

Exclusion is the strongest control surface in the role extension. It is also the most over-claimed: earlier prose said "exclusion denies access universally," but that's only true where handlers opt in to checking. The honest model is three-layer.

7.1 The three layers

Layer 1 (primary): Token revocation. When system/role:exclude runs, the handler deletes the excluded peer's role-derived tokens from system/capability/grants/role-derived/{context}/{peer_id}/.... The standard is_revoked check sees the missing tokens and rejects them on next use. This is the foundational mechanism — no new role-extension machinery; just the protocol's existing primitives.

Layer 2 (secondary): Block new derivation. Before any path mints a new token for a peer, the exclusion subtree is consulted. If system/role/{context}/excluded/{peer_id} exists, derivation is rejected. This applies to:

The invariant: no derivation path mints a token for an excluded peer in that context, regardless of which entry point is used.

Layer 3 (tertiary, opt-in): Handler-level checks. Handlers operating in a context-scoped namespace MAY check exclusion before processing requests:

handle_op(ctx, params):
  if is_excluded(context, ctx.execute.data.author, ctx):
    return error(403, "peer_excluded_in_context")
  ...

Group handlers SHOULD check; service-role handlers MAY; the generic system/tree handler does NOT. Layer 3 is the strongest semantics — "excluded peer cannot do anything in this context, even with a non-role cap" — but only where handlers opt in.

7.2 Fleet-wide reactive sweep

When Alice excludes Bob from group/team-alpha on alice_desktop, the exclusion entity syncs to alice_phone, alice_server, etc. Each runtime peer that receives the exclusion entity MUST sweep its own system/capability/grants/role-derived/{context}/{peer_id}/... subtree and delete any role-derived tokens it issued to the excluded peer.

Without this reactive bridge, alice_phone would keep issuing/holding role-derived tokens for Bob even after alice_desktop excluded him, defeating fleet-wide exclusion. The sweep is local to each runtime peer's own subtree (peers don't sweep each other's subtrees); the synced exclusion entity is the trigger.

This is what makes layer 1 actually fleet-wide rather than just "the issuing peer's tokens are revoked."

7.3 Exclusion vs. unassign

unassign removes a specific (peer, role) assignment; exclude adds a context-level denial regardless of assignment.

unassignexclude
What's removedOne assignment entity at assignment/{peer}/{role}Adds an entity at excluded/{peer}
What's revokedTokens derived from that specific assignmentAll role-derived tokens for the peer in the context
What's blockedFuture tokens from that roleAll future role-derived tokens for the peer
ReversibleYes — re-assignYes — unexclude (but tokens were deleted; re-assignment needed for fresh tokens)
Use case"Bob is no longer the manager (but is still an engineer)""Bob has been evicted; deny everything within this context"

Both compose with the standard is_revoked check for downstream rejection. Both are parallel revocation flows with different triggers.

7.4 What exclusion does NOT touch

A peer with non-role-derived tokens AND an exclusion entry: layer 1 doesn't touch those tokens (they're outside the role's storage path). Layer 3 catches them at handler-level if the handler opts in. Without layer 3, the tokens remain usable at handlers that don't check exclusion.

If you want exclusion to bite for a specific handler, that handler MUST check is_excluded(context, author, ctx) at request time.

7.5 Removing an exclusion does not auto-restore tokens

Layer 1 deleted the role-derived tokens. unexclude removes the exclusion entity but does NOT re-mint tokens. Re-assignment (or re-derive) is required to issue fresh tokens.


8. The assigner-coverage check: the security invariant

The role extension's central guarantee is: an assigner cannot mint tokens broader than their own authority. Without it, anyone with tree:put access to system/role/{context}/assignment/* could create assignments that derive arbitrary caps. With it, the assigner's caller capability is the upper bound on what the assignment mints.

(The check is named "RL2" in the spec. We refer to it descriptively in this guide.)

8.1 The check

hypothetical_grants = derived_grants    ; grants the role would mint
if not is_attenuated(hypothetical_grants, ctx.caller_capability):
  return error(403, "assigner_authority_insufficient", ...)

It uses the core protocol's existing is_attenuated helper. The check is "the role's grants are a (non-strict) attenuation of the caller's authority." Equal passes; strict subset passes; broader fails.

8.2 Fail-closed

If the assigner's cap doesn't cover any grant entry in the role, the entire assign MUST fail with 403 assigner_authority_insufficient. (An earlier draft suggested "omit the uncovered entry from the derived token" — that was a security regression letting low-authority callers receive partial tokens they didn't request. The current rule is fail-closed: surface the authority gap rather than silently mint a narrower token.)

8.3 Why this is stricter than chain-root

Other coherent-capability checks (continuation install, subscription subscribe, compute install) use chain-root: "the writer's identity must appear somewhere in the embedded cap's authority chain." That works for embedded caps because there is a single chain.

Role-derived grants are synthesized from a template; there's no single chain to walk. The right invariant is "the assigner can already do everything the assignment will let the assignee do" — coverage. The check uses scope arithmetic, not chain-walking. It's stricter (covers all five grant dimensions structurally) and tighter (no chain reachability concerns).

8.4 Determinism

resolve_templates and derive_grants MUST be deterministic across implementations. Non-deterministic derivation could pass the coverage check on one implementation and fail on another for the same inputs — breaking cross-implementation convergence. Cross-implementation test vectors for derivation determinism are recommended.


9. Bootstrap and anonymous fallback

9.1 Bootstrap exemption

The first role assignments on a fresh peer happen before the role handler is registered in the dispatch table. They are written via the SDK's L0 direct-store API under the peer-owner's authority. Bootstrap derivation MUST be callable only via L0; runtime code paths MUST NOT invoke L0 derivation. Leaving an L0 entry point reachable post-bootstrap is a soft privilege escalation surface — the assigner-coverage check is bypassed by construction on the L0 path.

9.2 Both bootstrap-derived and runtime-derived tokens are root caps

Per v2.0 PR-1, all role-derived capability tokens — bootstrap AND runtime — are root caps:

The two paths differ in how authority is established at issue time, not in the cap shape:

Both paths produce structurally identical caps. There is no "parent points to handler's grant" link for runtime caps — that was the v1.x model, which conflated issue-time authority (caller cap) with use-time chain validation (parent cap) and broke on production-shaped peers (peers with attenuated self-grants — the natural production shape). v2.0 separates the two cleanly: issue-time authority lives in RL2; use-time validity lives in the cap signature + tree binding.

Implementations MUST distinguish bootstrap from runtime derivation at the entry-point level (the bootstrap path runs only via L0; runtime via dispatched EXECUTE). The bootstrap path closes after handler registration — implementations MUST ensure the L0 derivation entry point is not reachable from any dispatched EXECUTE path or any code that runs after handler registration in the normal startup sequence.

9.3 Layer-2 exclusion applies to bootstrap, too

The is_excluded(context, peer_id) helper MUST fire on the L0 bootstrap path before issuing root caps, not only on the dispatched assign path. Implementations expose a single helper that both the handler and the L0 bootstrap path call. This prevents excluded peers from receiving bootstrap-derived caps via the only path that doesn't run through the role handler.

The helper is a pure tree:get — it returns a defined answer regardless of bootstrap order. Identity bootstrap and role bootstrap may run in any order; the exclusion check works correctly either way.

9.4 Initial-grant policy (anonymous fallback)

When an unknown peer initiates contact, the deployment chooses what role (if any) they receive. Three modes:

Configuration is stored at the normative path system/role/initial-grant-policy (renamed from system/role/bootstrap-policy in v1.6 alongside the broader "bootstrap" → "startup" / "initial-grant" terminology cleanup). Suggested shape:

system/role/initial-grant-policy: {
  unknown_peer:       "anonymous-deny",   ; or "anonymous-allow", "recognize-on-attestation"
  default_role:       "anonymous-reader",
  identity_required:  true                ; for recognize-on-attestation mode
}

Layer 2 (block-new-derivation) fires BEFORE the initial-grant policy. An excluded peer cannot bypass via anonymous-allow.

Defaults: unknown_peer: "anonymous-deny", default_role: null. Privacy-preserving and conservative; deployments opt-in to anonymous-allow when they have a public-facing role to grant.


10. Composing with identity

Identity tells you who a peer is. Roles tell you what a peer is allowed to do. The composition is at the seam where the operator key makes role assignments via the local peer→Op cap.

10.1 The local peer→Op cap is the caller capability

When system/identity:configure runs (the bootstrap exemption — see GUIDE-IDENTITY.md §3.2), it issues exactly one cap per runtime peer: a cap from the runtime peer to the operator key. The operator uses this cap as ctx.caller_capability when invoking system/role:assign. The assigner-coverage check validates that the operator's authority covers the role's grants.

10.2 What changes when the operator key rotates

Operator-key rotation is invisible at the cap layer. The local peer→Op cap is rooted at the runtime peer, not at the operator's identity. When the quorum signs a new operator-delegation, the runtime peer revokes the old local cap and issues a new one — but existing role-derived caps are unaffected (they were signed by the runtime peer, not by the operator key).

Future role assignments are made by the new operator key via the new local cap. The old operator key can no longer drive assign at this runtime peer. Existing assignments and derived caps continue to work.

This is the structural reason the operator key stays out of cap chains: rotating it is local management, not a global cap reissuance.

10.3 What changes when the runtime peer is retired

When the runtime peer is retired (its local peer→Op cap revoked), every cap chain rooted at that runtime peer dies on the next is_revoked walk:

The rotating peer SHOULD invoke rotation_reissue_outstanding_grants before retiring. This is the SDK-side helper that walks system/capability/grants/* under the rotating peer, re-issues each cap under a new authority, and emits each new cap for delivery via the consuming extension's normal flow.

10.4 What changes when Public_alice rotates

Nothing at the role layer. Public_alice rotation is processed by contacts via the attestation channel; cap chains are rooted at runtime peers, not at Public_alice. Role assignments persist; derived caps persist.

10.5 Identity-coherence cross-references

Cross-referenceWhat it says
Typical callerAssignments are typically authorized by the operator key via the local peer→Op cap. Role mechanics are unchanged regardless of caller — the operator is just the typical caller in identity-extension deployments.
Assignees can be any peer IDAn assignee may be an identity-extension-managed peer or a single-keypair (core-protocol-only) peer. The role extension does not interpret the assignee's identity-extension state; the cap targets whatever peer ID was named.
Bootstrap composesRole bootstrap and identity bootstrap share the L0 direct-store mechanism and the layer-2 exclusion check. The is_excluded helper is pure tree:get — it works regardless of which extension's bootstrap ran first.

The role extension does not depend on the identity extension. Core-protocol-only peers can use roles fully — assignments, exclusions, multi-role, delegation all work without identity. Identity is what makes the typical caller story (operator key via local cap) clean.


11. Composing with group

A group is a natural role-context. system/role/{group_id}/... is where group-scoped roles live. See GUIDE-GROUP.md for the group extension's full surface; this section covers the role-side seam.

11.1 Group context = role context

When a peer is a member of acme, the group's acme_id is the role context for any roles the member holds within Acme:

system/role/{acme_id}/manager                                   ← role definition
system/role/{acme_id}/assignment/{Public_alice_id}/manager      ← role assignment
system/role/{acme_id}/excluded/{Public_dave_id}                 ← group-scoped exclusion

Multi-role per (peer, context) means Alice can be both manager AND auditor in Acme. Tokens accumulate.

11.2 Member entry vs role assignment

Two ways to record "Alice is an admin in Acme":

These overlap. The convention pinned by the group extension:

For everyday "Alice is a member with role X," the member entry is sufficient. For complex multi-role situations, role assignments add finer granularity.

11.3 Governance and role assignments

The group extension's governance pattern (founder K-of-N, admin set, all-members, on-chain, hierarchical) determines who can sign role-context operations within the group. Role assignments are entity-creations; the governance signing rules apply.

For a founder_k_of_n group with threshold: 2, two founders must co-sign a system/role:assign. For admin_set, the configured admin threshold applies. The role handler's coverage check is unchanged — it still validates that the caller's authority (here, the K-of-N joint cap or the admin's cap) covers the role's grants.


12. What gets stored where (and who can write it)

PathWho writesMutation gatesSync exposure
system/role/{context}/{role_name} (definition)system/role:define op (default) OR direct tree:put (alternative; admin-gated)Coverage check at definition-write time + re-derive cascade on mutationPer the deployment's sync-scope caps; typically visible to all peers in the context
system/role/{context}/assignment/{peer_id}/{role_name}system/role:assign ONLYDirect tree:put rejected at content validationPer sync-scope caps; visible to context members
system/role/{context}/excluded/{peer_id}system/role:exclude ONLYDirect tree:put rejected at content validationPer sync-scope caps; SHOULD propagate to all runtime peers in fleet (the fleet-wide reactive sweep relies on it)
system/capability/grants/role-derived/{context}/{peer_id}/{token_hash}Role handler (assign / re-derive / delegate) ONLYPinned path so revocation flows can find tokensStorage-local; tokens are caps validated by the standard chain mechanics

Application-level capability grants should target handler operations, not raw tree paths. Per GUIDE-CAPABILITIES.md §2.2 (kernel-vs-handler principle):

Want to allowGrant thisNOT this
Defining rolessystem/role:definesystem/tree:put on system/role/{context}/{role_name}
Assigning rolessystem/role:assignsystem/tree:put on system/role/{context}/assignment/*
Excluding peerssystem/role:excludesystem/tree:put on system/role/{context}/excluded/*
Removing assignmentssystem/role:unassignsystem/tree:put (delete) on assignment paths
Re-deriving on definition changesystem/role:re-derive(handled internally by define)
Member-to-member delegationsystem/role:delegatedirect cap issuance bypassing role lifecycle

13. Pitfalls and antipatterns

13.1 Granting raw tree:put to assignment or exclusion paths

; ANTIPATTERN
{handlers: {include: ["system/tree"]},
 operations: {include: ["put"]},
 resources: {include: ["system/role/group/team-alpha/assignment/*"]}}

Allows the holder to fabricate assignments — and therefore mint role-derived caps — without the coverage check firing. Even if the holder doesn't have broad authority, they can scan for a role definition that gives broad grants and assign themselves to it.

; COHERENT
{handlers: {include: ["system/role"]},
 operations: {include: ["assign", "unassign"]},
 resources: {include: ["system/role/group/team-alpha/assignment/*"]}}

Routes through the handler. The coverage check rejects any assignment whose role grants exceed the assigner's authority.

13.2 Treating exclusion as universal denial

Exclusion is three-layer (§7), not magic. Layer 1 deletes role-derived tokens (the standard is_revoked check rejects them). Layer 2 blocks future role derivations. Layer 3 is opt-in handler-level checks. Non-role caps are not touched by exclusion. A peer with a directly-issued cap (e.g., from a runtime peer to a friend, not derived from a role) keeps it across an exclusion event.

If you want exclusion to bite for a specific handler, that handler MUST opt in (is_excluded(context, author, ctx) at request time).

13.3 Revoke-first re-derive

Don't. Issue T_new before revoking T_old. Revoke-first strands the assignee with no token if the issue leg fails. Issue-first is the MUST default; revoke-first is a deployment-configurable mode for specific security trade-offs only.

13.4 Forgetting the bootstrap exemption

The first role assignments on a fresh peer happen via L0 direct-store. Implementations that try to dispatch through system/role:assign during initial provisioning will fail (the role handler isn't registered yet). The L0 path is for the SDK's bootstrap code only; it MUST close after handler registration.

If your application code reaches the L0 derivation path post-bootstrap, you have a security hole. The coverage check is bypassed by construction on L0.

13.5 Defining a role with broader grants than the assigner holds

The role definition can carry any grants — the role extension doesn't validate the definition against any particular authority. The coverage check fires only at assign time, against the assigner's cap. This means: it's perfectly valid to have a super-admin role definition with wildcards; the check is what ensures only super-admin-level callers can actually assign it.

The danger: if you make the role definition mutable by lower-tier admins, and they bind it to a wildcard-cap handler op (e.g., they get system/role:define access without realizing it includes "and the resulting role can be assigned to anyone with sufficient authority elsewhere in the system"), they can engineer privilege-escalation paths via re-derive cascades. Bind define access to the deployment's admin set. §3.4.

13.6 Mixing role-derived caps with chain-root invariants

Role-derived caps don't have a single chain in the way embedded caps do. They're synthesized from a template at issuance. If you're trying to apply a chain-root check to a role-derived cap, you're using the wrong mechanism — the right one is the assigner-coverage check. See GUIDE-CAPABILITIES.md §3.3 for which extension uses which check.

13.7 Forgetting fleet-wide reactive sweep

When Alice excludes Bob on alice_desktop, the exclusion entity syncs to alice_phone. alice_phone MUST sweep its own role-derived subtree. Implementations that only revoke at the issuing peer leave stale tokens on other fleet members; cross-fleet attackers can use those tokens until they're independently revoked.

This is a real cross-implementation test surface: an exclusion arrives on a non-issuing runtime peer; that peer sweeps its own role-derived subtree; the issuing-side tokens that have already synced to it are deleted.

13.8 Assuming multi-role tokens union at verification

The chain verifier doesn't union grants across tokens. If Bob holds both manager and auditor tokens for group/team-alpha, he must present one of them (whichever covers the requested operation) on the EXECUTE. The verifier validates that one token. There's no "Bob's effective scope" computation.

If you write code that requires "the union of all of Bob's role tokens," you're operating outside the protocol's contract. The single-token-presentation model is intentional and matches the per-EXECUTE verification model.

13.9 Conflating role definition mutability with role assignment lifecycle

A role definition is a template. Mutating it cascades through re-derive (per §5) to all current assignees. Assignment lifecycle (assign, unassign) is per-assignee. Don't conflate the two: redefining manager to drop a permission does NOT remove anyone from the manager role — they continue as managers, with re-derived tokens reflecting the new permission set. Removing someone from the role requires unassign.


14. Worked examples

These walk concrete deployment shapes. The walkthroughs ground the role mechanics in real validation surfaces from EXPLORATION-DEPLOYMENT-SHAPES.md so you can trace from the guide back to the use case.

The first three walkthroughs (§14.0, §14.0a, §14.1) follow the same Acme small business through three deployment shapes: Stage-1 (role-only, no identity), the transition into Stage-2, and Stage-2 fully composed. The progression illustrates that role works without identity, that identity is genuinely additive, and that role state survives the transition.

14.0 Stage-1: Acme small business, role-only (no identity layer)

Acme is a small consulting firm: founder Alice runs the network; employees Bob, Carol, Dave use it day to day. Acme has decided NOT to install the identity extension yet — they want network management working before they take on the identity ceremony. This is a Stage-1 deployment.

What's installed. V7 + EXTENSION-ROLE + EXTENSION-SUBSCRIPTION + capability machinery. No identity extension. No quorum, no controller cert, no agent certs.

Identity model. Each person has one device with one keypair. A keypair = a peer. The peer's system/peer content_hash is its stable identifier. There is no "the same Alice across devices" — Alice's laptop is one peer, period. If Alice loses her laptop, she gets a new keypair = a new peer = new role assignments.

Setup — Alice creates the network

Alice's peer A boots up; her local SDK provisions root caps via the L0 startup-time path (per §9.2 / §4.5 of the role spec). At this point A holds caps allowing it to define roles and assign peers. No other peer is connected yet.

Alice defines two roles for the office context:

EXECUTE system/role:define
  resource: system/role/acme/staff
  params:   { grants: [
    { handlers: ["system/tree"],
      resources: ["shared/acme/*"],
      operations: ["get", "put"]
    },
    { handlers: ["system/tree"],
      resources: ["proposals/acme/*"],
      operations: ["get", "put"]
    }
  ]}
EXECUTE system/role:define
  resource: system/role/acme/manager
  params:   { grants: [
    ; everything staff has, plus role-management
    { handlers: ["system/tree"],
      resources: ["shared/acme/*", "proposals/acme/*"],
      operations: ["get", "put"]
    },
    { handlers: ["system/role"],
      resources: ["system/role/acme/assignment/*"],
      operations: ["assign", "unassign", "delegate"]
    }
  ]}

The role definitions land at:

system/role/acme/staff
system/role/acme/manager

Note manager includes system/role:delegate per RR-1.14 / PR-8.2 — it's a delegatable role. staff is not.

Bringing peers in — connection + initial assignments

Bob's peer B comes online and connects to A via standard V7 HELLO/AUTHENTICATE. Per Acme's deployment, A's initial-grant policy at system/role/initial-grant-policy is set to anonymous-deny (closed network — strangers don't get caps). So B's connection authenticates but B doesn't yet hold any role-derived cap.

Alice (out of band, knowing Bob's peer hash from when she set up his laptop) assigns B the staff role:

EXECUTE system/role:assign
  resource: system/role/acme/assignment/{B_peer_hex}/staff
  params:   { role: "staff" }

Where {B_peer_hex} is the lowercase hex of B's system/peer content_hash — what role's path encoding uses (per §3.1 of the spec). The handler:

  1. Validates the resource path resolves cleanly.
  2. Reads the role definition at system/role/acme/staff.
  3. Performs the RL2 check — Alice's caller cap MUST cover the role's grants. Alice's startup-time root caps cover everything; check passes.
  4. Resolves template variables (none in this role's grants).
  5. Issues a derived cap with granter: A_peer_hash, grantee: B_peer_hash, parent: null (root cap per v2.0 PR-1).
  6. Persists the cap at system/capability/grants/role-derived/acme/{B_peer_hex}/{token_hash} and the granter's signature at {cap_path}/signature.
  7. Writes the linkage entity at system/role/acme/derived-tokens/{B_peer_hex}/staff.

Tree state after Bob's assignment:

system/role/acme/staff                              ← role def (Alice's)
system/role/acme/manager                            ← role def (Alice's)
system/role/acme/assignment/{B_peer_hex}/staff      ← Bob's assignment
system/role/acme/derived-tokens/{B_peer_hex}/staff  ← linkage
system/capability/grants/role-derived/acme/{B_peer_hex}/{T_B_hash}        ← Bob's cap
system/capability/grants/role-derived/acme/{B_peer_hex}/{T_B_hash}/signature  ← cap signature

Bob receives his assignment

Bob's peer B has subscribed to system/role/acme/assignment/{B_peer_hex}/... at peer-config time (standard subscription extension flow). When Alice's assign executes and the assignment entity binds on A's tree, sync delivers the entity to B's tree. B's runtime peer picks up the assignment on the subscription's next tick.

B's local SDK now sees the derived cap (delivered via the same subscription surface or via system/capability/grants/role-derived/acme/{B_peer_hex}/... — implementation choice). B can now perform get and put on shared/acme/* and proposals/acme/* on Alice's peer (or on the household-server peer — same cap, scoped to acme paths). The cap is signed by Alice; V7 chain-walk validates against Alice's system/peer entity (which B already has via the connection handshake).

Adding more peers

Carol joins as another staff member. Same flow: Alice runs system/role:assign for Carol's {C_peer_hex}/staff. Carol gets her cap.

Dave joins as a manager (covers the office during Alice's vacations). Alice assigns Dave the manager role. Dave's derived cap covers staff resources AND system/role:delegate on system/role/acme/assignment/*. Dave can now run his own role assignments — he's a delegated administrator.

Day-to-day operations

Bob, Carol, Dave each perform operations using their derived caps. Each EXECUTE includes the cap in the envelope; V7's verify_capability_chain validates root → leaf (in this case, root IS leaf — root caps). RL1 enforces the cap's grants against the EXECUTE's resource/op.

If Bob tries system/role:assign for someone — denied. His staff cap doesn't cover system/role operations. RL1 rejects at dispatch.

If Dave tries system/role:delegate — succeeds for manager (his role includes delegate); fails for staff (the staff role grants in Dave's manager cap include delegate only on the manager role, not on staff). Per PR-8.2.

Removing peers

Carol leaves the company. Alice runs:

EXECUTE system/role:exclude
  resource: system/role/acme/excluded/{C_peer_hex}

The exclusion entity binds at the resource path. The handler's layer-1 sweep walks system/capability/grants/role-derived/acme/{C_peer_hex}/... and unbinds Carol's cap + signature sibling. Carol's linkage entity is removed. Bob and Dave's caps are unaffected — exclusion is per-peer, not per-context.

If Carol later attempts to use her cap (say she cached it locally), V7's chain-walk fails — the cap's tree binding is gone (V7 §5.5 is_revoked returns true when the cap path is not bound). Excluded.

What this Stage-1 deployment doesn't have:

For Acme as a closed-network small business, this Stage-1 deployment is a complete usable configuration. Many small deployments stay here.

14.0a Transitioning Acme to Stage-2 (installing identity)

Six months pass. Alice's laptop dies. She lost her keypair. The deployment recovers (Bob and Dave re-issue Alice's role to her new laptop's keypair), but the experience is rough — Alice had to manually re-provision, re-pair with everyone, etc. Alice decides to install identity.

What's added. EXTENSION-ATTESTATION + EXTENSION-QUORUM + EXTENSION-IDENTITY on Alice's peer. (Each peer in Acme decides independently whether to install identity. Bob and Dave can stay Stage-1; they interoperate fine.)

The transition for Alice's peer

Alice's existing peer A still has its keypair. The transition wraps it into an identity rather than replacing it.

Step 1 — Alice creates a quorum. She picks two friends (or trusted secondary devices) as backup-key-holders: a hardware token H1 and a paper-backup H2. K-of-N = 2-of-3 (Alice + H1 + H2). The quorum is created via L0 startup-time:

QUORUM.system/quorum:create
  signers:   [A_peer_hash, H1_peer_hash, H2_peer_hash]
  threshold: 2

The quorum entity binds at system/quorum/{q_hex}.

Step 2 — Alice mints a top-level controller cert binding her existing keypair as the controller. The cert is signed K-of-N by the quorum (Alice signs; one of H1/H2 signs):

ATTESTATION.system/attestation:create
  attesting: q_hex
  attested:  A_peer_hash    ← Alice's existing keypair, unchanged
  properties: { kind: "identity-cert", function: "controller", mode: "public" }

The cert binds at system/identity/public/cert/{cert_hash}.

Step 3 — Alice runs system/identity:configure to bind her peer-config to the trusted quorum:

EXECUTE system/identity:configure
  resource: system/identity/peer-config
  params:   { trusts_quorum: q_hex, bindings: [] }

The peer-config entity binds at system/identity/peer-config. Per PI-2 phase 4, a local-peer→controller cap mints at system/capability/grants/identity/peer-to-controller/{controller_hex} (where {controller_hex} is hex of the controller cert's content_hash — A's identity, in this case, since she's controller).

What survives the transition

Alice's role assignments (manager, etc.) bound at system/role/acme/assignment/{A_peer_hex}/manager — the {A_peer_hex} is hex of A's system/peer content_hash, which is unchanged because Alice's keypair is unchanged. Same hex; same path; entity still bound.

Same for the role-derived cap at system/capability/grants/role-derived/acme/{A_peer_hex}/{T_A_hash}. Same path; cap signed by granter: A_peer_hash (A's system/peer content_hash); A's system/peer entity is unchanged. Cap chain-validates.

Same for Bob, Carol, Dave's role assignments to A's manager-derived caps if they had any — those reference A's hash and survive.

TV-RV-2.1 in the wild. This is exactly what the validation profile's TV-RV-2.1 verifies: existing role state survives identity install. The Stage-1 deployment becomes a Stage-2 deployment by adding identity, with no disturbance to role.

What Alice gains

Alice can now add agents (per-device peers) under her identity:

What Alice's phone needs that she didn't already have

The phone is a NEW peer with a NEW content_hash. It needs its own role assignments. Alice's existing manager assignment is to {A_peer_hex} (her laptop), not to {P_phone_peer_hex}.

Alice runs (from her laptop, since the laptop holds the manager cap):

EXECUTE system/role:assign
  resource: system/role/acme/assignment/{P_phone_peer_hex}/manager
  params:   { role: "manager" }

Now her phone has a manager-derived cap. She can act as Acme's manager from her phone. Both her laptop and phone act for the same logical Alice; both have their own caps; either can rotate without affecting the other.

TV-RV-2.2 in the wild. New agent peers under the identity need new role assignments. The validation profile's TV-RV-2.2 catches impls that incorrectly assume identity-cert membership transfers role assignments — it doesn't, by design.

What about Bob and Dave

Bob and Dave can stay Stage-1 indefinitely. They have raw peer keypairs; they connect to Alice's laptop and phone via V7 caps; the identity layer is invisible to them. If Bob's peer wants to learn about Alice as an identity (rather than as two separate peers), he installs identity-aware contact caching — but he doesn't have to. Acme runs fine with mixed-stage peers.

When Bob eventually loses his laptop (or his phone, or both) and decides he wants recovery too, he runs the same transition for his peer. Same mechanics; independent of Alice. No coordination required — identity is per-peer-deployment.

What still doesn't work after the transition

Some things remain out of scope (per PROPOSAL-IDENTITY-INFRASTRUCTURE.md §3b informative scope-mapping):

For Acme's needs (recovery + multi-device for the founder), this is sufficient. The richer cases compose at higher layers.

14.1 Small business: Acme

Acme is a founder_k_of_n group (Alice, Bob, Carol; threshold 2). The group has its own identity stack and a runtime peer (partnership_server). Acme uses the role extension for in-group authority management.

Setup:

; Role definitions in Acme's context
system/role/{acme_id}/employee → {
  name: "employee",
  grants: [
    {handlers:   {include: ["system/tree"]},
     resources:  {include: ["shared/{context}/*"]},
     operations: {include: ["get", "list"]}},
    {handlers:   {include: ["system/group"]},
     resources:  {include: ["system/group/{context}"]},
     operations: {include: ["status"]}}
  ]
}

system/role/{acme_id}/engineer → {
  name: "engineer",
  grants: [
    ; engineer can put under shared/{context}/code/
    {handlers:   {include: ["system/tree"]},
     resources:  {include: ["shared/{context}/code/*"]},
     operations: {include: ["get", "put", "list"]}},
    ; ... plus employee-level grants (or assigned via multi-role)
  ]
}

system/role/{acme_id}/manager → {
  name: "manager",
  grants: [
    {handlers:   {include: ["system/tree"]},
     resources:  {include: ["shared/{context}/*"]},
     operations: {include: ["get", "put", "list", "delete"]}},
    {handlers:   {include: ["system/role"]},
     resources:  {include: ["system/role/{context}/assignment/*"]},
     operations: {include: ["assign", "unassign", "delegate"]}}     ; manager can manage roles + delegate (per §14.3 vacation coverage)
  ]
}

The delegate operation in the manager role's grants is what makes §14.3's vacation-coverage flow possible — without it, Bob (a manager) cannot drive :delegate to Carol because the protocol-layer dispatch check would reject the EXECUTE with 403 capability_denied: insufficient capability for handler scope. Delegate-ability is opt-in per role; see EXTENSION-ROLE.md §5.6 for the convention.

Onboarding Dave as an engineer:

  1. Acme's quorum (2-of-3 founders) drives system/role:assign (in implementation, the founder quorum signs the controller-cert attestation that :configure consumes; the resulting local-peer→controller cap is the single-sig caller cap on :assign. The quorum drives :assign through the configure-ceremony bridge, not as a direct multi-sig caller cap — V7 §5.5's verifyRootGranter requires the local peer in the cap's signers, which a founder quorum that doesn't include partnership_server cannot satisfy directly):
    resource: system/role/{acme_id}/assignment/{Public_dave_id}/engineer
    params:   {role: "engineer"}
    
  2. The coverage check validates: the founders' joint cap covers the engineer role's grants.
  3. Layer-2 exclusion: no exclusion entry for Dave → proceeds.
  4. Assignment entity persisted.
  5. Derived caps minted, signed by partnership_server's runtime peer, persisted at role-derived/{acme_id}/{Public_dave_id}/....
  6. Caps delivered to Dave (via Acme's onboarding flow — typically inbox push).

Multi-role: Dave is also given on-call admin:

resource: system/role/{acme_id}/assignment/{Public_dave_id}/admin
params:   {role: "admin"}

Dave now holds both engineer and admin tokens. When he EXECUTEs an operation, he presents whichever token covers it.

Eviction:

system/role:exclude
resource: system/role/{acme_id}/excluded/{Public_dave_id}

What happens:

If Acme's group handler ALSO removes Dave's member entry (system/group:remove_member), the member entry is removed AND any role assignments scoped to Acme's context are removed — see GUIDE-GROUP.md §11.2.

14.2 Open-source collaborator network

A small open-source project. Three role tiers with graduated participation:

system/role/{project_id}/proposer → {
  name: "proposer",
  grants: [
    ; can read public docs, submit issues/PRs to a designated inbox
    {handlers:   {include: ["system/tree"]},
     resources:  {include: ["public/{context}/*"]},
     operations: {include: ["get", "list"]}},
    {handlers:   {include: ["system/inbox"]},
     resources:  {include: ["system/inbox/{context}/issues/*"]},
     operations: {include: ["receive"]}}
  ]
}

system/role/{project_id}/contributor → {
  ; commits to project branches; reviews PRs
  ...
}

system/role/{project_id}/maintainer → {
  ; admin authority over the project group + role assignments
  ...
}

Anonymous fallback for unknown peers:

system/role/bootstrap-policy: {
  unknown_peer:  "allow",
  default_role:  "proposer"
}

A first-time visitor who EXECUTEs against the project's public infrastructure is bootstrap-derived a proposer cap. They can read public content and submit through the issues inbox without any explicit role assignment. Layer 2 exclusion still fires — a previously-excluded peer attempting via anonymous-allow is rejected.

Promotion to contributor:

A maintainer (already assigned maintainer) drives system/role:assign for a peer they recognize. The coverage check validates the maintainer's authority covers contributor's grants. The peer is now assigned; their proposer cap (if they had one) coexists with the new contributor cap.

Quorum drift mitigation:

Voluntary-participation groups face threshold fragility. Project maintainers may drift. Setting threshold: 3-of-7 (rather than 2-of-3) lets the project tolerate four maintainers going inactive before the quorum drops below threshold. This is deployment policy, not a role-extension mechanism.

14.3 Member-to-member delegation: vacation coverage

Prerequisite: the manager role's grants must include system/role:delegate on system/role/{context}/assignment/*. See §14.1's manager role definition; without that grant entry, Bob's :delegate EXECUTE fails at the protocol-layer dispatch check before the handler runs. Delegate-ability is opt-in per role (EXTENSION-ROLE.md §5.6).

Bob is a manager at Acme. He's going on vacation. He delegates his manager authority to Carol for two weeks:

bob_runtime_peer.execute(
  "system/role", "delegate",
  resource: "system/capability/grants/role-derived/{acme_id}/{Public_carol_id}/{token_hash}",
  params: {
    delegator:  Public_bob_id,
    delegate:   Public_carol_id,
    context:    "{acme_id}",
    role:       "manager",
    scope:      [<full manager grant set>],
    expires_at: 1741800000000   ; two weeks out
  },
  capability: <Bob's manager role-derived cap>
)

Carol now holds a delegation cap chained from Bob's manager cap. She can do everything a manager can do until expiry — except things that fail the coverage check against Bob's cap (which is impossible by construction; the scope is bounded to Bob's cap's coverage).

When Bob returns and the delegation expires, the standard is_revoked check rejects Carol's cap on next use. No handler-side cleanup needed.

If Bob is unassigned from manager mid-vacation, Carol's delegation cap chains from a now-revoked parent — she loses authority within sync-latency of Bob's unassign. Bob would need to re-issue under his new authority (if he was reassigned to manager), or Carol needs to be directly assigned.

14.4 Lateral trust escalation (adversarial)

Bob's runtime peer is compromised. The attacker holds Bob's runtime peer's keypair. What can they do with role-derived caps Bob holds?

What the attacker can do:

What the attacker cannot do:

Compromise window:

Bounded by:

The architecture keeps the blast radius bounded; it does not eliminate the compromise window. This is the structural property — see EXPLORATION-DEPLOYMENT-SHAPES.md §14.10 for the full walkthrough.


15. Reference

15.1 Entity types

TypePathCreated viaPurpose
system/role (definition)system/role/{context}/{role_name}system/role:define (default)Named grant template
system/role/assignmentsystem/role/{context}/assignment/{peer_id}/{role_name}system/role:assignBind peer to role within context
system/role/exclusionsystem/role/{context}/excluded/{peer_id}system/role:excludeDeny peer all role-derived access in context
system/capability/token (role-derived)system/capability/grants/role-derived/{context}/{peer_id}/{token_hash}Handler (assign / re-derive / delegate)Standard cap; pinned storage path

15.2 Handler operations (system/role)

OperationAuthorized byAsync?Purpose
defineCoverage check against the proposed grant set; admin-tier in practiceNoCreate or mutate role definition; cascades through re-derive
assignCoverage check against caller_capability; layer-2 exclusion check; typically the operator key via local capNoBind peer to role; mint derived caps
unassignSame authority as assign (SHOULD)NoRemove specific assignment; revoke its derived tokens
excludeCaller's cap covers the contextNo (with internal sync); fleet-wide reactive sweepAdd denial entity; layer-1 deletes role-derived tokens
unexcludeSame as excludeNoRemove denial entity; tokens are NOT auto-restored
re-deriveCaller authorizes against the role-definition pathPer-assignee independent flowsRe-issue tokens for all assignees on definition mutation; ordered writes
delegateCoverage check against the delegator's authority (not the operator's)NoMember-to-member delegation; cap rooted at delegator's runtime peer

15.3 Status codes

CodeWhen
200Operation succeeded
400 malformed_resourceResource path doesn't decompose to expected (context, peer, role) shape
400 invalid_assign_requestRequired field missing (e.g., role selector on assign)
401 capability_revokedEXECUTE in flight using a token that was just revoked by re-derive or unassign
403 assigner_authority_insufficientCoverage check failed — assigner's cap doesn't cover role-derived grants
403 assignee_excludedLayer-2 exclusion check blocked the assignment
403 peer_excluded_in_contextLayer-3 handler-level check rejected the request
404 role_not_foundAssignment references a role definition that doesn't exist

15.4 The role-identity-protocol seam

Core protocol capability system (chain verification)   ← unchanged
   ↑ used by
EXTENSION-IDENTITY (local peer→Op cap is the bridge)
   ↑ Op invokes
EXTENSION-ROLE (this guide)                             ← templates + assignments + exclusions
   ↑ produces
Role-derived caps (signed by runtime peer)              ← granter = runtime peer; chain root = local peer
   ↑ delivered to
Assignees (peers in the context)

15.5 Spec section pointers (for the reader who wants to dig deeper)

EXTENSION-ROLE.md:

Other:


16. Document history