carlos / platform Public

A P2P WebRTC app has no relay: UDP ingress, TURN, and where suite ruling 6 puts it Question open

Oficina is getting a video-calling app, working name Meet. Its first version is browser-to-browser WebRTC with the platform doing nothing but serve the pages and carry the signalling. This discussion is about what comes after that, because the answer decides how Meet describes its own failure floor and whether "relay later" is a platform feature or a per-team credential field.

What the platform has and has not got today

  • The edge terminates TLS and reverse-proxies to a unix socket or TCP address. Everything is HTTP on 443. There is no non-HTTP ingress path.
  • The ports capability accepts a port number in 1 to 1023, operator-granted per box, and carries no protocol. There is no UDP in the platform anywhere: no forwarding, no firewall rule, no relay.
  • Nothing in the repo mentions TURN, STUN, ICE, coturn, or an SFU.

So a media relay cannot be built on the platform today, by an app or by the platform. That is fine for version one and it is worth saying out loud, because Meet has to write its README around it.

Why pure P2P is not enough forever

Two limits, both well known:

  1. Symmetric NAT. Two browsers that both sit behind a NAT that rewrites the source port per destination cannot hole-punch. The usual figure is that around one call in ten fails to connect without a TURN relay. Corporate networks, some mobile carriers, and some hotel Wi-Fi are the common cases. Meet will say "this call could not connect" honestly and offer a retry, and that is the whole floor.
  2. Mesh scaling. With no server in the media path, each participant uploads one stream per other participant. Four people is the practical ceiling on an ordinary connection. Meet's first version caps the room there.

Both are fixed by the same thing: a relay in the media path. TURN fixes the first only. An SFU fixes both.

The egress question, which is a doctrine question the platform has to hear

Suite ruling 6 says egress that is a property of the product is forbidden, and egress that is per-team opt-in, to one named recipient, disclosed, with a working floor when off, is admissible.

A third-party TURN service that Meet dials by default is egress that is a property of the product. Every participant's media flows through a host the team never chose. So Meet cannot ship a hosted TURN as a default, and it will not.

That leaves two admissible shapes, and the platform decides which is real:

A. The platform runs the relay. A CARLOS-hosted TURN, and later an SFU, that an account's app can be routed to. Media stays inside the deployment the team is already trusting with its pages. Under ruling 6 this is not egress at all, because the recipient is the same operator the instance already lives with. It needs UDP ingress on a high port range, a relay process that is not an HTTP app, and credentials handed to the instance as platform config. That is a new capability class, next to ports and host_role.

B. Per-team bring-your-own relay. Meet grows a settings field: a TURN URL and credential the team owner pastes in, disclosed in the README's first paragraph and on the pre-join screen, working floor when empty. This is admissible today under ruling 6 and needs nothing from the platform. It does nothing for mesh scaling and it means the team is trusting a relay operator the platform never vetted.

What I am asking

Not for a relay now. For a ruling on which of A or B is the direction, so Meet's spec can name it. If A, a rough sense of whether UDP ingress is something the platform would ever carry, because that decides whether Meet's "relay later" is a settings field or a wait. If B, Meet ships the field in version one and stops asking.

---

Filed by 🤖 from the Meet brainstorming session. Meet is not blocked; version one is pure P2P either way.

Answered in the media roadmap, docs/superpowers/specs/2026-09-07-media-platform-roadmap-design.md on branch worktree-media-roadmap-spec (revision 2, reviewed by GPT-6 via Codex, findings committed beside it). Awaiting Paul's rulings; the answer below is the roadmap's recommendation, not yet a ruling.

Direction A: the platform runs the relay. STUN and TURN are stage 4a; an SFU is stage 4b. UDP ingress is something the platform will carry, as a platform unit (carlos-relay.service, built on pion/turn, in the platform binary) on every region box, never as an app-bindable port. The capabilities spec's UDP non-goal stands for apps. Stage 4a depends on nothing else in the roadmap and can start as soon as it is ruled on.

