# Envelope Schema

**Schema file:** `schemas/envelope.capnp`
**File ID:** `@0xe4a7f2c8b1d63509`

The Envelope is the legacy top-level wire message used in M1 for all quicproquo traffic. Every frame exchanged between peers was serialised as an Envelope, with the Delivery Service routing by `(groupId, msgType)` without inspecting the payload.

> **Note:** The Envelope is the M1-era framing format. The current M3+ architecture uses Cap'n Proto RPC directly via the [NodeService](node-service-schema.md) interface. The Envelope schema remains in the codebase for backward compatibility and for use in integration tests.

---

## Full schema listing

```capnp
# envelope.capnp -- top-level wire message for all quicproquo traffic.
#
# Every frame is serialised as an Envelope.
# The Delivery Service routes by (groupId, msgType) without inspecting payload.
#
# Field sizing rationale:
#   groupId / senderId : 32 bytes  -- SHA-256 digest
#   payload            : opaque    -- MLS blob or control data
#   timestampMs        : UInt64    -- unix epoch milliseconds; sufficient until year 292M
#
# ID generated with: capnp id
@0xe4a7f2c8b1d63509;

struct Envelope {
  # Message type discriminant -- determines how payload is interpreted.
  msgType     @0 :MsgType;

  # 32-byte SHA-256 digest of the group name.
  # The Delivery Service uses this as its routing key.
  # Zero-filled for point-to-point control messages (ping, keyPackageUpload, etc.).
  groupId     @1 :Data;

  # 32-byte SHA-256 digest of the sender's Ed25519 identity public key.
  senderId    @2 :Data;

  # Opaque payload. Interpretation is determined by msgType.
  payload     @3 :Data;

  # Unix timestamp in milliseconds at the time of send.
  timestampMs @4 :UInt64;

  enum MsgType {
    ping               @0;
    pong               @1;
    keyPackageUpload   @2;
    keyPackageFetch    @3;
    keyPackageResponse @4;
    mlsWelcome         @5;
    mlsCommit          @6;
    mlsApplication     @7;
    error              @8;
  }
}
```

---

## Field-by-field analysis

### `msgType @0 :MsgType`

A 16-bit enum discriminant (Cap'n Proto enums are encoded as UInt16). Determines how the `payload` field should be interpreted. The discriminant is the first field in the struct for efficient dispatch: a router can read the first two bytes of the struct section to decide how to handle the message without parsing any pointer fields.

### `groupId @1 :Data`

A 32-byte `Data` field containing the SHA-256 digest of the group name. The Delivery Service uses this as its primary routing key when the Envelope-based protocol is active.

**Sizing rationale:** SHA-256 produces a 32-byte (256-bit) digest. This is stored as a variable-length `Data` field rather than a fixed-size blob because Cap'n Proto does not have a fixed-size array type. Implementations must validate that the field contains exactly 32 bytes.

**Special case:** For point-to-point control messages (`ping`, `pong`, `keyPackageUpload`, `keyPackageFetch`), the `groupId` is zero-filled (32 zero bytes) because these messages are not associated with any group.

### `senderId @2 :Data`

A 32-byte `Data` field containing the SHA-256 digest of the sender's Ed25519 identity public key. This allows the receiver to identify the sender without inspecting the MLS-layer credentials.

**Sizing rationale:** Same as `groupId` -- SHA-256 digest, 32 bytes.

### `payload @3 :Data`

An opaque byte string whose interpretation depends on `msgType`.

### `timestampMs @4 :UInt64`

Unix epoch timestamp in milliseconds, set by the sender at the time of send. Encoded as a `UInt64`, which provides sufficient range until approximately year 292,000,000 -- effectively unlimited for practical purposes.

The timestamp is sender-asserted and **not** authenticated by the server. Receivers should treat it as advisory (for display ordering) rather than authoritative.

---

## MsgType enum

The `MsgType` enum defines nine message types. Each variant determines how the `payload` field is interpreted:

| Ordinal | Variant | Payload Contents | Direction |
|---|---|---|---|
| 0 | `ping` | Empty | Client -> Server or Peer -> Peer |
| 1 | `pong` | Empty | Server -> Client or Peer -> Peer |
| 2 | `keyPackageUpload` | openmls-serialised KeyPackage blob (TLS encoding) | Client -> Server |
| 3 | `keyPackageFetch` | Target identity key (32 bytes, raw Ed25519 public key) | Client -> Server |
| 4 | `keyPackageResponse` | openmls-serialised KeyPackage blob, or empty if none stored | Server -> Client |
| 5 | `mlsWelcome` | `MLSMessage` blob (Welcome variant) | Peer -> Peer (via DS) |
| 6 | `mlsCommit` | `MLSMessage` blob (PublicMessage / Commit variant) | Peer -> Group (via DS) |
| 7 | `mlsApplication` | `MLSMessage` blob (PrivateMessage / Application variant) | Peer -> Group (via DS) |
| 8 | `error` | UTF-8 error description string | Any direction |

### Control messages (0-1)

`ping` and `pong` are keepalive probes with empty payloads. They serve as health checks over long-lived connections.

### Authentication messages (2-4)

`keyPackageUpload`, `keyPackageFetch`, and `keyPackageResponse` implement the Authentication Service protocol over the Envelope format. In the current architecture, these operations are handled by the [NodeService RPC](node-service-schema.md) methods `uploadKeyPackage` and `fetchKeyPackage` instead.

### MLS messages (5-7)

`mlsWelcome`, `mlsCommit`, and `mlsApplication` carry MLS protocol messages as opaque blobs. The Envelope does not inspect or validate the MLS content; it simply transports the bytes between peers via the Delivery Service.

### Error messages (8)

`error` carries a UTF-8 string describing an error condition. Used for protocol-level error reporting (e.g., "no KeyPackage found for identity").

---

## Relationship to NodeService

The Envelope schema was the original M1 wire format. With the transition to QUIC + TLS 1.3 and Cap'n Proto RPC in M3, the Envelope's role has been superseded by the [NodeService interface](node-service-schema.md), which provides typed RPC methods for each operation.

The key differences:

| Aspect | Envelope (M1) | NodeService RPC (M3+) |
|---|---|---|
| Dispatch | Manual, based on `msgType` enum | Automatic, Cap'n Proto RPC method dispatch |
| Type safety | Payload is opaque `Data` | Each method has typed parameters and return values |
| Transport | QUIC + TLS 1.3 | QUIC + TLS 1.3 |
| Auth | None | Explicit `Auth` struct per method call |

---

## Further reading

- [Wire Format Overview](overview.md) -- serialisation pipeline context
- [NodeService Schema](node-service-schema.md) -- the current RPC interface that replaced Envelope-based dispatch
- [Auth Schema](auth-schema.md) -- standalone Authentication Service interface
- [Delivery Schema](delivery-schema.md) -- standalone Delivery Service interface
- [ADR-002: Cap'n Proto over MessagePack](../design-rationale/adr-002-capnproto.md) -- why Cap'n Proto was chosen for the wire format
