Skip to content

Architecture Decision Records

Key decisions behind football-trackers, with context and rationale. Captured from the original requirements discussion (June 2026).

ADR Decision
0001 Build a DIY system instead of using a commercial tracker
0002 WiFi (not BLE) as the field transport
0003 Real-time streaming (not store-and-sync)
0004 ESP32 (WEMOS Lite) + NEO-M8N wearable baseline
0005 Leg IMU and/or camera CV for technical metrics
0006 Local core + cloud relay; devices never face the internet
0007 Per-device MQTT identity + topic ACLs; plaintext on an isolated LAN
0008 Auth: argon2 + session cookies, roles, mTLS for the field↔relay link
0009 Caddy as the TLS-terminating edge (auto-HTTPS, only :443 public)
0010 30-day local retention + erasure; cloud holds aggregates only
0011 Keep bun:sqlite; swap to TimescaleDB only on a trigger
0012 Overlay network (WireGuard/Tailscale) for Profile-B remote access; defer the bespoke relay
0013 One dedicated, internet-isolated WPA2 AP for match day; broker TLS-PSK deferred behind triggers
0014 Per-device firmware secrets in NVS, set by a serial enrollment console (one shared image)
0015 FE auth: cookie-on-upgrade + principal-bound session authz; reject the bundle-baked token
0016 Player names via an authenticated per-session roster; location stores stay pseudonymous
0017 Post-match review/replay: one renderer, history read off the live loop
0018 Live position smoothing with a strict interpolation honesty rule (no fabricated positions)
0019 Youth age-banded speed zones from a per-session server config store (single source of truth)
0020 Tactical event detection from GPS (Track A: movement-derived phases); ball events (passes/shots) deferred (Track B)
0021 Local dev stack: Docker Compose backend + host-run coach view
0022 Dynamic provisioning: NVS broker host + mDNS (name.local) + captive-portal Wi-Fi setup
0023 Offline camera/CV match analysis (vision/, build-spec form): realises Track B / Path 2; prototyped on public footage, youth-footage gate deferred
0024 The coach view reports its own failures: a four-value client beacon, no free text (no error/RUM SDK)
0025 Operability: ordered graceful shutdown on a signal-deliverable PID 1, user_version migrations, verified VACUUM INTO backups inside ADR-0010's retention

Format: Context → Decision → Consequences → Alternatives considered.

ADR-0006–0014 realise the target architecture (security + dual deployment profile) derived from the architecture brief and the architecture board review. 0012 (overlay) amends 0006; 0013 was decided by a field-network expert panel; 0014 closes 0013's hard gate. ADR-0015–0018 come from the FE improvement panel (six experts + adversarial verification): 0015 implements 0008 for the browser; 0016 extends 0010's pseudonymity to name display; 0017 reuses the 30-day raw store (0010) for review without stalling live ingest; 0018 governs honest motion. ADR-0019 (Phase-4 coaching metrics) sources youth zone thresholds per session; 0020 adds GPS-only tactical event detection (Track A) and scopes the ball-event sensing (Track B); 0021 is the local Docker dev/bench stack used for the first real-device bring-up (local-bench-runbook). ADR-0023 realises 0020's deferred Track B / 0005's camera Path 2 as a standalone offline vision/ CV subproject (Veo/Trace-style player+ball tracking → top-down radar + stats), prototyped on public footage only; it inherits, does not discharge, 0020 §6's child-video DPIA — the real-youth-footage gate stays deferred to a future ADR. ADR-0024 (audit Phase 5) adds the first browser→server write: a four-value client beacon so a coach whose view has gone dark is visible from /metrics at all — deliberately not an error/RUM SDK, whose default payload (stack traces, URLs, DOM context) is exactly what 0010/0016 exist to keep children out of. The same phase amends 0019, which now also stores each session's four measured pitch corners (they were compiled into the client bundle, pointing at a bench in Belgrade). ADR-0025 (audit Phase 6) makes the box operable: an ordered graceful teardown on a process that can actually receive SIGTERM (docker stop went from exit 137 in 1.3 s to exit 0 in 0.1 s), a user_version migration ladder that refuses a store newer than the build, and VACUUM INTO backups that inherit 0010's retention window and are reached by 0010's erasure CLI — because a backup is a complete copy of the same children's location. It also closes the item Phase 5 deferred (server-side scan cancellation) and stores session tokens as sha256 verifiers, which is what makes carrying coaches' logins across a restart safe. TLS for the field box is explicitly still open.