Analytics Observability — closing the measurement-debt loop (taxonomy cleanup + a live GA4 path)
The project had shipped extensive analytics instrumentation but had no clean taxonomy and no live way to observe events landing — instrumentation was write-and-hope. This feature closed the measurement-debt loop across 16 merged PRs: the analytics taxonomy CSV was brought back into sync (56 missing rows → 0), declared-but-unfired iOS events went to zero, iOS event test coverage rose to 100%, and a live GA4 observability path shipped (MCP poll + SSE mirror + debug sink + analytics-watch CLI). It's a retroactive closure — the substance shipped across the PR roster and the one remaining task (D-2, GA4 dashboard conversion config) is operator-only and deferred to App Store launch.
What do T1 / T2 / T3 mean?▾
Problem
Before this feature, the project had shipped extensive analytics instrumentation but had no clean taxonomy and no live feedback loop. The taxonomy CSV was missing ~56 rows, several declared events never fired, the GA4 MCP was disconnected, and iOS analytics events were silently not firing because the analytics plist lacked correct target membership. There was no way to confirm events actually reached GA4 — instrumentation was write-and-hope. (T2 — declared from the feature's PR roster + 2026-05-13 session notes.)
What shipped (16 PRs, #332 → #388)
Phased across three tracks:
- Taxonomy + instrumentation correctness: CSV taxonomy backfill (PR #334), delete the unfired
ai_recommendationevent (PR #335), a forward-declared-events convention (PR #336), spec extensions (PR #337), iOS analytics tests (PR #338), external sync status (PR #339), and the iOS analytics plist target-membership fix that made events actually fire on device (PR #388). - Observability tooling: a local SSE mirror (PR #342), the
analytics-watchCLI (PR #345), a debug-sink adapter (PR #349), and live GA4 polling via the GA4 MCP (PR #351). - Live-connection fixes: a GA4 MCP env-var fix (PR #362) + a GA4 access-binding guide (PR #376).
- Phase scaffolding + reconcile: Phase 1 PRD (PR #332), Phase 3A spec scaffold (PR #354), Phase 3A reconcile-complete (PR #358).
Outcome
The taxonomy is in sync and the observability path is live — analytics is no longer write-and-hope. (T2 — declared.)
- Taxonomy drift: 56 missing CSV rows → 0; CSV ↔
AnalyticsEventenum ↔ code in sync (T2). - iOS event test coverage: 81% → 100% (112/112 events) via 19 new XCTest methods (T1 — instrumented from the XCTest suite).
- Declared-but-unfired iOS events: 4 → 0 (T2).
- GA4 MCP connectivity: disconnected → connected (env-var + access-binding fixes), with the
analytics-watchCLI + SSE mirror + debug-sink adapter operational (T2).
Honest framing
This is a retroactive closure: the feature shipped its substance across 16 merged PRs and sat in testing because the only remaining task is operator-gated. No formal PRD-defined primary metric was recorded in state.json at the time, so the metrics here are honestly tiered T2 (declared) rather than presented as instrumented outcomes.
The one open item — D-2 — is operator-only: configure GA4 conversions (workout_complete + nutrition_meal_logged) in the GA4 dashboard. No code; deferred to App Store launch because pre-launch TestFlight traffic is 100% same-day new-users (vanity numbers, not measurement). The Phase 1.B enforcement gates were folded into the v8.0 docket (F19/F20) rather than shipped in-feature, so the kill criterion's >5%-false-positive condition has no active gate to fire against — it was not triggered, and the hygiene + observability outcome shipped as planned.
Source case study (FitTracker2):
docs/case-studies/analytics-observability-case-study.md.
- •This is a retroactive closure — no formal PRD-defined primary metric existed in
state.jsonat the time, so most headline numbers (taxonomy drift, declared-but-unfired events, GA4 connectivity) are honestly tiered T2 (declared), not T1-instrumented; the source is largely the PR roster + session notes. - •The live GA4 loop is not yet validated end-to-end: the one remaining task (D-2 — configuring GA4 conversions for
workout_complete+nutrition_meal_logged) is operator-only and deferred to App Store launch, because pre-launch TestFlight traffic is 100% same-day new users (vanity numbers, not measurement). - •The Phase 1.B enforcement gates (CSV_TAXONOMY_DRIFT + GA4_MCP_DISCONNECTED) that this feature was scoped around were folded into the v8.0 docket (F19/F20) rather than shipped in-feature — so the taxonomy is cleaned but not yet mechanically gated against re-drifting.
- If the Phase 1.B taxonomy/connectivity gates (CSV_TAXONOMY_DRIFT + GA4_MCP_DISCONNECTED) produce >5% false-positive rate in their calibration window, defer gate enforcement to v7.10 and ship the hygiene-only outcome (taxonomy cleanup + instrumentation fixes retained regardless).