Files
quicproquo/docs/src/design-rationale/overview.md
Christian Nennemann 2e081ead8e chore: rename quicproquo → quicprochat in docs, Docker, CI, and packaging
Rename all project references from quicproquo/qpq to quicprochat/qpc
across documentation, Docker configuration, CI workflows, packaging
scripts, operational configs, and build tooling.

- Docker: crate paths, binary names, user/group, data dirs, env vars
- CI: workflow crate references, binary names, artifact names
- Docs: all markdown files under docs/, SDK READMEs, book.toml
- Packaging: OpenWrt Makefile, init script, UCI config (file renames)
- Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team
- Operations: Prometheus config, alert rules, Grafana dashboard
- Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths
- Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
2026-03-21 19:14:06 +01:00

6.3 KiB

Design Decisions Overview

This section collects the Architecture Decision Records (ADRs) that document the key design choices in quicprochat. Each ADR follows a standard format: context (why the decision was needed), decision (what was chosen), and consequences (trade-offs, benefits, and residual risks).

These decisions are not immutable. Each ADR has a status field and can be superseded by a later ADR if circumstances change. The goal is to preserve the reasoning behind each choice so that future contributors understand why the system works the way it does, not just how.


ADR index

ADR Title Status One-line summary
ADR-002 Cap'n Proto over MessagePack (v1) Superseded Zero-copy, schema-enforced serialisation with built-in async RPC replaced hand-rolled MessagePack dispatch. Superseded by ADR-007.
ADR-004 MLS-Unaware Delivery Service Accepted The DS routes opaque blobs by recipient key; it never inspects MLS content.
ADR-005 Single-Use KeyPackages Accepted The AS atomically removes a KeyPackage on fetch to preserve MLS forward secrecy.
ADR-007 v1 Cap'n Proto to v2 Protobuf Migration Accepted Replace Cap'n Proto RPC with custom Protobuf framing over QUIC streams for better ecosystem support, 44-method surface, and multi-threaded dispatch.

Design comparison

For a broader comparison of quicprochat's design against alternative messaging protocols (Signal, Matrix/Olm/Megolm), see Why This Design, Not Signal/Matrix/....


How to read an ADR

Each ADR page follows this structure:

  1. Status -- One of: Proposed, Accepted, Deprecated, Superseded. All current ADRs are Accepted unless noted.
  2. Context -- The problem or force that motivated the decision. What constraints existed? What alternatives were considered?
  3. Decision -- The specific choice that was made. What was selected and what was rejected?
  4. Consequences -- The trade-offs that result from the decision. What are the benefits? What are the costs? What residual risks remain?
  5. Code references -- Links to the source files where the decision is implemented.

ADR-007: v1 Cap'n Proto to v2 Protobuf Migration

Status: Accepted

Context

quicprochat v1 used Cap'n Proto for both serialisation and RPC dispatch via capnp-rpc. This worked well for the initial 8-method NodeService interface but had several limitations as the protocol expanded:

  • !Send constraint: capnp-rpc uses Rc<RefCell<>> internally, requiring all RPC futures to run on a tokio::task::LocalSet. This prevented multi-threaded dispatch and added complexity to every connection handler.
  • Schema growth friction: Cap'n Proto's capability-based RPC model does not map cleanly to large flat method tables. Adding the 36 new methods (keys, blob, device, federation, moderation, recovery, etc.) would have required significant schema refactoring.
  • ALPN collision: The b"capnp" ALPN identifier is not registered and could conflict with other Cap'n Proto deployments. A project-specific ALPN is cleaner.
  • Tooling: capnpc requires a system-wide binary installation or a vendored copy. prost-build with protobuf-src self-vendors protoc, eliminating the build-time dependency.

Decision

Replace capnp-rpc with a custom binary framing layer (quicprochat-rpc) and Protocol Buffers (prost) for payload serialisation:

  • Three frame types: Request (10-byte header), Response (9-byte header), Push (6-byte header), all carrying Protobuf-encoded payloads.
  • Method IDs are numeric u16 constants dispatched via a handler registry. One QUIC bidirectional stream per RPC call; push events on QUIC uni-streams.
  • ALPN changed from b"capnp" to b"qpc". Default port changed from 7000 to 5001.
  • Cap'n Proto legacy types are retained in quicprochat-proto for v1 compatibility but are no longer used for RPC dispatch.

Consequences

Benefits:

  • Full tokio::spawn concurrency (no LocalSet required).
  • 44-method RPC surface with clean numeric namespace and room to grow.
  • Self-contained build (no system protoc dependency).
  • Lighter middleware integration via Tower Service traits.
  • Push event delivery without polling.

Costs:

  • Lost Cap'n Proto zero-copy reads (Protobuf requires deserialisation). Acceptable because the hot path in the Delivery Service works with opaque bytes::Bytes without deserialisation.
  • Lost promise pipelining from capnp-rpc. Not required for the current RPC surface; can be re-added with a future streaming RPC design.
  • v1 clients are no longer wire-compatible with v2 servers.

Code references

  • Frame format: crates/quicprochat-rpc/src/framing.rs
  • Method IDs: crates/quicprochat-proto/src/lib.rs (method_ids module)
  • Proto schemas: proto/qpc/v1/*.proto

Cross-cutting themes

Layered security

The core principle is that no single layer is trusted alone. QUIC/TLS transport encryption protects metadata and provides server authentication; MLS provides end-to-end content encryption with forward secrecy and post-compromise security; OPAQUE ensures the server never learns the user's password.

Server minimalism

ADR-004 and ADR-005 reflect a design philosophy where the server does as little as possible. The DS does not parse MLS messages. The AS enforces single-use semantics through atomic removal rather than complex state tracking. This minimalism reduces the server's attack surface and makes it easier to audit.

Schema-first design

The v2 protocol defines all messages and method IDs in checked-in source files (proto/qpc/v1/*.proto and crates/quicprochat-proto/src/lib.rs). Every wire type is documented, versioned, and evolvable through the standard Protobuf schema evolution rules (adding optional fields, reserving removed field numbers).


Further reading