Stream Player & Mobile App — Roadmap
:::caution Status — read before trusting any single section below Three source documents cover this work, at three different points in time, and they disagree in framing:
docs/user-device-management-module-roadmap.md— the original plan (tiers, tracks, sequencing). Reads as not yet started.docs/user-device-jira-breakdown.md— the plan broken intoAZD-*tickets. No in-document status tracking; reads as not yet started.docs/user-device-implementation-plan.md(3,148 lines) — an execution log, added specifically to record "the delta between the plan and the code." Its own section headers report phases through Phase 8 as begun/complete, covering nearly everyAZD-*ticket in the breakdown above, plus work the original plan didn't anticipate (AZD-87 mobile client, AZD-89 App Installs directory).
This page is built primarily from the execution log, since it is explicitly the newest and most authoritative of the three for "what's actually been done" — but the log itself does not describe a final production cutover, and DEPLOY.md (see Backend Deployment) independently confirms both rollout flags this work introduced are still in staged/off mode in production as of that document. Treat "done" below as "backend/API work landed and verified against a dev database and a fake device," not "fully rolled out to production traffic."
:::
Scope
Two device kinds are managed here, plus the mobile app:
- Transmitter — already built and in service; see Transmitter roadmap.
- Stream player — the user end device a member buys. This is the primary subject of this roadmap.
- Mobile app (
app_installs) — not a device; a free client entitled by mosque membership.
Full architectural distinctions (identity/ownership/authorization/mosque binding/playback entitlement) live in Concepts — this page does not repeat them.
Why this module exists — three defects the plan names
| # | Defect | Fixed by |
|---|---|---|
| 1 | Push tokens were per-user, not per install — a member with two phones got azaan on only one (last to register wins) | Track B1/B2 |
| 2 | Preferences were per-membership, not per endpoint — no way to mute one device and not another | Track A2/B4 |
| 3 | Sold stream players were authorized by nobody — self-registered via POST /api/v1/devices, no approval, no lifecycle gate, plaintext wifi_password | Track A |
Scope tiers
| Tier | Contains | Why |
|---|---|---|
| V1 | Inventory, devices, ownership, assignments, lifecycle, membership entitlement, MQTT auth+ACL, per-device trigger, stream authorization, Caddy+Icecast protection, audit, Super Admin registry, legacy migration | Required to ship a stream player at all — each item is either an authorization gate or a record the gates depend on |
| V1.1 | Outbox reconciliation, fleet monitor improvements, /why endpoint, push-token migration, multi-worker cache invalidation (LISTEN/NOTIFY) | Correctness at scale — what stops being true once the fleet is too large for support to reason about individually |
| V2 | OTA, secure element, broker HA, advanced fleet analytics, automated rollout | Required before broad customer shipment — OTA is the only post-ship route to fix a security defect once units are in homes |
Gate 0 — pre-code validation
| Gate | Decision | Blocks |
|---|---|---|
| 0-A | Broker deployment sizing, TLS renewal, Postgres authn/authz wiring, rate limits, monitoring, backup, upgrade runbook, HA trigger — each failure mode needs a rehearsed recovery | Fleet rollout past pilot |
| 0-B | BOM freeze — one part must do A2DP + network audio concurrently, secure boot, signed firmware, anti-rollback fuses, flash encryption, A/B OTA slots | Firmware start, therefore Track A3 onward |
Per the execution log, the broker (0-A) resolved to self-hosted EMQX — see Device Deployment. BOM freeze (0-B) status is not confirmed in the sources available to this page.
Sequence — two tracks over a shared foundation
Track 0 — shared foundation (blocking for both tracks)
| # | Scope | Plan status | Execution-log status |
|---|---|---|---|
| 0.1 | devices, device_inventory, device_ownership, device_assignments, device_audit_log; split user_devices; drop wifi_password | Planned | Phase 1 — complete (AZD-20, 21, 22, 23–28) |
| 0.2 | core/device_fanout.py skeleton, shadow harness | Planned | Phase 2 (AZD-80–82) — done, folded into the fan-out flip below |
| 0.3 | Playback authorization + stream-token infrastructure (core/playback_authorization.py, core/stream_tokens.py, /stream-auth/*, Caddy forward_auth, STREAM_AUTHZ_ENFORCE) | Planned | Phase 3, "the stream gate" — done (P3-1..P3-6); enforcement flag still off in production, see Backend Deployment |
| 0.4 | Broker platform — EMQX in compose, TLS, Postgres authn/authz, rate limits, dashboard lockdown, metrics, backup/upgrade runbook, republish-retained | Planned | AZD-40/41 "the broker is ours" — done; AZD-42 (limits/lockdown/metrics) — done; AZD-43 — done |
The recipient fan-out flip (Track 0.2, "Phase 2: the flip" in the log) —
one push token, one user → per-install delivery — is logged done through
P2-8, including AZD-100's rollback rehearsal. Production enforcement is
governed by FANOUT_V2, still staged per Backend Deployment.
Track A — Stream Player (commercial product)
| # | Scope | Plan status | Execution-log status |
|---|---|---|---|
| A1 | Registry + state machine + audit writer; claim identity cross-validation | Planned | Phase 4 (AZD-50, 52, 55) — begun; (AZD-51, 53, 54) — "all but complete" |
| A2 | core/device_authz.py policy incl. membership step; GET /devices/{id}/why | Planned | Phase 4, as above |
| A3a | Transport + presence — rx MQTT binding, claim endpoint, UNBOUND config, fake player | Planned | Phase 5 (AZD-58, 59) — begun |
| A3b | Entitlement propagation — unbind_mosque/deauthorize, membership fan-out, outbox, entitlement lease | Planned | Phase 5 (AZD-61, 62, 64) — begun; AZD-63 (outbox) — Phase 5 complete |
| A4 | Binding axis — ownership + assignment transactions, owner rebind, concurrency tests | Planned | Phase 6 (AZD-65, 66, 67) — "the owner's own binding" |
| A5 | Per-device trigger publish, replacing the mosque-wide topic | Planned | Phase 6 (AZD-68) — begun, "and the hole the mosque-wide topic leaves" |
| A6 | Super-admin panel (registry page, approval queue) + member "My devices" screen | Planned | Phase 7 (AZD-70, 71, 72) — panel and asset screen; (AZD-74, 75, V1.1) — Phase 7 finished |
| A7 | Health, alerts, timed propagation tests, power-loss convergence, authorization catch-up | Planned | Phase 8 (AZD-76, 77, 78) — "the simulated-fleet tests" |
Estimated ~25 engineering days for backend/platform alone (36 days including Track 0); firmware, hardware/BOM, integration, and pilot hardening are tracked separately (TBD, owned by firmware/procurement).
Track B — Mobile app (free tier, no dependency on Track A)
| # | Scope | Plan status | Execution-log status |
|---|---|---|---|
| B1 | app_installs, per-install push tokens/preferences, legacy token backfill | Planned | Folded into Phase 1/2 (AZD-80–82) — done |
| B2 | App-install fan-out query, shadow mode, invalid-token harvesting | Planned | Phase 2 — done |
| B3 | core/app_authz.py policy, enrollment, push-token routes, stream-token exchange | Planned | Phase 6 finished notes AZD-86 done (AZD-85 already done) |
| B4 | Per-install preferences + app settings screen | Planned | Covered under Phase 6/7 panel work |
| B5 | AppInstallsPage, drop users.*_token, remove legacy flags | Planned | AZD-89 "App Installs directory" logged; dropping legacy columns is the deliberate point-of-no-return step — not confirmed done, and per the original plan is gated on adoption telemetry, not a date |
The log also records work outside the original plan's scope: AZD-87 (a
real mobile client, azan360-app) and AZD-73 ("a real bug the lifecycle
rehearsal caught that nothing before it could have").
Milestones (as originally planned)
| Milestone | Definition |
|---|---|
| M0 — Safe foundation | Track 0.1+0.2 + B1+B2 (~9 days), no hardware. Fixes the live push-token defect independent of the stream player shipping at all. |
| M0.5 — Hardware/audio feasibility | Proves the BOM part can do A2DP + network audio concurrently, arbitration within 500ms, secure boot/anti-rollback/A/B OTA — before firmware work is committed. Closes Gate 0-B. |
| M1 — First authorized stream player, end to end | claim → PENDING → approve → ACTIVE → bind → azaan plays → membership removed → azaan stops (Bluetooth keeps working) → rebind → revoke → credentials wiped, with no reboot/re-claim anywhere. |
Rollout — two independent flips
Order matters: fan-out first, stream gate second — fan-out populates
app_installs with real rows before anything starts denying listeners.
| Stage | Revert trigger | Action |
|---|---|---|
FANOUT_V2=enforce | Delivery failure above baseline | =shadow — one env var, no deploy |
STREAM_AUTHZ_ENFORCE=enforce | Any denial of a unit that should be authorized | =monitor — instant |
Drop users.*_token (B5) | Old-app adoption below threshold | Do not drop — irreversible, gated on telemetry not a date |
Full mechanics and current flag state: Backend Deployment.
Open decisions still unresolved in the source documents
- Entitlement lease length — 7 days proposed, to be decided together with the broker's measured recovery target (0-A), not independently.
- Stream-token TTL — 120s proposed, pending confirmation against retry behavior on a weak link.
- Rebind rate limiting — low-risk either way, still open.
mosque_devices-as-inventory migration split — the implementation log flags that AZD-23's migration ofmosque_devicesintodevicesisn't a clean one-to-one mapping for rows in theinventorystate.POST /api/v1/devicesself-registration retirement — the original defect-3 mechanism; the log notes nothing in the ticket breakdown explicitly retires it, and it must be removed or gated once the claim path fully lands or the new authorization model has an open door beside it.
Biggest operational risk
Every pre-existing self-registered user_devices row (device_type='receiver')
was approved by nobody. At the stream-gate cutover, all such devices enter
the Super Admin approval queue and stop streaming until approved — the
rollout plan requires the pending-approval queue to reach zero, tracked
by serial number, before the stream authorization flip. See
Deployment — Stream Gate Rollout for the
monitoring query that verifies this.