Skip to main content

Analytics & telemetry — requirements

Epic — measurement substrate. MVP-1 requirements for the analytics-telemetry goal (see the feature architecture and C4 observability). This epic is the governing spec for the three-bucket data model — it is referenced by every other epic that claims a KPI, because a KPI is only real if it is observable and minor-safe. The bucket rules below are pass/fail launch conditions, audited each release and sampled continuously in production. Not legal advice — counsel signs off the consent and child-exemption posture alongside COPPA.

TypeNon-functional
Layer (build approach)Cross — config-gated + consent-gated emission (pre-emit filter in SDK + RLS)
RICER 8 × I 1 × C 80% / E 3 = 2.1 · Tier MVP-1 (measurement substrate — enables every other KPI)
KPI (summary)Event-pipeline coverage % · opt-in % · zero minor PII — see Success criteria
Traces tofeature analytics-telemetry + C4 observability
Depends onChildren's privacy (COPPA) (no-child-marketing gate; minor pre-emit filter) · Terms & consent (opt-in records, consent version)

The three-bucket data model (governing spec)

Every datum the product touches falls into exactly one bucket. The bucket decides its identity, its permitted use, and its consent basis. This table is the contract the rest of the requirements set links to — no event, table, or export may straddle two buckets.

BucketWhatIdentityUseConsent
Operationalrun the app (wallets / goals / completions)per-member, incl. childmake the feature workchild PII → VPC (see COPPA)
Internal BI"a feature was used" — event type + coarse propsde-identified, content-free, NO child identityunderstand the productexempt (internal-ops); never marketing
Marketingcount · plan type · parent-feature-active flagparent / account onlyupsell, lifecycle, re-engagementadult first-party; never child-derived / targeted

Hard rules (non-negotiable, enforced — not just documented):

  1. Minor-safe pre-emit filter. No event may carry a child identifier or child content (chore/step names, goal titles, free text). The filter sits at the substrate, before any provider sees the payload — operational child PII never crosses into BI or marketing.
  2. Internal BI is internal-only. It is never used for marketing, behavioral ads, audience-building, or to contact a person. It answers "was this feature used," not "who used it."
  3. Marketing allow-list is closed. Exactly count + plan type + parent-feature-active flags, each defined by a PARENT action (e.g. a parent enabling a feature), never derived from a child's activity. Anything outside the allow-list is not marketing data.
  4. Config-gated + consent-gated emission. A provider only initializes when its config token is present and opt-in consent is recorded; otherwise emission is a clean no-op. (Mixpanel was removed in an earlier pass — it is re-introduced here only as config-gated + consented.)

Success criteria (definitive KPI)

Success = all three hold, verified each release and sampled continuously in production:

  • Coverage: 100% of MVP-1 KPI events are instrumented and emitting through the substrate (each KPI in the prioritization matrix maps to a live event; zero "claimed but unobservable" KPIs).
  • Opt-in: consent opt-in is captured for > X% of eligible adult accounts (X set with product/counsel; see Open decisions) — and no event emits before opt-in is recorded.
  • Zero minor PII: the release + nightly audit finds zero events carrying a child identifier or child content, and zero cross-bucket leakage.

Window: continuous; every release must re-prove all three before ship. A single minor-PII event or cross-bucket leak fails the gate (no ramp — this is a privacy floor, not a tunable funnel metric).

Measurement & audit

The substrate is instrumented so the gate is provable, not asserted. The audit proves zero minor PII and bucket separation directly from emitted payloads:

CheckWhenWhat it asserts
Pre-emit filter unit suiteCI, every commita payload with a child id / chore name / free text is dropped or scrubbed before any provider call
Bucket-tag lintCI, every commitevery event declares exactly one bucket; marketing events carry only allow-listed fields (count, plan_type, *_active)
Coverage manifestreleaseeach MVP-1 KPI ↔ a live event id; flags any KPI with no emitting event
Opt-in ledger samplerelease + nightlyemitted events have a matching consent record; 0 events precede opt-in
Minor-PII scanrelease + nightlysampled provider payloads → assert 0 child identifiers, 0 child content, 0 cross-bucket fields
No-third-party-child checkreleaseno child-bucket field reaches any external provider or export

