Bump workspace version to 1.0.7 via scripts/set-release-version.sh
(Cargo workspace, Cargo.lock, Helm chart, docs metadata).
- Pin embedded Web UI to synctv-app v1.1.27
(fb614c04153c81fbccbbfc0b688f10ffb7e9df9a)
## Security
- **tokio 1.54.0 → 1.54.1** — RUSTSEC-2026-0384 / GHSA-5wjr-9356-4wc8:
race condition between `tokio::time::Interval::poll_tick` and the
multi-thread runtime's time driver causes a **use-after-free on the
timer waker/task state in safe Rust** (introduced by tokio#8458, fixed
by tokio#8692). `cargo audit` is clean again; only the allowed
RUSTSEC-2024-0449 maintenance warning (unsafe-libyaml behind the pinned
serde_yaml) remains.
## Performance (breaking-major, analyzed per policy)
- **parking_lot 0.12.5 → 0.13.0**: significantly improved
`Mutex`/`RwLock` throughput under contention via deferred wakeups. The
0.13 breaking changes live in the `lock_api` layer (`RawMutex::Guard`
replacing `GuardMarker`, split rwlock guards), which this workspace
never touches — only the high-level `Mutex`/`RwLock` types are used (145
sites). The 0.12 line remains in the graph via `antlr4rust → cel →
prost-protovalidate` until those publish compatible releases; both
versions coexist legally.
## Toolchain
- nightly-2026-10-20 → **nightly-2026-10-22** (rust-toolchain.toml + all
workflows)
## Documented pins re-verified, unchanged
| pin | status |
|---|---|
| bincode 2.0.1 | 3.0.0 is still the intentionally unbuildable
placeholder (no real 3.x) |
| dashmap 7.0.0-rc2 | still no 7.0.0 final |
| opaque-ke 4.1.0-pre.2 | still the newest release |
| webauthn-rs 0.6.1-dev | still the newest release |
| testcontainers 0.27 | testcontainers-modules 0.15 still requires ^0.27
|
| serde_yaml 0.9.34 | replacement migration (saphyr/noyalib) risks
config-format divergence for the loader; kept as documented |
## Validation
- `make check`, `make clippy-check -D warnings`, `make fmt-check` clean
- `cargo nextest run --workspace --run-ignored all`: **6970/6970
passed**
- `cargo audit` and `cargo deny check` clean
CI (Security Audit / Dependency Policy) is currently red on main due to
two newly published advisories:
- rustls-webpki 0.103.15 -> 0.103.16 (RUSTSEC-2026-0382, certificate
verification may accept distrusted intermediates)
- webpki-root-certs 1.0.9 -> 1.0.10 (RUSTSEC-2026-0383, bundled root
certificates trust distrusted emSign intermediates)
Both are semver-compatible lockfile updates; no code changes required.
## Dependency updates (exact pins)
- **http-acl 0.13.1 → 0.14.0**: security hardening — IPv4-embedded IPv6
ranges (IPv4-compatible `::/96`, translated `::ffff:0:0:0/96`, NAT64
`64:ff9b::/96`, 6to4 `2002::/16`) are now denied by default; previously
`::7f00:1` or `64:ff9b::7f00:1` could reach loopback through the SSRF
guard. Also fixes the reqwest resolver dropping every getaddrinfo answer
carrying port 0
- lru 0.19.0 (iterator Send/Sync bounds), ammonia 4.2.4, opendal 0.59.5,
oxc 0.155.0, rustls 0.23.46, rusty_aac 1.1.1, rusty_h264 0.16.1,
webpki-roots 1.0.10, zstd 0.14.3, tokio-util 0.7.20 and other
transitives
## http-acl 0.14 adaptation (synctv-common SSRF guard)
0.14 refuses adding non-global ranges via `add_denied_ip_range` at build
time. Our `DEFAULT_EXTRA_DENIED_RANGES` contained `2002::/16`, so
`try_build` failed and the guard silently degraded to the **deny-all
fallback** (every IP and hostname blocked). Fix:
- `2002::/16` moved to `DEFAULT_NON_GLOBAL_DENIED_RANGES` (the
SsrfPolicy-side list used by `is_ip_blocked`), together with the other
embedded ranges: `::/96`, `::ffff:0:0:0/96`, `64:ff9b:1::/48`
- new `test_ipv4_embedded_ipv6_blocked` covers all embedded probe forms
(including the NAT64 metadata address) and a global IPv6 control
## tokio 1.53.2 → 1.54.0
1.54.0 starts accepted sockets as readable and writable, so the raw-TCP
mocks in `error_handling_tests` could flush the response before the
client had written its request; hyper rejects such a connection with
`UnexpectedMessage` inside `reqwest::get`. The mocks now drain the
request head before answering (normal HTTP ordering, matches a real
server) and the suite passes on 1.54.0.
## Kept on purpose
- bincode 2.0.1 (3.0.0 placeholder), dashmap 7.0.0-rc2, opaque-ke
4.1.0-pre.2, webauthn-rs 0.6.1-dev, serde_yaml, testcontainers 0.27
(modules 0.15 still requires ^0.27)
## Toolchain / CI
- nightly-2026-10-13 → **nightly-2026-10-20** (rust-toolchain.toml + all
workflows)
- actions already float on latest major tags (checkout v7, cache v6, …)
— no changes needed
## Validation
- `make check`, `make check-all-targets`, `make clippy-check -D
warnings`, `make fmt-check` clean
- `cargo nextest run --workspace --run-ignored all`: **6970/6970
passed** (incl. docker-backed e2e)
- `cargo deny check`: advisories/bans/licenses/sources all ok
Bump workspace version to 1.0.5 via scripts/set-release-version.sh
(Cargo workspace, Cargo.lock, Helm chart, docs metadata).
- Pin embedded Web UI to synctv-app v1.1.25
(5f4a678c3008a49f414fc1e6a41068a01bb2c8ba)
- docker.yml: Flutter 3.47.2 -> 3.47.7 to match the client toolchain
requirement
## Dependency updates (exact pins)
- tokio 1.53.2, tokio-util 0.7.20, hyper 1.12.0, serde_json 1.0.154,
uuid 1.28.0, toml 1.1.8, opendal 0.59.4, ammonia 4.2.3, config 0.15.27,
http-acl 0.13.1, tokio-rustls 0.26.6, retina 0.4.21, zstd 0.14.2,
ffmpeg-sys-next 9.0.1, oxc 0.154.0, webrtc/rtc 0.21.1
- **rusty_aac 0.5.0 → 1.1.0**: 1.x is the stabilized release with a
security-hardening pass (crafted fill-element panic on overflow-checked
builds, stale decoder state after failed blocks; output bit-identical
otherwise). Usage in this repo is test-only, no API changes needed
- **html-escape 0.2.15 → 0.3.0**: fixes multi-codepoint HTML entity
decoding and speeds up common named entities; `decode_html_entities` is
used by provider title extraction
Deliberately kept (documented/intentional pins): bincode 2.0.1 (3.0.0
placeholder), dashmap 7.0.0-rc2, opaque-ke 4.1.0-pre.2, webauthn-rs
0.6.1-dev, serde_yaml, testcontainers 0.27 (testcontainers-modules 0.15
requires ^0.27).
## Toolchain and CI
- nightly-2026-09-27 → **nightly-2026-10-13** (rust-toolchain.toml + all
workflows)
- removed the `cargo-features = ["trim-paths"]` gate (stabilized in
1.101)
- actions: checkout v7, cache v6, setup-node v7, dependency-review v5,
setup-helm v5, create-pull-request v8
## Code changes required by new clippy lints (2026-10-13)
- `try_from_instead_of_from_str`: `RoomResourceKind` now implements
`FromStr` (call site uses `.parse()`)
- `items_after_test_module`: moved 7 `#[cfg(test)]` modules to end of
file
## Dependency cleanup (new cargo `unused_dependencies` lint)
- removed 13 unused crate-level deps, including `tonic-web` which is now
unused workspace-wide
- moved test-only `tempfile` (synctv-proxy) and `anyhow`
(synctv-media-providers) to dev-dependencies
- kept `synctv-cluster` in synctv-livestream: referenced only by TLS
feature forwarding (`synctv-cluster/tls-*`), which the lint does not
account for — a known false positive
## Validation
- `make check`, `make check-all-targets`, `make clippy-check`, `make
fmt-check` all clean
- `cargo nextest run --workspace --run-ignored all`: **6969/6969
passed** (incl. docker-backed e2e)
Stacks on #476 (same `ci.yml` regions are touched); merge after it or
rebase.
Motivation: rust-lang/rust#161748 (next-solver memory blowup) and
bevyengine/bevy#25512 (fix by avoiding deep structural auto-trait
resolution). This PR applies the applicable parts to SyncTV's build with
fresh measurements, and documents the deliberate non-changes.
## Measurements (nightly-2026-09-27, `cargo check -p synctv-api-http
--all-targets`, isolated recheck, 10-core arm64)
| Configuration | Wall | Peak rustc RSS |
| --- | --- | --- |
| `-Znext-solver=coherence` + `-Zthreads=8` (current) | 32 s | 3.8 GB |
| `-Znext-solver=coherence` + `-Zthreads=1` | 53 s | 2.8 GB |
| `-Znext-solver=globally` + `-Zthreads=8` | 37 s | **14.9 GB** |
- The #161748 regression **persists on current nightlies**: the
globally-enabled next solver still needs ~4x the peak memory on
`synctv-api-http` — beyond a 16 GB CI runner. Keeping
`-Znext-solver=coherence` is validated, now with numbers in the config
comment.
- `-Zthreads=8` is a good trade: 40% faster than 1 thread for +0.9 GB
peak. Kept, documented.
- Cold dev builds (`CARGO_TARGET_DIR` isolated, full dep graph): LLVM
**660 s / 7.7 GB** vs cranelift **671 s / 7.4 GB** → a cranelift dev
backend brings no win here (rustc frontend dominates this workspace), so
it is **not** introduced.
## Changes
- `.cargo/config.toml`: Linux gnu targets (x86_64 + aarch64) now link
with **lld** and keep `-z pack-relative-relocs`, replacing the ad-hoc
flags the Docker builder passed. `nextest` test binaries and `make
build-workspace` in CI now link with parallel lld instead of
single-threaded bfd. Target-specific rustflags replace
`build.rustflags`, so the nightly solver/thread flags are repeated in
the Linux tables and documented once in `[build]`.
- CI installs the `lld` package on every Linux job.
- `Dockerfile`: the duplicated `-Clink-arg` flags are removed (config
provides them).
- `CONTRIBUTING.md`: local Linux builds now note the `lld` package.
- Not done (deliberately): a bevy#25512-style code change (eager/manual
`Send`/`Sync` impls to shorten auto-trait chains). With coherence
enabled the heaviest crate peaks at 3.8 GB, well inside CI budgets, so
the invasive rewrite buys nothing today; the measured numbers above make
that call explicit.
## Validation
- `SQLX_OFFLINE=true cargo check -p synctv-api-http` passes with the new
config (non-Linux hosts keep the original rustflags — target tables only
match Linux).
- `cargo fmt --check` clean. The `cross-*` Makefile targets run
check/clippy only, so cross containers never invoke the linker and do
not need lld.
## Summary
**Toolchain**
- Advance the pinned toolchain `nightly-2026-08-31` →
`nightly-2026-09-27` in `rust-toolchain.toml` and every workflow that
names it. The date pin is independent of the next-solver memory
regression — that regression is present in the pinned nightly too and is
handled by `-Znext-solver=coherence` in `.cargo/config.toml`, which is
unchanged.
**Dependency updates (33 pins, current releases at 2026-09-28)**
- Majors: **webrtc/rtc 0.20.4 → 0.21.0** (TURN relayer STUN fix on
0.20.5 plus the 0.21 line's SCTP zero-window deadlock, ICE-restart stall
and data-channel drop fixes), **utoipa 5.5.0 → 6.0.0** +
**utoipa-swagger-ui 9.0.2 → 10.0.1** (OpenAPI 3.2 support, Swagger UI
5.32.6), **brotli 8 → 9**, **zstd 0.13 → 0.14**, **opendal 0.58.2 →
0.59.3** (+http transport), **redis 1.6.0 → 1.7.1**, **rusty_h264 0.12 →
0.16**, **oxc 0.147 → 0.151**.
- Minors/patches: tower-http 0.7.1, reqwest 0.13.5, jsonwebtoken 11.1.0,
uuid 1.26.1, clap 4.6.7, clap_complete 4.6.11, config 0.15.26,
hyper-util 0.1.21, indexmap 2.14.2, ipnet 2.12.2, lru 0.18.5, rand
0.10.3, rustls 0.23.45, tokio-rustls 0.26.5, thiserror 2.0.21, toml
1.1.6, ammonia 4.2.0, and the transitive refresh in `Cargo.lock`. tokio
stays at 1.53.1 (current).
**Code adaptations**
- `synctv-xiu` webrtc 0.21: `RtpCodecKind` is now `#[non_exhaustive]`
(wildcard arms at five match sites), `TrackLocalStaticSample::new` takes
a `now: Instant`, and `rtc`'s `Sample` replaces `Default` with
`Sample::new(now)` in tests.
- `rusty_h264` drops `default-features = false`: the split-out
`rusty_h264-common` requires std (or `libm`) for its floating-point
math, and it is a test-only dependency here.
**Patch removal**
- The `[patch.crates-io]` combine override (installed for the Redis 1.6
`opaque!` future-incompat report) is removed: redis 1.7 no longer
depends on combine, clippy confirmed the patch was unused, and the
lockfile resolves cleanly from the registry.
**Deliberately unchanged (documented in Cargo.toml)**
- bincode 2.0.1 (3.0.0 is an intentionally unbuildable placeholder),
testcontainers 0.27.3 (modules 0.15 requires ^0.27), rand/sha2 legacy
aliases, dashmap 7.0.0-rc2, opaque-ke 4.1.0-pre.2, webauthn-rs
0.6.1-dev, serde_yaml.
## Validation
- `SQLX_OFFLINE=true cargo check/clippy --workspace --all-targets` clean
under nightly-2026-09-27.
- `cargo fmt --check` clean; `cargo deny check` passes.
- `nextest`: **5353 passed, 1618 skipped** — includes the
WebRTC/streaming e2e suites exercising the new webrtc 0.21 stack and
rusty_h264 0.16 encoders.
- `make proto-freshness`: regenerated proto artifacts produce no diff.
Supersedes #462, #463, #464, #465, #466, #467, #468, #469, #470 and #475
(the open dependabot batch), which can be closed on merge.
Fix failures found during the frontend integration sweep:
- Hydrate image attachment views in chat edit/reaction/pin/unpin
responses, pinned-message lists, and durable pin replay. These mutations
could commit successfully and then fail serialization or disconnect
observers.
- Expose room password presence in client/admin projections, using batch
lookups for room lists.
- Preserve nullable category IDs in room-creation review queries so
uncategorized submissions can be read and listed; refresh SQLx metadata.
Validation: Rust formatting, workspace Clippy with warnings denied, five
Docker-backed chat regression tests, eight secondary-read repository
integration tests, eight CLI human-output tests, protobuf freshness, and
a dynamic-Web backend build passed. Remote CI results are tracked on
this PR.
Frontend integration: https://github.com/synctv-org/synctv-app/pull/70
## Summary\n- bump server, Helm chart, and documentation metadata to
1.0.4\n- pin production Web UI to synctv-app commit
24109a49b2e27f7772d067fae66b622431c942d5\n- use Flutter 3.47.2 in the
Docker Web UI build to satisfy the pinned client SDK\n- release the
latest upstream main commit 940d566ff\n\n## Verification\n- make
fmt-check\n- make validate-helm\n- make check\n- make web-ui-build
(Flutter 3.47.2; generated version.json 1.1.24+23)\n-
verify-web-ui-pin\n- cargo test -p synctv-api-common user_block --locked
## Summary
- split Prometheus definitions into domain-owned modules backed by one
registry
- eagerly initialize every metric family and fail startup on invalid or
duplicate definitions
- add reusable RAII guards for gauges and relay durations across
cancellation, panic, and retry paths
- centralize HTTP, WebSocket, gRPC, and relay recording behind
bounded-label helpers
- enforce the 65-metric bilingual catalog from registered descriptors
- persist completed explicit-message deletion and user-ban moderation
steps
- update the pinned Rust toolchain and CI jobs to `nightly-2026-08-25`
- make the CI Clippy job fail on every Rust warning with `-D warnings`
- keep Helm configuration-validation builds visible to avoid silent-run
termination
- separate Rust caches by artifact type and let Helm reuse codegen
artifacts from the Build job
- work around the nightly global next-solver memory regression and
remove obsolete rustc recursion-limit overrides
- preserve workspace Rust flags in Docker builds while applying linker
flags to the final binary
## Compatibility
- preserve all existing metric names, HELP text, label order, and
histogram buckets
- keep route-template and bounded status/error labels
- return HTTP 500 when Prometheus exposition encoding fails
- keep the latest nightly while restoring the previous coherence-only
trait-solver behavior
## Nightly memory regression
The failed CI jobs were terminated by the runner while compiling
`synctv-api-http` (SIGTERM, exit 143). Local peak-RSS measurements for
that crate were:
| Configuration | Peak RSS |
| --- | ---: |
| `nightly-2026-08-21` | 6.62 GB |
| `nightly-2026-08-25`, global next solver | 10.76 GB |
| `nightly-2026-08-25`, `-Znext-solver=coherence` | 6.64 GB |
Rust enabled `-Znext-solver=globally` by default on nightly in
[rust-lang/rust#160619](https://github.com/rust-lang/rust/pull/160619).
The same memory blow-up is tracked in
[rust-lang/rust#161748](https://github.com/rust-lang/rust/issues/161748),
and the official tracking issue documents `-Znext-solver=coherence` as
the temporary opt-out. The workspace now applies that option in
`.cargo/config.toml`.
The Dockerfile previously set `RUSTFLAGS` for linker options, which
overrode the workspace configuration and re-enabled the global solver
inside image builds. It now uses `cargo rustc` to pass linker flags only
to the final binary, preserving the workspace solver setting for every
crate.
All rustc `#![recursion_limit = "256"]` attributes were removed. The
default limit passes with the coherence-only solver, confirming that a
higher recursion limit was unrelated to the CI termination.
## Validation
- `make build-workspace`
- `make clippy-check` (`--workspace --all-targets -- -D warnings`)
- `cargo check --locked -p synctv-api-http`
- `cargo check --locked -p synctv-api-common -p synctv-api-grpc`
- `cargo test -p synctv-api-common --lib` (655 passed, 165 Docker tests
ignored)
- `cargo test -p synctv-core metrics --lib` (9 passed)
- `make fmt-check`
- `docker build --check .`
- `make validate-helm`
- `actionlint .github/workflows/ci.yml .github/workflows/helm-ci.yml`
- `npm run validate` in `docs` (124 pages, 353 links, Astro 0
errors/warnings)
## Summary
- add an asynchronous admin chat moderation job pipeline for message,
reaction, and ban cleanup
- expose the moderation operation through HTTP/gRPC admin APIs and
persist resumable worker progress
- remove the obsolete version parameter from the async moderation path
while retaining CAS for synchronous chat edits/deletes
## Verification
- cargo check --workspace --all-targets
- cargo test -p synctv-core --lib (1350 passed)
- cargo test -p synctv-api-common --lib (655 passed)
- PostgreSQL moderation race regression passed
## Summary
- classify empty Emby item lookups and provider 404 responses as not
found
- stop dynamic browse breadcrumb traversal after a visible item when an
ancestor is hidden
- preserve errors for missing target items and non-not-found provider
failures
## Validation
- `cargo test -p synctv-media-providers`
- `cargo test -p synctv-core`
- `cargo test -p synctv-api-common --lib`
- `cargo clippy -p synctv-media-providers -p synctv-core --all-targets
-- -D warnings`
- `cargo fmt --all -- --check`
- real Emby dynamic-playlist browse and playback validation with two
media items
## Summary
- Allow Emby users with an explicitly empty password to authenticate.
- Preserve password whitespace and keep missing credentials or empty API
keys invalid.
- Accept passwordless credentials through the remote provider API and
expose the CLI `--no-password` option.
- Update proto comments and CLI documentation.
## Validation
- Media provider test suite: 552 passed, 5 ignored.
- Focused Emby, gRPC, CLI, formatting, clippy, proto freshness, and
documentation checks passed.
## Summary
- add Chinese and English Nginx reverse proxy guides for the SyncTV HTTP
and WebSocket entrypoint
- disable Nginx proxy caching and upstream response buffering for all
API, Web UI, HLS, and media responses
- preserve SyncTV `Cache-Control` headers and explain why request
buffering remains independent
- link the guide from navigation, production checks, and media latency
troubleshooting
## Scope
The example covers the HTTP/WebSocket entrypoint only. It does not add
gRPC, RTMP, or Ingress configuration.
## Verification
- `npm run validate` in `docs`
- docs content validation: 124 pages, 353 internal links
- Astro diagnostics: 0 errors, 0 warnings, 0 hints
- static build and internal link validation
- `git diff --check`
`nginx -t` was not run because Nginx is not installed in the local
environment.
## Summary
- add a `web-ui-dynamic` feature that serves a mutable Web distribution
without compile-time embedding
- add `server.web_ui_directory` and `SYNCTV_SERVER_WEB_UI_DIRECTORY`,
with per-request disk reads, SPA fallback, security headers, and path
containment checks
- keep `web-ui` release builds embedded while allowing the same
production binary and Docker image to use a mounted runtime directory
- configure `make dev-serve` to use `synctv-web-ui/dist` and document
both modes
## Docker behavior
CI continues to build release images with `web-ui`, so embedded assets
remain the default. Mounting a distribution and setting
`SYNCTV_SERVER_WEB_UI_DIRECTORY` makes that directory authoritative at
runtime.
## Verification
- `cargo fmt --all -- --check`
- `git diff --check`
- `cargo test -p synctv-api-http --features web-ui-dynamic
http::web_ui::tests`
- `cargo test -p synctv-api-http --features web-ui http::web_ui::tests`
- `cargo check -p synctv --features web-ui-dynamic`
- `cargo check -p synctv --features web-ui`
## Summary
- add the RTMP advertised address runtime setting and use it when
returning generated publish URLs
- generate reusable media-scoped publish sessions through the
provider-owned workflow
- preserve single-use publish keys while allowing creators to mint
replacements
- update runtime-setting documentation and generated protobuf contracts
## Verification
- cargo check -p synctv --features web-ui
- git diff --check
- end-to-end RTMP publish and playback against the embedded Web UI
## Summary
- verify that the embedded Web UI serves the SPA entrypoint directly at
the root path without a redirect
- use idiomatic Option handling in the embedded asset response path
- remove outdated setup sections from the English and Chinese project
READMEs
## Validation
- cargo test -p synctv-api-http --features web-ui
root_serves_spa_entrypoint_without_redirect
- make clippy-check
- cargo fmt --check
- git diff --check
## Summary
- keep non-zero Range requests when a HEAD response does not advertise
Accept-Ranges
- use slice caching when the upstream answers 206 and preserve
full-response fallback when it answers 200
- add regression coverage for inconclusive HEAD probes and range
fallback behavior
## Validation
- cargo test -p synctv-proxy --test slice_cache_tests (94 passed)
- cargo check --workspace --all-targets
- cargo fmt --check
- verified Alist, Emby, and Bilibili proxy seeks return correct 206
responses and byte ranges
## Summary
- add structured HTTP and gRPC access logs with request correlation,
status-aware levels, response bytes, and complete body lifecycle timing
- route diagnostic and access events through independently configurable
text or JSON outputs with non-blocking writers and dropped-line
accounting
- honor the configured IANA timezone across text, JSON, diagnostic, and
access logs with DST-aware RFC 3339 offsets
- keep HTTP metric labels bounded while logging concrete unmatched paths
without query strings
- reduce local development noise by defaulting global logs to info while
retaining component debug logs
## Behavior
- HTTP 2xx and 3xx complete at info, 4xx at warn, and 5xx or body
failures at error
- slow handlers are raised to warn; ordinary client response
cancellation remains debug
- gRPC completion waits for final trailers and records the canonical
gRPC status
- request IDs are validated, propagated in responses, and included in
mapped gRPC errors
- header latency and full response lifecycle latency are reported
separately
- log timestamps use the configured time.timezone and include a numeric
UTC offset
## Testing
- cargo test -p synctv-api-common transport_access_log --lib
- cargo test -p synctv-core logging::tests --lib
- cargo test -p synctv resource_options::tests --lib
- cargo clippy for affected crates and all targets with warnings denied
- cargo check --workspace --all-targets
- cargo fmt --all -- --check
- git diff --check
- local server smoke tests for request completion, request ID
propagation, byte counts, query omission, and Asia/Shanghai timestamps
in diagnostic and access logs
## Summary
- bump the server, Helm chart, and documentation metadata to v1.0.3
- keep the Compose image fallback validation aligned with the
configurable image tag
- release the latest upstream main commit with personal user blocking
## Verification
- `make fmt-check`
- `make validate-helm`
- `cargo test -p synctv-api-common user_block --locked`
## Summary
- add idempotent block, unblock, and paginated block-list APIs across
HTTP, gRPC, service, and repository layers
- hide blocked creators from authenticated personal discovery while
leaving anonymous discovery unchanged
- preserve joined, favorited, and directly accessed rooms with a
creator_blocked marker
- filter blocked users from message history, search, context, pinned
messages, replay, unread counts, live events, and reconnect replay
- add the user_blocks migration, SQLx offline metadata, and
repository/API coverage
## Behavior
Blocking is private to the current account. It does not alter the
blocked account or public anonymous discovery. Detailed personal room
lists remain complete and expose creator_blocked so clients can label
the creator. Observer-specific realtime filtering advances event cursors
and fails closed when block-state lookup is unavailable.
## Verification
- make check
- make fmt-check
- cargo test --workspace: 5222 passed
- synctv-api-common library tests: 638 passed, 165 ignored
- PostgreSQL favorite-room creator_blocked integration test
- SQLx prepare across the workspace
- real two-account HTTP, realtime, and room-flow integration test
Client: https://github.com/synctv-org/synctv-app/pull/47
## Summary
- disable the built-in SyncTV STUN server by default across application,
Compose, image, environment, and Helm configuration
- default `webrtc.externalIceServers` to Cloudflare and Google public
STUN endpoints while preserving persisted overrides and explicit `[]`
- update deployment validation, tests, examples, and English/Chinese
documentation
Tailscale is not included because it publishes STUN endpoints through
its dynamic DERP map and does not provide a stable general-purpose
hostname suitable for this default.
## Testing
- `cargo fmt --all -- --check`
- `cargo test -p synctv-core --test global_settings_registry_tests`
- `cargo test -p synctv-core
test_runtime_settings_default_to_public_stun_servers`
- `cargo test -p synctv default_webrtc_config_disables_builtin_stun`
- `cargo check --workspace --all-targets`
- `bash scripts/validate-helm.sh`
- Compose configuration rendering
- `npm run validate:content`
- `npm run check`
- `git diff --check`
## Summary
- split room, node, and global presence into authenticated member and
anonymous guest counts
- deduplicate member and guest actors independently while retaining
total connection counts
- update discovery ordering and realtime online-count messages to use
the separated values
- remove the previous mixed online-user field as a breaking protocol
change
Frontend: https://github.com/synctv-org/synctv-app/pull/41
## Verification
- make nextest-default (5231 passed)
- make clippy-check
- make proto-freshness
- cargo fmt --all -- --check
- git diff --check
## Summary
- allow `synctv room visibility` to omit the actor and run as an
authenticated management-plane operation
- preserve optional `--username`, `--user-id`, and `--email` actor
selection for normal room permission enforcement
- keep the permission bypass confined to the management service and
reuse the same visibility update, cache invalidation, and guest
revocation behavior
Follow-up to #419.
## Validation
- `cargo fmt --all -- --check`
- `cargo check -p synctv-core -p synctv-management -p synctv`
- `cargo clippy -p synctv-core -p synctv-management -p synctv
--all-targets -- -D warnings`
- `cargo test -p synctv room_visibility` (3 passed)
- verified `synctv room visibility --help` shows actor selectors as
optional and still requires exactly one of `--public` or `--private`
## Summary
- store room public visibility as a first-class database field
independent of password and guest settings
- expose visibility through client, management, HTTP, gRPC, CLI,
discovery, and room creation review flows
- add `synctv room visibility ROOM_ID --public|--private` for changing
existing rooms with an explicit actor
- deny anonymous guest access to private rooms while preserving
authenticated member access
- revoke existing guest sessions immediately when a room becomes private
without disconnecting authenticated members
## Validation
- `cargo fmt --all -- --check`
- `cargo clippy -p synctv-core -p synctv-api-common -p synctv-api-http
-p synctv-api-grpc -p synctv --all-targets -- -D warnings`
- `cargo clippy -p synctv-management -p synctv --all-targets -- -D
warnings`
- `cargo check -p synctv-core -p synctv-api-common -p synctv-api-http -p
synctv-api-grpc -p synctv`
- `cargo check -p synctv-management -p synctv`
- `cargo test -p synctv room_visibility`
- verified `synctv room visibility --help` renders the required actor
and exactly one of `--public` or `--private`
- targeted realtime bridge, guest principal, realtime event, and room
service tests
- real macOS client verification: changing a public room to private
terminated the guest connection in about two seconds with
`REALTIME_TERMINATION_CODE_GUEST_ACCESS_REVOKED`; authenticated users
remained connected
## Changes
- Remove PostgreSQL and Redis host port publications from production and
development Compose files.
- Keep application connectivity through the existing Compose networks
and service names.
## Validation
- docker compose -f docker-compose.dev.yml config --quiet
- git diff --check
## Summary
- Treat explicit null collection fields in Bilibili VOD playback
responses as empty collections.
- Cover DASH streams, backup URLs, Dolby audio, supported formats, DURL
responses, subtitles, video pages, UGC collections, and PGC episodes.
- Resolve Bilibili DASH resources from versioned playback metadata and
provide every equivalent CDN URL to the existing candidate executor.
- Prefer the URL selected by the player, then retry primary and backup
CDNs before committing the downstream response.
## Root cause
Bilibili occasionally emits explicit null for fields normally
represented as JSON arrays, which caused playback/start deserialization
errors. Proxy DASH resource requests also used a single CDN even though
the provider response included backup URLs.
## Validation
- cargo fmt --all -- --check
- cargo check for synctv-api-common, synctv-api-http, and
synctv-api-grpc
- synctv-media-providers Bilibili tests: 91 passed
- synctv-core Bilibili tests: 17 passed, 2 ignored
- candidate executor tests: 2 passed
- Real playback/start, proxy MPD, Range request, CDN fault injection,
dev build, and dev smoke checks
Fix OIDC ID Token validation when the provider returns an issuer with a
trailing slash.
SyncTV removes trailing slashes from the configured issuer, but
`jsonwebtoken` previously performed an exact issuer check before
SyncTV's normalized issuer comparison. This caused valid tokens from
providers such as Authentik to fail with `InvalidIssuer`.
The JWT validation now accepts both trailing-slash variants of the same
issuer while continuing to reject unrelated issuers.
Added RS256/JWKS regression tests covering both accepted configurations
and a different-issuer rejection case.
## Summary
- remove the `dev-start` and `dev-stop` Makefile targets and related
documentation
- keep `dev-serve` as the foreground development server entry point
- make the provider smoke test own and clean up its server process
- keep `dev-down` and `dev-clean` scoped to Compose services
## Verification
- `bash -n scripts/dev-e2e-smoke.sh`
- Makefile dry runs for `dev-serve`, `dev-down`, and `dev-clean`
- `make help` target inspection
- `git diff --check`
- existing development backend health check