diff --git a/docs/superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md b/docs/superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md index 1b202bd..4b5ec3c 100644 --- a/docs/superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md +++ b/docs/superpowers/specs/2026-06-28-slice-1-webrtc-loopback-design.md @@ -47,7 +47,7 @@ though it pre-paves the tap by exposing the PCM boundary as a clean trait seam. | Trickle ICE | When real-world NATs demand it (likely step 5) | Non-trickle (one POST with offer+candidates, one response with answer+candidates) suffices for local loopback and keeps the dev loop zero-dependency. | | The tap itself (audio routing to an external echo process) | Spearhead step 2 | Slice 1 *pre-paves* the tap by exposing the PCM boundary as `AudioSource`/`AudioSink` traits in `rutster-media`; step 2 implements the WSS tap client behind that seam. | | The brain (STT/LLM/TTS) | Spearhead step 3 | Slice 1 echoes; step 2 swaps echo for an external process, step 3 swaps echo-process for a real brain. | -| Barge-in / VAD-driven playout kill | Spearhead step 4 | No reflex to police yet; no VAD even on the inbound side. | +| Barge-in / VAD-driven playout kill | Spearhead step 4 | No reflex to enforce yet; no VAD even on the inbound side. | | PSTN trunk (SIP client) | Spearhead step 5 | WebRTC-only ingress in slice 1; ADR-0003's Rust-native trunk SIP lands with the trunk integration. | | Spend cap / abuse gate | Spearhead step 6 | No trunk yet to gate spend against. | | CDR emission, event bus, OTel traces beyond the per-channel `tracing` span | Later rungs | PORT_PLAN keeps these as services *around* the core; slice 1 has one peer, one channel, no fanout needed. | @@ -243,16 +243,20 @@ policy is match-and-continue). ### 4.1 HTTP surface (slice 1) -- `POST /v1/sessions` → mint a `RtcSession` keyed by a fresh `ChannelId`. Returns - `{ "session_id": "" }`. -- `POST /v1/sessions/:id/offer` (body: browser SDP offer) → core produces SDP answer. - Returns the answer as `application/sdp`. -- `POST /v1/sessions/:id/ice` (body: browser ICE candidate, non-trickle) → feed to - str0m. Returns `204 No Content`. +- `POST /v1/sessions` → mint a `RtcSession` (which owns a fresh `Channel`; the + `ChannelId` *is* the session id). Returns `{ "session_id": "" }`. +- `POST /v1/sessions/:id/offer` (body: browser SDP offer, including all ICE + candidates — non-trickle) → core produces SDP answer (including its ICE candidates), + feeds candidates to str0m, returns the answer as `application/sdp`. - `DELETE /v1/sessions/:id` → tear down: transition `Channel` to `Closing → Closed`, drop the `RtcSession`, close the peer connection cleanly via str0m. - `GET /` → serve the static HTML test client. +There is **no separate `/ice` endpoint** in slice 1. Non-trickle ICE bundles all +candidates into the SDP offer/answer exchange, so one POST (`/offer`) carries +everything. A separate `/ice` endpoint is a step-5 concern (trickle ICE — see the +out-of-scope table). + ### 4.2 ICE strategy Non-trickle ICE. Browser gathers all candidates, sends offer+candidates in one POST, @@ -285,8 +289,16 @@ A single self-contained HTML file with inline JS, no build step. Behavior: ### 4.5 Session lifecycle -- Sessions held in an in-process `DashMap` in the binary crate. -- Idle timeout: 5 min hard cutoff. No re-INVITE / renegotiation in slice 1. +- Sessions held in an in-process `DashMap` in the binary crate. + The `ChannelId` (a UUID newtype from `rutster-call-model`) is the session id surfaced + in the REST API. `RtcSession` owns both the str0m `Live` + codecs and the `Channel` + (signaling state); see §3.1 and §5. +- Idle timeout: 5 min hard cutoff. "Idle" = no RTP packets received from the peer in + the last 60 s (a longer window than the strict "no RTCP" definition, because RTC + quiet periods are normal; no-RTP-for-60s is a real "the browser tab is dead" signal). + Implemented as a per-session deadline checked on each poll cycle; no per-session + tokio task spawned (which would clutter the runtime and pre-pave the wrong pattern + for the dedicated timing thread in step 4). - Restart on browser refresh: new session. No resumability. ### 4.6 API posture