Findings are recorded against the release; a non-zero minor-PII or cross-bucket result blocks ship and triggers the COPPA no-child-marketing gate.

Scope

The measurement substrate for MVP-1: the emit path (Bloc → Repository → client_sdk facade → Service → Adapter → effect provider), the pre-emit filter, the config-gate (dart-define token presence), the consent-gate (opt-in record), and the three-bucket tagging applied to every event. Covers the re-introduction of a product-analytics provider (Mixpanel or alternative — see Open decisions) and the internal-BI event stream. Out: dashboards, marketing automation tooling, and the error-tracking path (Sentry) which is config-gated separately in observability.

Non-functional requirements

NFR-ANALYTICS-1 — Minor-safe pre-emit filter at the substrate

Priority: P1 · Status: 🔨 to build Statement. A single pre-emit filter, owned by the SDK service (not the UI), strips or rejects any payload carrying a child identifier or child content (chore/step names, goal titles, free text) before any provider is called. AcceptanceGiven any event with a minor identifier or child content When it reaches the emit path Then it is dropped or scrubbed before the provider call, and the audit records 0 such events reaching a provider.

NFR-ANALYTICS-2 — Three-bucket separation enforced

Priority: P1 · Status: 🔨 to build Statement. Every event is tagged with exactly one bucket (operational / internal BI / marketing); fields permitted in one bucket cannot appear in another (operational child PII never enters BI or marketing). AcceptanceGiven an event definition When it is built Then CI lint fails if it omits a bucket tag or carries a field outside that bucket's allow-list.

NFR-ANALYTICS-3 — Config-gated emission

Priority: P1 · Status: 🔨 to build Statement. A provider initializes only when its config token is present (dart-define); with no token the provider never initializes and events are a clean no-op in dev, tests, and unconfigured production. AcceptanceGiven no provider token When an event fires Then nothing is sent and no SDK is initialized; Given a token Then the provider initializes once.

Priority: P1 · Status: 🔨 to build Statement. No analytics or marketing event emits until an opt-in consent record exists for the account; revoking opt-in stops emission. Acceptance

  • Given an account without recorded opt-in When an event would fire Then it is suppressed (the audit shows 0 events preceding opt-in).
  • Given opt-in is revoked Then emission stops and the change is recorded against Terms & consent.

NFR-ANALYTICS-5 — Internal BI is de-identified and internal-only

Priority: P1 · Status: 🔨 to build Statement. Internal-BI events are content-free and carry no child identity; they are never used for marketing, behavioral ads, audience-building, or to contact a person. AcceptanceGiven an internal-BI event When emitted Then it carries only event type + coarse props (no person identity, no child id), and no downstream job reads BI to target or contact a user.

NFR-ANALYTICS-6 — Closed marketing allow-list, parent-defined

Priority: P1 · Status: 🔨 to build Statement. Marketing data is exactly count + plan type + parent-feature-active flags, each defined by a parent/account action and never derived from a child's activity. AcceptanceGiven a marketing event When built Then it contains only allow-listed fields keyed to the parent/account; Given any field derived from a child's activity Then lint rejects it from the marketing bucket.

NFR-ANALYTICS-7 — Opaque / hashed references; no third-party child data

Priority: P1 · Status: 🔨 to build Statement. Identifiers in any emitted event are opaque/hashed (per-install salt, as in observability); no child-bucket field reaches any external provider or export. AcceptanceGiven any emitted event When it leaves the device Then its identity field is a hashed/opaque ref and the no-third-party-child check passes.

NFR-ANALYTICS-8 — KPI-event pipeline coverage

Priority: P1 · Status: 🔨 to build Statement. Every MVP-1 KPI in the prioritization matrix maps to a live, emitting event through the substrate; no KPI is claimed without an observable event. AcceptanceGiven the MVP-1 KPI set When the coverage manifest runs at release Then 100% of KPIs resolve to a live event id, and any unmapped KPI fails the release check.

