§18 — Registry Federation Protocol v1.6

RCAN registries federate like email — a robot on registry-1.example can consent-grant access to a robot on registry-2.hospital without either registry having full control over the other's records. v1.6 defines the complete cross-registry trust, consent propagation, and ESTOP propagation protocol.

Protocol 66 Invariant — Federation Safety: Local safety ALWAYS wins regardless of federation status. Cross-registry commands require local consent verification on the receiving robot. An ESTOP from a federated robot is accepted; a RESUME from a federated robot requires local consent verification AND active local owner acknowledgement.

Registry Tiers

The RCAN protocol defines a three-tier registry hierarchy that controls what trust assertions each registry may make:

TierValueTrust LevelExample
Root "root" Signs authoritative registry keys; manages global trust anchors RRF (rcan.dev)
Authoritative "authoritative" Verified by root; can issue LoA 2/3 JWTs; must pass annual audit hospital-registry.nhs.uk, acme-robots.com
Community "community" Self-signed; can only issue LoA 1 JWTs; robots MAY reject for control commands homelab-registry.local, maker-registry.io

The registry_tier field appears on registry records and in JWT claims issued by that registry:

// Registry record (in Robot Registry Foundation)
{
  "registry_id":    "hospital-registry.nhs.uk",
  "registry_tier":  "authoritative",
  "public_key_fp":  "sha256:4a3f...",
  "signed_by":      "root.rcan.dev",
  "audit_expiry":   "2027-01-15",
  "dnssec_txt":     "_rcan.hospital-registry.nhs.uk"
}

// JWT claim from authoritative registry
{
  "iss": "hospital-registry.nhs.uk",
  "sub": "user-uuid-alice",
  "registry_tier": "authoritative",
  "loa": 2,
  "aud": "rcan://hospital-registry.nhs.uk/med/delivery/v2/unit-04",
  "scope": ["control"],
  "exp": 1741003600
}

FEDERATION_SYNC Message (Type 12) — Full Payload Spec

The FEDERATION_SYNC message (type 12) is the wire format for cross-registry data synchronization. It was reserved in v1.5; v1.6 defines the complete payload.

// FEDERATION_SYNC (type 12) payload
{
  "source_registry":  "registry-1.acme.com",
  "target_registry":  "hospital-registry.nhs.uk",
  "sync_type":        "consent",     // "consent" | "revocation" | "key"
  "federation_id":    "uuid-v4",     // Unique ID for this sync event
  "payload":          { ... },       // Type-specific payload (see below)
  "signature":        "ed25519:...", // Source registry signs the full payload
  "timestamp":        1741000000,
  "expires_at":       1741003600     // Sync event expiry (1h typical)
}

sync_type: "consent"

// payload for sync_type: "consent"
{
  "consent_record": {
    "request_id":      "uuid-v4",
    "requester_ruri":  "rcan://registry-1.acme.com/org/model/v1/robot-A",
    "target_ruri":     "rcan://hospital.nhs.uk/med/delivery/v2/unit-04",
    "granted_scopes":  ["status", "control"],
    "granted_by":      "user-uuid-owner-B",
    "granted_at":      1741000000,
    "expires_at":      1741086400,
    "source_registry": "registry-1.acme.com",
    "target_registry": "hospital-registry.nhs.uk",
    "owner_signature": "ed25519:...",  // Owner of target robot signs the grant
    "chain_hash":      "sha256:..."    // HMAC chain continuity
  }
}

sync_type: "revocation"

// payload for sync_type: "revocation"
{
  "revocation_record": {
    "rrn":          "rcan://registry-1.acme.com/org/model/v1/robot-A",
    "revoked_at":   1741000000,
    "reason":       "key_compromise",
    "authority":    "registry-1.acme.com",
    "propagate_to": ["hospital-registry.nhs.uk"]  // Explicit propagation list
  }
}

sync_type: "key"

// payload for sync_type: "key"
{
  "key_record": {
    "registry_id": "registry-1.acme.com",
    "kid":         "kid-abc123",
    "public_key":  "ed25519:...",
    "algorithm":   "Ed25519",
    "use":         "sig",
    "exp":         1772536000,
    "issued_at":   1741000000
  }
}

Cross-Registry JWT Trust Chains

When Robot A (on registry-1) needs to command Robot B (on registry-2), registry-2 must be able to verify that the JWT was issued by a trusted source — without registry-2 having direct knowledge of registry-1's users.

Trust Chain Construction

  1. Root signs authoritative registry keys. The root registry (rcan.dev) signs the public key of registry-1 and stores the signed record.
  2. JWT carries the issuing registry tier. JWTs from registry-1 carry "registry_tier": "authoritative" and the issuer's kid.
  3. Registry-2 verifies via trust chain. Robot B fetches registry-1's public key from its DNSSEC TXT record, verifies the root signature on that key, then verifies the JWT signature.
