Docs Menu

Specs

spec/appendix-worked-examples.md · shown exactly as it appears in the repository

Appendix — Worked Examples

License: This text is CC-BY-4.0; the field names, enum values, and other machine-readable schema elements defined here are also MPL-2.0 — see LICENSE-DOCS and LICENSE.

This appendix is non-normative. It traces the message-level mechanics of 02-connections.md and 03-cross-domain.md against concrete, illustrative data, so a reader can see the field shapes actually flow rather than only read the normative rules in isolation. Domain identifiers, keys, and codes below are placeholders — example.chat is not a real Bonafide domain.

A. Connection handshake

Two parties, the inviter and the joiner (terminology from 02-connections.md), both run apps built on the example.chat domain.

Step 1 — Inviter generates a key pair and a connection code

The inviter generates an X25519 key pair and a connection code, then registers the code with the router before sharing it out-of-band (e.g. as a QR code):

POST /send-key/register
{
  "sendKey": "7bf483tnrpq2",
  "receiveUrl": "https://inviter.example.chat/a2a/receive",
  "routingToken": "ag3F9...opaque, encrypted with the deployment key...",
  "expiryDate": "2026-10-27T00:00:00.000Z",
  "domain": "example.chat"
}

Here sendKey is the connection code itself — the router's registry doesn't distinguish a "code" from an ordinary key, it's simply the value registered first. The inviter's local connection record is saved with cxnStatus: "codeSent".

Step 2 — Joiner checks liveness, generates its own key pair

The joiner checks the code is live:

GET /send-key/7bf483tnrpq2
→ { "isCxnCode": true }

It then generates its own X25519 key pair and registers a freshly generated key with the router — the key the inviter will use to reach it from now on:

POST /send-key/register
{
  "sendKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
  "receiveUrl": "https://joiner.example.chat/a2a/receive",
  "routingToken": "k3nQ1...opaque, encrypted with the deployment key...",
  "expiryDate": null,
  "domain": "example.chat"
}

The joiner's local connection record is saved with cxnStatus: "cxnSent".

Step 3 — Joiner sends cxnEstablished

The joiner shapes a message — shapeMessage produces an envelope/payload pair per 01-messaging.md:

{
  "envelope": {
    "version": 1,
    "messageId": "f3a1c2e4-...",
    "sendKey": "7bf483tnrpq2",
    "domain": "example.chat"
  },
  "payload": {
    "type": "connection",
    "operation": "cxnEstablished",
    "messageContent": [{
      "sendKey": "01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44",
      "receiveKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
      "cxnCode": "7bf483tnrpq2",
      "myCxnName": "Sam",
      "alias": {"cxnAliasCode": "..."},
      "dhPublicKey": "k9f2VqT3...joiner's X25519 public key, base64..."
    }]
  }
}

Note the envelope's sendKey is still the connection code — this message is addressed to whatever row the router has for it, which at this point is still the inviter's codeSent registration. Per 07-security.md, cxnEstablished is encrypted with the bootstrap deployment key, not a DH-derived secret — none exists yet. Only payload is encrypted (into encryptedPayload); what actually crosses the wire to the router is:

POST /a2a-messaging/send
{
  "version": 1,
  "messageId": "f3a1c2e4-...",
  "sendKey": "7bf483tnrpq2",
  "domain": "example.chat",
  "encryptedPayload": "U2FsdGVkX1...opaque ciphertext..."
}

The router looks up (sendKey, domain), finds the inviter's row, and forwards — appending the stored routingToken, which it never decrypts or inspects:

POST https://inviter.example.chat/a2a/receive
{
  "version": 1,
  "messageId": "f3a1c2e4-...",
  "sendKey": "7bf483tnrpq2",
  "domain": "example.chat",
  "encryptedPayload": "U2FsdGVkX1...",
  "routingToken": "ag3F9..."
}

Step 4 — Inviter replaces its registration and computes the secret

The inviter decrypts the payload (with the deployment key), then atomically repoints its router registration from the connection code to the key the joiner generated on its behalf:

POST /send-key/replace
{
  "oldSendKey": "7bf483tnrpq2",
  "newSendKey": "01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44",
  "domain": "example.chat"
}

It computes dhSharedSecret from its own private key and the joiner's dhPublicKey, discards its private key, and updates its connection record — note the key swap: the joiner's receiveKey (from step 3) becomes the inviter's own sendKey (how it addresses the joiner from now on), and the joiner's sendKey becomes the inviter's receiveKey:

{
  "sendKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
  "receiveKey": "01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44",
  "cxnStatus": "cxnEstablished",
  "theirDhPublicKey": "k9f2VqT3...",
  "dhSharedSecret": "n4Qp8r...base64, never transmitted, computed locally...",
  "dhPrivateKey": null
}

Step 5 — Inviter sends complete

{
  "envelope": {
    "version": 1,
    "messageId": "a7c9d1f2-...",
    "sendKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
    "domain": "example.chat"
  },
  "payload": {
    "type": "connection",
    "operation": "complete",
    "messageContent": [{
      "myCxnName": "Jordan",
      "isA": "connection",
      "dhPublicKey": "vR7mZx...inviter's X25519 public key, base64..."
    }]
  }
}

Still encrypted with the bootstrap deployment key, for the same reason as step 3 — complete carries the very public key the joiner needs to compute the shared secret for the first time.

Step 6 — Joiner completes, sends refreshSubscriptions

The joiner computes the same dhSharedSecret from its own private key and the inviter's dhPublicKey, discards its private key, sets cxnStatus: "cxnEstablished", and sends subscription / refreshSubscriptions. From this message onward, every message on this connection is encrypted with dhSharedSecret rather than the bootstrap key. The inviter replies with its own alias on this same secret, completing the one-way-then-the-other alias exchange described in step 3 above.

