§8.5 — Cloud Relay Identity v1.5

RCAN messages can arrive via human operators, peer robots, or cloud functions. Without a sender_type field, the audit trail cannot distinguish autonomous cloud automation from human-initiated commands — a critical gap for incident investigation and consent enforcement.

The Problem

The Flutter → Cloud Functions → Firestore → robot bridge pipeline (the "castor bridge") allows Firebase Cloud Functions with no RURI to send commands to robots. These commands carry a human operator's JWT but are generated autonomously. Before v1.5:

  • Audit records showed only the JWT sub (user UUID), not the execution path
  • Consent requests from cloud functions appeared identical to human-originated requests
  • A cloud function could generate consent requests that appeared to come from the human operator directly

sender_type Enum

All RCAN messages from this revision onward MUST include a sender_type field in the message envelope (see the live compatibility matrix for current revisions):

ValueMeaningNotes
"human" Command originated from a human operator directly Default when field is absent (v1.4 compatibility)
"robot" Command sent by another RCAN robot (R2R) Robot RURI in source field; delegation chain required
"cloud_function" Command generated by a cloud function or serverless runtime MUST include cloud_provider field
"system" Internal system message (watchdog, scheduler, etc.) No robot signing key available

Additional Fields for Cloud Functions

When sender_type: "cloud_function", the message MUST also include:

{
  "sender_type":    "cloud_function",
  "cloud_provider": "firebase",          // "firebase" | "aws_lambda" | "gcp_run" | "azure_fn" | other
  "function_name":  "castor-bridge-v2", // Name/ID of the specific function
  "function_region": "us-central1"      // Optional: cloud region
}

Why Cloud Functions Cannot Forge sender_type

A cloud function has no access to the robot's Ed25519 private key. Therefore:

  • If the message is signed with the robot's key, sender_type must be "robot"
  • If the message carries a human JWT but no robot signature, sender_type must be "human" or "cloud_function"
  • A cloud function that sets sender_type: "human" will be recorded with the JWT's sub as the human — this is technically possible but dishonest, and creates operator accountability
  • The audit trail records sender_type alongside the JWT subject — regulators and investigators can see the full chain

JWT Custom Claims for Cloud Functions

JWTs issued to cloud functions MUST include sender_type in the custom claims block:

{
  "sub":         "firebase-cloud-functions",
  "aud":         "rcan://rcan.dev/org/robot/v1/unit-001",
  "scope":       ["control"],
  "sender_type": "cloud_function",
  "cloud_provider": "firebase",
  "exp":         1741003600
}

Robots MUST reject service tokens that claim sender_type: "cloud_function" but have no cloud_provider field.

Consent Requests from Cloud Functions

When a CONSENT_REQUEST (type 20) arrives with sender_type: "cloud_function", the target robot's owner notification MUST display the cloud function identity prominently:

// Owner notification payload when sender_type == "cloud_function"
{
  "title":    "⚠️ Service Consent Request",
  "body":     "Firebase function 'castor-bridge-v2' (on behalf of Craig) is requesting\naccess to Robot B with scope: [control]",
  "highlight": "cloud_function: castor-bridge-v2 via firebase"
}

// vs. normal human request
{
  "title":    "Consent Request",
  "body":     "Craig is requesting access to Robot B with scope: [control]"
}

This prevents social engineering where a cloud function generates consent requests that appear to come from the human operator directly.

Audit Trail Requirements

CommitmentRecords MUST include:

  • sender_type — always
  • cloud_provider and function_name — when sender_type == "cloud_function"
  • JWT sub — the human or service identity authorizing the command

These fields cannot be modified after the commitment record is written (HMAC chain integrity).

rcan-py Usage

from rcan.message import RCANMessage
from rcan.types import SenderType

# Cloud function creating a command
msg = RCANMessage(
    cmd="move_forward",
    target="rcan://rcan.dev/org/robot/v1/unit-001",
    sender_type=SenderType.CLOUD_FUNCTION,
    cloud_provider="firebase",
    function_name="castor-bridge-v2",
    ...
)

See Also