diff --git a/packages/core/db/build.mjs b/packages/core/db/build.mjs index 4bcc10d..8fa4ba3 100644 --- a/packages/core/db/build.mjs +++ b/packages/core/db/build.mjs @@ -57,6 +57,7 @@ const VENDORS = { netflix: ['Netflix, Inc.', 'public', 'US', 'subscription'], 'nextcloud-gmbh': ['Nextcloud GmbH', 'private', 'DE', 'subscription'], 'signal-foundation': ['Signal Technology Foundation', 'nonprofit', 'US', 'none'], + 'kino-ai': ['Kino AI', 'private', 'US', 'subscription'], // discovered products land here until a human or agent resolves the owner unknown: ['Unknown vendor', 'unknown', 'unknown', 'unknown'], }; @@ -90,6 +91,10 @@ async function build({ fresh = false } = {}) { const db = new DatabaseSync(DB_PATH); db.exec('PRAGMA foreign_keys = ON'); db.exec(readFileSync(join(HERE, 'schema.sql'), 'utf8')); + migrate(db); + // second pass: migrate drops the views so this recreates them with the current + // definitions. Views cannot be altered, only replaced. + db.exec(readFileSync(join(HERE, 'schema.sql'), 'utf8')); // vendors const insVendor = db.prepare(`INSERT OR REPLACE INTO vendor @@ -127,6 +132,23 @@ async function build({ fresh = false } = {}) { return { migrated, seeds }; } +// ---------------------------------------------------------------- migrate +// The database authors itself, so it must survive its own schema changes. +// Never drop and rebuild: add the column, drop the views, let schema.sql recreate. +function migrate(db) { + const cols = (t) => db.prepare(`PRAGMA table_info(${t})`).all().map((c) => c.name); + + if (!cols('product').includes('priority')) { + db.exec('ALTER TABLE product ADD COLUMN priority INTEGER NOT NULL DEFAULT 0'); + } + if (!cols('assessment').includes('source_available')) { + db.exec('ALTER TABLE assessment ADD COLUMN source_available INTEGER NOT NULL DEFAULT 1'); + } + for (const v of ['v_backlog', 'v_coverage', 'v_product_score']) { + db.exec(`DROP VIEW IF EXISTS ${v}`); + } +} + // ---------------------------------------------------------------- verify function verify() { const db = new DatabaseSync(DB_PATH, { readOnly: true }); @@ -167,6 +189,20 @@ function verify() { const v = db.prepare("SELECT value FROM score WHERE assessment_id = ? AND dimension_id = 'verifiability'").get(a.id)?.value; if (a.reproducible_build === 0) ok(v <= 3, `${a.id}: no reproducible build but verifiability ${v} > 3`); if (a.reproducible_build === 1) ok(v <= 6, `${a.id}: experimental reproducible build but verifiability ${v} > 6`); + + // Closed source cannot exceed 5 on anything without independent evidence. + // Reputation is tier 3. Tier 3 is not a measurement. + if (a.source_available === 0) { + const above = db.prepare('SELECT * FROM score WHERE assessment_id = ? AND value > 5').all(a.id); + for (const s of above) { + const independent = db.prepare(` + SELECT COUNT(*) AS n FROM evidence + WHERE assessment_id = ? AND dimension_id = ? + AND tier <= 2 AND kind IN ('capture','filing')`).get(a.id, s.dimension_id).n; + ok(independent > 0, + `${a.id}/${s.dimension_id}: closed source scored ${s.value} > 5 with no capture or court filing to back it`); + } + } } const scores = db.prepare('SELECT * FROM v_product_score').all(); diff --git a/packages/core/db/schema.sql b/packages/core/db/schema.sql index 6de60db..f940228 100644 --- a/packages/core/db/schema.sql +++ b/packages/core/db/schema.sql @@ -43,6 +43,8 @@ CREATE TABLE IF NOT EXISTS product ( boss INTEGER NOT NULL DEFAULT 0 CHECK (boss IN (0,1)), art_slot TEXT, monthly_cost REAL, + -- priority > 0 puts a product on the board even before anything discovers it + priority INTEGER NOT NULL DEFAULT 0, notes TEXT, UNIQUE(id, platform) ); @@ -65,6 +67,9 @@ CREATE TABLE IF NOT EXISTS assessment ( 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)), + -- 1 = we can read the code. 0 = closed. Closed source cannot exceed 5 on any + -- dimension unless independent evidence (a capture or a court filing) says so. + source_available INTEGER NOT NULL DEFAULT 1 CHECK (source_available IN (0,1)), weighted_total REAL NOT NULL CHECK (weighted_total BETWEEN 0 AND 100), grade TEXT NOT NULL, weakest_dimension TEXT NOT NULL REFERENCES dimension(id), @@ -180,17 +185,20 @@ 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); --- What to assess next, ordered by how much the thing is actually used. +-- What to assess next. Anything a human put on the board outranks anything a +-- crawler found, and within each tier usage decides. CREATE VIEW IF NOT EXISTS v_backlog AS SELECT p.id AS product_id, p.name, p.platform, p.category_id, - MAX(d.usage_signal) AS usage, + p.priority AS priority, + COALESCE(MAX(d.usage_signal), 0) AS usage, GROUP_CONCAT(DISTINCT d.source) AS found_via FROM product p -JOIN discovery d ON d.product_id = p.id +LEFT JOIN discovery d ON d.product_id = p.id WHERE NOT EXISTS (SELECT 1 FROM assessment a WHERE a.product_id = p.id) + AND (d.id IS NOT NULL OR p.priority > 0) GROUP BY p.id -ORDER BY usage DESC; +ORDER BY p.priority DESC, usage DESC; diff --git a/packages/core/db/seed/0002-board.sql b/packages/core/db/seed/0002-board.sql new file mode 100644 index 0000000..28cbfef --- /dev/null +++ b/packages/core/db/seed/0002-board.sql @@ -0,0 +1,28 @@ +-- ===================================================================== +-- 0002 — the board, 2026-10-29 +-- Products a human put on the queue. priority > 0 outranks anything a crawler found. +-- +-- Neither of these has an assessment yet, and neither gets a number until it +-- has evidence. Slack is closed source, so when it is assessed it will be capped +-- at 5 on every dimension that has no capture or filing behind it. +-- ===================================================================== + +UPDATE product + SET priority = 10, + notes = 'Queued 2026-10-29 by Christian for the Big Tech Report. Closed source, so verifiability caps at 3 and no dimension may exceed 5 without independent capture or court filing. Score is reputation, not measurement.' + WHERE id = 'slack'; + +INSERT OR IGNORE INTO product + (id, vendor_id, name, category_id, platform, source_url, license, + sovereign, boss, art_slot, monthly_cost, priority, notes) +VALUES + ('kino-ai', 'kino-ai', 'Kino', 'video', 'web', 'https://kino.ai', NULL, + 0, 0, NULL, NULL, 10, + 'Queued 2026-10-29. YC S23, four people, browser-native editor and AI media manager. Christian signed up same day and asked founders for WebDAV / Nextcloud compatibility. Closed source.'); + +INSERT OR IGNORE INTO discovery + (product_id, source, source_url, usage_signal, usage_metric, observed_at, raw) +VALUES + ('kino-ai', 'manual', 'https://www.ycombinator.com/companies/kino-ai', NULL, 'queued_by_operator', + '2026-10-29T15:22:00-05:00', + '{"batch":"S23","founders":["Wesley Block","Luke Igel"],"note":"formerly local-first desktop logging tool; now browser-native editor"}'); diff --git a/packages/core/src/data/generated/catalog.json b/packages/core/src/data/generated/catalog.json index 9a22e46..3945b6c 100644 --- a/packages/core/src/data/generated/catalog.json +++ b/packages/core/src/data/generated/catalog.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-10-29T18:23:54.071Z", + "generated_at": "2026-10-29T20:23:11.576Z", "method_version": "btr-0.1", "dimensions": [ { @@ -2634,6 +2634,28 @@ "headline": null, "assessed_on": null }, + { + "id": "kino-ai", + "name": "Kino", + "platform": "web", + "category_id": "video", + "vendor_id": "kino-ai", + "sovereign": 0, + "boss": 0, + "art_slot": null, + "monthly_cost": null, + "vendor": "Kino AI", + "entity_type": "private", + "jurisdiction": "US", + "business_model": "subscription", + "weighted_total": null, + "grade": null, + "policy": null, + "infra": null, + "weakest_dimension": null, + "headline": null, + "assessed_on": null + }, { "id": "teams", "name": "Microsoft Teams", @@ -2829,7 +2851,7 @@ ], "evidence": [ { - "id": 8, + "id": 15, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "local_data_exposure", "claim": "Attachments are stored unencrypted on the filesystem; the SQLCipher key is held by the OS keyring.", @@ -2840,7 +2862,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 9, + "id": 16, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Electron media permission defaults to false and audio is denied unless the user has enabled mediaPermissions.", @@ -2851,7 +2873,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 10, + "id": 17, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Zero analytics, tracker or crash-upload dependencies across 224 packages.", @@ -2862,7 +2884,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 11, + "id": 18, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Only two audio capture call sites exist and both are reachable only from an explicit UI action.", @@ -2873,7 +2895,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 12, + "id": 19, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "verifiability", "claim": "Signal states reproducible builds for macOS and Windows are not available, and the Linux build is experimental.", @@ -2884,7 +2906,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 13, + "id": 20, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "server_retention", "claim": "Grand jury subpoena response returned two timestamps and nothing else.", @@ -2895,7 +2917,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 14, + "id": 21, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "ownership_jurisdiction", "claim": "Signal Technology Foundation is a 501(c)(3) with EIN 82-4506840.", @@ -2908,7 +2930,7 @@ ], "findings": [ { - "id": 5, + "id": 9, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "high", "title": "Received attachments are stored unencrypted on disk", @@ -2917,7 +2939,7 @@ "cve": "CVE-2023-24069" }, { - "id": 6, + "id": 10, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "medium", "title": "No verified reproducible build for the Linux package", @@ -2926,7 +2948,7 @@ "cve": null }, { - "id": 7, + "id": 11, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "medium", "title": "Local database key is held in the OS keyring", @@ -2935,7 +2957,7 @@ "cve": null }, { - "id": 8, + "id": 12, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "low", "title": "Registration requires a phone number", diff --git a/packages/core/src/data/generated/catalog.ts b/packages/core/src/data/generated/catalog.ts index d70aa23..1b3ca15 100644 --- a/packages/core/src/data/generated/catalog.ts +++ b/packages/core/src/data/generated/catalog.ts @@ -39,7 +39,7 @@ export interface CatalogDimension { } export const catalog = { - "generated_at": "2026-10-29T18:23:54.071Z", + "generated_at": "2026-10-29T20:23:11.576Z", "method_version": "btr-0.1", "dimensions": [ { @@ -2674,6 +2674,28 @@ export const catalog = { "headline": null, "assessed_on": null }, + { + "id": "kino-ai", + "name": "Kino", + "platform": "web", + "category_id": "video", + "vendor_id": "kino-ai", + "sovereign": 0, + "boss": 0, + "art_slot": null, + "monthly_cost": null, + "vendor": "Kino AI", + "entity_type": "private", + "jurisdiction": "US", + "business_model": "subscription", + "weighted_total": null, + "grade": null, + "policy": null, + "infra": null, + "weakest_dimension": null, + "headline": null, + "assessed_on": null + }, { "id": "teams", "name": "Microsoft Teams", @@ -2869,7 +2891,7 @@ export const catalog = { ], "evidence": [ { - "id": 8, + "id": 15, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "local_data_exposure", "claim": "Attachments are stored unencrypted on the filesystem; the SQLCipher key is held by the OS keyring.", @@ -2880,7 +2902,7 @@ export const catalog = { "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 9, + "id": 16, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Electron media permission defaults to false and audio is denied unless the user has enabled mediaPermissions.", @@ -2891,7 +2913,7 @@ export const catalog = { "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 10, + "id": 17, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Zero analytics, tracker or crash-upload dependencies across 224 packages.", @@ -2902,7 +2924,7 @@ export const catalog = { "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 11, + "id": 18, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Only two audio capture call sites exist and both are reachable only from an explicit UI action.", @@ -2913,7 +2935,7 @@ export const catalog = { "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 12, + "id": 19, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "verifiability", "claim": "Signal states reproducible builds for macOS and Windows are not available, and the Linux build is experimental.", @@ -2924,7 +2946,7 @@ export const catalog = { "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 13, + "id": 20, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "server_retention", "claim": "Grand jury subpoena response returned two timestamps and nothing else.", @@ -2935,7 +2957,7 @@ export const catalog = { "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 14, + "id": 21, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "ownership_jurisdiction", "claim": "Signal Technology Foundation is a 501(c)(3) with EIN 82-4506840.", @@ -2948,7 +2970,7 @@ export const catalog = { ], "findings": [ { - "id": 5, + "id": 9, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "high", "title": "Received attachments are stored unencrypted on disk", @@ -2957,7 +2979,7 @@ export const catalog = { "cve": "CVE-2023-24069" }, { - "id": 6, + "id": 10, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "medium", "title": "No verified reproducible build for the Linux package", @@ -2966,7 +2988,7 @@ export const catalog = { "cve": null }, { - "id": 7, + "id": 11, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "medium", "title": "Local database key is held in the OS keyring", @@ -2975,7 +2997,7 @@ export const catalog = { "cve": null }, { - "id": 8, + "id": 12, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "low", "title": "Registration requires a phone number", diff --git a/packages/web/public/catalog.json b/packages/web/public/catalog.json index 9a22e46..3945b6c 100644 --- a/packages/web/public/catalog.json +++ b/packages/web/public/catalog.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-10-29T18:23:54.071Z", + "generated_at": "2026-10-29T20:23:11.576Z", "method_version": "btr-0.1", "dimensions": [ { @@ -2634,6 +2634,28 @@ "headline": null, "assessed_on": null }, + { + "id": "kino-ai", + "name": "Kino", + "platform": "web", + "category_id": "video", + "vendor_id": "kino-ai", + "sovereign": 0, + "boss": 0, + "art_slot": null, + "monthly_cost": null, + "vendor": "Kino AI", + "entity_type": "private", + "jurisdiction": "US", + "business_model": "subscription", + "weighted_total": null, + "grade": null, + "policy": null, + "infra": null, + "weakest_dimension": null, + "headline": null, + "assessed_on": null + }, { "id": "teams", "name": "Microsoft Teams", @@ -2829,7 +2851,7 @@ ], "evidence": [ { - "id": 8, + "id": 15, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "local_data_exposure", "claim": "Attachments are stored unencrypted on the filesystem; the SQLCipher key is held by the OS keyring.", @@ -2840,7 +2862,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 9, + "id": 16, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Electron media permission defaults to false and audio is denied unless the user has enabled mediaPermissions.", @@ -2851,7 +2873,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 10, + "id": 17, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Zero analytics, tracker or crash-upload dependencies across 224 packages.", @@ -2862,7 +2884,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 11, + "id": 18, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "telemetry_egress", "claim": "Only two audio capture call sites exist and both are reachable only from an explicit UI action.", @@ -2873,7 +2895,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 12, + "id": 19, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "verifiability", "claim": "Signal states reproducible builds for macOS and Windows are not available, and the Linux build is experimental.", @@ -2884,7 +2906,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 13, + "id": 20, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "server_retention", "claim": "Grand jury subpoena response returned two timestamps and nothing else.", @@ -2895,7 +2917,7 @@ "observed_at": "2026-10-29T13:13:56-05:00" }, { - "id": 14, + "id": 21, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "dimension_id": "ownership_jurisdiction", "claim": "Signal Technology Foundation is a 501(c)(3) with EIN 82-4506840.", @@ -2908,7 +2930,7 @@ ], "findings": [ { - "id": 5, + "id": 9, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "high", "title": "Received attachments are stored unencrypted on disk", @@ -2917,7 +2939,7 @@ "cve": "CVE-2023-24069" }, { - "id": 6, + "id": 10, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "medium", "title": "No verified reproducible build for the Linux package", @@ -2926,7 +2948,7 @@ "cve": null }, { - "id": 7, + "id": 11, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "medium", "title": "Local database key is held in the OS keyring", @@ -2935,7 +2957,7 @@ "cve": null }, { - "id": 8, + "id": 12, "assessment_id": "A-2026-10-29-SIGNAL-DESKTOP", "severity": "low", "title": "Registration requires a phone number", diff --git a/packages/web/public/oath.json b/packages/web/public/oath.json index 57f241b..3e6856b 100644 --- a/packages/web/public/oath.json +++ b/packages/web/public/oath.json @@ -1,10 +1,10 @@ { - "digest": "5aadd709b394ab19e13a65aa21fb5182cb736f53e2b60c175abebb8dd11f1d8a", + "digest": "37a9774dc7569335d8135828aa5b97d294e26252185b0b31dc4a8f4dadde59db", "algorithm": "sha256", "file": "catalog.json", - "byte_length": 79846, - "rebuilt_at": "2026-10-29T18:23:54.074Z", + "byte_length": 80389, + "rebuilt_at": "2026-10-29T20:23:11.578Z", "method_version": "btr-0.1", - "products": 122, + "products": 123, "assessed": 1 } \ No newline at end of file