v0.50.0
OpenMOSS/MOSS-Transcribe-Diarizev0.50.0Aug 23, 2026by github-actions[bot]
Upgrading?
AI Summary
This release introduces a new configuration key to reserve RTP ports for outbound calls, preventing inbound surges from starving origination. It also bumps forge-media, which changes the DTLS-SRTP certificate type from RSA-2048 to ECDSA P-256.
Key Highlights
- New config `[media].reserved_outbound_calls` to prioritize outbound calls.
- forge-media bump changes DTLS-SRTP certificate from RSA-2048 to ECDSA P-256.
- New metrics for RTP reserve blocks and reserved calls.
- Exact floor for RTP reservation implemented in the allocator.
New Features
- RTP port reservation for outbound calls to prevent starvation.
- DTLS-SRTP certificate type change to ECDSA P-256.
- New metrics `siphon_ai_rtp_reserve_blocks_total` and `siphon_ai_rtp_reserved_outbound_calls`.
Full Release Notes
**One new config key and one dependency bump that reaches the wire.**
`[media].reserved_outbound_calls` closes the last gap the load plan found:
inbound and outbound calls draw RTP ports from one pool, first-come-first-served,
so on a node that both answers and originates an inbound surge could starve
origination *completely* with no inbound-side symptom. Set it and the inbound
allocator stops at a floor, leaving the rest to origination. Default `0` is the
old unreserved behaviour, so **upgrading changes nothing until you set it**.
⚠️ **DTLS-SRTP deployments read the forge-media bump below before upgrading.**
The certificate moves from RSA-2048 to ECDSA P-256, which is live wherever
`[media].srtp_offer = "dtls"` is set. Almost every peer prefers P-256 already —
it is what browsers present — but a peer that can only do RSA key exchange
would newly fail.
**No protocol change** (still `1`), CDR still v8. Adds one config key, two
metrics, and a `--inspect-config` line. Wants a restart to take effect.
### Changed
- **forge-media pinned to `1fe94a502efa`** — nine commits, three of them
filed from this project. [#120](https://github.com/thevoiceguy/forge-media/pull/120)
adds `PortPool::allocate_reserving(min_free)` and
`MediaSessionConfig::min_free_port_pairs`, which is what makes
`[media].reserved_outbound_calls` above an *exact* floor: the check now
happens inside the allocator's own critical section instead of as a
free-pair read taken just before it, so concurrent INVITE setup can no
longer dip below the reservation. Both land in the same release, so no
published build ever had the softer version.
[#122](https://github.com/thevoiceguy/forge-media/pull/122) fixed
forge's broken-link CI step (it grepped for a word rustdoc never
prints, so it had passed unconditionally while eleven unresolved links
accumulated) and [#123](https://github.com/thevoiceguy/forge-media/pull/123)
gave `forge-vad` a types-only `forge-core` dependency so two of them
could be live links. Neither changes anything we compile.
**One upstream change reaches our wire without us asking for it:**
forge-media #117 + #118 move the DTLS-SRTP certificate from **RSA-2048
to ECDSA P-256** and offer ECDHE-ECDSA first. We build `forge-engine`
with `dtls`, so this is live wherever `[media].srtp_offer = "dtls"`
(0.9.4) is set. The SDP `a=fingerprint` stays `sha-256` — only the key
type, and therefore the hash value, change — and P-256 is what browsers
and webrtc-rs present, so it is the more interoperable default as well
as ~100× faster to generate. A peer that can only do RSA key exchange
would now fail where it previously worked. The `delayed_offer_dtls`
SIPp scenario covers the handshake.
The remaining commits (#116, #119, #121) are WebRTC/ICE work in
`forge-webrtc` / `forge-ice`, neither of which we depend on. #115 bumps
forge-media's own siphon-rs submodule to v2026.08.19; we pin siphon-rs
directly at v2026.08.21 and the two stay separate package sources, but
the version skew closed — forge's vendored `sip-sdp` goes 0.3.0 → 0.3.1,
matching what our tag carries.
### Added
- **`[media].reserved_outbound_calls`: hold RTP ports back for
origination** (#556). Inbound and outbound draw port pairs from one
pool, first-come-first-served, with no reservation between them, so on
a node that both answers and originates an inbound surge starves
origination *completely* — and there is no inbound-side symptom while
it happens. Measured on 0.49.9 with the pool shrunk to 60 calls and 50
inbound + 20 outbound asked for: inbound established **50/50** and
stayed healthy for the whole window while **10 of 20 originates
failed** (`test-harness/load/RESULTS-0.49.9-mixed-and-soak.md` §2). The
starved direction is usually the one with a deadline attached — a
scheduled callback or a notification, versus a caller who will redial —
so the default allocation is the opposite of how an operator would
prioritise it, and the only lever was `[sip.admission]`: a global
inbound concurrency cap standing in for a pool reservation, with the
two numbers drifting apart the moment `rtp_port_range` changed.
Set `reserved_outbound_calls = N` and the **inbound** allocator refuses
once the pool's free pairs reach `N`, leaving the rest to origination,
which is not gated. Counted in calls, not ports (one call is one pair).
The refusal reuses the exhausted-pool path from #554/#555 unchanged —
`503 Service Unavailable` + `Retry-After`, counted as
`siphon_ai_invites_total{result="rejected_capacity"}` — deliberately:
a caller must not be able to tell "we are empty" from "the rest is
spoken for", since both mean try another node. The split is
operator-side, on the new **`siphon_ai_rtp_reserve_blocks_total`**
(published as a zero baseline, so "the reserve has never had to shed"
reads as `0` rather than as a missing series) alongside
**`siphon_ai_rtp_reserved_outbound_calls`**, the configured threshold as
a gauge. Startup logs the split (`pool_calls` / `reserved_outbound_calls`
/ `inbound_calls`) and `--inspect-config` prints it.
Default `0` — unreserved, byte-for-byte the pre-0.50 behaviour.
Restart-required. A value at or above the pool's capacity fails at load
with the capacity in the message: reserving the whole pool would answer
`503` to every INVITE forever, which is a concurrency cap wearing a
media knob's clothes. **The floor is exact** — it is evaluated inside
the RTP pool allocator's own critical section, so no amount of
concurrent INVITE setup can dip below it and `N` needs no slack (that
took an upstream change; see the forge-media bump below). The one
caveat, in `docs/CONFIG.md`: the knob decides *who loses* when the pool
is too small, it does not make it bigger. `rtp_port_range` must still
be sized for the sum of both directions; `docs/DEPLOY.md` now says so
under its own heading, with the measurement and the reason an operator
watching HTTP status codes sees none of it (`POST /admin/v1/calls`
answers `202` and the port failure arrives later on the
`outbound_failed` webhook).
- **The SIPp harness asserts the outbound delayed-offer counter, not just
"answered"** (#558). The `outbound_delayed_uas` phase drives a full
RFC 3264 delayed offer — offerless INVITE out, the peer's offer in its
2xx, our answer in the ACK — but asserted only
`siphon_ai_outbound_calls_total{result="answered"}`, which *any*
originate satisfies. `siphon_ai_outbound_delayed_offer_total` (#406) is
the counter that says the negotiation itself succeeded, and nothing in
CI asserted it, so its documented result set could rot unnoticed. The
scenario's own `check_it` proves the ACK carried an m-line; this adds
the daemon's side of the same claim, and catches an originate that
answers while the negotiation lands on `missing_sdp_offer`,
`invalid_remote_media`, `media_activate` or an `srtp_*` label.
---
**Artifacts**
- Static musl binaries for `x86_64` and `aarch64` (`.tar.gz`).
- Debian packages (`.deb`) for `amd64` and `arm64` — `apt install ./siphon-ai_*_amd64.deb`.
- `SHA256SUMS` — verify with `sha256sum -c SHA256SUMS`.
- `*-sbom.cdx.json` — CycloneDX SBOM.
- `SHA256SUMS.cosign.bundle` — cosign keyless signature over the checksums.
- Container: `ghcr.io/thevoiceguy/siphon-ai:v0.50.0` (multi-arch, cosign-signed).
Verify the checksums signature:
```
cosign verify-blob --bundle SHA256SUMS.cosign.bundle \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp 'release.yml@' SHA256SUMS
```
Related Repositories
public-apis/public-apis★ 491,126
A collective list of free APIs
NousResearch/hermes-agent★ 255,893
The agent that grows with you
yt-dlp/yt-dlp★ 199,990
A feature-rich command-line audio/video downloader
microsoft/markitdown★ 191,477
Python tool for converting files and office documents to Markdown.
Significant-Gravitas/AutoGPT★ 187,699
AutoGPT is the vision of accessible AI for everyone, to use and to build on. Our mission is to provide the tools, so that you can focus on what matters.