Native diagnostics with Crashlytics

Decision, 7 October 2026: Crashlytics remains the mobile crash reporter. Reaktor uses its own identity, service evidence and investigation tools around it. The proposed hosted Measure stack and the custom Measure-compatible backend are superseded. Measure SDK wrappers, dashboard client, service interceptor, Hangar tab and local launcher have been removed from source. Historical local data is preserved.

Status: this is the implementation contract. Crashlytics adapters and symbol-upload tooling exist; kernel-owned span and Crashlytics report reads are implemented. Complete release qualification, provider report readback, graph attribution and Firebase Performance still need evidence. A documentation deployment does not establish those capabilities.

Role and boundaries

FunctionOwnerBoundary
Android/iOS crashes, nonfatal errors and supported Android ANRsCrashlyticsNative collection, grouping, symbol processing, provider dashboard and alerts
Mobile startup, network and custom operation timingsFirebase Performance + Reaktor performanceQualify each transport/UI path and retain required-measurement coverage
Trace, session, graph and build identityReaktorOne identity propagated into crash context, service spans and analytics
Product analyticsExisting ClickHouse pipelineFirebase Analytics stays removed; breadcrumbs are explicit
Frame, CPU/memory and leak investigationExisting Reaktor probes and platform toolsNo claim that Crashlytics replaces resource timelines or leak attribution
Structured operation evidence and tester reportsReaktor journalBounded, durable, scrubbed records; attachments are separately governed
Reproduction and release comparisonShadowLater investigation workspaces and regression capsules
Operator and AI accessKernel, Hangar and Agent WorkspaceProvider reads retain source, environment, coverage and freshness

Crashlytics and Firebase Performance are no-cost products. Optional exports, custom storage and compute have separate costs. AI model usage has a separate budget. This choice removes the dedicated Measure hosting requirement; the app's regional capacity still needs measured load and latency evidence. Firebase pricing

Current integration

The reviewed source has:

  • reaktor-telemetry/CrashlyticsAdapter.kt, AndroidCrashlytics.kt and DarwinCrashlytics.kt, exposing exceptions, logs and user ID.
  • BestBuds installs those adapters in BestBudsActivity.kt and BestBudsViewController.kt. Platform collection and early crash context must be checked independently of these late adapter assignments.
  • Android applies the Crashlytics Gradle plugin. fastlane/Fastfile already has upload_ios_dsyms_to_crashlytics!, used by distribution lanes.
  • Android and iOS no longer initialize or depend on Measure. Hangar keeps its own Reaktor device agent; Crashes reads retained Firebase reports through a kernel graph node and the trusted workspace door.
  • ServiceSpan carries application session, environment, build, node and graph digest. The ClickHouse migration is prepared but not applied; reads introspect the existing schema and disclose missing join columns. Both mobile build identities still use a pending graph digest.
  • The live Firebase provider starts, but its Crashlytics read is unavailable because Firebase CLI is signed out. Reports remain unknown or failed until sign-in and exact-app readback succeed. Android and iOS app registrations currently mix dev and production; retained reports explicitly disclose this shared scope.

Inspect source and dependency graphs before each implementation item. Extend the existing Crashlytics adapter; a second reporter interface is unnecessary when there is one selected provider. Reusable context and evidence belong in Reaktor, product wiring in BestBuds, and workbench presentation in modules/engine.

Release builds and crash reporting

Each build has a checked mapping from application/bundle ID and tier to Firebase project/app registration. Development test events must be distinguishable from production. A custom environment key alone does not prove isolation of provider-wide crash-free metrics or alerts.

Initialize native reporting and stable build/environment context before product startup. Keep upload work asynchronous. Verify collection settings and launch from the home screen without DevTools, Xcode launch environment or a debugger.

Release lanes retain the exact R8 mapping, native symbols where applicable, app dSYMs and embedded Kotlin framework dSYMs. Existing upload code is reused, but the acceptance is readable frames for the exact distributed build, not merely a successful upload command. A controlled bad configuration must fail the release lane. Apple symbol processing

