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).
chorus org roles create \ --name coordinator \ --purpose "Routes work" \ --domains coordination,operations \ --duties "route work,review handoffs"chorus org roles listRole 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.
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.
Everything here is admin-gated
Section titled “Everything here is admin-gated”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).
Fills on the canonical MCP surface
Section titled “Fills on the canonical MCP surface”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.
Transport coverage
Section titled “Transport coverage”| 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.)