/// // Background diagnostics from prod devices (feedback 2 §4). // // The family runs Amber on hardware I can't attach a debugger to, so the app // batches a small stream of diagnostic events here: uncaught errors, player // error codes, per-session playback summaries (host/resolution/stalls), and — // the signal for the "a track falls behind" reports — the audio/subtitle delay // the user dialed in to fix desync, plus stall counts. It's deliberately // low-volume and best-effort so it never slows the device (see TelemetryService). // // Privacy/security: rows are owned by the authenticated user (create-only for // them, no read/update/delete), and the client redacts addon tokens / stream // URLs to their host before anything is written — never a credential or a full // signed URL. Reading is superusers-only (that's how I query it). migrate((app) => { const users = app.findCollectionByNameOrId("users") const logs = new Collection({ type: "base", name: "client_logs", // Create-only for the owner; nobody but a superuser can read them back. listRule: null, viewRule: null, createRule: "@request.auth.id != '' && user = @request.auth.id", updateRule: null, deleteRule: null, fields: [ { type: "relation", name: "user", required: true, collectionId: users.id, cascadeDelete: true, maxSelect: 1, }, // "error" | "player" | "session" | "info" — a coarse bucket for filtering. { type: "text", name: "kind", required: true, max: 20 }, // Short event slug, e.g. "uncaught", "exo_error", "session_summary", // "av_delay_applied". { type: "text", name: "event", required: true, max: 80 }, // Human-ish message / error text (already redacted client-side). { type: "text", name: "message", max: 4000 }, // Structured payload (redacted): host, resolution, stalls, delayMs, etc. { type: "json", name: "meta", maxSize: 20000 }, // Device/app context so I can tell platforms apart without a debugger. { type: "text", name: "appVersion", max: 40 }, { type: "text", name: "platform", max: 40 }, { type: "text", name: "device", max: 120 }, // Client-set event time (device clock); `created` is the server clock. { type: "date", name: "ts" }, { type: "autodate", name: "created", onCreate: true }, ], indexes: [ "CREATE INDEX `idx_client_logs_created` ON `client_logs` (`created`)", "CREATE INDEX `idx_client_logs_kind` ON `client_logs` (`kind`)", ], }) app.save(logs) }, (app) => { try { app.delete(app.findCollectionByNameOrId("client_logs")) } catch (_) { /* already gone */ } })