The physical-device matrix covers handled Kotlin errors, unhandled Kotlin/Native errors, platform fatal crashes and supported Android ANRs. Add NDK collection only where a shipped native component needs it and qualify that path. Preserve native crash handlers. An unhandled Kotlin hook may attach Kotlin context but must preserve fatal termination, coexist with the debug hook, and distinguish any companion nonfatal from the fatal report.

After a test crash, relaunch the app and allow pending uploads. iOS checks detach the debugger before forcing the crash. Record observed delivery time; a five-minute test target is not a provider SLA. Apple crash test procedure

Joining crashes to the graph

Reaktor owns an application session ID with explicit startup, foreground/background and logout boundaries. It is distinct from Firebase's internal session identifiers, authentication sessions, graph activation IDs and trace IDs. Reuse existing session ownership only if its lifecycle is correct; do not take identity from Measure or parse undocumented Firebase SDK state.

Use a small reserved set of custom keys:

ContextKeys
Buildreaktor_graph_id, reaktor_snapshot_id, source_revision, service_version
Locationreaktor_node_id, reaktor_route, reaktor_port_id, reaktor_contract_id
Runtimedeployment_environment_name, reaktor_session_id, backend_region
Error operationreaktor_trace_id, reaktor_operation

Keys are bounded and scrubbed; optional unknown values remain unknown. Routes use patterns. Trace, activation and session IDs never become metric labels. Crashlytics supports 64 key-value pairs and 64 KB of rolling session logs, so reserve room for product context and treat logs as breadcrumbs rather than a durable journal. Custom keys and logs

SDK custom keys describe mutable process context. Concurrent service calls must not overwrite one global trace key and imply that an unrelated fatal crash belongs to that request. Attach operation identity to handled-error evidence where supported; keep concurrent operation links in structured breadcrumbs/journal records. A native fatal event's current route/session is context, not proof of request causality.

Continue existing traceparent propagation. Add allowlisted Reaktor session baggage, validate it at ingress, and carry the session through server/Worker logs, ServiceSpan and analytics. Session identity stays separate from identity/authority: baggage grants no access. Add schema fields compatibly to dev and production and retain older-writer support.

Performance and client evidence

Firebase Performance supplements Crashlytics for startup, network and custom code timings. This is a separate SDK integration, not an existing Crashlytics feature. Verify the actual KMP HTTP engines and Compose routes; host-level automatic screen traces do not prove individual route coverage. Performance Monitoring

Reaktor service boundaries supply explicit operation timings and existing performance reports supply frame/startup/memory budgets. Required measurements retain passed, exceeded, no collector and measurement failed. CPU/memory timelines, screenshots and a replay viewer remain separately scoped; they are not required for initial crash reporting.

The minimum journal records route changes, service outcomes/retries, graph and build context, and references to crash/provider artifacts. It uses the existing evidence/receipt ownership, with an on-device disk budget, batching, retry, deduplication, retention and dropped/failed counters. Keep request/response bodies out of ordinary production telemetry. A bounded in-memory DevTools stream is not a durable journal.

Move the minimum client evidence transport ahead of launch; keep full Shadow reproduction after launch. Telemetry availability must not block startup or user requests.

Hangar, DevTools and MCP

Firebase provides Crashlytics MCP tools to read issues, events and numerical reports. Firebase does not charge for those tools or public API reads; MCP features are experimental. Reuse the existing Firebase provider through the Agent Workspace door, with the existing trust and authority controls. An edit to the trusted provider manifest still requires its normal trust acceptance. Crashlytics MCP

Kernel reads select project/app registration, platform, environment, version and window. Use the provider's issue ID/grouping and preserve deep links. Tests establish which filters, paging and numerical reports the pinned provider supports; do not assume arbitrary custom-key filtering or exhaustive event access.

