Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ <h2>The register, stated plainly.</h2>
<div class="reg-row">
<div>
<div class="reg-what">Signed records an outside party can rebuild</div>
<div class="reg-detail">Of <span data-measured="signed-record-coverage:signedRecordTotal">11,432</span> signed records across <span data-measured="signed-record-coverage:storeFileCount">13</span> record stores we hold, written between <span data-measured="signed-record-coverage:signedRange.earliestDate">2026-08-05</span> and <span data-measured="signed-record-coverage:signedRange.latestDate">2026-08-15</span>, <strong><span data-measured="signed-record-coverage:headline.neverRebuildable.count">3,089</span> of them (<span data-measured="signed-record-coverage:headline.neverRebuildable.percentOfSigned">27.0%</span>) cannot be checked by anyone.</strong> They are <code><span data-measured="signed-record-coverage:headline.neverRebuildable.kinds">instructed, report</span></code> records, and <strong>no version of the engine ever published rebuilds their signed bytes</strong>, so there is nothing to verify a signature against. This is not a version pin and it is not a gap that a reader can work around by installing something else: those records carry a signature that no counterparty, and no one here, can check. <strong>Principle 04 below says the evidence is portable or it isn't evidence.</strong> For better than a quarter of what we sign, it isn't. A further <span data-measured="signed-record-coverage:headline.notRebuildableAtNpmLatest.count">122</span> <code><span data-measured="signed-record-coverage:headline.notRebuildableAtNpmLatest.kinds">resolution</span></code> records are rebuildable at some published version but not at the one <code>npm install</code> serves; section 04 of <a href="/verify" style="color:var(--amber); text-decoration:none;">/verify</a> carries that. Every figure in this row is read from <code>results/</code> and fails the build if the copy and the measurement disagree.</div>
<div class="reg-detail">Of <span data-measured="signed-record-coverage:signedRecordTotal">11,432</span> signed records across <span data-measured="signed-record-coverage:storeFileCount">13</span> record stores we hold, written between <span data-measured="signed-record-coverage:signedRange.earliestDate">2026-08-05</span> and <span data-measured="signed-record-coverage:signedRange.latestDate">2026-08-15</span>, <strong><span data-measured="signed-record-coverage:headline.neverRebuildable.count">3,089</span> of them (<span data-measured="signed-record-coverage:headline.neverRebuildable.percentOfSigned">27.0%</span>) cannot be checked by anyone.</strong> They are <code><span data-measured="signed-record-coverage:headline.neverRebuildable.kinds">instructed, report</span></code> records, and <strong>no version of the engine ever published rebuilds their signed bytes</strong>, so there is nothing to verify a signature against. This is not a version pin and it is not a gap that a reader can work around by installing something else: those records carry a signature that no counterparty, and no one here, can check. <strong>Principle 04 below says the evidence is portable or it isn't evidence.</strong> For better than a quarter of what we sign, it isn't. A second class, resolution records, was rebuildable at some published version but not at the one <code>npm install</code> served, for four releases. That count is now <span data-measured="signed-record-coverage:headline.notRebuildableAtNpmLatest.count">0</span>; section 02 of /verify carries what happened. Every figure in this row is read from <code>results/</code> and fails the build if the copy and the measurement disagree.</div>
</div>
<div class="reg-state no"><span data-measured="signed-record-coverage:headline.neverRebuildable.percentOfSigned">27.0%</span> unverifiable</div>
</div>
Expand Down
7 changes: 4 additions & 3 deletions results/engine-payload-exports.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"versionCount"
],
"computedAgainstRegistryState": {
"npmLatest": "1.0.0-rc.12",
"npmLatest": "1.0.0-rc.21",
"npmRc": "1.0.0-rc.21",
"versionCount": 26
},
Expand All @@ -33,7 +33,7 @@
}
},
"package": "@observer-protocol/policy-engine",
"npmLatest": "1.0.0-rc.12",
"npmLatest": "1.0.0-rc.21",
"npmRc": "1.0.0-rc.21",
"versionCount": 26,
"withdrawals": {
Expand All @@ -56,7 +56,8 @@
"npmLatestExports": [
"evaluationVerdictPayload",
"lapsePayload",
"refusalPayload"
"refusalPayload",
"resolutionPayload"
],
"versions": [
{
Expand Down
20 changes: 9 additions & 11 deletions results/signed-record-coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"no reader sees. A figure that announces it is unchecked where nobody reads the",
"announcement is a marker doing no work, so the announcement is now in the artifact."
],
"measuredOn": "2026-08-16",
"measuredOn": "2026-08-23",
"provenance": {
"corpusHalf": {
"fields": [
Expand All @@ -25,7 +25,7 @@
"signedRange",
"headline.neverRebuildable"
],
"measuredOn": "2026-08-16",
"measuredOn": "2026-08-23",
"over": "13 store file(s) under ~/op-artifacts",
"reDerivable": false,
"why": "The stores are working artifacts outside this repository. CI cannot reach them, so these figures are a DATED MEASUREMENT and nothing re-confirms them. The per-file sha256 list below exists so a later run can prove it measured the same population."
Expand All @@ -36,12 +36,12 @@
"headline.notRebuildableAtNpmLatest"
],
"computedAgainstRegistryState": {
"npmLatest": "1.0.0-rc.12",
"npmLatest": "1.0.0-rc.21",
"source": "results/engine-payload-exports.json",
"thatFileMeasuredOn": "2026-08-23"
},
"reDerivable": true,
"why": "These depend on which symbols npm's `latest` exports, which is public and is re-read by check-measured-figures.mjs on every run. THEY GO FALSE WHEN THE `latest` DIST-TAG MOVES, without this file being touched. If npm latest is not 1.0.0-rc.12, treat every field listed here as stale."
"why": "These depend on which symbols npm's `latest` exports, which is public and is re-read by check-measured-figures.mjs on every run. THEY GO FALSE WHEN THE `latest` DIST-TAG MOVES, without this file being touched. If npm latest is not 1.0.0-rc.21, treat every field listed here as stale."
}
},
"storesRoot": "~/op-artifacts",
Expand Down Expand Up @@ -102,7 +102,7 @@
"total": 122,
"rebuildRoute": "resolutionPayload",
"rebuildableEver": true,
"rebuildableAtNpmLatest": false
"rebuildableAtNpmLatest": true
}
],
"headline": {
Expand All @@ -116,12 +116,10 @@
"note": "No published version of the package rebuilds these. Not fragile, not version-pinned: unverifiable by anyone, permanently, on the evidence of every version published to date."
},
"notRebuildableAtNpmLatest": {
"kinds": [
"resolution"
],
"count": 122,
"percentOfSigned": "1.1%",
"note": "Rebuildable at some published version but NOT at 1.0.0-rc.12, which is what npm install serves a reader today."
"kinds": [],
"count": 0,
"percentOfSigned": "0.0%",
"note": "Rebuildable at some published version but NOT at 1.0.0-rc.21, which is what npm install serves a reader today."
}
},
"files": [
Expand Down
16 changes: 7 additions & 9 deletions scripts/measured-figures.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
" - a key marked on a page and absent from here -> FAIL, an undeclared figure",
"",
"Adding a row is a deliberate act. Deleting one is too, and it should be argued for",
"in the commit that does it rather than happening as a side effect of an edit."
"in the commit that does it rather than happening as a side effect of an edit.",
"REMOVED 2026-08-23: signed-record-coverage:headline.notRebuildableAtNpmLatest.kinds.",
"The kinds list is now empty because the count is 0, so there is no class name to show and",
"the declaration's own reason -- 'a count with no class name cannot be acted on' -- no longer",
"has a subject. Removed in the same commit as the copy change, per the check's instruction that",
"dropping a measured claim must not be something an edit does quietly."
],
"required": [
{
Expand Down Expand Up @@ -49,7 +54,7 @@
"index.html",
"verify.html"
],
"why": "The resolution records a reader on npm latest cannot check. On both pages because the register states the scale and /verify states the cause."
"why": "The resolution records a reader on npm latest cannot check. Now 0, and KEPT AT ZERO rather than dropped: a page that stops citing the figure when it clears cannot show a reader that it ever was not zero. On both pages because the register states the scale and /verify states the cause."
},
{
"key": "signed-record-coverage:storeFileCount",
Expand Down Expand Up @@ -100,13 +105,6 @@
],
"why": "The claim is that latest sits INSIDE the withdrawn band. Naming the band without naming latest leaves the reader to make the comparison the page is asserting."
},
{
"key": "signed-record-coverage:headline.notRebuildableAtNpmLatest.kinds",
"mustAppearIn": [
"index.html"
],
"why": "Which class is affected by the withdrawn constructor. A count with no class name cannot be acted on."
},
{
"key": "engine-payload-exports:withdrawals.resolutionPayload.0.lastPresentBefore",
"mustAppearIn": [
Expand Down
119 changes: 119 additions & 0 deletions scripts/sentences-at-risk.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
#!/usr/bin/env node
/**
* WHICH SENTENCES DOES A PENDING VALUE CHANGE PUT AT RISK?
*
* A `data-measured` span updates itself. The sentence around it does not. When a measured
* value changes, every span holding it moves and every sentence built on it stays — and a
* self-updating value inside a static sentence reads as FRESHLY CHECKED while being false.
* That is worse than stale, which is visible, and worse than false, which the checks catch.
*
* Measured 2026-08-23: the section 02 swap was planned as six parts and named two dependents.
* It missed two sentences. One of them — "We hold [122] signed resolution records that a
* reader on [1.0.0-rc.21] has no route to" — was INSIDE the section being swapped, and both
* of its spans updated correctly while the sentence inverted in meaning.
*
* THE POPULATION THAT WOULD HAVE CAUGHT THEM is not a list anyone maintains. It is derivable:
* every sentence containing a span whose value the change moves. This derives it.
*
* node scripts/sentences-at-risk.mjs <baseline-ref>
*
* BEFORE a change: stage or write the new results/, then run with HEAD as the baseline. The
* new values are on disk, the old ones are in the ref, and every affected sentence prints
* before anything is committed.
*
* AFTER a change: pass the ref from before it, to audit what was missed.
*
* IT DOES NOT DECIDE. Whether a sentence survives its span changing is a judgement about
* meaning, and no check makes it. This prints the sentence and the old and new values beside
* each other so the judgement is made deliberately rather than skipped. A sentence that is
* invariant is common and fine; the point is that it was looked at.
*
* Exit 0 always when it runs: it is an enumerator, not a gate. A gate that guessed at meaning
* would be the fourth way to be wrong about this.
*/
import { readFileSync, readdirSync, existsSync } from 'node:fs';
import { execFileSync } from 'node:child_process';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const baseRef = process.argv[2];
if (!baseRef) {
console.error('usage: node scripts/sentences-at-risk.mjs <baseline-ref>');
console.error(' e.g. HEAD, or the commit before the change you are auditing.');
process.exit(2);
}

const at = (ref, path) => {
try { return execFileSync('git', ['show', `${ref}:${path}`], { cwd: root, encoding: 'utf8' }); }
catch { return null; }
};
const dig = (obj, path) => path.split('.').reduce((o, k) => {
if (o == null) return undefined;
const m = /^(.*)\[(\d+)\]$/.exec(k);
return m ? o[m[1]]?.[Number(m[2])] : o[k];
}, obj);

// ─── which measured values moved ────────────────────────────────────────────────────────────
const resultsDir = join(root, 'results');
const moved = new Map(); // "file:path" -> {before, after}
for (const f of readdirSync(resultsDir).filter((n) => n.endsWith('.json'))) {
const name = f.replace(/\.json$/, '');
const now = JSON.parse(readFileSync(join(resultsDir, f), 'utf8'));
const oldRaw = at(baseRef, `results/${f}`);
if (oldRaw === null) { console.log(` results/${f} did not exist at ${baseRef}; every span on it is new.`); continue; }
const old = JSON.parse(oldRaw);
const walk = (a, b, path) => {
const keys = new Set([...Object.keys(a ?? {}), ...Object.keys(b ?? {})]);
for (const k of keys) {
const pa = path ? `${path}.${k}` : k;
const va = a?.[k], vb = b?.[k];
if (va && typeof va === 'object' && !Array.isArray(va)) { walk(va, vb, pa); continue; }
if (JSON.stringify(va) !== JSON.stringify(vb)) moved.set(`${name}:${pa}`, { before: va, after: vb });
}
};
walk(old, now, '');
}

// ─── every span on every served page, and the sentence it sits in ───────────────────────────
const pages = [];
(function walkDir(d) {
for (const e of readdirSync(d)) {
if (e === 'dist' || e === 'node_modules' || e.startsWith('.')) continue;
const p = join(d, e);
try { if (readdirSync(p).length >= 0) { walkDir(p); continue; } } catch { /* a file */ }
if (e.endsWith('.html')) pages.push(p);
}
})(root);

const SPAN = /<span[^>]*data-measured="([^"]+)"[^>]*>([^<]*)<\/span>/g;
const prose = (h) => h.replace(/<[^>]+>/g, '').replace(/&[a-z]+;|&#\d+;/g, ' ').replace(/\s+/g, ' ');

let found = 0;
console.log(`\nBaseline ${baseRef}. ${moved.size} measured value(s) moved.\n`);
for (const [key, d] of moved) {
const hits = [];
for (const page of pages) {
const html = readFileSync(page, 'utf8');
SPAN.lastIndex = 0;
let m;
while ((m = SPAN.exec(html)) !== null) {
if (m[1] !== key) continue;
// the sentence: back to the previous ". " or block tag, forward to the next
const a = Math.max(0, html.lastIndexOf('. ', m.index) + 2, html.lastIndexOf('>', html.lastIndexOf('<p', m.index)) + 1);
const dot = html.indexOf('. ', m.index + m[0].length);
const b = dot === -1 ? Math.min(html.length, m.index + 400) : dot + 1;
hits.push({ rel: page.slice(root.length + 1), text: prose(html.slice(a, b)).trim() });
}
}
if (!hits.length) continue;
found += hits.length;
console.log(` ${key}`);
console.log(` ${JSON.stringify(d.before)} -> ${JSON.stringify(d.after)}`);
for (const h of hits) console.log(` [${h.rel}] ${h.text.slice(0, 260)}`);
console.log('');
}

console.log(found
? `${found} sentence(s) contain a span whose value moved. EACH NEEDS A JUDGEMENT: is it still\ntrue with the new value? This does not decide that, and nothing else does either.`
: 'No sentence on any page contains a span whose value moved.');
Loading
Loading