igneum/site/verify/test.html
igneum-labs 8e158fa633 Text-overlap sweep (tools/ci/overlap-check.mjs) and the first fixes it found: the home hero's step pill no longer sits on the caption (pill at the corners' baseline, caption above it, at every width), the address page title wraps, the ledger's status badges wrap, the litepaper's mobile contents bar bleeds by the real gutter, the verify harness table scrolls (7 October 2026, 15:5x UK)
The sweep renders every served page in a headless Chromium at 390, 768, 1024, 1280 and 1600 in light and dark (the hero at rest and at each step), reads every visible run of text and flags text covered by another element, clipped by overflow hidden, or past the viewport; a fixture with one deliberate overlap of each kind is flagged before any sweep is trusted. It runs on a build box when this machine has no browser (infra/build-server/overlap-browser.sh installs Chromium there without root).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 13:55:13 +00:00

43 lines
3.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Igneum light client test</title>
<meta name="robots" content="noindex">
<style>body{font-family:ui-monospace,Menlo,monospace;font-size:13px;background:#0C0C0E;color:#E8E4DA;padding:24px;max-width:960px}h1{font-size:16px}table{display:block;overflow-x:auto;max-width:100%}code{overflow-wrap:anywhere}td{padding:4px 10px;border-bottom:1px solid #222;vertical-align:top}.ok{color:#7ED957}.bad{color:#F2541B}</style>
</head>
<body>
<h1>Igneum light client, version zero: genuine and tampered</h1>
<p>Each row verifies in this tab with <code>verify/core.js</code>. Source: <code id="src"></code></p>
<table id="t"><thead><tr><td>case</td><td>verified</td><td>ms</td><td>reason</td><td>signers</td><td>active</td><td>total</td><td>headers</td></tr></thead><tbody></tbody></table>
<pre id="done"></pre>
<script type="module">
import { fetchCheckpoint, verify, LIBRARIES } from './verify.js';
const q = new URLSearchParams(location.search);
const url = q.get('api') || '/api/checkpoint' + (q.get('source') ? `?source=${q.get('source')}` : '');
const data = await fetchCheckpoint(url);
document.getElementById('src').textContent = `${url} (index ${data.index}, ${data.source}, ${data.chain_id}) libraries ${JSON.stringify(LIBRARIES)}`;
const clone = () => JSON.parse(JSON.stringify(data));
const cases = [];
cases.push(['genuine', clone()]);
cases.push(['genuine again (warm)', clone()]);
{ const d = clone(); const s = d.certificate.aggregate_signature_hex; const b = (parseInt(s.slice(20, 22), 16) ^ 1).toString(16).padStart(2, '0'); d.certificate.aggregate_signature_hex = s.slice(0, 20) + b + s.slice(22); cases.push(['signature, one bit flipped', d]); }
{ const d = clone(); const bm = parseInt(d.certificate.bitmap_hex.slice(0, 2), 16); let p = 0; while (!(bm & (1 << p))) p++; d.certificate.bitmap_hex = (bm & ~(1 << p)).toString(16).padStart(2, '0') + d.certificate.bitmap_hex.slice(2); cases.push([`one voter dropped from the bitmap (position ${p})`, d]); }
{ const d = clone(); const i = d.voters.findIndex((v, k) => d.certificate.bitmap_hex && (parseInt(d.certificate.bitmap_hex.slice(0, 2), 16) & (1 << k))); const pk = d.voters[i].pubkey_hex; d.voters[i].pubkey_hex = pk.slice(0, 10) + ((parseInt(pk.slice(10, 12), 16) ^ 4).toString(16).padStart(2, '0')) + pk.slice(12); cases.push(['a signer\'s public key altered', d]); }
{ const d = clone(); d.hash = d.hash.slice(0, 62) + ((parseInt(d.hash.slice(62), 16) ^ 1).toString(16).padStart(2, '0')); d.headers[d.headers.length - 1].hash = d.hash; cases.push(['checkpoint hash altered', d]); }
{ const d = clone(); const h = d.headers[Math.floor(d.headers.length / 2)]; h.nonce = String(BigInt(h.nonce) ^ 1n); cases.push(['a header nonce altered', d]); }
{ const d = clone(); d.headers.splice(Math.floor(d.headers.length / 2), 1); cases.push(['a header removed from the chain', d]); }
{ const d = clone(); const bm = parseInt(d.certificate.bitmap_hex.slice(0, 2), 16); const quiet = d.voters.filter((v, k) => !(bm & (1 << k))); if (quiet.length) { for (const v of quiet) v.weight = Math.round(v.weight * 10); cases.push(['a non-signer\'s weight raised 10x (floor)', d]); } else { for (const v of d.voters) v.weight = 0; cases.push(['every weight zeroed', d]); } }
const tb = document.querySelector('#t tbody');
const out = [];
for (const [name, d] of cases) {
const r = verify(d);
out.push({ name, ...r });
const tr = document.createElement('tr');
tr.innerHTML = `<td>${name}</td><td class="${r.verified ? 'ok' : 'bad'}">${r.verified}</td><td>${r.ms}</td><td>${r.reason || ''}</td><td>${r.signers ?? ''}</td><td>${r.weight_fraction_active ?? ''}</td><td>${r.weight_fraction_total ?? ''}</td><td>${r.headers_checked ?? ''}</td>`;
tb.appendChild(tr);
}
document.getElementById('done').textContent = 'RESULTS ' + JSON.stringify(out);
</script>
</body>
</html>