Skip to content

Roles & fills

A role is a named capability you emit signals as — coordinator, reviewer, whatever your org needs. A fill is the assignment that says a given identity holds a given role. You can’t emit --from-role <r> unless you (the caller) have a fill binding you to that role. The deliberate exception is an authenticated admin: the zero-config bootstrap admin can create the first role and immediately emit as it before any fills exist. For ordinary members, fills are load-bearing, not bookkeeping.

Both live in the org domain alongside rings, handled in src/commands/handlers/org.ts.

A role carries name, optional purpose, and two string arrays domains and duties (OrgCreateRoleSchema, org.ts:44-49). Only name is required (1–100 chars). Roles get full CRUD: create, list, get, update, delete (org.ts:86-161).

Terminal window
chorus org roles create \
--name coordinator \
--purpose "Routes work" \
--domains coordination,operations \
--duties "route work,review handoffs"
chorus org roles list

Role definitions are organization-wide. They do not accept --ring or --capabilities; ring membership belongs to rings, while capabilities belong to identities.

A fill is just an identity ↔ role pair (OrgCreateFillSchema, org.ts:74-77). It is create / list / delete only — there is no update (org.ts:251-294). To re-point an identity, delete the fill and create a new one.

Terminal window
chorus org fills create --identity <identity> --role <role-id>
chorus org fills list --identity <identity> # or --role <role-id>

RPC method names are slash-form on the wire — org/create_fill, org/list_fills, org/delete_fill (src/rpc/methods.ts:1288-1290; exact-match dispatch, no dot→slash fallback). The dotted org.* forms are catalog command ids, not JSON-RPC methods.

op catalog id RPC method notes
create org.create_fill org/create_fill 409 ORG_FILL_DUPLICATE if the pair already exists (org.ts:258-265)
list org.list_fills org/list_fills filter by identity or role; returns [] if you pass neither (org.ts:277-279)
delete org.delete_fill org/delete_fill 404 ORG_FILL_NOT_FOUND if the id is unknown (org.ts:291)

Why fills matter beyond non-admin emit: approval gates resolve approver roles → fills → identities when snapshotting a gate’s voter pool (gap-2 §1.5, §1.4). An empty fill set means an empty approver pool.

Every org handler — including reads — starts with requireAdmin(ctx), which evaluates the policy admin:manage on the org resource and throws 403 if you’re not an admin (org.ts:22-40, applied at :93, :113, :123, :167, :187, :211, :256, :275, :288).

The canonical, deployed MCP surface is packages/chorus-mcp (the chorus_* tools). Roles and rings get full CRUD there (chorus_create_role, chorus_list_rings, …). Fills do not — no chorus_*_fill tool is registered anywhere in packages/chorus-mcp/src/tools.ts (gap-2 F5).

There is, however, a second MCP layer in-tree: the server’s opt-in embedded MCP. It is absent by default and mounted at /mcp only when EMBEDDED_MCP_ENABLED=true. It registers org_* tools through registerOrgTools (src/mcp/memory-tools.ts:668), and that set includes org_create_fill / org_list_fills / org_delete_fill (src/mcp/org-tools.ts:102-104). So an MCP client pointed at the server’s own /mcp endpoint can create a fill. gap-2 treats this embedded layer as divergent (gap-2 F9): its network boundary is hardened, but its tool names and coverage are not interchangeable with the canonical chorus_* package.

capability REST RPC (wire) catalog MCP (canonical chorus_*) SDK
roles /roles org/* org.* chorus_*_role org.*
fills /fills org/*_fill org.*_fill none org.fills*

(Adapted from gap-2 §3. The bold gap is F5 — no chorus_*_fill on the canonical surface. The server’s opt-in embedded /mcp layer does expose org_*_fill, but it is a distinct divergent surface from gap-2 F9, so it isn’t counted here.)