diff --git a/Cargo.lock b/Cargo.lock
index 8eccb23..6b39034 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -511,6 +511,16 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "csilgen-transport"
+version = "0.1.0"
+dependencies = [
+ "ciborium",
+ "ciborium-io",
+ "serde",
+ "thiserror 1.0.69",
+]
+
[[package]]
name = "ctr"
version = "0.9.2"
@@ -704,6 +714,17 @@ dependencies = [
"syn",
]
+[[package]]
+name = "diesel_migrations"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d0f4a98124ba6d4ca75da535f65984badec16a003b6e2f94a01e31a79490b8"
+dependencies = [
+ "diesel",
+ "migrations_internals",
+ "migrations_macros",
+]
+
[[package]]
name = "diesel_table_macro_syntax"
version = "0.3.0"
@@ -867,7 +888,7 @@ dependencies = [
"atomic 0.6.1",
"pear",
"serde",
- "toml",
+ "toml 0.8.23",
"uncased",
"version_check",
]
@@ -1688,13 +1709,16 @@ dependencies = [
"chrono",
"ciborium",
"clap",
+ "csilgen-transport",
"diesel",
+ "diesel_migrations",
"ed25519-dalek",
"env_logger",
"hickory-resolver",
"liblinkkeys",
"log",
"num_cpus",
+ "qrcode",
"r2d2",
"rand 0.8.5",
"rcgen",
@@ -1706,7 +1730,7 @@ dependencies = [
"serde_json",
"threadpool",
"tokio",
- "toml",
+ "toml 0.8.23",
"urlencoding",
"uuid",
"x509-parser",
@@ -1784,6 +1808,27 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+[[package]]
+name = "migrations_internals"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36c791ecdf977c99f45f23280405d7723727470f6689a5e6dbf513ac547ae10d"
+dependencies = [
+ "serde",
+ "toml 0.9.12+spec-1.1.0",
+]
+
+[[package]]
+name = "migrations_macros"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36fc5ac76be324cfd2d3f2cf0fdf5d5d3c4f14ed8aaebadb09e304ba42282703"
+dependencies = [
+ "migrations_internals",
+ "proc-macro2",
+ "quote",
+]
+
[[package]]
name = "mime"
version = "0.3.17"
@@ -2166,6 +2211,12 @@ dependencies = [
"yansi",
]
+[[package]]
+name = "qrcode"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d68782463e408eb1e668cf6152704bd856c78c5b6417adaee3203d8f4c1fc9ec"
+
[[package]]
name = "quinn"
version = "0.11.9"
@@ -2789,6 +2840,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -3210,11 +3270,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
- "serde_spanned",
- "toml_datetime",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.11",
"toml_edit",
]
+[[package]]
+name = "toml"
+version = "0.9.12+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
+dependencies = [
+ "serde_core",
+ "serde_spanned 1.1.1",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "winnow 0.7.15",
+]
+
[[package]]
name = "toml_datetime"
version = "0.6.11"
@@ -3224,6 +3297,15 @@ dependencies = [
"serde",
]
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
[[package]]
name = "toml_edit"
version = "0.22.27"
@@ -3232,10 +3314,19 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap",
"serde",
- "serde_spanned",
- "toml_datetime",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.11",
"toml_write",
- "winnow",
+ "winnow 0.7.15",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
+dependencies = [
+ "winnow 1.0.3",
]
[[package]]
@@ -3931,6 +4022,12 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "winnow"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
+
[[package]]
name = "wit-bindgen"
version = "0.51.0"
diff --git a/Cargo.toml b/Cargo.toml
index 2ecb7e6..2e34ec2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,6 @@
[workspace]
members = [
+ "crates/csilgen-transport",
"crates/liblinkkeys",
"crates/linkkeys",
"demoappsite",
diff --git a/crates/csilgen-transport/Cargo.toml b/crates/csilgen-transport/Cargo.toml
new file mode 100644
index 0000000..73f3581
--- /dev/null
+++ b/crates/csilgen-transport/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "csilgen-transport"
+version = "0.1.0"
+edition = "2024"
+license = "Apache-2.0"
+# VENDORED from catalystcommunity/csilgen @ 17c59ef (transports/rust). Temporary:
+# the git dependency already works (public repo), but we run the copy while the
+# crate is still being iterated, then swap to the git dep at the end. Do NOT
+# hand-edit beyond the conformance test below — re-sync from upstream instead.
+description = "Vendored CSIL transport family reference implementation."
+
+[dependencies]
+ciborium = "0.2"
+ciborium-io = "0.2"
+serde = { version = "1.0", features = ["derive"] }
+thiserror = "1.0"
diff --git a/crates/csilgen-transport/src/carrier.rs b/crates/csilgen-transport/src/carrier.rs
new file mode 100644
index 0000000..c805d7b
--- /dev/null
+++ b/crates/csilgen-transport/src/carrier.rs
@@ -0,0 +1,169 @@
+//! Carrier seams — the bring-your-own-carrier boundary (conventions doc §7).
+//!
+//! The library owns envelope codecs, framing, and lifecycle; the *carrier* (the
+//! byte/datagram transport) is injected. A host supplies QUIC, WebRTC, a platform
+//! media stack, or anything else by implementing one of these traits — without
+//! changing the library.
+
+use crate::conventions::{MAX_FRAME_DEFAULT, Result, TransportError};
+use std::io::{Read, Write};
+
+/// A stream/frame carrier: sends and receives one *delimited message* at a time.
+/// Used by CSIL-RPC and CSIL-Events. Built-in implementations frame with a 4-byte
+/// big-endian length prefix; a host may implement this over WebSocket binary
+/// frames, a WebTransport stream, etc.
+pub trait FrameCarrier {
+ fn send_frame(&mut self, bytes: &[u8]) -> Result<()>;
+ /// Receive the next frame, or `None` at a clean end of stream.
+ fn recv_frame(&mut self) -> Result