open-standards/joule-code/joule-code-rs/crates/joule-moq-relay/relay.example.toml
Transaction Science 7a188614b1 joule-moq-relay: count each author's audience inputs in its chain's order, not in arrival order
Each group of a member's events track rides a stream of its own, and the relay reads each
stream in a task of its own, so two inputs sent back to back reached the floor in either
order. The floor counted them as they came: with a limit of one, it could forward a member's
second input (seq 1) and refuse its first (seq 0), leaving subscribers with seq 1 and a gap at
seq 0, the opposite of what a verifier counting in chain order decides.

The floor now keeps each author's chain as it has received it (the first seq it lacks, the
seqs above it, and the inputs waiting) and decides an input once it holds every record of the
chain below it and no input below it waits. Floor::events_filter is async: an input ahead of a
gap waits, with the rest of its stream, until the gap fills or the floor's hold runs out
([floor] hold_ms, 2 000 by default). One whose hold runs out is refused input_out_of_order,
whichever object of its author's finds it so, and the chain is counted from the record after
it. An input that arrives after a later input of its author was forwarded is refused
input_out_of_order too. Floor::events_step is the synchronous step, tested with explicit
instants; Floor::waiting counts the inputs held.

Tests: tests/floor.rs sends a member's seq 1 first, waits until the relay holds it, then
sends seq 0: seq 0 forwarded, seq 1 refused input_over_limit; and seq 1 alone, refused
input_out_of_order no sooner than the hold, after which a late seq 0 is forwarded. Unit tests
cover a chat between two votes, an object whose records are out of seq order, the chain
counted from after a refused input, and an input whose stream is gone. The hardened test
from Train D is kept; its config gains `..FloorConfig::default()`.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 03:56:05 -04:00

127 lines
5.3 KiB
TOML

# joule-moq-relay: an example configuration. Every field is optional; these are the
# defaults unless a comment says otherwise. Nothing secret belongs here: keys are files
# the operator provides, and tokens are named by their SHA-256.
# Native QUIC (ALPN moqt-22) and WebTransport over HTTP/3 (ALPN h3), one UDP port.
quic_listen = "127.0.0.1:4443"
# The WebSocket fallback (plain HTTP upgrade; put TLS in front of it).
websocket_listen = "127.0.0.1:4480"
# The path of the WebTransport CONNECT and of the WebSocket upgrade.
path = "/moq"
# The New Session URI a shutdown GOAWAY names; empty reuses the current one.
goaway_uri = ""
[tls]
# cert_path = "/etc/joule-moq-relay/fullchain.pem"
# key_path = "/etc/joule-moq-relay/key.pem"
# Without files: a development certificate for these names, its SHA-256 printed.
dev_names = ["localhost", "127.0.0.1", "::1"]
[cache]
max_bytes = 268435456
[limits]
max_sessions = 4096
max_requests = 256
max_uni_streams = 1024
max_request_updates = 16
max_auth_token_cache_size = 16384
max_rendezvous_ms = 30000
upstream_timeout_ms = 5000
fill_timeout_ms = 2000
write_concurrency = 8
max_object_bytes = 16777216
unknown_alias_wait_ms = 1000
goaway_timeout_ms = 5000
# 0 for each of these: no limit. A session at max_session_ms is closed; one sending past
# max_ingress_kbps (averaged over ingress_burst_ms) is closed; the binary exits with status
# 70 past max_resident_mb. A rule with `unlimited = true` exempts its sessions from the first
# two.
max_session_ms = 0
max_ingress_kbps = 0
ingress_burst_ms = 4000
max_resident_mb = 0
websocket_buffer_bytes = 67108864
idle_timeout_ms = 30000
quic_send_window_bytes = 1048576
[receipts]
receipts_track_suffix = "_receipts"
# Check objects in this namespace against their receipts at the door (example; not a
# default). An empty trust list accepts any key whose signature verifies. A field "*"
# matches any field; `trust_field = 1` with `prefix = ["*", "*", "video"]` would accept, in
# each room, the key its namespace's second field names (WAI live-rooms §6.1).
[[receipts.door]]
prefix = ["example", "live"]
trust = []
hold_ms = 2000
[summary]
# key_path = "/etc/joule-moq-relay/summary-seed"
# out_path = "/var/log/joule-moq-relay/sessions.jsonl"
# The room grants tokens carry, for the per-hop session records of WAI rooms (an example;
# not a default): a session whose SETUP presents this token runs under this grant.
[[metering.grants]]
token_sha256 = "1df4b07d05b9b3b1115b4b6b22af319a2f99564a2fa2573a34896063c7513c10"
grant = "0000000000000000000000000000000000000000000000000000000000000001"
joules_ceiling_micro = 5000000
funds_ceiling = 500
currency = "EUR"
[auth]
required = false
# An example grant; the hash below is of the placeholder string "replace-me".
[[auth.grants]]
prefix = ["example"]
token_sha256 = "1df4b07d05b9b3b1115b4b6b22af319a2f99564a2fa2573a34896063c7513c10"
publish = true
subscribe = true
# true: the token is a room grant's record (WAI live-rooms §4.1), honoured only on a
# session whose SETUP proved possession of its principal's key (§10.5.2).
proof = false
# true: a session presenting this token is exempt from max_session_ms and max_ingress_kbps.
unlimited = false
# true: the token may seek back, fetching a track below its live edge (WAI live-rooms
# §10.6). A room grant from an authority below seeks back when its powers hold seek.
seek = false
# The DVR store (an example; not a default): a window of the tracks under each prefix, kept
# on disk with their receipts and served to FETCH below the live edge (WAI live-recording
# §8). Groups go oldest first, whole, past max_age_s (at most 86400, 24 hours) or past
# max_bytes for the window's tracks together. The store survives a restart: on start every
# object is checked against its receipt again, and one that does not verify is dropped,
# counted, and never served. `room_keys` are the room authorities and clocks whose decision
# events withhold what they deny or redact (WAI live-moderation §6.3). `takedown_keys` are the
# services whose signed takedowns the store honours (WAI takedown §10.1): it forgets what each
# covers and signs a purge under purges/ with the summary key.
[dvr]
path = "/var/lib/joule-moq-relay/dvr"
room_keys = []
takedown_keys = []
[[dvr.window]]
prefix = ["*"]
max_age_s = 900
max_bytes = 4294967296
# The floor of WAI rooms (WAI co-performance §6): the relay reads each room's turn schedules
# and handovers from the events tracks it forwards, and does not forward a media object a
# performer publishes out of turn under a schedule that refuses. A schedule is honoured from a
# room key (these, and the keys of `auth.authorities`) or from a principal whose session proved
# a grant with the direct power. Absent: the relay holds no floor. The relay counts each
# author's audience inputs in the order of its chain (co-performance §7.3): an input that arrives
# ahead of a gap in its author's chain waits up to `hold_ms` for the gap to fill, and is refused
# `input_out_of_order` if it has not.
[floor]
room_keys = []
hold_ms = 2000
# A room authority (an example; not a default): every WRG1 grant its key signs is honoured,
# beside its proof and within its window, with the rights live-rooms §10.2 gives it. `rooms`
# narrows it to these room ids; empty, every room it grants.
# [[auth.authorities]]
# key = "<the authority's Ed25519 key, hex>"
# rooms = []