Hangar's Crashes pane reads provider evidence without requiring a DevTools device connection or retained Measure reads. Unknown credentials, unavailable API, partial windows and stale reads produce explicit facts, never zero crashes or green health. Node-level counts are qualified only when attribution/filter coverage is complete; sampled event examples do not establish population counts.

The kernel owns credentials; panes never accept or persist provider tokens. Read access and issue-state/note writes have separate authority. Defer issue mutations unless the existing command/approval path covers them. No Firebase service credentials are shipped in a client or exposed through MCP output.

Remove the default Measure launcher, Docker controls and Measure read path. Historical local data may remain on disk until separately reviewed cleanup; removing an integration does not authorize deleting recordings.

Alerts and service levels

Crashlytics reports stability; existing Grafana covers request availability, latency, message delivery and infrastructure. Qualify stability denominators and eligible sessions on the shipping SDK with Analytics absent. If a desired metric is unavailable, show missing coverage and keep it out of a passing launch gate.

Route actionable regressions to the selected paging channel and prove delivery/acknowledgement with a dev test. Zero crashes in a quiet app is normal and cannot be an ingestion-failure alert. Use a controlled dev canary with an expected report, known build and bounded delivery window to test reporting freshness. A canary failure warns about observability; customer-impact paging follows measured impact.

Privacy and retention

Use a pseudonymous account ID only where approved. Clear it at logout and account switch, and reset account-specific breadcrumbs/context. Pseudonymous identifiers remain governed data. Scrub credentials and personal data from logs, keys and exception messages before upload.

Firebase documents 90-day retention for crash traces and associated identifiers before removal starts. Clearing a user ID does not delete old reports; Firebase directs customer-specific record deletion to support. Do not claim a configurable 30-day Crashlytics retention policy or that short retention automatically satisfies account deletion. Govern provider records, optional exports, Reaktor journals and backups separately. Firebase privacy, report customization

Firebase controls Crashlytics processing locations on its global infrastructure. India/UK product latency priorities do not establish Crashlytics data residency. Set supported storage placement and access policies for our own journals independently. User-facing app latency is qualified against actual API/database routing.

Shadow investigation

A Crashlytics issue or a Reaktor tester report may become a Shadow anomaly. Carry the provider issue/event reference, available app session and operation references, exact build, environment and node. Admit only observed context; an absent journal means reproduction coverage is partial.

Shadow later reproduces a failure in a sanitised workspace and retains a regression capsule. Crashlytics continues as the crash system of record. Screenshots and payloads require the existing capture/privacy contract and are not silently added to crash logs.

Acceptance

  1. Release collection: controlled Android/iOS release crashes arrive after relaunch without DevTools; native/Kotlin/ANR coverage is recorded by platform.
  2. Symbols: exact distributed artifacts produce readable application and Kotlin frames, and the release lane rejects a controlled symbol/configuration failure.
  3. Isolation: known dev reports cannot affect production dashboards/alerts without an explicit, documented filter boundary.
  4. Identity: one operation joins its explicit error/breadcrumb to the same backend trace and Reaktor session; concurrent calls keep distinct operation links.
  5. Reads: an authorised AI and Hangar read the same provider issue, with provenance/freshness; unavailable and partial sources remain visible.
  6. Performance: required startup/service/route probes have physical-device receipts and missing-probe gates; Firebase Performance coverage is explicit.
  7. Privacy: logout/account switch clears context; synthetic sensitive values are scrubbed; retention/deletion responsibilities are recorded.
  8. Reporting and paging: a dev canary and a regression test reach their intended channels and preserve evidence.
  9. Measure removal: shipping dependency graphs/binaries and default workbench startup contain no active Measure SDK, backend requirement or interceptor.

The implementation order and receipts live in the private operations wave plan. These checks do not establish 10,000-user capacity; that requires separate workload, load and soak qualification.