open-standards/wai/wai-fleet
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Transaction Science dcfb9068a1
All checks were successful
joule-code conformance gate / joule-moq-relay, every target (pull_request) Successful in 3m12s
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (pull_request) Successful in 48s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (pull_request) Successful in 3m24s
standards workspaces build + test / which workspaces this change touches (pull_request) Successful in 9s
joule-code conformance gate / joule-moq-ingest, every target (pull_request) Successful in 5m44s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Successful in 1m24s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (pull_request) Successful in 9s
joule-moq-remote host agent / joule-moq-remote · does this change touch it (pull_request) Successful in 7s
WAI examples / WAI examples · does this change touch them (pull_request) Successful in 9s
wai-fleet service / does this change touch it (pull_request) Successful in 7s
joule-moq-room room service / joule-moq-room · does this change touch it (pull_request) Successful in 6s
WAI Studio / does this change touch it (pull_request) Successful in 6s
joule-moq-web browser client / joule-moq-web · does this change touch it (pull_request) Successful in 6s
wai-app and Create / wai-app · does this change touch it (pull_request) Successful in 6s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (pull_request) Successful in 38s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (pull_request) Successful in 59s
wai-fleet service / service · linux (pull_request) Successful in 2m18s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (pull_request) Successful in 2m58s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (pull_request) Successful in 30s
joule-moq-remote host agent / joule-moq-remote, every target (linux-arm64) (pull_request) Successful in 1m44s
WAI examples / WAI examples · portable · linux (pull_request) Successful in 10m15s
standards workspaces build + test / joule-code-rs (joule-code/joule-code-rs, joule-code-rs, linux-arm64, true) (pull_request) Successful in 7m20s
WAI examples / WAI examples · android-app · linux-arm64 (pull_request) Successful in 5m37s
WAI Studio / Studio in WebKit · linux (pull_request) Successful in 12m3s
joule-moq-room room service / joule-moq-room, every target (pull_request) Successful in 4m11s
joule-moq-remote host agent / joule-moq-remote, every target (macos) (pull_request) Successful in 1m7s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 11m32s
wai-fleet service / service · macos (pull_request) Successful in 57s
WAI examples / WAI examples · the archive, the readiness check, the browser and Apple · macos (pull_request) Successful in 13m39s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (pull_request) Successful in 5m54s
WAI Studio / Studio in Chromium and Firefox · macos (pull_request) Successful in 11m14s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (pull_request) Successful in 13m7s
joule-moq-relay: a fan-out holds up no other track, and each core has its own socket
A big room's fan-out held the relay's one lock for as long as it took to reach
every subscriber, so every other room waited. A track's subscribers are now copy
on write: a fan-out takes a snapshot under the lock and delivers outside it, so
the time under the lock no longer grows with the audience.

With a snapshot, a fan-out could still push to a subscription that has just
left, which would put a stream after its cancel or past its PUBLISH_DONE count.
So a subscription is marked gone under its own lock, and a fan-out holds that
lock across each push: a push comes before the mark, or sees it and isn't made.

One UDP socket and one endpoint driver carried every peer's packets. The
listener now binds one socket per core on its one port, each its own endpoint.
Steering by address, which SO_REUSEPORT does by default, would end a session
whenever a viewer's address changed: its packets would reach a socket holding no
such connection. So a small kernel filter steers by the first byte of the
connection ID, and each socket's endpoint issues IDs that come back to it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-02 19:05:27 -05:00
..
src wai-fleet: a fleet as a service, enrolments and records kept append-only, reports signed and kept with their bundles 2026-10-27 15:31:13 -04:00
Cargo.lock joule-moq-relay: a fan-out holds up no other track, and each core has its own socket 2026-11-02 19:05:27 -05:00
Cargo.toml wai-fleet: a fleet as a service, enrolments and records kept append-only, reports signed and kept with their bundles 2026-10-27 15:31:13 -04:00
README.md wai-fleet: a fleet as a service, enrolments and records kept append-only, reports signed and kept with their bundles 2026-10-27 15:31:13 -04:00
smoke.sh wai-fleet: a fleet as a service, enrolments and records kept append-only, reports signed and kept with their bundles 2026-10-27 15:31:13 -04:00

wai-fleet

