Files
silo-server/migrations/sql/20260720084550_client_diagnostic_reports.sql
Quick104andClaude Fable 5 a6348b3dc5 fix(diagnostics): address PR #445 review findings
- bundle: reject tar entry names that differ from their trimmed form instead
  of normalizing padded names into the allowlist
- repo: reserve expected bytes on receiving rows and count receiving+ready in
  the per-user byte quota so concurrent/multi-node uploads can't overshoot
- contract: require the crash object for event report types and keep it absent
  for manual; add contract tests
- settings/service: seed diagnostics.server_instance_id atomically via
  insert-if-absent and adopt the winning value across nodes
- bundle/service: capture the embedded manifest.json during ValidateBundle and
  reject reports whose embedded manifest disagrees with the part-1 manifest
  (minus archive); add tests
- admin: delete the DB row before the blob on DeleteReport; log bucket/key when
  the blob delete fails instead of leaving a visible report with a missing bundle
- bundle: reject PAX/GNU tar formats and extension records that smuggle bytes
  past validation; add a PAX-archive rejection test
- migration: add CHECK constraints for state, report_type, and platform
- docs: add text/jsonc language identifiers to the two unfenced code blocks
- cleanup: log-and-continue per report and aggregate errors so one poisoned
  report no longer blocks the whole run; update tests
- tasks: give diagnostics its own cleanup interval key instead of reusing the
  opslog key, and bound the startup settings lookup

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e3QjbPo96ed9Mn2qRiUkh
2026-07-21 10:03:11 -04:00

32 lines
1.2 KiB
SQL

-- +goose Up
CREATE TABLE client_diagnostic_reports (
id UUID PRIMARY KEY,
short_id TEXT NOT NULL,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
profile_id TEXT,
state TEXT NOT NULL CHECK (state IN ('receiving', 'ready', 'failed')),
captured_at TIMESTAMPTZ NOT NULL,
received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
report_type TEXT NOT NULL CHECK (report_type IN ('crash', 'anr', 'native_crash', 'hang', 'abnormal_exit', 'manual')),
platform TEXT NOT NULL CHECK (platform IN ('android', 'android-tv', 'ios', 'tvos')),
app_version TEXT NOT NULL,
crash_summary TEXT,
manifest JSONB NOT NULL,
playback_session_ids TEXT[] NOT NULL DEFAULT '{}',
blob_bucket TEXT,
blob_key TEXT,
blob_bytes BIGINT,
uncompressed_bytes BIGINT,
blob_sha256 TEXT
);
CREATE UNIQUE INDEX client_diagnostic_reports_short_id_lower_idx
ON client_diagnostic_reports (lower(short_id));
CREATE INDEX client_diagnostic_reports_user_received_idx
ON client_diagnostic_reports (user_id, received_at DESC);
CREATE INDEX client_diagnostic_reports_received_idx
ON client_diagnostic_reports (received_at);
-- +goose Down
DROP TABLE IF EXISTS client_diagnostic_reports;