Skip to main content

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 into AZD-* 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 every AZD-* 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:

  1. Transmitter — already built and in service; see Transmitter roadmap.
  2. Stream player — the user end device a member buys. This is the primary subject of this roadmap.
  3. 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

#DefectFixed by
1Push tokens were per-user, not per install — a member with two phones got azaan on only one (last to register wins)Track B1/B2
2Preferences were per-membership, not per endpoint — no way to mute one device and not anotherTrack A2/B4
3Sold stream players were authorized by nobody — self-registered via POST /api/v1/devices, no approval, no lifecycle gate, plaintext wifi_passwordTrack A

Scope tiers

TierContainsWhy
V1Inventory, devices, ownership, assignments, lifecycle, membership entitlement, MQTT auth+ACL, per-device trigger, stream authorization, Caddy+Icecast protection, audit, Super Admin registry, legacy migrationRequired to ship a stream player at all — each item is either an authorization gate or a record the gates depend on
V1.1Outbox 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
V2OTA, secure element, broker HA, advanced fleet analytics, automated rolloutRequired 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

GateDecisionBlocks
0-ABroker deployment sizing, TLS renewal, Postgres authn/authz wiring, rate limits, monitoring, backup, upgrade runbook, HA trigger — each failure mode needs a rehearsed recoveryFleet rollout past pilot
0-BBOM freeze — one part must do A2DP + network audio concurrently, secure boot, signed firmware, anti-rollback fuses, flash encryption, A/B OTA slotsFirmware 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)

#ScopePlan statusExecution-log status
0.1devices, device_inventory, device_ownership, device_assignments, device_audit_log; split user_devices; drop wifi_passwordPlannedPhase 1 — complete (AZD-20, 21, 22, 23–28)
0.2core/device_fanout.py skeleton, shadow harnessPlannedPhase 2 (AZD-80–82) — done, folded into the fan-out flip below
0.3Playback authorization + stream-token infrastructure (core/playback_authorization.py, core/stream_tokens.py, /stream-auth/*, Caddy forward_auth, STREAM_AUTHZ_ENFORCE)PlannedPhase 3, "the stream gate" — done (P3-1..P3-6); enforcement flag still off in production, see Backend Deployment
0.4Broker platform — EMQX in compose, TLS, Postgres authn/authz, rate limits, dashboard lockdown, metrics, backup/upgrade runbook, republish-retainedPlannedAZD-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)

#ScopePlan statusExecution-log status
A1Registry + state machine + audit writer; claim identity cross-validationPlannedPhase 4 (AZD-50, 52, 55) — begun; (AZD-51, 53, 54) — "all but complete"
A2core/device_authz.py policy incl. membership step; GET /devices/{id}/whyPlannedPhase 4, as above
A3aTransport + presence — rx MQTT binding, claim endpoint, UNBOUND config, fake playerPlannedPhase 5 (AZD-58, 59) — begun
A3bEntitlement propagation — unbind_mosque/deauthorize, membership fan-out, outbox, entitlement leasePlannedPhase 5 (AZD-61, 62, 64) — begun; AZD-63 (outbox) — Phase 5 complete
A4Binding axis — ownership + assignment transactions, owner rebind, concurrency testsPlannedPhase 6 (AZD-65, 66, 67) — "the owner's own binding"
A5Per-device trigger publish, replacing the mosque-wide topicPlannedPhase 6 (AZD-68) — begun, "and the hole the mosque-wide topic leaves"
A6Super-admin panel (registry page, approval queue) + member "My devices" screenPlannedPhase 7 (AZD-70, 71, 72) — panel and asset screen; (AZD-74, 75, V1.1) — Phase 7 finished
A7Health, alerts, timed propagation tests, power-loss convergence, authorization catch-upPlannedPhase 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)

#ScopePlan statusExecution-log status
B1app_installs, per-install push tokens/preferences, legacy token backfillPlannedFolded into Phase 1/2 (AZD-80–82) — done
B2App-install fan-out query, shadow mode, invalid-token harvestingPlannedPhase 2 — done
B3core/app_authz.py policy, enrollment, push-token routes, stream-token exchangePlannedPhase 6 finished notes AZD-86 done (AZD-85 already done)
B4Per-install preferences + app settings screenPlannedCovered under Phase 6/7 panel work
B5AppInstallsPage, drop users.*_token, remove legacy flagsPlannedAZD-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)

MilestoneDefinition
M0 — Safe foundationTrack 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 feasibilityProves 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 endclaim → 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.

StageRevert triggerAction
FANOUT_V2=enforceDelivery failure above baseline=shadow — one env var, no deploy
STREAM_AUTHZ_ENFORCE=enforceAny denial of a unit that should be authorized=monitor — instant
Drop users.*_token (B5)Old-app adoption below thresholdDo 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

  1. Entitlement lease length — 7 days proposed, to be decided together with the broker's measured recovery target (0-A), not independently.
  2. Stream-token TTL — 120s proposed, pending confirmation against retry behavior on a weak link.
  3. Rebind rate limiting — low-risk either way, still open.
  4. mosque_devices-as-inventory migration split — the implementation log flags that AZD-23's migration of mosque_devices into devices isn't a clean one-to-one mapping for rows in the inventory state.
  5. POST /api/v1/devices self-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.