v0.48.17
thevoiceguy/siphon-aiv0.48.17Aug 13, 2026by github-actions[bot]
Upgrading?
AI Summary
Maintenance release bumping `siphon-rs` to fix session timer CSeq reservation and a bug where `missing_sdp_answer` could never happen.
Key Highlights
- Bumped `siphon-rs` to reserve CSeq before request leaves, preventing race conditions in refresh/teardown.
- Fixed `missing_sdp_answer` logic to dispatch early-offer ACKs.
- Corrected session timer arming log description.
Full Release Notes
### Changed
- **Bumped siphon-rs `b9f5a3bf66f2` → `9f70b83011f2`** — [siphon-rs#100](https://github.com/thevoiceguy/siphon-rs/pull/100) (its issue #99, filed from this project's #490): the UAC session refresh reserves the CSeq it consumes *before* the request leaves, instead of committing it after the response. Upstream's window was the one #491 closed here — for a whole round trip the shared dialog still read the pre-request number, so an owner request racing the refresh (teardown BYE, hold re-INVITE, REFER) reused it. `start_session_timer` had documented that as unclosable at that layer, on the reasoning that the alternative was serialising behind the dialog's write lock across a ~32 s round trip; reserving holds it for two clones and an increment instead. **Nothing changes on the wire here**: nothing in this daemon calls `CallHandle::start_session_timer`, and our own outbound legs are covered by `DialogSource::reserve_cseq` on the `shared_dialog` upstream never touches. What it buys is removing the last reason not to adopt the upstream timer — the gate [#484](https://github.com/thevoiceguy/siphon-ai/issues/484) now names. Verified per CLAUDE.md §7.5: workspace suite 1,161 passed / 0 failed, SIPp signalling 37/38 locally (only `barge_in_pause`, which needs a `setcap` this box lacks) and green in CI, no glue changes, and the #490 repro re-run unchanged against the bumped build.
### Fixed
- **`missing_sdp_answer` could never happen** (issue #497). The daemon's packet pump dropped every body-less ACK before dispatch, on the reasoning that an early-offer ACK carries no SDP and needs no application handling. True of that population — and the premise fails on the one the outcome exists for: a **delayed-offer** ACK with no SDP is the peer declining to answer our offer (RFC 3261 §13.2.2.4 requires it there). That call was never classified; it sat half-established, holding its forge media session and RTP ports, until Timer H expired **32 s** later and recorded `ack_timeout` — a cause that is supposed to mean no ACK arrived at all. The CDR variant, the `siphon_ai_delayed_offer_total{result="missing_sdp_answer"}` label and the `docs/DEPLOY.md` row all described an outcome the daemon could not produce, and a trunk that consistently declines offers looked like a network fault: exactly the confusion #425 added the variant to prevent.
- Both downstream layers were already correct and both were unreachable — `sip-glue`'s `on_ack` documents forwarding body-less ACKs for this precise reason, and `BridgingAcceptor::on_ack` handles both populations (an ACK for a dialog we are not holding matches nothing and is ignored). The #425 tests call `on_ack` directly, which is why a green suite never caught it. **The fix is one layer up**, in the pump: dispatch every ACK. The cost is one dialog-map probe per early-offer ACK, once per call.
- Live before/after on the same lab: the declining ACK now reaps at once — CDR `termination.cause = "missing_sdp_answer"`, `duration_ms` 0, `answered_at` null, and the metric label finally moves — where before it was `ack_timeout` at `duration_ms` 32001.
- Covered from now on by a new harness phase, `delayed_offer_no_answer`, which asserts the metric label *and* the CDR cause. The CDR grep doubles as the timing assertion: a regression to the Timer-H path writes no CDR for another 32 s, so it fails rather than passing slowly. Verified to fail against the previous binary.
- **The outbound session-timer arming log no longer describes a cadence it does not use.** It read *"we refresh at half the interval"*, which #490 made wrong in the same release that shipped it — refreshes run a five-second guard ahead of the half-way point. It now logs `refresh_period_secs`, computed by the same function the loop schedules from, so the line cannot drift from the code again; the prose says "we refresh on that period" and the two branches (we-refresh vs callee-refreshes) are split so only the relevant one carries the field. `docs/CONFIG.md` and `docs/DEPLOY.md` were already corrected in #490 — this was the one place left saying the old thing, and the tense of `reserve_cseq`'s doc comment is fixed with it.
---
**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.48.17` (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
A cross-platform desktop All-in-One assistant for Claude Code, Codex, OpenCode, OpenClaw, Grok Build & Hermes Agent. Only official website: ccswitch.io
An open-source remote desktop application designed for self-hosting, as an alternative to TeamViewer.
Build smaller, faster, and more secure desktop and mobile applications with a web frontend.
π RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video.
Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one