NFR-ANALYTICS-9 — Coarse / bucketed values, never raw economy amounts

Priority: P2 · Status: 🔨 to build Statement. Funnel events emit bucketed values (e.g. token_value_bucket: '1-3' | '4-9' | '10+'), never raw token amounts or per-member economy detail. AcceptanceGiven an economy funnel event When emitted Then numeric values are bucketed and no raw wallet/goal amount appears in the payload.

NFR-ANALYTICS-10 — Release + nightly audit proves the gate

Priority: P1 · Status: 🔨 to build Statement. An automated audit runs each release and nightly, asserting zero minor PII, bucket separation, opt-in precedence, and KPI coverage; a failure blocks ship. AcceptanceGiven the audit When it finds a minor-PII event, cross-bucket field, or pre-opt-in emission Then the result is non-zero and the release gate fails.

Architecture considerations

  • The pre-emit filter lives in the SDK service, not the UI or the effect provider: the service is the single choke point on the one data path (Bloc → Repository → client_sdk facade → Service → Adapter), so no direct write or alternate UI can bypass it. The provider (e.g. MixpanelEffect in the outside layer) only ever sees already-filtered, already-bucketed payloads.
  • Config-gating is dart-define token presence (MIXPANEL_TOKEN et al.): no token → the provider never initializes → events drop. Mirrors the existing observability pattern.
  • Consent-gating reads the opt-in record from Terms & consent; emission is suppressed until opt-in and stops on revocation.
  • Opaque/hashed refs: a per-install hashing salt anonymizes the distinct id; numeric funnels are bucketed. No raw child identity, no raw economy amount.
  • No third-party child data: child-bucket fields are filtered at the substrate and can never reach an external provider; household-scoped RLS already isolates the operational data the filter draws from.
  • Bucket tagging is structural: events declare their bucket at definition time so the CI lint can enforce the allow-list — separation is a build-time property, not a runtime hope.

Design work (ahead of build)

  • Consent / opt-in UX — a clear, separate-from-T&C opt-in surface for analytics, with an explicit revoke path (ties to Terms & consent).
  • Data-disclosure surface — a plain-language "what we measure and why" view stating the three buckets, that internal BI is never used to contact or advertise, and that children are never marketed to or tracked.
  • Bucket reference card — the canonical table other epics link to when declaring a KPI, so authors pick the right bucket at design time.
  • Audit report layout — how the release/nightly result (coverage %, opt-in %, minor-PII count) surfaces to the team as a pass/fail gate.

Decisions (resolved for MVP-1)

Resolved — see the MVP-1 decisions log for the canonical record, rationale, and status legend (✅ decided · ⚖️ counsel confirms · 🔜 MVP-1.x).

  • D-ANALYTICS-1 — Provider. ⚖️ Mixpanel, consented + config-gated (anchor). It receives only de-identified internal-BI events (content-free, no child identifier) + parent-only marketing signals (count / plan type / parent-feature-active flags); never child PII. Added to the sub-processor / DPA list. Re-introduces the previously-removed Mixpanel behind the consent + config gate.
  • D-ANALYTICS-2 — Opt-in target. ⚖️ MVP-1 target ≥ 60% adult opt-in; treat < 40% as a consent-surface problem, not a target miss. Confirm with product/counsel.
  • D-ANALYTICS-3 — Config gate.Build-time dart-define for the Mixpanel token/enable in MVP-1 (no network dependency); a remote-config path comes later.
  • D-ANALYTICS-4 — Internal-BI exemption. ⚖️ Adopt the internal-operations posture — the de-identified, content-free, never-marketing BI stream sits outside consent-for-marketing. This is the governing three-bucket spec; counsel confirms.

Out of scope (MVP-1)

  • Marketing-automation tooling and lifecycle campaigns (the allow-list is in scope; the campaign engine is not).
  • BI dashboards / warehouse modeling (the emit + filter substrate is in scope; the analytics surface is not).
  • Error/crash tracking (Sentry) — config-gated separately in observability.
  • Teen (13–17) consent nuances and any third-party sharing → handled with COPPA and Terms & consent.