openalice-radio
[Atlas card](https://atlas.blal.pro/repos/openalice-radio) · Live at **`radio.blal.pro`**
Architecture rating
Clean AST: zero circular deps, 0 dead imports, no god-modules/star-imports/naming violations, valid manifest (100) and conventional repo layout; only minor env drift (DOMAIN undeclared, RUST_LOG unused) docks it.
Strong manifest (100, clear summary, owns-tables + exposes declared), AGENTS.md, docs/ dir, and 19 @feature annotations; weakened by a thin README excerpt and 33 missing docstrings/21 format issues in source.
Single tests.rs with ~5 unit tests covering hub fanout, no-subscriber case, channel isolation, and now-playing serialization (incl. null track) — good for the hub core but no integration/CRUD/scheduler/WS-subscribe path coverage; the 5 are flagged unreferenced (harness-discovered).
Owns 4 well-named tables (radio_channels/tracks/play_state/subscribers) but only a single 0001_init.sql migration and no visible RLS, FK/constraint, or per-tenant isolation evidence despite shared+per-tenant channel model; GDPR/PII handling unstated.
Hub fanout has tested channel isolation and empty-subscriber handling, low avg complexity (3.09), zero cycles; but no signal for SIGTERM graceful shutdown, WS timeouts, retries or idempotency, and health probe returns 404 with 0% 24h uptime.
Public HTTP+WS service (radio.blal.pro/v1/radio/*) with no evidence of authn/authz, rate-limiting, security headers, or SSRF/injection guards in any signal; tenant-scoped channels imply auth need but none shown — grading conservatively on absence.
RUST_LOG declared but unused (env drift) and health endpoint probes 404/down (uptime 0%, no matched_path) — health wiring broken; no metrics, tracing, or structured-log evidence surfaced.
source · auto-grader-2026-06-16
Ecosystem manifest
.atlas-deps.ymlStreaming radio service: shared + per-tenant channels with scheduler, now-playing WS fanout, and tracks CRUD. BGM source for the live page picker and Hub /account/radio BFF.
- openalice-authshared-jwtvia SHARED_JWT_SECRETVerify oa_session JWTs on owner-gated routes (create channel, add/remove track, share-track).
- via cargoShared axum plumbing: CORS policy, JWT verifier trait, AuthUser extractor, internal HTTP client.
- via RADIO_CDN_BASEBase URL for resolving relative audio_url values in now-playing WS frames and track listings.
- DATABASE_URL
- SHARED_JWT_SECRET
- RADIO_PUBLIC_URL
- RADIO_CDN_BASE
- RADIO_PORT
- RADIO_DB_PASSWORD
- RUST_LOG
- docker_image: openalice-radio
- http_endpoint: https://radio.blal.pro/v1/radio/*
- ws_endpoint: wss://radio.blal.pro/v1/radio/channels/:slug/subscribe
- radio_channels
- radio_tracks
- radio_play_state
- radio_subscribers
- radio.now_playing
Atlas grepped the repo's source for env reads (rust env::var · ts process.env · py os.environ · shell $VAR) and diffed against the manifest's consumes_env. Two lists below: stale declarations and undeclared reads.
- DOMAIN
- RUST_LOG
Roadmap · what's planned next
- lyria-share-pipelineplanned12%End-to-end: persona Lyria generation → private channel → POST /share-track promotes Lyria-generated music beds into a shared community stream.
- persistent-scheduler-cursorplanned4%Survive process restart cleanly — re-derive radio_play_state cursor + remaining position from started_at on boot so listeners don't all snap to track[0] when the pod recycles.
- per-tenant-track-quotaplanned9%Enforce a per-tenant track / total-duration cap on POST /tracks so a single account can't fill the shared DB with multi-hour uploads.
- crossfade-and-gap-tuningplanned12%Pre-roll the next track ~2s before the current track's duration_ms elapses + emit a `next_track` WS frame so clients can crossfade without a perceptible gap.
- hub-bff-radio-pageplanned8%Wire the Hub /account/radio page end-to-end: list private channels, create/delete, manage tracks, and pick the BGM source for a live broadcast.
Code composition
tokei · loc- Rust83.4%
- YAML7.0%
- SQL3.6%
- TOML3.5%
- Dockerfile1.9%
- Other0.7%
- Rust83.4%
- YAML7.0%
- SQL3.6%
- TOML3.5%
- Dockerfile1.9%
- Other0.7%
- Rust83.4%· 1,014
- YAML7.0%· 85
- SQL3.6%· 44
- TOML3.5%· 42
- Dockerfile1.9%· 23
- Shell0.7%· 8
| Language | % code | code | comments | blanks | files |
|---|---|---|---|---|---|
| Rust | 83.4% | 1,014 | 50 | 119 | 14 |
| YAML | 7.0% | 85 | 16 | 5 | 2 |
| SQL | 3.6% | 44 | 22 | 8 | 1 |
| TOML | 3.5% | 42 | 18 | 14 | 1 |
| Dockerfile | 1.9% | 23 | 4 | 13 | 1 |
| Shell | 0.7% | 8 | 3 | 4 | 1 |
| Markdown | 0.0% | 0 | 167 | 66 | 3 |
Telemetry ribbon · last 90 days
5 eventsradio/auth
1 feature- radio.auth.optional-extractorsrc/handlers/channels.rs:28OptionalAuth FromRequestParts extractor — yields Some(AuthUser) when JWT validates, None on any failure (missing header / invalid sig / expired) without 401-ing the request; required so /channels can mix public shared listings with private owner-scoped rows in one handler.type:OptionalAuthsince 0.1.0
radio/channels
3 features- radio.channels.listsrc/handlers/channels.rs:3channels plus (when a valid JWT is presented) the caller's private channels; unauthenticated callers see shared-only. @feature radio.channels.create — POST /v1/radio/channels creates a per-tenant private channel (slug [a-z0-9-]+, 409 on collision) and immediately spawns its scheduler task so the new channel starts ticking.
- radio.channels.now-playingsrc/handlers/channels.rs:104GET /v1/radio/channels/{slug}/now-playing returns a one-shot snapshot of the current scheduler cursor for a channel; HTTP fallback for clients that can't open a WS.api:GET /v1/radio/channels/{slug}/now-playingsince 0.1.0
- radio.channels.require-ownersrc/handlers/channels.rs:188Owner-gate helper — resolves `radio_channels` row by id and 403s when the caller's tenant_id does not match `owner_tenant_id` (or the channel is shared, not private); single chokepoint for every tracks/share mutation.since 0.1.0
radio/crate
1 feature- radio.lib.cratesrc/lib.rs:7openalice-radio library — exposes AppState, handlers, hub, scheduler, seed, and db modules so integration tests + the bin can share the same router shape without duplicating wiring.crate:openalice-radiotype:AppStatesince 0.1.0
radio/db
2 features- radio.db.connectsrc/db.rs:24Async PgPool builder used by the binary bootstrap; centralizes pool tuning so every handler shares the same connection limits + acquire deadline.since 0.1.0
- radio.db.poolsrc/db.rs:6Postgres pool factory — wraps PgPoolOptions with `max_connections=10` + `acquire_timeout=5s` so handlers fast-fail to 503 instead of hanging when the DB is overloaded.fn:db::connectsince 0.1.0
radio/health
1 feature- radio.health.endpointsrc/handlers/health.rs:3GET /healthz returns 200 "ok" for Traefik liveness probing; no DB touch so it stays green even when Postgres is unreachable.api:GET /healthzsince 0.1.0
radio/hub
2 features- radio.hub.fanoutsrc/hub.rs:13frames from the scheduler to every WS subscriber on a channel in O(1) publish; lock-free on the hot path via dashmap entry().
- radio.hub.now-playing-framesrc/hub.rs:33Serialized NowPlaying wire frame — channel_id/slug + optional track metadata + ISO-8601 `started_at` so clients can compute the live playhead as `now - started_at` (server clock skew tolerant).type:NowPlayingsince 0.1.0
radio/metrics
1 feature- radio.metrics.endpointsrc/handlers/metrics.rs:8GET /metrics Prometheus exposition — emits `radio_build_info` gauge from a OnceLock-cached Registry so Prometheus confirms the scrape target without per-request alloc.api:GET /metricssince 0.1.0
radio/scheduler
2 features- radio.scheduler.bootstrapsrc/scheduler.rs:65spawn_loop discovers every row in `radio_channels` at boot and spawns one per-channel tick task; newly-created channels are added at create-time by the channels handler so the boot scan only needs to run once.since 0.1.0
- radio.scheduler.ticksrc/scheduler.rs:18radio_play_state cursor when elapsed >= duration_ms; recovers from operator mutations within one tick cycle. @feature radio.scheduler.now-playing — current_now_playing() computes the snapshot NowPlaying frame from DB without advancing the cursor; called by both the HTTP now-playing endpoint and the WS subscribe handler for the initial frame.
radio/seed
1 feature- radio.seed.shared-channelssrc/seed.rs:5platform-owned shared channels (lo-fi-24-7, synthwave-24-7, jazz-cafe, ambient-spacy) with CC0 CDN tracks; base URL overridable via RADIO_CDN_BASE.
radio/server
1 feature- radio.server.bootstrapsrc/main.rs:15Axum router boot — wires DB pool + Hub + handlers, seeds shared channels, spawns per-channel scheduler tasks, and binds the HTTP/WS listener on RADIO_PORT.since 0.1.0
radio/state
1 feature- radio.state.appsrc/state.rs:15AppState — cheap-clone bundle of (PgPool, Hub, jwt_secret, public_url) injected into every Axum handler and the scheduler task; implements JwtVerifier so the shared HS256 validator from openalice-axum-common can extract AuthUser from this state.type:AppStatesince 0.1.0
radio/tracks
2 features- radio.tracks.crudsrc/handlers/tracks.rs:3provides owner-gated track append and removal; order_index auto-assigned as MAX+1 when not supplied. @feature radio.tracks.share — POST /v1/radio/channels/:id/share-track copies a private track into a shared channel; intended path for publishing Lyria-generated music beds to the community streams.
- radio.tracks.listsrc/handlers/tracks.rs:216GET /v1/radio/channels/{id}/tracks lists tracks in `order_index` order; shared channels are public, private channels require a JWT whose tenant matches `owner_tenant_id` (403 otherwise).api:GET /v1/radio/channels/{id}/trackssince 0.1.0
radio/ws
1 feature- radio.ws.subscribesrc/handlers/ws.rs:13called BEFORE the DB snapshot so no scheduler ticks are lost between snapshot fetch and subscription registration; lagged receivers are disconnected so clients reconnect cleanly.