Reports Management
The Reports Management endpoints let clients send operational failure reports (crashes, failed updates, failed installs) and let admins and team users view an aggregated, privacy-conscious picture of rollout health by app, version, channel, platform, and architecture.
The design goal is to keep ingestion cheap, stable, and bounded: a minimal report costs a single MongoDB upsert, grouping never depends on stack traces or logs, and storage growth is capped.
What reports are (and are not)
Reports give you:
- A simple public endpoint for clients to send update/install/runtime failure events.
- An aggregated view of rollout health grouped by stable technical dimensions.
- Optional, size-bounded debug details stored separately for support investigations.
- Groundwork for a future auto-pause / rollback decision engine.
Reports are not a logging pipeline, an APM, or a crash-analytics platform, and they intentionally avoid collecting user identity.
Enabling reports
Report ingestion is gated by a dedicated environment flag, similar to TUF routes. When REPORTS_ENABLED=false, neither the ingestion endpoint nor the read API is registered.
REPORTS_ENABLED=true
REPORTS_MAX_BODY_BYTES=262144
REPORTS_MAX_DETAILS_COMPRESSED_BYTES=131072
REPORTS_MAX_DETAILS_DECOMPRESSED_BYTES=1048576
REPORTS_BLOB_RETENTION_DAYS=30
REPORTS_MAX_BLOBS_PER_GROUP=10
REPORTS_STORAGE_PREFIX=reports
REPORTS_RATE_LIMIT_PER_KEY_PER_MINUTE=100
Notes:
- Enabling reports also forces a Redis connection, since rate limits depend on Redis.
- Detail blobs reuse the existing
STORAGE_DRIVER/S3_*configuration but are written to the private bucket (S3_BUCKET_NAME_PRIVATE) and are only retrievable through short-lived presigned URLs.
Authentication model
There are two distinct auth paths:
- Ingestion (
POST /reports/ingest) is a public client endpoint authenticated by a per-app report key (rpk_+ 64 hex), sent asAuthorization: Bearer rpk_.... It does not require JWT. Report keys are managed through the existingreport_keyslifecycle (one key per app withreportsenabled). - Read API (
GET /reports/groups,GET /reports/groups/:groupHash/blobs) is authenticated with a JWT and gated byCheckPermission(download, apps). Admins see every app under their account; team users only see apps in theirallowed_apps.
Report keys are effectively public client credentials shipped inside the client; abuse protection comes from the REPORTS_ENABLED gate, the per-app reports flag, and rate limits — not from key secrecy.
Grouping model
Every report is reduced to a deterministic groupHash built only from stable dimensions:
sha256(application.name + "|" + application.version + "|" + application.channel + "|" + system.platform + "|" + system.arch + "|" + event.type + "|" + event.reason)
Debug details, timestamps, client IP, and device identifiers are excluded from the hash, so unique stack traces never fragment a group. Because app names are unique only per (app_name, owner), the grouping identity stored in Mongo is the composite (app_id, groupHash), which keeps tenants fully isolated.
Privacy boundary
Reports avoid user identity by design. Allowed dimensions are app name, version, channel, platform, arch, event type, reason, an anonymous X-Device-ID (used only for rate limits and dedup, never stored in report documents or the hash), and optional technical details in a blob. Stripping secrets and personal data from details is the responsibility of the client/SDK.