UND-1 · REFERENCE DATA

FIRST EDITIONREV. 2026-08

Undertow

A Fluid Framework–compatible collaborative document service in .NET, wire-compatible with the Gleam Floodgate server it was ported from and with the Levee Elixir server for the Phoenix protocol.

  • One process
  • One port
  • Two wire protocols
  • + REST

§1 Operating procedure

  1. Obtain the source. Undertow ships as source — there is no package to install.

    git clone https://github.com/tylerbutler/undertow.git && cd undertow
  2. Start the server. Builds the container from local source and waits for the healthcheck.

    docker compose up -d --wait

    Without Docker (needs the .NET SDK):d

    UNDERTOW_JWT_SECRET=dev-tenant-secret-key dotnet run --project src/Undertow.Server
  3. Verify, then connect. Point an official Fluid/Routerlicious driver or a levee-client at port 3000.

    curl http://localhost:3000/health
One port · :3000
/socket.io/

Engine.IO v4 / Socket.IO v5 — official Fluid & Routerlicious drivers

/socket/websocket

Phoenix Channels V2 — levee-driver & levee-client

REST

documents · deltas · token mint · git-like Historian storage

Fig. 1 Endpoint assignment. One process serves all three surfaces from a single port.

§2 Wire formats

Socket.IO event frame
4

Engine.IO
MESSAGE

2

Socket.IO
EVENT

["op", …payload]

JSON array — event name, then arguments

Fig. 2 A 42["op",…]frame as the drivers send it. The Fluid clientId is the Engine.IOsid itself.
Phoenix Channels V2 frame
join_ref
ref
topic
event
payload
Fig. 3 Five-element JSON array. Document topics take the form document:{tenant}:{documentId}.

§3 Conformance record

Recorded 2026-08-07 against the Gleam Floodgate reference at 2687b5f. The byte-level contract lives in the golden wire fixtures; counts move with the repository, and the fixtures — not this card — are the authority.

SuiteGateResultRemarks
Routerlicious conformanceajust test-dual-mode38 pass3 skipped · 1 todo
Phoenix + cross-mode conformancejust test-dual-mode7 passboth protocols, one process
Drop-in parity vs. Levee suiteblevee integration suites53 / 54one intentional 401
Readiness checksstartup probes8 / 8health, auth, storage
levee-example integrationexample app suite15 passlevee-client against Undertow
Unit + integration testsdotnet test Undertow.slnx187 passall tiers
todo-list multi-user e2eclive browser flow9 / 9real container loader

§4 Notes — deliberate divergence

Undertow departs from its references only on purpose, and every departure is written down. The full accounting is in manual §6 and tests/fixtures/wire/README.md.

  1. a

    The 3 skipped and 1 todo are the reference suite's own baseline at capture time — Floodgate posts the same counts.

  2. b

    The single non-passing parity case is an intentional 401 response, recorded as ADR-009.

  3. c

    Passing 9/9 required echoing the supplied IClient verbatim rather than key-sorted: the container loader's assert 0x4b2 demands byte-identity with the object the client sent. The Gleam reference tripped this in live browser flows; both servers now also pin initialSignals to [], matching Levee.

  4. d

    dev-tenant-secret-key is the development secret used by the example suites. Set your own UNDERTOW_JWT_SECRET anywhere that is not a laptop.

  5. e

    A Phoenix join with vsn=1.0.0 is rejected 403 before the WebSocket upgrade — the fixtures pin 403, not the 400 an earlier plan assumed.

§5 Parameters

Configuration is read from environment variables at startup. Every UNDERTOW_* key transitionally falls back to its FLOODGATE_* spelling, so one compose file can drive either binary; the source of each value is logged at startup. Full table: manual §3.

ParameterDefaultPurpose
UNDERTOW_JWT_SECRET— requiredTenant JWT signing secret. The server refuses to start without it.
PORT3000Listen port. PORT wins over UNDERTOW_PORT.
UNDERTOW_BINDlocalhostBind address.
UNDERTOW_TENANT_IDfluidTenant id used in topics and tokens.
UNDERTOW_STORAGE_BACKENDetsets / shelf = SQLite (WAL); memory = in-process only.
UNDERTOW_DATA_DIRpriv/undertow_dataSQLite data directory.
UNDERTOW_MAX_FRAME_BYTES16777216Frame cap; also advertised in the Engine.IO open and IConnected.
UNDERTOW_MAX_CONNECTIONS4096Connection ceiling. Per-IP ceiling: 256. 0 = unlimited.
UNDERTOW_MESSAGE_RATE1000Messages/sec per connection; burst 2000. 0 = unlimited.
UNDERTOW_HEARTBEAT_INTERVAL_MS30000Heartbeat cadence; timeout 60000 ms.
UNDERTOW_DOC_IDLE_MS300000Idle-document eviction window.
UNDERTOW_ALLOWED_ORIGINS(unset)Origin allow-list for browser clients; * allows all.

required — the server refuses to start without it. For rate and connection limits, 0 means unlimited.