§11.2 — Consent Wire Protocol v1.5
Earlier RCAN protocol drafts implemented consent via Firestore only — non-interoperable across runtimes. Subsequent revisions define a complete on-wire RCAN message format for consent so any RCAN-compliant runtime can participate in the consent ecosystem.
Message Types
| Type | Integer | Direction | Signer |
|---|---|---|---|
| CONSENT_REQUEST | 20 | Requester → Target Robot | Requester JWT |
| CONSENT_GRANT | 21 | Target Robot → Requester | Target Robot's Owner JWT |
| CONSENT_DENY | 22 | Target Robot → Requester | Target Robot's Owner JWT |
CONSENT_REQUEST Payload
{
"request_id": "uuid-v4", // Unique consent request ID
"requester_ruri": "rcan://rcan.dev/org/arm/v1/unit-001",
"requester_owner": "craig@example.com", // Human identity of requester's owner
"target_ruri": "rcan://rcan.dev/org/delivery/v1/unit-002",
"requested_scopes": ["control", "status"], // R2RAM scopes requested
"duration_hours": 24, // Consent validity period
"justification": "Arm needs to hand package to delivery robot",
"consent_type": "cross_robot", // "cross_robot" | "training_data" | "observer"
"data_categories": [], // ["video","audio","location","biometric"] if training
"expires_at": 1741086400 // Request expires at (not grant expiry)
} JSON Schema
{
"$schema": "https://rcan.dev/schemas/consent-request-v1.5.json",
"required": ["request_id","requester_ruri","requester_owner","target_ruri",
"requested_scopes","duration_hours","justification"],
"properties": {
"request_id": {"type":"string","format":"uuid"},
"requested_scopes": {"type":"array","items":{"type":"string"},"minItems":1},
"duration_hours": {"type":"number","minimum":0.016,"maximum":8760},
"consent_type": {"enum":["cross_robot","training_data","observer"]}
}
} CONSENT_GRANT Payload
{
"request_id": "uuid-v4", // Echo of CONSENT_REQUEST.request_id
"granted_scopes": ["control", "status"], // May be subset of requested_scopes
"expires_at": 1741086400, // Unix timestamp of grant expiry
"reason": "Approved for package handoff",
"grant_token": "eyJ..." // JWT minted by registry for requester
} CONSENT_GRANT MUST be signed by the target robot's owner JWT. Receiving a CONSENT_GRANT without a valid owner-level JWT signature MUST be rejected.
CONSENT_DENY Payload
{
"request_id": "uuid-v4",
"reason": "Access denied — unknown requester"
} Cross-Robot Consent Flow (Sequence)
Robot A (requester) Robot B (target) Human B (owner of B)
| | |
|-- CONSENT_REQUEST (20) -->| |
| (request_id: X, | |
| scopes: [control]) | |
| |-- push notification ----->|
| | "Robot A requests |
| | control access" |
| | |
| |<-- approve (RCAN app) ----|
| | |
|<-- CONSENT_GRANT (21) ----| |
| (signed by Human B JWT)| |
| (grant_token: JWT) | |
| | |
|-- COMMAND (1) ----------->| |
| (Authorization: grant_token) |
| | |
|<-- RESPONSE (2) ----------| |
| | |
§11.3 — Third-Party Human Control Flow v1.5 SHOULD
Gap: GAP-22 — The complete 7-step flow was not spec'd as a normative sequence.
Human A Registry Robot B Human B (owner of B) | | | | 1. |-- GET /api/v1/robots -->| | | | (discover Robot B) | | | | | | | 2. |-- CONSENT_REQUEST(20) ----------------->| | | (to Robot B RURI) | | | | | 3. |-- notification ---->| | | "Human A requests | | | control access" | | | | 4. |<-- CONSENT_GRANT(21)| | | (signed by HumanB)| | | | 5. |<-- grant_token (JWT) ---------------------| | | (minted by Robot B's registry, scoped) | | | | | 6. |-- COMMAND(1) ---------------------------->| | | Authorization: grant_token | | | | | 7. |<-- RESPONSE(2) ---------------------------| | | | | Audit trail: Human A → (consent grant from Human B) → Robot B
Scoped JWT Minting (Step 5)
The registry mints a scoped JWT for Human A → Robot B on receipt of the consent grant:
POST /api/v1/consent/{request_id}/mint-token
Authorization: Bearer <human-b-owner-jwt>
// Response
{
"grant_token": "eyJ...", // JWT valid for granted_scopes only
"expires_at": 1741086400,
"aud": "rcan://rcan.dev/org/delivery/v1/unit-002",
"scopes": ["control", "status"]
} The grant_token cannot exceed the granted scopes. The registry enforces this at mint time.
Offline Consent Fallback
When the registry is unreachable, consent records MUST be exportable as signed JSON blobs for local operation:
// Offline consent blob format
{
"format": "rcan-consent-blob-v1",
"grant": { ...CONSENT_GRANT payload... },
"signature": "ed25519:...", // Signed by Human B's key
"issued_at": 1741000000,
"offline_usable_until": 1741086400 // Max offline usage window
} Robots MUST validate the blob signature before accepting offline consent. Offline blobs are single-use per session (cannot be replayed after registry reconnection).
Audit Requirements
All consent events MUST be written to the audit trail:
- CONSENT_REQUEST sent/received (with
request_id, scopes, expiry) - CONSENT_GRANT or CONSENT_DENY (with
request_id, granting owner, granted scopes) - Command execution under consent (linking
grant_tokentorequest_id) - Consent expiry (automatic audit event when grant expires)
See Also
- Training Data Consent — consent_type="training_data" flows
- Cloud Relay Identity — consent requests from cloud functions
- Command Delegation Chain — robot-to-robot delegation after consent
- Offline Operation — offline consent blob usage
- MessageType 20/21/22