// Cross-registry JWT (issued by registry-1, trusted by registry-2)
{
  "iss":            "registry-1.acme.com",
  "sub":            "user-uuid-alice",
  "registry_tier":  "authoritative",
  "kid":            "reg1-key-2026a",
  "loa":            2,
  "aud":            "rcan://hospital.nhs.uk/med/delivery/v2/unit-04",
  "scope":          ["status"],
  "cross_registry": true,
  "consent_id":     "uuid-of-federated-consent-record",
  "exp":            1741003600
}

Verification Steps (Robot B's perspective)

1. Parse JWT header → extract kid, iss
2. Lookup iss in local federation cache
   → if miss: fetch _rcan.registry-1.acme.com TXT (DNSSEC)
   → extract public key fingerprint
   → fetch JWKS from registry-1.acme.com/.well-known/rcan-keys.json
   → verify JWKS signature against root public key (rcan.dev)
3. Verify JWT signature against registry-1's public key
4. Validate cross_registry: true + consent_id present
5. Lookup consent_id in local federated consent store
   → verify consent covers requested scope
   → verify consent not expired
6. Check loa >= min_loa_for_control (P66 manifest field)
7. Apply local_safety_wins (Protocol 66 invariant — local always wins)

Trust Anchor Discovery (DNSSEC)

The RCAN protocol uses DNSSEC TXT records for trust anchor discovery, eliminating the need for a centralised PKI while leveraging the existing DNS trust hierarchy.

DNS Record Format

// TXT record at: _rcan.<registry-domain>
// Example: _rcan.registry-1.acme.com

_rcan.registry-1.acme.com. 3600 IN TXT "v=rcan1; tier=authoritative; kfp=sha256:4a3f8c...; sig=ed25519:signed-by-root"

// Fields:
// v=rcan1       — record version (required)
// tier=         — "root" | "authoritative" | "community"
// kfp=          — SHA-256 fingerprint of registry's current Ed25519 public key
// sig=          — Root registry's Ed25519 signature over "v=rcan1;tier=...;kfp=..." string

Discovery Flow

ROBOT B verifying ROBOT A (from registry-1.acme.com):

1. DNS query: _rcan.registry-1.acme.com TXT  (DNSSEC validated)
2. Parse TXT: tier=authoritative, kfp=sha256:4a3f...
3. Verify sig field using root registry public key (pre-loaded / cached)
4. Fetch JWKS: https://registry-1.acme.com/.well-known/rcan-keys.json
5. Match key fingerprint against kfp in TXT record
6. Use matched public key to verify incoming JWT
DNSSEC required: Robots MUST NOT accept TXT record results from DNS resolvers that cannot confirm DNSSEC validation. Community registries (tier=community) that lack DNSSEC MUST be treated as untrusted and their JWTs rejected for control-scope commands.

Federated Consent Record Portability

A ConsentRecord issued by registry-1 and sync'd to registry-2 via FEDERATION_SYNC must be accepted by robots on both registries. The format is designed to be self-verifying:

// Portable ConsentRecord (accepted by both registries)
{
  "schema_version":    "1.6",
  "request_id":        "uuid-v4",
  "requester_ruri":    "rcan://registry-1.acme.com/org/model/v1/robot-A",
  "requester_owner":   "user-uuid-alice@registry-1.acme.com",
  "target_ruri":       "rcan://hospital.nhs.uk/med/delivery/v2/unit-04",
  "target_owner":      "user-uuid-bob@hospital.nhs.uk",
  "granted_scopes":    ["status"],
  "consent_type":      "cross_registry",
  "granted_at":        1741000000,
  "expires_at":        1741086400,
  "source_registry":   "registry-1.acme.com",
  "target_registry":   "hospital-registry.nhs.uk",
  "owner_jwt_sub":     "user-uuid-bob@hospital.nhs.uk",
  "owner_signature":   "ed25519:...",  // Target robot owner signs this record
  "sync_id":           "uuid-of-FEDERATION_SYNC-event",
  "chain_hash":        "sha256:..."    // HMAC continuity for audit
}

Portability Rules

  • The owner_signature is over the entire record (excluding chain_hash). Both registries can verify it using the owner's public key.
  • Records are stored in both registries' local consent stores after sync.
  • Expiry is enforced locally — the record is deleted from both stores at expires_at.
  • Revocation is propagated via FEDERATION_SYNC with sync_type: "revocation".

Cross-Registry ESTOP Propagation

When Robot A (on registry-1) issues an ESTOP targeting Robot B (on registry-2), the ESTOP bypasses the normal consent-gate and federation trust chain — consistent with the Protocol 66 invariant that ESTOP is always accepted from any identified source.

Propagation Rules

  • ESTOP (type 6, SAFETY) from a cross-registry robot is accepted immediately, before trust chain verification.
  • The receiving robot MUST log the cross-registry ESTOP with the source RURI and source_registry.
  • RESUME after a cross-registry ESTOP requires: (a) verified consent record, (b) trust chain verification, AND (c) local owner acknowledgement.
  • An ESTOP from a registry-1 robot does NOT authorize that robot to issue RESUME — RESUME requires consent.

12-Step Federation Flow: Alice → Robot B

Alice is a user on registry-1. Robot B is registered on registry-2. Alice wants to read Robot B's status.

Alice                 Registry-1            Registry-2            Robot B
  │                      │                      │                     │
  │ 1. Request federation │                      │                     │
  │   consent for Robot B │                      │                     │
  │──────────────────────▶│                      │                     │
  │                       │ 2. Lookup Robot B     │                     │
  │                       │   registry (DNS/RRF)  │                     │
  │                       │──────────────────────▶│                     │
  │                       │ 3. Registry-2 info    │                     │
  │                       │◀──────────────────────│                     │
  │                       │                       │ 4. Forward consent  │
  │                       │   CONSENT_REQUEST      │   request to        │
  │                       │   (FEDERATION_SYNC)   │◀───────────────────▶│
  │                       │                       │                     │
  │                       │                       │ 5. Robot B notifies │
  │                       │                       │   owner (Bob)       │
  │                       │                       │────────────────────▶│ Bob
  │                       │                       │                     │──▶ notify
  │                       │                       │                     │
  │                       │                       │ 6. Bob approves     │
  │                       │                       │   CONSENT_GRANT     │
  │                       │                       │◀────────────────────│
  │                       │                       │                     │
  │                       │ 7. FEDERATION_SYNC    │                     │
  │                       │   sync_type=consent   │                     │
  │                       │◀──────────────────────│                     │
  │                       │                       │                     │
  │                       │ 8. Mint cross-registry│                     │
  │                       │   JWT for Alice       │                     │
  │                       │ (scope=status,        │                     │
  │                       │  cross_registry=true) │                     │
  │ 9. Receive scoped JWT │                       │                     │
  │◀──────────────────────│                       │                     │
  │                       │                       │                     │
  │ 10. Send STATUS request to Robot B (with JWT) │                     │
  │───────────────────────────────────────────────────────────────────▶│
  │                       │                       │                     │
  │                       │                       │ 11. Robot B verifies│
  │                       │                       │   trust chain:      │
  │                       │                       │   DNSSEC → JWKS →   │
  │                       │                       │   JWT → consent     │
  │                       │                       │                     │
  │ 12. STATUS response   │                       │                     │
  │◀───────────────────────────────────────────────────────────────────│

Protocol 66 and Federation

Invariant §18.1: Federation status never weakens local safety. A robot with federation_enabled: false in its Protocol 66 manifest MUST reject all cross-registry commands, including STATUS requests, regardless of JWT validity.

The Protocol 66 manifest gains a new federation_enabled field in v1.6:

GET /api/safety/manifest

{
  "protocol":             66,
  "rcan_version":         "1.6",
  ...
  "federation_enabled":   true,          // false = reject all cross-registry commands
  "trusted_registries":   [              // Explicit allowlist (optional; [] = trust all authoritative)
    "hospital-registry.nhs.uk",
    "acme-robots.com"
  ],
  "min_loa_for_control":  2              // Minimum LoA required for cross-registry control scope
}

Implementation Notes

  • Robots with federation_enabled: false ignore all FEDERATION_SYNC messages.
  • Community registry JWTs (tier=community) MUST NOT be accepted for cross-registry control scope, even when federation_enabled: true.
  • The trusted_registries allowlist, when non-empty, acts as an explicit federation allowlist — cross-registry commands from unlisted registries are rejected regardless of tier.
  • Cross-registry consent records have a maximum expiry of 7 days; long-term access requires periodic re-consent.

rcan-py Federation API

from rcan.federation import FederationSync, FederatedConsentStore, TrustChainVerifier

# Build and send a FEDERATION_SYNC (consent)
sync = FederationSync.build_consent_sync(
    source_registry="registry-1.acme.com",
    target_registry="hospital-registry.nhs.uk",
    consent_record=consent_record,
    signing_key=registry_signing_key,
)
await node.send(sync)

# Verify a cross-registry JWT on receipt
verifier = TrustChainVerifier(
    root_public_key=RRF_ROOT_PUBLIC_KEY,
    dns_resolver=dnssec_resolver,
)
claims = await verifier.verify(jwt_token)
# claims.registry_tier == "authoritative"
# claims.loa == 2
# claims.cross_registry == True

# Store a federated consent record
store = FederatedConsentStore(db_path="~/.rcan/fed-consent.db")
store.put(consent_record)
valid = store.check(requester_ruri, target_ruri, scope="status")

Implementation Notes

  • DNSSEC dependency: Robots in environments without DNSSEC-capable DNS resolvers MUST fall back to root-signed JWKS fetched over HTTPS (not DNS) and MUST log a WARNING when using the HTTPS fallback.
  • Federation cache TTL: Federated registry public keys SHOULD be cached for 1 hour; cross-registry consent records MUST be refreshed at most every 24 hours.
  • Circular federation: Registry A trusting Registry B trusting Registry A (circular) is explicitly prohibited. Implementations MUST detect cycles in the trust chain and reject with FEDERATION_TRUST_CYCLE.
  • Offline federation: When registry-2 is unreachable, Robot B continues to honour cached cross-registry consent records for up to 1 hour (consistent with §14 offline operation mode).
  • ESTOP does not require federation: Cross-registry ESTOP is always accepted. Do not gate ESTOP on federation configuration or DNSSEC lookup availability.

See Also