Repository navigation
Spec: offer only the SRT directions Strom supports (#628) - #630
Merged
eyevinn-developer[bot] merged 1 commit intoNov 11, 2026
Merged
Conversation
…s Strom supports Refs #628 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Phase 1.5 spec gate (team-lead): approve
I checked the spec (docs/specs/srt-direction-capabilities.md) and ADR-014 against #628, open-live main (src/routes/status.ts, src/lib/srt-connect.ts, src/services/listener-ports.ts, docs/openapi.yaml), draft #629, strom#1257 and strom#1258. The design is sound and covers every point in #628.
Checklist
- API contract:
server-info.srt, the 422 on POST/PATCH sources and outputs, andconnect.warning - Data model: no changes, nothing persisted, no migration (explained)
- Mermaid sequence diagram
- Config: no new Open Live env vars. Strom env vars are mapped to their effect. No Strom pipeline JSON change
- Open questions: 12 listed, each with a proposed answer. All resolved below
- ADR follows the template, and 014 is the next free number
- Only
docs/specs/**anddocs/decisions/**are touched
Resolutions (team decisions)
- Capabilities source and poller. Accepted. One generalised #629 cache fetches
/api/srt-relayand/api/capabilitiesin the same refresh. If either half fails, the shared entry uses the 5 s error TTL, and each half keeps its own last-known value. The §3.1 payload still needs to be posted on strom#1257. server-infoshape. Accepted.srt {listener, caller, relay, ports, state}is added.srtPorts,srtPortStateandsrtRelaystay exactly as they are.- Refusal. Accepted. 422 with
{error, statusCode, code: "srt-direction-unsupported", direction}, following the existing 422s. This is the first route to addcode, which is fine because it only adds fields. #629's relay 422 adopts the samecode. Document it in openapi on all four routes. - Unsupported versus unknown. Accepted. Refuse only when the state is
okand listener isfalse.unreachableandunknownaccept and setsrt-capabilities-unknown. Older Strom accepts with no warning. connect.warning. Accepted.reasonstays null-uri-only.warningdoes not depend onuriand can appear next to areason.- PATCH. Accepted. Refuse only a PATCH that changes the direction to an unsupported one. The gate runs before the port checks, so a refused
srt://:0never takes a port. - Existing records. Accepted. Still listed, still activate, flow generator untouched, explained by
connect.warning. - Older Strom. Accepted, and it is safe. A 404 gives
unsupported(today's behaviour). Other failures giveunreachable, which never refuses. Parsing fails open. - Port outside
STROM_PORTS. Accepted. Warn, do not refuse. The legacy reservation's 422 still applies while it is active. - Relay source of truth. Accepted.
/api/srt-relayis authoritative, and a disagreement is logged at warn. - Sequencing. Implementation starts only after #629 is merged to main. Do not stack it on the draft branch. Studio work follows open-live-studio#259 and starts after the backend is on staging.
- Rendezvous. Accepted. Not gated and not offered.
srtDirection. Accepted as written.mode=listenerwith a host counts as a listener. Hostlessmode=callermaps tonull. Each case gets a unit test.- ADR status. Flip ADR-014 from Proposed to Accepted in the first implementation PR.
No human decision needed.
eyevinn-developer
Bot
deleted the
docs/628-srt-direction-capabilities-spec
branch
November 11, 2026 17:25
eyevinn-developer Bot
added a commit
that referenced
this pull request
Nov 13, 2026
Add connect.warning `srt-port-outside-strom-ports` for a plain srt:// listener whose port is outside every span Strom reports in network.ports (last-known while Strom is unreachable), when no legacy srtPorts reservation is active. `srt-listener-unsupported` wins when Listener itself is unsupported. #630 resolution 9, required by the team-lead sign-off on #629. Tests cover the span checks, the reservation and null-ports cases, and that create (201) and PATCH (200) on sources and outputs carry connect.warning. openapi documents the new value on all four routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eyevinn-developer Bot
added a commit
that referenced
this pull request
Nov 13, 2026
Spec: status Implemented, Strom >= 0.8.2 (strom#1260/#1262), stale strom#1257/#1258 and draft references fixed, /api/srt-relay marked removed, superseded sections marked inline, #630 resolutions 11-14 folded in with the team-lead sign-off decisions. ADR-014: Accepted (amended 2026-11-13); Decision 4 and the "Hide only in the UI" alternative rewritten to record why modes are reported and only relays are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eyevinn-developer Bot
added a commit
that referenced
this pull request
Nov 13, 2026
* fix(flow-generator): decrypt SRT output passphrases before they reach Strom SRT output URLs are stored with the passphrase encrypted at rest (#260), like source addresses, but only source addresses were decrypted in activateStromFlow. Output URLs went into srt_uri raw, so Strom got the encv1: ciphertext as the passphrase and the receiver could never decrypt the stream. Decrypt a copy of each mpegtssrt/efpsrt output URL before the output blocks are built. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(srt): SRT relay addresses with a sticky, server-generated id Sources (srt/efp) and SRT outputs (mpegtssrt/efpsrt) accept srt+relay://<id>[?params]: a session on the SRT relay (srt-splice) that Strom fronts and resolves itself. Open Live never learns where the relay is; it owns the id and the passphrase. - srt+relay://auto asks the server for an id, like srt://:0 asks for a port: a random 128-bit base64url id is generated once and stored. A patch with auto keeps the id the doc already has, so the address the outside party dials survives activation, restarts, flow regeneration and Strom redeploys. An explicit id is stored as given. - srtUrl() validates the relay form: id [A-Za-z0-9_-]{1,64}, the same query charset and 10-79 passphrase rule, and rejects mode, streamid, localaddress and localport (Strom sets them when it calls the relay). The passphrase is encrypted at rest as for any SRT address. - Relay addresses skip listener-port reservation and clash checks, and are never declared as flow ports. Flow generation passes the (decrypted) relay URI to srt_uri unchanged. - StromClient.system.srtRelay() reads GET /api/srt-relay; the result is cached ~30 s (404 from an older Strom = not configured; other failures serve the last good answer). - connect: outputs' dial-in address resolves relay addresses to srt://<relay public>?streamid=<id> (source srt-relay, mode caller, streamid), or uri null with reason srt-relay-not-configured / srt-relay-public-unknown. port is now nullable. Sources get the same connect field. Strom is only asked when a relay doc is in the reply. - GET /api/v1/server-info adds srtRelay: { configured, public }. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(srt): unique relay ids across sources and outputs, 16+ char explicit ids A relay id is the streamid both callers use on the relay, which pairs exactly two callers per id, so an id may be held by one document only. Sources and outputs share one namespace. - Create and patch refuse an id another source or output holds with 409 and a message naming that document. Like listener ports, create checks again after the write: a clash undoes the write, then a generated (auto) id is regenerated and retried, an explicit one is refused. A doc's own id never counts against it. - Explicit ids must be [A-Za-z0-9_-]{16,64}; a shorter one is a 400 that says why (anyone who guesses the id can connect to the public relay port). auto is unchanged and generates 22 characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(srt): one resolver for SRT ports and relay ids; non-blocking relay status Code-review follow-ups on the SRT relay branch: - One resolver, resolveSrtAddress() (next to resolveListenerAddress), and one scan, usedSrtBindings(), for listener ports and relay ids alike; the resolve/write/re-check/undo loop is shared by sources and outputs, create and update (services/srt-address-write.ts). relay-ids.ts is folded in. - PATCH resolves and checks the EFFECTIVE address whenever the address or the stream type changes, so a whip -> srt patch can no longer store a literal `auto` relay id or one another document holds. - PATCH re-checks after the write like POST, and undoes the write (restoring the previous document) on a clash. - The relay status is stale-while-revalidate: requests are served from cache and refreshed in the background, the cache is warmed at startup, and only a lookup with nothing cached waits (at most 3 s). - Strom unreachable is no longer "not configured": the status keeps the last known values with state unreachable/unknown; server-info carries `state`, and connect.reason says srt-relay-unreachable. - Create/patch to a relay address is refused with 422 when Strom positively reports no relay; accepted while Strom cannot be asked. - srtUrl() matches the srt+relay:// scheme case-insensitively, like the parser and Strom. - srt-connect uses one host:port parser for authored URIs and the relay's public address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(srt): refuse new SRT addresses that GStreamer would read differently GStreamer's SRT elements in Strom match query keys and mode values exactly and drop most problems silently: `PassPhrase=` or `mode=Caller` is ignored (an address that looks encrypted runs unencrypted), a bad escape or %00 sets nothing, and a raw '#' cuts the URI. Strom now refuses such a URI when it builds the flow, so Open Live accepted it on save and the production failed only at activation. - lib/srt-uri-check.ts: checkSrtUriAsGStreamerReads, a port of Strom's validate_srt_uri (types/src/srt_uri.rs): scheme exactly srt:// (or srt+relay://), no raw '#', no malformed escape or %00, keys and mode values in GStreamer's spelling (the message names it), known mode, no key given twice, passphrase 10-80 decoded UTF-8 bytes, pbkeylen 0/16/24/32. - srtUrl(url, { newValue }) runs it on a value being written: create, and a PATCH that sends address/url. A PATCH without one (rename) checks the stored address only by the older rules, so existing documents keep working. - The passphrase limit is 80 bytes (libsrt HAICRYPT_SECRET_MAX_SZ), not 79, and is measured decoded everywhere, so %20 counts as one byte. A stored passphrase that passed the old rule (length as written) still passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(srt): accept ! * ' ( ) ~ in an SRT address query encodeURIComponent leaves these RFC 3986 unreserved/sub-delim characters unescaped, so the studio (and gateways, Companion) sent a passphrase such as "my!secret(2024)" as written, and the query allowlist answered 400 "Invalid SRT relay address format" with no hint at the cause. Both srt:// and srt+relay:// now share one allowlist that includes them. GStreamer and Strom pass them through unchanged, and the address only travels as JSON. Double quote, backslash, space and angle brackets stay refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(srt): refuse an SRT query key without a value A query key without '=' (srt://:5000?latency) crashes GStreamer 1.28.6 when the SRT element starts, and Strom now refuses such an srt_uri when it builds the flow. checkSrtUriAsGStreamerReads refuses it on save too, naming the key, for srt:// and srt+relay:// alike, so a new address never reaches Strom in that form. The message for a mode in the wrong case follows Strom's correction: GLib 2.88 and later read it ignoring case, older GLib ignores it, so the mode would differ between builds. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(srt)!: read Strom's capabilities; named SRT relays; report SRT modes Strom replaced GET /api/srt-relay (never released) with GET /api/capabilities, which reports the SRT modes that can work, the SRT relays by name with their public address, inbound reachability and STROM_PORTS. Relay URIs gained a relay name: srt+relay://<id>[@<relay>]. - services/strom-capabilities.ts replaces srt-relay-status.ts: one cached, non-blocking lookup of GET /api/capabilities, same timings (30 s fresh, stale-while-revalidate, 5 s after a failure, 3 s first wait, warmed at startup). State ok / unsupported (404: an older Strom, every mode and no relay) / unreachable (last known values) / unknown. Parsing fails open: a mode is off only when Strom says false; malformed relays and port spans are dropped. - Named relays: @<relay> is parsed, validated ([a-z0-9-]{1,32}) and written (the default relay without @, as Strom writes it). `auto` keeps the stored id and, without @, the stored relay, so renewing the passphrase never moves the stream; auto@<relay> moves it, auto@default moves it back. The dial-in address uses the named relay's public address; connect gains `relay`, and reason `srt-relay-unknown` for a name Strom does not report. A definite answer that the relay is not there refuses the write with 422 and `code` (srt-relay-not-configured | srt-relay-unknown) and `relays` (the configured names). Relay ids stay unique across all relays. - #628: server-info `srt { state, modes { listener, caller, rendezvous }, relays [{ name, public }], inboundReachable, ports }` replaces the unreleased `srtRelay`. Modes are reported, never refused: a listener or rendezvous address on a Strom that says it cannot work is stored and gets connect.warning (srt-listener-unsupported / srt-rendezvous-unsupported). Strom's own host is still resolved from the authored host, SRT_PUBLIC_HOST, then the STROM_URL host. The legacy port reservation is unchanged. - docs/openapi.yaml documents the new fields, reasons and 422 body. BREAKING CHANGE: server-info `srtRelay` is replaced by `srt` (only the draft studio branch read it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): note how Strom's capabilities API landed for #628 ADR-014 and the SRT direction spec were written before Strom settled on GET /api/capabilities with SRT modes and named relays, and before GET /api/srt-relay was dropped. Add a dated note on what #629 now implements where it differs (one endpoint, server-info `srt` shape, modes reported rather than refused, relay 422 codes) and rename the Strom env vars (STROM_SRT_RELAY_PUBLIC, STROM_SRT_RELAYS). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(srt): keep the stored passphrase when a relay address moves A patch with srt+relay://auto[@<relay>] on a document that already holds a relay address now keeps the stored (encrypted) passphrase, and the stored pbkeylen, when the query has no passphrase key. The passphrase protects the stream end to end and the relay never sees it, so moving to another relay no longer makes the operator type it again. An empty passphrase= removes it; passphrase=<value> sets it as before. An empty passphrase= is dropped from every relay address before it is stored, so it never reaches Strom. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(srt): warn when a listener port is outside Strom's ports Add connect.warning `srt-port-outside-strom-ports` for a plain srt:// listener whose port is outside every span Strom reports in network.ports (last-known while Strom is unreachable), when no legacy srtPorts reservation is active. `srt-listener-unsupported` wins when Listener itself is unsupported. #630 resolution 9, required by the team-lead sign-off on #629. Tests cover the span checks, the reservation and null-ports cases, and that create (201) and PATCH (200) on sources and outputs carry connect.warning. openapi documents the new value on all four routes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): record the #629 amendment in the spec and ADR-014 Spec: status Implemented, Strom >= 0.8.2 (strom#1260/#1262), stale strom#1257/#1258 and draft references fixed, /api/srt-relay marked removed, superseded sections marked inline, #630 resolutions 11-14 folded in with the team-lead sign-off decisions. ADR-014: Accepted (amended 2026-11-13); Decision 4 and the "Hide only in the UI" alternative rewritten to record why modes are reported and only relays are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(specs): mark the superseded #628 spec passages inline Bring §13 T1/T3-T7, the §6 studio bullets, the §8 sequence diagram, §3.2, §4.1-§4.3, §5.1, §9 and the §2 table rows in line with what #629 ships: one fetch of GET /api/capabilities, modes reported on connect.warning (srt-listener-unsupported, srt-port-outside-strom-ports), no srt-direction-unsupported 422 and no srt-capabilities-unknown. Mark ADR-014 Decisions 1-3 as amended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: eyevinn-developer[bot] <343978855+eyevinn-developer[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Epic-workflow Phase 1 spec for #628: only offer, and only accept, the SRT directions the Strom instance supports. Docs only. The PR touches nothing outside
docs/specs/anddocs/decisions/, and contains no implementation code.Refs #628
docs/specs/srt-direction-capabilities.md: API contract (server-info.srt, 422 refusal on create/PATCH,connect.warning), data model (no changes), Mermaid sequence diagram, config, dependencies (none new), open questions, risks, test cases.docs/decisions/ADR-014-strom-reported-srt-capabilities.md(Status: Proposed): Strom is the source of truth, there is one shared cached poller, and writes are refused only when Strom gives a definite answer.Dependencies and sequencing
GET /api/capabilities(inbound_reachable,ports,srt {listener, caller, relay}). The parser also accepts the same fields onGET /api/srt-relayif Strom picks that option. Until it ships, every Strom reads asunsupportedand behaviour does not change.srt-relay-status.tscache and extends itsresolveSrtAddress()gate. It does not re-specify the relay.Open questions (each with a proposed answer)
GET /api/capabilities, and parse either option. One refresh of one generalised feat(srt): SRT relay addresses with a sticky id (srt+relay://) #629 cache fetches both endpoints.server-infoshape. Proposed: addsrt: {listener, caller, relay, ports, state}. KeepsrtPorts,srtPortStateandsrtRelayas they are.{error, statusCode, code: "srt-direction-unsupported", direction}. feat(srt): SRT relay addresses with a sticky id (srt+relay://) #629's relay 422 adopts the samecodeanddirection.okand the direction is reportedfalse. When the state is unreachable or unknown, accept and setconnect.warning: "srt-capabilities-unknown".connectreason" whenuriresolves. Proposed: a new optionalconnect.warning.reasonstays null-uri-only.connect.warningexplains the problem.state: "unsupported", and everything is offered.STROM_PORTS. Proposed: warn, not refuse. Where the legacy reservation is active, its existing 422 stands./api/srt-relayis authoritative, and a disagreement is logged.Test Plan
mainor on the named PR branchChecklist
What a human does next
Nothing.
team-leadreviews the spec and ADR against the Phase 1 gate. The Strom contract is proposed upstream on Eyevinn/strom#1257.🤖 Generated with Claude Code