feat: self-authoring ingest, relevance gate, and the Ranch Oath
The roster writes itself. The scores do not. - db/ingest/sources.mjs: GitHub and Flathub adapters. GitHub is ranked by stars (usage); Flathub is a curated store of end-user Linux apps, so relevance is free there and has to be earned on GitHub. - db/ingest/run.mjs: discovers into the roster as unassessed products, never as scores. Every discovery leaves a receipt (source, url, usage metric, timestamp) so a reader can see where a product came from and why it ranks. - LooksLikeUserSoftware gate: topic:messaging sorts RabbitMQ to the top because brokers are highly starred and are not things people install. - build.mjs no longer wipes the database. The DB authors itself, so a rebuild would have thrown the roster away. --fresh is now explicit. - v_backlog view: what to assess next, ordered by measured usage. - packages/web/public/oath.html: the Ranch Oath, with an ASCII Bible crest, bound to a SHA-256 of catalog.json. The page recomputes the digest in the browser and reports VERIFIED or BROKEN. An oath you cannot break is decoration. Roster now 122 products, 1 assessed. All instrument checks pass.
This commit is contained in:
parent
b6a3b0722b
commit
d433a0e75e
10 changed files with 6910 additions and 29 deletions
|
|
@ -14,7 +14,8 @@
|
|||
"preview": "npm run preview -w @bigtech-sucks/web",
|
||||
"test": "npm run test -w @bigtech-sucks/core",
|
||||
"db:build": "node packages/core/db/build.mjs",
|
||||
"db:verify": "node packages/core/db/build.mjs verify"
|
||||
"db:verify": "node packages/core/db/build.mjs verify",
|
||||
"db:ingest": "node packages/core/db/ingest/run.mjs"
|
||||
},
|
||||
"license": "UNLICENSED"
|
||||
}
|
||||
|
|
@ -14,6 +14,7 @@
|
|||
*/
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { readFileSync, writeFileSync, readdirSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
|
@ -56,6 +57,8 @@ const VENDORS = {
|
|||
netflix: ['Netflix, Inc.', 'public', 'US', 'subscription'],
|
||||
'nextcloud-gmbh': ['Nextcloud GmbH', 'private', 'DE', 'subscription'],
|
||||
'signal-foundation': ['Signal Technology Foundation', 'nonprofit', 'US', 'none'],
|
||||
// discovered products land here until a human or agent resolves the owner
|
||||
unknown: ['Unknown vendor', 'unknown', 'unknown', 'unknown'],
|
||||
};
|
||||
|
||||
const OUTLAW_VENDOR = {
|
||||
|
|
@ -80,8 +83,10 @@ const ID_OVERRIDES = { '1password': 'onepassword', '23andme': 'twentythreeandme'
|
|||
const slug = (s) => ID_OVERRIDES[s] ?? s.replace(/[^a-z0-9]+/gi, '-').toLowerCase();
|
||||
|
||||
// ---------------------------------------------------------------- build
|
||||
async function build() {
|
||||
if (existsSync(DB_PATH)) unlinkSync(DB_PATH);
|
||||
async function build({ fresh = false } = {}) {
|
||||
// Never wipe by default: the database authors itself, and a rebuild that
|
||||
// deletes discovered products would throw away the roster. --fresh is explicit.
|
||||
if (fresh && existsSync(DB_PATH)) unlinkSync(DB_PATH);
|
||||
const db = new DatabaseSync(DB_PATH);
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
db.exec(readFileSync(join(HERE, 'schema.sql'), 'utf8'));
|
||||
|
|
@ -222,7 +227,27 @@ function exportSite() {
|
|||
dimensions, products, assessments, scores, evidence, findings,
|
||||
};
|
||||
|
||||
writeFileSync(join(GEN_DIR, 'catalog.json'), JSON.stringify(payload, null, 2));
|
||||
// The bytes we hash are the bytes we serve. The oath is bound to this digest,
|
||||
// so an oath page that does not match the data is detectable by a stranger.
|
||||
const catalogBytes = JSON.stringify(payload, null, 2);
|
||||
writeFileSync(join(GEN_DIR, 'catalog.json'), catalogBytes);
|
||||
|
||||
const PUB = join(CORE, '..', 'web', 'public');
|
||||
mkdirSync(PUB, { recursive: true });
|
||||
writeFileSync(join(PUB, 'catalog.json'), catalogBytes);
|
||||
|
||||
const digest = createHash('sha256').update(catalogBytes).digest('hex');
|
||||
const oath = {
|
||||
digest,
|
||||
algorithm: 'sha256',
|
||||
file: 'catalog.json',
|
||||
byte_length: Buffer.byteLength(catalogBytes),
|
||||
rebuilt_at: new Date().toISOString(),
|
||||
method_version: payload.method_version,
|
||||
products: products.length,
|
||||
assessed: products.filter((p) => p.weighted_total !== null).length,
|
||||
};
|
||||
writeFileSync(join(PUB, 'oath.json'), JSON.stringify(oath, null, 2));
|
||||
|
||||
const ts = `// GENERATED FILE - do not edit by hand.
|
||||
// Source of truth: packages/core/db/bigtech.db
|
||||
|
|
@ -295,7 +320,8 @@ if (mode === 'verify') {
|
|||
const r = verify();
|
||||
report(r);
|
||||
} else {
|
||||
const { migrated, seeds } = await build();
|
||||
const fresh = process.argv.includes('--fresh');
|
||||
const { migrated, seeds } = await build({ fresh });
|
||||
console.log(`built ${DB_PATH}`);
|
||||
console.log(` legacy roster absorbed: ${migrated} products (unassessed)`);
|
||||
console.log(` seeds applied: ${seeds.join(', ')}`);
|
||||
|
|
|
|||
152
packages/core/db/ingest/run.mjs
Normal file
152
packages/core/db/ingest/run.mjs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
#!/usr/bin/env node
|
||||
/**
|
||||
* Self-authoring ingest.
|
||||
*
|
||||
* The roster writes itself. The scores do not.
|
||||
*
|
||||
* DISCOVERY automatic. A source names a piece of software and a usage number.
|
||||
* ASSESSMENT never automatic. A score requires a written rationale and tier-1
|
||||
* or tier-2 evidence, and the build gate enforces that.
|
||||
*
|
||||
* That split is the whole design. If the ingest wrote scores, the oath on the
|
||||
* front page would be a lie the same afternoon.
|
||||
*
|
||||
* Run: npm run db:ingest
|
||||
* Dry run: npm run db:ingest -- --dry
|
||||
*/
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join, dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { ADAPTERS, looksLikeUserSoftware } from './sources.mjs';
|
||||
|
||||
const HERE = dirname(fileURLToPath(import.meta.url));
|
||||
const DB_PATH = join(HERE, '..', 'bigtech.db');
|
||||
const DRY = process.argv.includes('--dry');
|
||||
|
||||
/** What to go looking for, and what to rank it by. */
|
||||
const TARGETS = [
|
||||
{ category: 'messaging', source: 'flathub', query: 'messaging' },
|
||||
{ category: 'messaging', source: 'github', query: 'messaging' },
|
||||
{ category: 'email', source: 'flathub', query: 'email' },
|
||||
{ category: 'email', source: 'github', query: 'mail-client' },
|
||||
{ category: 'passwords', source: 'flathub', query: 'password' },
|
||||
{ category: 'browser', source: 'flathub', query: 'web browser' },
|
||||
{ category: 'notes', source: 'flathub', query: 'notes' },
|
||||
{ category: 'files', source: 'github', query: 'file-sync' },
|
||||
{ category: 'photos', source: 'flathub', query: 'photo' },
|
||||
// Flathub is a curated store of end-user Linux apps, so relevance is free.
|
||||
// GitHub topics are uncurated, so relevance has to be earned by a classifier.
|
||||
// Prefer the store wherever it has the category. Verified 2026-10-29: the
|
||||
// GitHub topic 'local-llm' returned Langroid and Pangu.js, neither of which
|
||||
// is a product a person installs.
|
||||
{ category: 'ai', source: 'flathub', query: 'llm' },
|
||||
];
|
||||
|
||||
const slug = (s) =>
|
||||
s.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 48);
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
async function main() {
|
||||
const db = new DatabaseSync(DB_PATH);
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
db.exec(readFileSync(join(HERE, '..', 'schema.sql'), 'utf8'));
|
||||
|
||||
const existing = new Map(
|
||||
db.prepare('SELECT id, LOWER(name) AS n FROM product').all().map((r) => [r.n, r.id]),
|
||||
);
|
||||
const taken = new Set(db.prepare('SELECT id FROM product').all().map((r) => r.id));
|
||||
|
||||
const insProduct = db.prepare(`
|
||||
INSERT OR IGNORE INTO product
|
||||
(id, vendor_id, name, category_id, platform, source_url, license, sovereign, boss, art_slot, monthly_cost, notes)
|
||||
VALUES (?, 'unknown', ?, ?, ?, ?, ?, 0, 0, NULL, NULL, ?)`);
|
||||
|
||||
const insDiscovery = db.prepare(`
|
||||
INSERT OR IGNORE INTO discovery
|
||||
(product_id, source, source_url, usage_signal, usage_metric, observed_at, raw)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`);
|
||||
|
||||
const stats = { targets: 0, rowsSeen: 0, newProducts: 0, newReceipts: 0, failed: [] };
|
||||
|
||||
for (const target of TARGETS) {
|
||||
const adapter = ADAPTERS[target.source];
|
||||
if (!adapter) { console.warn(` ! unknown source ${target.source}`); continue; }
|
||||
stats.targets++;
|
||||
|
||||
let rows;
|
||||
try {
|
||||
rows = await adapter(target.query);
|
||||
} catch (err) {
|
||||
stats.failed.push(`${target.source}/${target.query}: ${err.message}`);
|
||||
console.warn(` FAIL ${target.source}/${target.query} ${err.message}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
let added = 0;
|
||||
let dropped = 0;
|
||||
for (const row of rows) {
|
||||
// Relevance gate first. A highly starred message broker is not a product
|
||||
// a person chooses, and a roster full of them is a roster nobody trusts.
|
||||
if (row.platform === 'source' && !looksLikeUserSoftware(row)) { dropped++; continue; }
|
||||
stats.rowsSeen++;
|
||||
const key = row.name.toLowerCase();
|
||||
let id = existing.get(key);
|
||||
|
||||
if (!id) {
|
||||
let candidate = slug(row.name);
|
||||
if (taken.has(candidate)) candidate = `${candidate}-${slug(target.category)}`;
|
||||
if (taken.has(candidate)) continue;
|
||||
|
||||
if (!DRY) {
|
||||
insProduct.run(
|
||||
candidate, row.name, target.category, row.platform,
|
||||
row.sourceUrl, row.raw?.license ?? null,
|
||||
`Discovered by ${target.source} ingest on ${now.slice(0, 10)}. Unassessed.`,
|
||||
);
|
||||
}
|
||||
taken.add(candidate);
|
||||
existing.set(key, candidate);
|
||||
id = candidate;
|
||||
stats.newProducts++;
|
||||
added++;
|
||||
}
|
||||
|
||||
if (!DRY) {
|
||||
const r = insDiscovery.run(
|
||||
id, target.source, row.sourceUrl, row.usage, row.usageMetric,
|
||||
now, JSON.stringify(row.raw),
|
||||
);
|
||||
if (r.changes) stats.newReceipts++;
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
` ${target.source.padEnd(8)} ${String(target.query).padEnd(16)} ${target.category.padEnd(11)} ${String(rows.length).padStart(3)} seen, ${added} new, ${dropped} not-an-app`,
|
||||
);
|
||||
}
|
||||
|
||||
const backlog = db.prepare('SELECT * FROM v_backlog LIMIT 12').all();
|
||||
const cov = db.prepare('SELECT * FROM v_coverage').all();
|
||||
const assessed = cov.filter((c) => c.status !== 'unassessed').length;
|
||||
db.close();
|
||||
|
||||
console.log('');
|
||||
console.log(DRY ? 'DRY RUN, nothing written' : 'INGEST COMPLETE');
|
||||
console.log(` targets attempted : ${stats.targets}`);
|
||||
console.log(` rows seen : ${stats.rowsSeen}`);
|
||||
console.log(` new products : ${stats.newProducts}`);
|
||||
console.log(` new receipts : ${stats.newReceipts}`);
|
||||
console.log(` failed sources : ${stats.failed.length}`);
|
||||
console.log('');
|
||||
console.log(`COVERAGE ${assessed} assessed / ${cov.length} products`);
|
||||
if (backlog.length) {
|
||||
console.log('');
|
||||
console.log('BACKLOG, ordered by usage');
|
||||
for (const b of backlog) {
|
||||
console.log(` ${String(b.usage).padStart(8)} ${b.name.padEnd(28)} ${String(b.category_id).padEnd(11)} via ${b.found_via}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => { console.error(err); process.exit(1); });
|
||||
97
packages/core/db/ingest/sources.mjs
Normal file
97
packages/core/db/ingest/sources.mjs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Discovery adapters.
|
||||
*
|
||||
* Each adapter returns a normalised shape:
|
||||
* { name, platform, sourceUrl, usage, usageMetric, raw }
|
||||
*
|
||||
* Rules for an adapter:
|
||||
* - it discovers SOFTWARE, it never scores it
|
||||
* - it records where it found the thing and how much it is used
|
||||
* - it fails loudly, never silently returning an empty list on a blocked fetch
|
||||
*
|
||||
* Sources currently live:
|
||||
* github open API, no key needed, 60 requests/hour unauthenticated
|
||||
* flathub open API, POST /api/v2/search, no key
|
||||
*
|
||||
* Sources deliberately not automated:
|
||||
* alternativeto Cloudflare-protected. Readable through a real browser engine
|
||||
* (verified 2026-10-29) but their robots.txt asks ai-train=no.
|
||||
* Treated as a demand signal for manual/periodic import, not a
|
||||
* crawler target. Do not add a scraper here without a decision.
|
||||
*/
|
||||
|
||||
const UA = 'bigtech-sucks-ingest/0.1 (+https://bigtech.sucks; assessment roster)';
|
||||
|
||||
async function getJson(url, init = {}) {
|
||||
const res = await fetch(url, {
|
||||
...init,
|
||||
headers: { 'User-Agent': UA, Accept: 'application/json', ...(init.headers || {}) },
|
||||
});
|
||||
if (!res.ok) throw new Error(`${url} -> HTTP ${res.status}`);
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ relevance
|
||||
// Usage rank alone is not a roster. `topic:messaging` sorts RabbitMQ to the top,
|
||||
// because a message broker is highly starred and not a thing a human installs.
|
||||
// A candidate must look like software a person uses, or it is dropped and the
|
||||
// reason is recorded.
|
||||
const NOT_AN_APP =
|
||||
/\b(broker|queue|mqtt|amqp|kafka|nats|pulsar|rocketmq|emqx|nsq|rabbitmq|database|sdk|library|libraries|framework|protocol|server|infrastructure|plugin|extension|binding|middleware|gateway|proxy|driver|api client|spec|specification|awesome list)\b/i;
|
||||
const APP_HINT =
|
||||
/\b(app|application|client|desktop|chat|messenger|messaging|mobile|gui|editor|browser|vault|manager|suite)\b/i;
|
||||
|
||||
export function looksLikeUserSoftware(row) {
|
||||
const text = `${row.name} ${row.raw?.description ?? ''}`;
|
||||
if (NOT_AN_APP.test(text)) return false;
|
||||
if (row.raw?.homepage) return true;
|
||||
return APP_HINT.test(text);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ GitHub
|
||||
export async function githubByTopic(topic, { minStars = 500, limit = 10 } = {}) {
|
||||
const q = encodeURIComponent(`topic:${topic} stars:>=${minStars} archived:false`);
|
||||
const data = await getJson(
|
||||
`https://api.github.com/search/repositories?q=${q}&sort=stars&order=desc&per_page=${limit}`,
|
||||
);
|
||||
return (data.items || []).map((r) => ({
|
||||
name: r.name.replace(/[-_]/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()),
|
||||
platform: 'source',
|
||||
sourceUrl: r.html_url,
|
||||
usage: r.stargazers_count ?? 0,
|
||||
usageMetric: 'github_stars',
|
||||
raw: {
|
||||
full_name: r.full_name,
|
||||
stars: r.stargazers_count,
|
||||
forks: r.forks_count,
|
||||
license: r.license?.spdx_id ?? null,
|
||||
description: r.description,
|
||||
homepage: r.homepage || null,
|
||||
language: r.language,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Flathub
|
||||
export async function flathubSearch(query, { limit = 10 } = {}) {
|
||||
const data = await getJson('https://flathub.org/api/v2/search', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ query, filters: [] }),
|
||||
});
|
||||
return (data.hits || []).slice(0, limit).map((h) => ({
|
||||
name: h.name,
|
||||
platform: 'linux-flatpak',
|
||||
sourceUrl: h.app_id ? `https://flathub.org/apps/${h.app_id}` : null,
|
||||
// Flathub does not publish install counts in this endpoint. Presence in the
|
||||
// distro store is a weak but real usage signal, scored low on purpose.
|
||||
usage: 100,
|
||||
usageMetric: 'flathub_presence',
|
||||
raw: { app_id: h.app_id, summary: h.summary, developer: h.developer_name ?? null },
|
||||
}));
|
||||
}
|
||||
|
||||
export const ADAPTERS = {
|
||||
github: (topic) => githubByTopic(topic),
|
||||
flathub: (query) => flathubSearch(query),
|
||||
};
|
||||
|
|
@ -113,6 +113,24 @@ CREATE TABLE IF NOT EXISTS offramp (
|
|||
PRIMARY KEY (product_id, replacement)
|
||||
);
|
||||
|
||||
-- ---------------------------------------------------------------- discovery
|
||||
-- The database authors itself. Every product the ingest finds leaves a receipt
|
||||
-- here, so a reader can see WHERE it came from and HOW MUCH it is used.
|
||||
-- Discovery may create a product. Discovery may NEVER create a score.
|
||||
CREATE TABLE IF NOT EXISTS discovery (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
product_id TEXT NOT NULL REFERENCES product(id),
|
||||
source TEXT NOT NULL CHECK (source IN ('github','flathub','alternativeto','manual','seed')),
|
||||
source_url TEXT,
|
||||
usage_signal INTEGER,
|
||||
usage_metric TEXT,
|
||||
observed_at TEXT NOT NULL,
|
||||
raw TEXT,
|
||||
UNIQUE(product_id, source, source_url)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_discovery_product ON discovery(product_id);
|
||||
|
||||
-- ---------------------------------------------------------------- derived views
|
||||
-- The single number the game and the report both use.
|
||||
CREATE VIEW IF NOT EXISTS v_product_score AS
|
||||
|
|
@ -161,3 +179,18 @@ SELECT
|
|||
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.
|
||||
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,
|
||||
GROUP_CONCAT(DISTINCT d.source) AS found_via
|
||||
FROM product p
|
||||
JOIN discovery d ON d.product_id = p.id
|
||||
WHERE NOT EXISTS (SELECT 1 FROM assessment a WHERE a.product_id = p.id)
|
||||
GROUP BY p.id
|
||||
ORDER BY usage DESC;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
2947
packages/web/public/catalog.json
Normal file
2947
packages/web/public/catalog.json
Normal file
File diff suppressed because it is too large
Load diff
183
packages/web/public/oath.html
Normal file
183
packages/web/public/oath.html
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>The Ranch Oath — bigtech.sucks</title>
|
||||
<style>
|
||||
:root{
|
||||
--ink:#0d0f0e;
|
||||
--bone:#e8e2d4;
|
||||
--dim:#8a8272;
|
||||
--amber:#d9a441;
|
||||
--good:#7fae7a;
|
||||
--bad:#c9563f;
|
||||
--rule:#2a2d2a;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
body{
|
||||
margin:0;padding:3rem 1.25rem 6rem;
|
||||
background:var(--ink);color:var(--bone);
|
||||
font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;
|
||||
font-size:15px;line-height:1.7;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
.sheet{max-width:62rem;margin:0 auto}
|
||||
pre{overflow-x:auto;margin:0;font-size:clamp(7px,1.35vw,13px);line-height:1.15;color:var(--dim)}
|
||||
.book{text-align:center;margin:0 0 .5rem}
|
||||
.book pre{display:inline-block;text-align:left;color:var(--bone);
|
||||
font-size:clamp(8px,1.6vw,15px);line-height:1.22;letter-spacing:0}
|
||||
h1{
|
||||
text-align:center;font-size:clamp(1.1rem,3vw,1.75rem);letter-spacing:.42em;
|
||||
font-weight:400;margin:1.5rem 0 .35rem;text-transform:uppercase;
|
||||
}
|
||||
.sub{text-align:center;color:var(--dim);letter-spacing:.18em;font-size:.72rem;text-transform:uppercase;margin-bottom:2.5rem}
|
||||
.rule{border:0;border-top:1px solid var(--rule);margin:2.5rem 0}
|
||||
.oath{max-width:44rem;margin:0 auto;font-size:clamp(.86rem,1.5vw,1.02rem)}
|
||||
.oath p{margin:0 0 1.15rem}
|
||||
.oath .open{color:var(--dim)}
|
||||
.oath .vow{color:var(--bone)}
|
||||
.oath .coda{
|
||||
text-align:center;margin:2.25rem 0 0;letter-spacing:.06em;
|
||||
}
|
||||
.oath .coda strong{font-weight:400;color:var(--amber)}
|
||||
.swear{
|
||||
text-align:center;margin:2.5rem 0 0;font-size:clamp(1rem,2.4vw,1.35rem);
|
||||
letter-spacing:.12em;color:var(--bone);
|
||||
}
|
||||
.subpoena{
|
||||
text-align:center;color:var(--dim);font-size:.8rem;letter-spacing:.14em;
|
||||
text-transform:uppercase;margin-top:.6rem;
|
||||
}
|
||||
.seal{
|
||||
max-width:44rem;margin:3rem auto 0;padding:1.25rem 1.35rem;
|
||||
border:1px solid var(--rule);background:rgba(255,255,255,.015);
|
||||
}
|
||||
.seal dl{display:grid;grid-template-columns:max-content 1fr;gap:.35rem 1.25rem;margin:0;font-size:.82rem}
|
||||
.seal dt{color:var(--dim);white-space:nowrap}
|
||||
.seal dd{margin:0;word-break:break-all}
|
||||
.status{margin:1.1rem 0 0;padding-top:1rem;border-top:1px dashed var(--rule);font-size:.82rem}
|
||||
.status .tag{letter-spacing:.16em;font-weight:700}
|
||||
.ok{color:var(--good)}
|
||||
.bad{color:var(--bad)}
|
||||
.unknown{color:var(--dim)}
|
||||
.note{max-width:44rem;margin:2.5rem auto 0;color:var(--dim);font-size:.8rem;line-height:1.6}
|
||||
a{color:var(--amber);text-decoration:none;border-bottom:1px solid rgba(217,164,65,.35)}
|
||||
a:hover{border-bottom-color:var(--amber)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sheet">
|
||||
|
||||
<div class="book"><pre id="crest"></pre></div>
|
||||
|
||||
<h1>The Ranch Oath</h1>
|
||||
<div class="sub">Taken at bigtech.sucks · over a living database</div>
|
||||
|
||||
<hr class="rule" />
|
||||
|
||||
<div class="oath">
|
||||
<p class="open">I, Christian Sass, of sound mind and sober judgment, do solemnly swear, with my hand on the Book:</p>
|
||||
|
||||
<p class="vow">That every number published on this site is a number we measured, and that every measurement names the artifact it came from.</p>
|
||||
|
||||
<p class="vow">That where we could not prove a thing, we have said so in the same size type as the things we could.</p>
|
||||
|
||||
<p class="vow">That no claim here rests on our word alone.</p>
|
||||
|
||||
<p class="vow">That we would rather publish a lower number than a comfortable one.</p>
|
||||
|
||||
<p class="vow">That when we are shown to be wrong, the correction is published with its date and the original stays visible.</p>
|
||||
</div>
|
||||
|
||||
<div class="swear">the truth, the whole truth, and nothing but the truth</div>
|
||||
<div class="subpoena">so help me God</div>
|
||||
|
||||
<div class="seal">
|
||||
<dl>
|
||||
<dt>Sworn by</dt><dd>Christian Sass, Binary Ranch</dd>
|
||||
<dt>On</dt><dd id="sworn-on">—</dd>
|
||||
<dt>By the method</dt><dd id="method">—</dd>
|
||||
<dt>Over the digest</dt><dd id="digest">—</dd>
|
||||
<dt>Covering</dt><dd id="coverage">—</dd>
|
||||
</dl>
|
||||
<p class="status"><span class="tag" id="verdict">CHECKING</span> <span id="verdict-detail"> </span></p>
|
||||
</div>
|
||||
|
||||
<p class="note">
|
||||
This page recomputes the SHA-256 of the catalog the site is serving, in your browser, and compares it to
|
||||
the digest printed above. Nothing is trusted in transit. If the two disagree, the oath has been broken and
|
||||
you are reading a promise that has gone stale: the data changed and the words did not.
|
||||
Verification is a plain <code>crypto.subtle.digest</code> over <code>catalog.json</code>, and you can repeat it
|
||||
yourself with <code>sha256sum catalog.json</code>.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// The crest. A Bible, patriarchal cross on the cover.
|
||||
const CREST = String.raw`
|
||||
______________________
|
||||
/ /|
|
||||
/ _|_ / |
|
||||
/ | / |
|
||||
/ __|__ / |
|
||||
/ | / |
|
||||
/ | / |
|
||||
/_____________________/ |
|
||||
| | |
|
||||
| | /
|
||||
| | /
|
||||
| | /
|
||||
|_____________________| /
|
||||
|/
|
||||
`;
|
||||
document.getElementById('crest').textContent = CREST.trim('\n');
|
||||
|
||||
const $ = (id) => document.getElementById(id);
|
||||
|
||||
async function verify() {
|
||||
try {
|
||||
const [oathRes, catalogRes] = await Promise.all([
|
||||
fetch('oath.json', { cache: 'no-store' }),
|
||||
fetch('catalog.json', { cache: 'no-store' }),
|
||||
]);
|
||||
if (!oathRes.ok || !catalogRes.ok) throw new Error('artifacts missing');
|
||||
|
||||
const oath = await oathRes.json();
|
||||
const bytes = await catalogRes.arrayBuffer();
|
||||
const hash = await crypto.subtle.digest('SHA-256', bytes);
|
||||
const hex = Array.from(new Uint8Array(hash))
|
||||
.map((b) => b.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
|
||||
$('sworn-on').textContent = oath.rebuilt_at.replace('T', ' ').replace(/\..*$/, ' UTC');
|
||||
$('method').textContent = oath.method_version;
|
||||
$('digest').textContent = hex;
|
||||
$('coverage').textContent =
|
||||
`${oath.assessed} of ${oath.products} products assessed, ${oath.byte_length.toLocaleString()} bytes`;
|
||||
|
||||
const verdict = $('verdict');
|
||||
const detail = $('verdict-detail');
|
||||
if (hex === oath.digest) {
|
||||
verdict.textContent = 'VERIFIED';
|
||||
verdict.className = 'tag ok';
|
||||
detail.textContent = `The catalog you are reading hashes to the digest this oath was sworn over.`;
|
||||
} else {
|
||||
verdict.textContent = 'BROKEN';
|
||||
verdict.className = 'tag bad';
|
||||
detail.textContent =
|
||||
`MISMATCH. Sworn over ${oath.digest.slice(0, 16)}… but serving ${hex.slice(0, 16)}…. This oath is stale.`;
|
||||
}
|
||||
} catch (err) {
|
||||
$('verdict').textContent = 'UNVERIFIED';
|
||||
$('verdict').className = 'tag unknown';
|
||||
$('verdict-detail').textContent = String(err && err.message ? err.message : err) +
|
||||
'. The artifacts this oath is bound to could not be read.';
|
||||
}
|
||||
}
|
||||
|
||||
verify();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
packages/web/public/oath.json
Normal file
10
packages/web/public/oath.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"digest": "5aadd709b394ab19e13a65aa21fb5182cb736f53e2b60c175abebb8dd11f1d8a",
|
||||
"algorithm": "sha256",
|
||||
"file": "catalog.json",
|
||||
"byte_length": 79846,
|
||||
"rebuilt_at": "2026-10-29T18:23:54.074Z",
|
||||
"method_version": "btr-0.1",
|
||||
"products": 122,
|
||||
"assessed": 1
|
||||
}
|
||||
Loading…
Reference in a new issue