A WAI fleet (extensions/fleet.md) as a service. An organisation enrols the nodes it runs by their signing keys: relays, compositors, ingest gateways, room services, ledgers and audit collectors. Each node contributes the records it already signs, as it signs them: a relay's session summaries, and every hop's per-hop records with their ledgers, with the room descriptors that place those in time. The service verifies each record against its node's enrolled key, keeps it append-only, and for a period signs the fleet report with the organisation key. The report has one line per node and one per room, with sessions, bytes and energy, and each line cites the records it came from. Energy keeps its acquisition class: one row per scope and class, never summed across either. An unmetered figure is counted as unmetered, never as zero joules, and a total over measured and unmetered figures says it is partial.

Every report is kept with its bundle: the report, its enrolments and its records. Any verifier of the extension checks a bundle without this service, the corpus's second implementation among them:

python3 -I ../live-conformance/fleet_verify.py --bundle week.wai-fleet.json
wai verify week.wai-fleet.json

Accounts, members, sign-on, quotas and billing are not here.

Running it

cargo build --locked --release
WAI_FLEET_DIR=/path/to/fleet WAI_FLEET_KEY=/path/to/organisation.key WAI_FLEET_ADDR=127.0.0.1:8252 \
  ./target/release/wai-fleet serve
variable meaning
WAI_FLEET_DIR the directory the service owns (required); created when absent
WAI_FLEET_KEY the organisation key's file: 32 bytes, the Ed25519 seed; made with mode 0600 when absent
WAI_FLEET_ADDR the address it listens on (default 127.0.0.1:8252)

wai-fleet key prints the organisation key of WAI_FLEET_KEY. wai-fleet verify FILE prints a bundle's outcome. Enrolments are signed with the organisation key, by wai enrolment create, and given to the service; the service signs none itself.

wai-fleet collect URL SOURCE reads a node's summary file and gives each line to the service at URL as a summary record: SOURCE is a path (a relay's summary.out_path) or an http or https address that answers with the file. It prints how many lines were held and how many were refused, by reason. Collecting the same file again holds nothing twice: a line held already is refused record_duplicate.

The directory:

LOCK                                  held exclusively while a process serves the directory
enrolments.jsonl                      {"enrolment": hex}, one a line, in the order admitted
records.jsonl                         each record's carriage, one a line, in the order admitted
reports/<digest>.wfr                  a report
reports/<digest>.wai-fleet.json       its bundle

The two logs are append-only: a line is written whole and the log synced before the request that admitted it is answered, and a line a crash cut short, never answered, is cut off on start. Reports are written whole (to a temporary name, synced, renamed into place). On start the service takes the lock (a second process on the same directory is refused), then reads every enrolment, record and report back and verifies each again, and refuses to start on any that does not.

Endpoints

GET /v1/fleet the service, its organisation key and its limits
GET /v1/fleet/health ok
POST /v1/fleet/enrolments {"enrolment": <hex>}: an enrolment the organisation key signed, read and verified, for a node not enrolled yet; 201 with its node, role and digest
GET /v1/fleet/enrolments every enrolment, with its node, role, label, span, digest and the records held of it
POST /v1/fleet/records {"records": [...]}: each record in its carriage, in order, verified against its node's enrolment and every record held; 200 with how many were held and refused, and each one's kind and identity or its reason
GET /v1/fleet/rooms/{room}/hops the per-hop records held of a room, each record and ledger, as a receipt ledger's room source holds them
POST /v1/fleet/reports {"start": <µs>, "end": <µs>}: the report of the period over the enrolments whose span meets it and the records held in it, signed, verified and kept; 201, its digest, its outcome and its bundle
GET /v1/fleet/reports every report kept, with its period
GET /v1/fleet/reports/{digest} a report's bundle and outcome
POST /v1/fleet/verify a bundle: its outcome

A refusal is 422 with {"outcome": "refuse", "reason"}, the extension's reason; a record refused in a batch is named in the batch's answer. A body that is not what the endpoint takes is 400; an unknown report is 404. A request body is at most 32 MiB and 10 000 records. Requests that change the fleet run one at a time; POST /v1/fleet/verify verifies at most two bundles at a time.

The service has no accounts. It holds the organisation key because it signs reports. It accepts an enrolment only when the organisation key signed it and a record only when it verifies under an enrolled node's key, whoever submits either. It listens on a loopback address unless told otherwise: who may reach it is decided in front of it.

Checks

cargo test --locked                    # the corpus's report signed byte for byte, the directory
                                       # recovered after a restart, a line cut short cut off, one
                                       # process per directory, the corpus's summaries read and
                                       # written again by the relay's own types, and records the
                                       # relay's own code signs collected from a file over HTTP
bash smoke.sh http://127.0.0.1:18252   # over HTTP, against an instance started with the corpus's
                                       # organisation key (see .github/workflows/wai-fleet.yml)