B. Extending a connection to a new domain, then subscribing

Continuing directly from Example A: Jordan and Sam's connection is established on example.chat. Jordan also runs an app on a second domain, example.photos, and wants to extend the same connection there rather than establish a separate one — per 03-cross-domain.md's requirement to reuse existing key material instead of generating a new key pair.

Step 1 — Jordan (requester) sends cxnDomainRequest

Jordan adds the new domain to her own connection record's domain array and saves a pending cxnDomainInfo entry for it, then sends the request — addressed under the domain they already share, since example.photos isn't registered with the router on either side yet:

{
  "envelope": {
    "version": 1,
    "messageId": "b2e4f6a8-...",
    "sendKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
    "domain": "example.chat"
  },
  "payload": {
    "type": "connection",
    "operation": "cxnDomainRequest",
    "messageContent": [{
      "isA": "cxnDomainInvite",
      "newDomain": "example.photos",
      "myCxnName": "Jordan"
    }]
  }
}

Only after sending does Jordan register her existing receiveKey under the new domain:

POST /send-key/register
{
  "sendKey": "01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44",
  "receiveUrl": "https://inviter.example.chat/a2a/receive",
  "routingToken": "ag3F9...",
  "expiryDate": null,
  "domain": "example.photos"
}

Per 03-cross-domain.md's register-before-addressed invariant, this ordering is safe precisely because nothing is routed under example.photos until Sam replies — the rule doesn't bind this registration to the send above, since that send was routed under example.chat.

Step 2 — Sam receives the request

Sam's connection record gets the same domain array update and a pending cxnDomainInfo entry on her side, with theirCxnName: "Jordan". No message is sent yet — accepting is a separate, explicit step, same as establishing the connection itself in the first place (03-cross-domain.md's MUST NOT auto-accept/auto-decline).

Step 3 — Sam accepts: registers first, then sends cxnDomainAccepted

This time the register-before-addressed ordering is load-bearing, since cxnDomainAccepted itself is routed under example.photos:

POST /send-key/register
{
  "sendKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
  "receiveUrl": "https://joiner.example.chat/a2a/receive",
  "routingToken": "k3nQ1...",
  "expiryDate": null,
  "domain": "example.photos"
}

Then:

{
  "envelope": {
    "version": 1,
    "messageId": "c5a7b9d1-...",
    "sendKey": "01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44",
    "domain": "example.photos"
  },
  "payload": {
    "type": "connection",
    "operation": "cxnDomainAccepted",
    "messageContent": [{
      "isA": "cxnDomainInvite",
      "domain": "example.photos",
      "myCxnName": "Sam"
    }]
  }
}

The router looks up ("01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44", "example.photos") — the row Jordan just registered in step 1 — and forwards to Jordan.

Step 4 — Jordan receives acceptance

Jordan marks her own cxnDomainInfo for example.photos as cxnEstablished, with theirCxnName: "Sam". No further router call is needed — she already registered under the new domain in step 1. Both parties' connection records now list domain: ["example.chat", "example.photos"], reusing the same sendKey/receiveKey/ dhSharedSecret as the original connection — nothing new was generated for the extension.

Step 5 — Sharing content and subscribing on the new domain

With the domain extended, example.photos's own content model applies (04-access-control.md, 05-content-model.md). Say example.photos declares album as a subbable content type — per the subbable rule, its container subscription label is its own ck; a separate, finer-grained content subscription label, ${albumCK}Content, covers the individual photos inside it. Extending the connection (steps 1-4 above) only made example.photos addressable between Jordan and Sam — it granted no access to anything. The following is the separate, explicit access-control exchange that actually shares something.

5a. Jordan shares one of her albums with Sam. This adds a local from subscription (container label = the album's own ck) and sends the album item itself as ordinary shared content — no subscribe message is involved on Jordan's side:

{
  "envelope": {
    "version": 1,
    "messageId": "d8f1a3c5-...",
    "sendKey": "01933b3e-9a44-7f12-bd03-1c2d3e4f5061",
    "domain": "example.photos"
  },
  "payload": {
    "type": "content",
    "operation": "create",
    "messageContent": [{
      "isA": "album",
      "ck": "01933c7a-5e22-7d94-8b11-2f3a4b5c6d7e",
      "version": 1,
      "title": "Summer trip",
      "originDOS": 1,
      "remainingDOS": 1,
      "shareSequence": 42
    }]
  }
}

5b. Sam receives and persists the album, recorded with receivedFromCxnId set to this connection (05-content-model.md).

5c. Sam subscribes to the album's content. Having seen the album, Sam decides (a domain policy choice, not a protocol requirement) to also receive whatever photos are in it. This adds her own local to subscription — this time for the content label, ${albumCK}Content, not the container label from step 5a — and sends the subscribe message that announces it:

{
  "envelope": {
    "version": 1,
    "messageId": "e1a2b3c4-...",
    "sendKey": "01933b3e-2f3d-7c91-9a21-8e1a9b7c0d44",
    "domain": "example.photos"
  },
  "payload": {
    "type": "subscription",
    "operation": "subscribe",
    "messageContent": [{
      "subLabel": "01933c7a-5e22-7d94-8b11-2f3a4b5c6d7eContent",
      "domain": "example.photos",
      "subStatus": "subscribed"
    }]
  }
}

5d. Jordan receives the subscribe message. This adds the mirrored from/to pair on her own side for the same content label, and fulfills it — if the album already has photos in it, each one is sent to Sam the same way the album itself was in step 5a (content / create, same shareSequence stamped across the whole batch per 06-propagation.md). If the album is still empty, there's nothing to fulfill yet, and nothing is sent until Jordan adds a photo later.