chore: INFRA-214 Audit & consolidate app analytics pipelines (PostHog / Supabase / dead custom-API)#131
Merged
Conversation
… (T1) Records the three-sink current-state audit (PostHog-direct live; Supabase analytics_events ops-only with orphaned crisis emitters; custom-API api.being.fyi fully dead) and the decided target routing model (Option 1: PostHog-direct + honest DPIA re-scope) in analytics-architecture.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deletes the fully-orphaned custom-API analytics path that never ran: - AnalyticsService.ts (incl. AnalyticsPrivacyEngine: the never-executed k-anonymity / differential-privacy / session-rotation engine and the orphaned crisis_intervention_triggered emitter). - AnalyticsOrchestrator + all unused orchestrator types in analytics/index.ts; the barrel now exports only the live PostHog tier (PostHogProvider, PHIFilter, useAnalytics, useFeatureFlag, deletion workflow). - SyncStatusIndicator: drops the "Analytics Service" panel (it read AnalyticsService.getStatus() from the dead path and showed hardcoded privacyCompliance/networkSecurity — never real signal); now sync-only. - Deletes 2 dead test files that asserted the never-ran engine (analytics-service-integration, week3-analytics-hipaa-compliance). The HIPAA-named file tested controls that never executed; Being is not a HIPAA entity — removal signed off by the compliance planning pass. api.being.fyi network-layer remnants (NetworkSecurityService URLs/pins + the 4 unused request wrappers) are inert placeholder config and are removed separately in T2b. No live control weakened (the engine had zero callers). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the dead-path deletion (AC3). api.being.fyi never resolved (DNS-checked), had placeholder certs, and NETWORK_CONFIG self-documented the URLs as "placeholders for future". Removed: - NETWORK_CONFIG PRODUCTION/STAGING/DEVELOPMENT_API_URL + the api.being.fyi CERTIFICATE_PINS placeholder block. - The four uncalled request wrappers (crisisApiRequest, uploadAssessmentData, professionalApiRequest, bulkDataOperation), determineApiBaseUrl + the apiBaseUrl field, and validateAPIConnectivity (a swallowed fetch to the dead /health endpoint on every crisis-security init). - API_CERTIFICATE_PINS + its getPinsForHost branch in certificate-pinning.ts. Kept the generic secureRequest primitive + its helpers (calculateDataHash / generateRequestId stay used by the kept response-validation path) and ALL Supabase certificate pinning + CrisisSecurityProtocol init — no live control touched. Updated the two affected security tests; ratcheted .eslint-baseline.json (-27 violations from the deletions). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reverses the "PostHog = crisis telemetry" part of AC2 after crisis + compliance + architect planning passes unanimously found PostHog unsuitable for the vital-interests crisis-detection event: - PostHog SDK does not init without analytics consent → silent drop on the common no-consent path = false "all-clear" safety-monitoring gap. - Privacy policy promises Being NEVER collects PHQ-9/GAD-7/mental-health data in-app; a crisis-detection event is a PHQ/GAD-derived signal → FTC §5 exposure, and PHIFilter would itself reject the payload. - PostHog distinct_id is device-persistent; compliance requires session-rotation, which the Supabase analytics_events schema enforces. New routing rule (legal-basis partition, no dual-write): PostHog = consent-gated product analytics; Supabase analytics_events = vital-interest crisis telemetry + ops. Two sanitizers kept intentionally (PHIFilter reject-gate vs Supabase bucket-transform); shared invariant: no raw PHQ/GAD integer leaves the device. Supabase→PostHog forward deferred; FEAT-129 queries Supabase for v1. Records the durable-fire-time-enqueue requirement (else the safety gap is merely relocated to the offline/no-userId path). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wires the canonical vital-interest crisis-detection event to the Supabase analytics_events sink (decided in the T3 routing correction; PostHog is unsuitable — won't init without consent + can't meet session-rotation). assessmentStore.handleCrisisDetection: after the dedup guard + triggerEmergencyResponse, fire-and-forget emit of a PII-free, pre-bucketed event (trigger_type, severity_bucket, intervention_surfaced, assessment_type) — NEVER the raw triggerValue/score. Wrapped so a telemetry failure can never block or suppress the 988 path. SupabaseService.trackCrisisDetection + flushCrisisAnalytics + loadCrisisAnalyticsQueue: a SEPARATE durable queue (own AsyncStorage key, never evicted by the ops queue) persisted at fire-time, so a first-run/offline crisis is not silently dropped (the gap the architect flagged). user_id is reconciled at flush time; undeliverable events are retained and escalated to the local security log. Flush hooks on init + app-foreground. Test-first: crisisTelemetryEmit.unit.test.ts pins the safety-critical emit contract (PII-free payload, emit-once-per-episode dedup, fire-and-forget). The deeper durable-queue reconciliation is covered by the T6 landing test. .eslint-baseline.json accepts the new test file's typed-parse skip (same systemic condition as sibling src/ test files). Verified: typecheck clean; test:unit 379, test:clinical 94, test:crisis-detection 82 pass; the 988 Alert path is unchanged (pre-existing assessmentStore.test failures predate this). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anitizer (T4)
Completes the legal-basis routing model on the Supabase sink:
- Deletes the orphaned useCloudAnalytics hook (trackAssessmentCompletion /
trackCrisisEvent / trackFeatureUse) and the index.ts trackAnalyticsEvent wrapper.
These routed PRODUCT analytics to analytics_events — a path that shouldn't exist
(product analytics → PostHog; crisis → trackCrisisDetection). They were never called.
- Adds a consent gate to SupabaseService.trackEvent: operational telemetry reaches
analytics_events only with `canPerformOperation('analytics')` (which also honors
universal opt-out / GPC). Invariant: nothing reaches analytics_events without analytics
consent EXCEPT the vital-interest crisis-detection event (separate bypass).
- Hardens sanitizeAnalyticsProperties: buckets ANY clinically-named numeric
(score|result|phq|gad|severity|ideation|suicid), closing the hole where a raw value
under a key like `phq9_total`/`severity` passed through un-bucketed. Operational
numerics (size_mb, duration_ms, operation_count, …) are not clinical and pass through.
Note: gate basis (`analytics` vs `cloud_sync`/legitimate-interest for ops telemetry) is
flagged for the T5 compliance pass to confirm.
Tested (analyticsGate.unit.test.ts): consent gate drops without consent, records with;
clinical numeric bucketed while operational numerics pass. typecheck clean; test:unit 382.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…(T5/T6) T5 (compliance docs, drafted by the compliance agent): - DPIA v1.2: documents crisis-detection telemetry as a new processing activity (§2, §4) under GDPR Art. 6(1)(d)/9(2)(c) vital interests, recorded to first-party Supabase analytics_events without analytics consent, PII-free/bucketed. Revised §6.1 Scenario 1 to the two-sink model and to state explicitly that k-anonymity/differential-privacy are NOT claimed (the dead engine providing them was deleted in T2). Added §7 controls 13-14 and a §9 material-change record + v1.2 change-log entry. Founder self-certifies pre-launch. - New lia-crisis-telemetry.md: standalone vital-interests assessment (necessity, balancing, safeguards). Added to the legal-registry internal-only set. - privacy-policy.md v1.6: §3 + §5.2 disclose that the crisis-detection event is recorded to first-party storage WITHOUT analytics consent (the prior "crisis never gated by opt-out" text only covered 988 access, not recording). Regenerated legal content. - T4 gate-basis correction (compliance ruling): SupabaseService.trackEvent now gates ops telemetry on `cloud_sync` (not `analytics`) — a cloud-sync user shouldn't lose backup telemetry for declining product analytics; still honors universal opt-out. T6 (verifiable crisis-landing test): - crisisTelemetryDurable.unit.test.ts pins the durable-queue behavior the architect required: a first-run/offline crisis is durably enqueued (no silent drop), reconciles user_id and lands in analytics_events, retains on failure, PII-free, fire-and-forget. The migration note already lives in analytics-architecture.md (no data migration — pre-launch). typecheck clean; test:unit 387; test:crisis-detection 82; legal-registry consistent. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…4-audit-analytics-pipelines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes INFRA-214
Consolidates Being's analytics onto a documented legal-basis routing model and deletes the dead custom-API path.
Routing model (no dual-write): PostHog = consent-gated product analytics; Supabase
analytics_events= vital-interest crisis telemetry + operational telemetry.Tranches:
analytics-architecture.md).AnalyticsService+ never-run k-anon/DP engine +api.being.fyinetwork/cert layer).crisis_detected) routed to Supabase, not PostHog (3-agent decision: PostHog won't init without consent → false all-clear; contradicts privacy policy; device-persistent distinct_id). Durable fire-time enqueue (no silent drop on first-run/offline).trackEventconsent gate (cloud_sync) + sanitizer hardening + deleted orphaned product-analytics-to-Supabase emitters.lia-crisis-telemetry.md+ privacy-policy v1.6 (discloses vital-interest recording without analytics consent).Net: large dead-code removal. typecheck clean; test:unit 387; test:clinical 94; test:crisis-detection 82; legal-registry consistent; Maestro safety suite 4/4.
k-anon/DP explicitly NOT claimed (never ran). Supabase→PostHog forward deferred (own DPIA). FEAT-129 re-scoped to Supabase. Counsel review of the vital-interests basis due before 500 EU users (DPIA §10).
🤖 Generated with Claude Code