release / govulncheck (push) Successful in 17s
release / web (push) Successful in 1m33s
release / go (push) Successful in 1m49s
release / integration (push) Successful in 5m42s
release / android (push) Successful in 5m59s
release / Build signed APK (releases and dev) (push) Successful in 6m9s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Applies rule 32's migration exception: each header still says what the schema was, what changes and what it prevents, but task, milestone, decision and rule ids are replaced by the practice they stood for, the operator quotes by the decision, single-install counts are dropped, and pointers to a deleted package and an external design doc are gone. golang-migrate keeps no checksum, so editing applied migrations' comments is safe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
55 lines
2.5 KiB
SQL
55 lines
2.5 KiB
SQL
-- Device diagnostics / debug-reporting subsystem.
|
|
--
|
|
-- When an account has users.debug_mode_enabled = true, that account's
|
|
-- client(s) stream a timeseries of device state here: connectivity +
|
|
-- server-health transitions (roaming / poor-data recovery), UPnP/Sonos
|
|
-- sync events (locked-phone desync), and power/lifecycle state (Doze /
|
|
-- battery-optimization, the prime suspect for the desync). The admin
|
|
-- /admin/diagnostics view filters by account + device + time window and
|
|
-- exports the slice as JSON for offline analysis.
|
|
--
|
|
-- `kind` is a COARSE category gated by a CHECK whitelist; the finer event
|
|
-- discriminator + all event-specific fields live in the `payload` jsonb. This
|
|
-- keeps the enum small and stable so new event variants don't require a
|
|
-- migration (only a new *category* would: a new CHECK value means swapping the
|
|
-- constraint in the same migration). If you add a category here, mirror it in
|
|
-- the handler whitelist in internal/api/diagnostics.go.
|
|
--
|
|
-- Two timestamps on purpose: occurred_at is the client device clock
|
|
-- (when the event happened on the phone — may be skewed/wrong, e.g. in
|
|
-- a dead zone), received_at is the server clock at ingest. Retention
|
|
-- pruning is keyed on received_at so a bad client clock can't keep rows
|
|
-- alive forever.
|
|
|
|
CREATE TABLE diagnostic_events (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
client_id text NOT NULL,
|
|
app_version text,
|
|
os_version text,
|
|
kind text NOT NULL,
|
|
payload jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
occurred_at timestamptz NOT NULL,
|
|
received_at timestamptz NOT NULL DEFAULT now(),
|
|
CONSTRAINT diagnostic_events_kind_check
|
|
CHECK (kind IN (
|
|
'connectivity', 'upnp_sync', 'power', 'lifecycle',
|
|
'heartbeat', 'http'
|
|
))
|
|
);
|
|
|
|
-- Admin reads filter by account then device, ordered newest-first.
|
|
CREATE INDEX idx_diagnostic_events_user
|
|
ON diagnostic_events (user_id, occurred_at DESC);
|
|
CREATE INDEX idx_diagnostic_events_client
|
|
ON diagnostic_events (client_id, occurred_at DESC);
|
|
-- Retention pruner deletes by server-clock age.
|
|
CREATE INDEX idx_diagnostic_events_received
|
|
ON diagnostic_events (received_at);
|
|
|
|
-- Per-account opt-in for diagnostics reporting. Admin-set primarily
|
|
-- (flip remotely while a bug is live); the client also exposes a local
|
|
-- OFF switch. Default false: no account reports until explicitly enabled.
|
|
ALTER TABLE users
|
|
ADD COLUMN IF NOT EXISTS debug_mode_enabled boolean NOT NULL DEFAULT false;
|