-- bigtech.sucks — living database schema -- The website is a view over this. Nothing in the site may hardcode a number -- that lives here. -- -- Design rule: an assessment is an ARTIFACT with a date, a version, a method and -- evidence. A score without an assessment is a placeholder and is stored as NULL, -- never as a guess. PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL; -- ---------------------------------------------------------------- who owns it CREATE TABLE IF NOT EXISTS vendor ( id TEXT PRIMARY KEY, name TEXT NOT NULL, legal_entity TEXT, entity_type TEXT CHECK (entity_type IN ('nonprofit','private','public','state','unknown')), jurisdiction TEXT, -- ISO 3166-1 alpha-2 business_model TEXT CHECK (business_model IN ('advertising','subscription','hardware','license','mixed','none','unknown')), parent_id TEXT REFERENCES vendor(id), founded TEXT, notes TEXT ); -- ---------------------------------------------------------------- what it is CREATE TABLE IF NOT EXISTS category ( id TEXT PRIMARY KEY, label TEXT NOT NULL, sensitivity REAL NOT NULL CHECK (sensitivity BETWEEN 0 AND 1), nextcloud TEXT ); CREATE TABLE IF NOT EXISTS product ( id TEXT PRIMARY KEY, vendor_id TEXT NOT NULL REFERENCES vendor(id), name TEXT NOT NULL, category_id TEXT REFERENCES category(id), -- platform is part of the identity: Signal iOS != Signal Desktop platform TEXT NOT NULL, source_url TEXT, license TEXT, sovereign INTEGER NOT NULL DEFAULT 0 CHECK (sovereign IN (0,1)), boss INTEGER NOT NULL DEFAULT 0 CHECK (boss IN (0,1)), art_slot TEXT, monthly_cost REAL, notes TEXT, UNIQUE(id, platform) ); -- ---------------------------------------------------------------- the instrument CREATE TABLE IF NOT EXISTS dimension ( id TEXT PRIMARY KEY, label TEXT NOT NULL, weight REAL NOT NULL CHECK (weight > 0), anchor_10 TEXT NOT NULL, anchor_0 TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS assessment ( id TEXT PRIMARY KEY, product_id TEXT NOT NULL REFERENCES product(id), version TEXT NOT NULL, source_commit TEXT, assessed_on TEXT NOT NULL, method_version TEXT NOT NULL, -- 0 = none, 1 = available but experimental/unverified, 2 = verified reproducible_build INTEGER NOT NULL DEFAULT 0 CHECK (reproducible_build IN (0,1,2)), weighted_total REAL NOT NULL CHECK (weighted_total BETWEEN 0 AND 100), grade TEXT NOT NULL, weakest_dimension TEXT NOT NULL REFERENCES dimension(id), headline TEXT NOT NULL, -- the caveat, in one sentence not_tested TEXT, UNIQUE(product_id, version, assessed_on) ); CREATE TABLE IF NOT EXISTS score ( assessment_id TEXT NOT NULL REFERENCES assessment(id) ON DELETE CASCADE, dimension_id TEXT NOT NULL REFERENCES dimension(id), value REAL NOT NULL CHECK (value BETWEEN 0 AND 10), rationale TEXT, PRIMARY KEY (assessment_id, dimension_id) ); -- evidence is the only thing that makes a score legitimate CREATE TABLE IF NOT EXISTS evidence ( id INTEGER PRIMARY KEY AUTOINCREMENT, assessment_id TEXT NOT NULL REFERENCES assessment(id) ON DELETE CASCADE, dimension_id TEXT REFERENCES dimension(id), claim TEXT NOT NULL, tier INTEGER NOT NULL CHECK (tier IN (1,2,3,4)), kind TEXT CHECK (kind IN ('source','capture','record','filing','advisory','paper','claim')), artifact TEXT, -- path inside the vault, or URL sha256 TEXT, observed_at TEXT ); CREATE TABLE IF NOT EXISTS finding ( id INTEGER PRIMARY KEY AUTOINCREMENT, assessment_id TEXT NOT NULL REFERENCES assessment(id) ON DELETE CASCADE, severity TEXT NOT NULL CHECK (severity IN ('critical','high','medium','low','info')), title TEXT NOT NULL, detail TEXT, status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open','fixed','by_design','disputed','accepted')), disclosed_on TEXT, cve TEXT ); CREATE TABLE IF NOT EXISTS offramp ( product_id TEXT NOT NULL REFERENCES product(id), replacement TEXT NOT NULL, nextcloud TEXT, notes TEXT, PRIMARY KEY (product_id, replacement) ); -- ---------------------------------------------------------------- derived views -- The single number the game and the report both use. CREATE VIEW IF NOT EXISTS v_product_score AS SELECT p.id AS product_id, p.name AS product, p.platform, p.category_id, v.id AS vendor_id, v.name AS vendor, v.business_model, v.jurisdiction, a.id AS assessment_id, a.assessed_on, a.version, a.weighted_total, a.grade, a.weakest_dimension, a.headline, -- policy axis: who holds your data, and what can they be made to hand over CAST(ROUND(10 * ( 0.40 * (SELECT value FROM score WHERE assessment_id = a.id AND dimension_id = 'server_retention') + 0.30 * (SELECT value FROM score WHERE assessment_id = a.id AND dimension_id = 'ownership_jurisdiction') + 0.30 * (SELECT value FROM score WHERE assessment_id = a.id AND dimension_id = 'user_control_exit') )) AS INTEGER) AS policy, -- infra axis: can you verify it, and what does it leave on your disk CAST(ROUND(10 * ( 0.50 * (SELECT value FROM score WHERE assessment_id = a.id AND dimension_id = 'verifiability') + 0.30 * (SELECT value FROM score WHERE assessment_id = a.id AND dimension_id = 'dependency_surface') + 0.20 * (SELECT value FROM score WHERE assessment_id = a.id AND dimension_id = 'local_data_exposure') )) AS INTEGER) AS infra FROM product p JOIN vendor v ON v.id = p.vendor_id LEFT JOIN assessment a ON a.product_id = p.id AND a.assessed_on = (SELECT MAX(assessed_on) FROM assessment WHERE product_id = p.id); -- A product with no assessment must surface as unassessed, not as a number. CREATE VIEW IF NOT EXISTS v_coverage AS SELECT p.id AS product_id, p.name, p.platform, CASE WHEN a.id IS NULL THEN 'unassessed' ELSE a.grade END AS status, a.weighted_total FROM product p LEFT JOIN assessment a ON a.product_id = p.id AND a.assessed_on = (SELECT MAX(assessed_on) FROM assessment WHERE product_id = p.id);