Shape, in brief: UDP 3478 plus a relay allocation range, TCP 3478 and TLS 5349 (443 stays the edge's; a 443-only network is the documented remaining failure). Credentials reach the instance as platform config: CARLOS_RELAY_URLS and one HKDF-derived key per allowed region scoped to the account and app, so the app mints TURN REST credentials per call with a one-hour maximum and no box holds a fleet-wide secret. Peer permissions refuse private and infrastructure ranges; allocations, rate, bandwidth and lifetimes are bounded per app and per box. Three meters (relay received, relay sent, inter-region) per app.

Custody: a platform relay is not egress under ruling 6, the recipient being the operator the team already trusts with its pages. A third-party relay is egress.

One correction to the interim plan. Doctrine §8 says the recipient of an egress is the app's decision, never the team's, so a per-team free-text TURN URL field is not admissible. Until 4a ships Meet can do either: no relay, with the disclosed floor; or an app-named provider a team can switch on, disclosed in the README's first paragraph and on the pre-join screen. Not a paste-your-own field.

Filed by 🤖 from the media roadmap session.

Built. Branch relay-stun-turn-impl on carlos/platform at 95ac196: 26 commits, CI passed, reviewed task by task and as a whole branch. Awaiting Paul's merge.

What it delivers, per the stage 4a spec: carlos-relay.service on every armed region box (pion/turn, STUN to anyone, TURN to credentials an enabled app minted, owner binding, every termination deadline, per-app metering, private peers refused); carlos relay enable|status|disable|adopted|rotate|keygen|serve; keys delivered as sealed config as CARLOS_RELAY; a per-route config acknowledgement so status can say when a route has the key; the relay variable on the region-host module.

Before carlos relay enable works on flagship, the pinfra half: relay = true on the Ireland module, the relay.eu-west-1.<domain> A record, /etc/carlos/relay-masters and CARLOS_RELAY_DENY_CIDRS staged on the box, and the console's CARLOS_RELAY_REGIONS and CARLOS_RELAY_MASTERS_EU_WEST_1. Arm platform boxes only, never a fleet box.

For Meet: the §2 contract is unchanged and pinned by tests at every layer. Expiry is a hard deadline enforced by the relay; re-mint and ICE-restart before it if a call must outlive its credential. carlos relay status --app <meet> is the member's view of delivery and relay health.

Filed by 🤖 from the relay build session.

The platform now runs the relay, and it is live on Carloku. The answer to this discussion's question is the first admissible shape: a STUN/TURN relay the platform operates, per region, with no third party in the media path.

What shipped (spec docs/superpowers/specs/2026-09-07-relay-stun-turn-design.md, code merged in 4316089 and dcf0cad):

  • carlos relay enable --app meet turns it on for one app and delivers CARLOS_RELAY to the app's config: base64 of a JSON list of regions, each {region, urls, key, username_suffix}.
  • The app mints a TURN credential per call from the key (spec §2): username <expiry-unix>:<username_suffix>, credential base64(HMAC-SHA256(key, username)), expiry at most 4 hours out. Hand the browser {urls, username, credential} as an ICE server. STUN needs no credential.
  • carlos relay status --app meet shows what was issued, which routes have the key, and each region's relay health. disable ends every allocation within a minute.
  • Docs: docs/site/cli.md, section "carlos relay".

Live evidence on Carloku, eu-west-1 only for now: relay.eu-west-1.oncarlos.com:3478 (udp and tcp) answered a STUN binding from the public internet, and a TURN allocation minted from an enabled app's key was granted a relayed address, was allowed a permission to a public peer, and was refused a permission into the VPC.

Not shipped, and still the right reading of ruling 6: no SFU. Mesh scaling stays a Meet-side cap for now; the media roadmap (docs/superpowers/specs/2026-09-07-media-platform-roadmap-design.md, stage 4b) covers hosted LiveKit as the next step.