Mandates
A mandate is a signed JWT issued by a principal to an authorized actor. The usual actor is an agent, but the same primitive can authorize an integration, automation, webhook bridge, game host, market monitor, or ticket router. It says: “this actor (identified by this public key) MAY act on my behalf in these rooms, on these actions, with these caps, until this expiration.”
Mandates are optional. The participant token is the baseline credential on both transports — REST and MCP alike — and rooms default to auth: "either". A mandate is the identity-attested upgrade: a room that needs to know whose authority is behind each action sets auth: "mandate_required", and every action then carries a mandate via the X-Mandate header. An operator advertises which credential paths it accepts as auth_modes in its discovery document (/.well-known/grp.json). Mandate-joined participants get no participant token back from join — the mandate is the credential — and resolve their seat by the verified mandate’s agent DID on subsequent calls, including the long-poll (wait / next-action works via mandate exactly as via token).
The format is compact JWS (RFC 7515) — the same boring shape every OAuth/OIDC stack already speaks. Header + payload + signature, base64url-encoded, dot-separated. alg=EdDSA (Ed25519) at v0.1.
Shape
The compact JWS on the wire:
<base64url(header)>.<base64url(payload)>.<base64url(signature)>Header:
{ "alg": "EdDSA", "typ": "grp-mandate+jwt", "kid": "key-1" }Payload (decoded):
{
"iss": "https://ana.example",
"sub": "did:key:z6Mk…ana-claude",
"jti": "urn:uuid:6f...",
"nbf": 1746662400,
"exp": 1778198400,
"grp": {
"rooms": ["urn:grp:room:household-789"],
"actions": ["choose", "discuss", "propose", "ask", "start_choosing", "close"],
"weight_cap": 1,
"rate_caps": { "limit": 100, "window_seconds": 3600 },
"trust_stage": 2
}
}The payload is JCS-canonicalized (RFC 8785) before base64url-encoding — that makes the signing bytes deterministic across implementations.
Authority and actors
GRP separates the authority from the thing exercising it:
- A principal or room role owns the authority.
- An actor exercises that authority: an agent, integration, automation, or direct principal session.
- A participant is eligible to discuss and/or decide inside a decision.
Those are not always the same. A room admin can ask questions without submitting choices in them. An integration can ask a question because a principal authorized it to use ask. A department agent can discuss and decide because its principal is a participant.
The audit trail should make the attribution clear:
Decision opened by Scott
Decision opened by Scott's agent · Authorized by Scott
Decision opened by Linear triage integration · Authorized by Acme Product OpsScope evaluation
The room performs seven scope checks on every mandated call:
- Signature. The mandate verifies under the public key resolved from the issuer’s JWKS at
.well-known/grp.json(looked up by the JWSkidheader). - Validity window.
nowis betweennbfandexp. - Room match. The target room is in
grp.rooms(orgrp.roomsis["*"]for an unrestricted mandate). - Action match. The requested action is in
grp.actions. - Weight cap. The action’s weight (e.g., quadratic credits) is ≤
grp.weight_cap. - Rate cap. The action’s per-window rate is ≤
grp.rate_caps.limitpergrp.rate_caps.window_seconds. - Revocation status. The mandate’s
jtiis not in the issuer’s revocation list (published at the same.well-known/grp.json).
A failure on any check rejects the action with a structured error (see Errors).
Trust stages
grp.trust_stage is a 0–3 integer encoding how much friction the principal accepted at issuance:
| Stage | Friction | Typical use |
|---|---|---|
| 0 | None — implicit on first action | Demo / tutorial |
| 1 | Email confirmation | Casual auth |
| 2 | Authenticated session (operator-held key, OIDC/OAuth sign-in) — or device-held key at the principal’s option | Default for personal use |
| 3 | Hardware key + identity attestation | Institutional / financial |
Rooms can require minimum trust stages — e.g., a co-op room with bylaw-required identity attestation might enforce trust_stage >= 3 for board choice actions.
Restricted keys
The MCP transport pairs mandates with restricted keys (rk_*) — short-lived bearer tokens issued by the room’s OAuth server, scoped to a single resource via RFC 8707 Resource Indicators. A request presents:
Authorization: Bearer rk_...X-Mandate: <compact JWS>X-Body-Signature: <Ed25519(JCS(body))>— reserved, not enforced at v0.1; servers accept but do not yet require or verify it
The room verifies the bearer key and the mandate before processing the action. The rk_* is short-lived (hour-scale); the mandate is long-lived (year-scale). Compromising one without the other doesn’t authorize anything.
Revocation
A principal revokes a mandate by adding an entry (keyed by the mandate’s jti) to the revocations array in their .well-known/grp.json. Rooms that observe the revocation reject subsequent calls with the revoked mandate. Revocations are append-only and the issuer document is fetched fresh per Cache-Control (1-hour cap).
Cross-references
- Specification: Mandates — normative spec
- Specification: Transport — how mandates ride on the wire