From d6d428825f313d01f63cf42ec0087bef0010b68c Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 15:47:12 -0600
Subject: [PATCH 1/7] A latest-exports predicate, so the section 02 swap is
mechanical
/verify section 02 says one thing while npm's latest sits inside the
resolutionPayload withdrawal band and the opposite once it moves past. The
marker on those sentences is latest-does-not-export. There was no predicate for
the other side, so the swap on the day would have been a hand edit to a claim
nobody could check until after it was made.
Safe to add before the move: it is unused until a marker names it, and adding
it changes no verdict.
NOT DEFINED AS THE NEGATION OF THE OTHER, deliberately. latest-does-not-export
returns false when the version is unmeasured, because an unevaluable claim is
not true. So does this one. A predicate written as `!other` would turn that
refusal into a pass in one direction, which is the failure mode the pair exists
to prevent.
---
scripts/check-measured-figures.mjs | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/scripts/check-measured-figures.mjs b/scripts/check-measured-figures.mjs
index e024284..76d29ae 100644
--- a/scripts/check-measured-figures.mjs
+++ b/scripts/check-measured-figures.mjs
@@ -511,6 +511,32 @@ if (engine) {
// which one they are looking at.
const PREDICATES = {
// True when the version npm's `latest` tag serves does NOT export the named symbol.
+ // The affirmative of the same question. Both exist because BOTH SIDES OF A TAG MOVE NEED A
+ // PREDICATE: the page says one thing before `latest` moves past a withdrawal band and the
+ // opposite after, and a swap between two markers is mechanical where a hand edit on the day
+ // is not. Adding this one is safe BEFORE the move — it is unused until a marker names it.
+ //
+ // NOT THE NEGATION OF THE OTHER, deliberately. `latest-does-not-export` returns false when
+ // the version is unmeasured, and so does this: an unevaluable claim is not true, whichever
+ // direction it points, and a predicate defined as `!other` would turn one of those refusals
+ // into a pass.
+ 'latest-exports': (res, arg, fresh) => {
+ const version = fresh ?? res.npmLatest;
+ const entry = (res.versions ?? []).find((v) => v.version === version);
+ if (!entry) {
+ return {
+ ok: false,
+ why: `npm serves ${version} at latest, and results/ carries no measurement for it. ` +
+ `The claim cannot be evaluated, which is not the same as it being true. ` +
+ `Re-run: node scripts/measure-engine-payload-exports.mjs`,
+ };
+ }
+ const exports_ = entry.exports ?? [];
+ return exports_.includes(arg)
+ ? { ok: true, why: `latest (${version}) exports \`${arg}\`.` }
+ : { ok: false, why: `the version at latest (${version}) does NOT export \`${arg}\`, so this sentence is false.` };
+ },
+
'latest-does-not-export': (res, arg, fresh) => {
const version = fresh ?? res.npmLatest;
const entry = (res.versions ?? []).find((v) => v.version === version);
From 49c48507f2bfe06b85e3ce6ef5c130251120ce98 Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 16:38:33 -0600
Subject: [PATCH 2/7] Re-measure after the dist-tag move: latest is now
1.0.0-rc.21
Committed alone, ahead of the copy change, because npmLatest is compared by
several markers and mixing the two makes the copy commit unreviewable.
npmLatest 1.0.0-rc.12 -> 1.0.0-rc.21
npmLatestExports now includes resolutionPayload
The withdrawal band is unchanged: resolutionPayload is still absent from rc.9
through rc.12. What changed is that latest no longer points inside it.
---
results/engine-payload-exports.json | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/results/engine-payload-exports.json b/results/engine-payload-exports.json
index 1ee14f3..18a8cf3 100644
--- a/results/engine-payload-exports.json
+++ b/results/engine-payload-exports.json
@@ -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
},
@@ -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": {
@@ -56,7 +56,8 @@
"npmLatestExports": [
"evaluationVerdictPayload",
"lapsePayload",
- "refusalPayload"
+ "refusalPayload",
+ "resolutionPayload"
],
"versions": [
{
From 81a4ff2663e91d302da64cc7244bf2309d94b1eb Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 16:39:55 -0600
Subject: [PATCH 3/7] Section 02 swapped: a resolution record now checks with
the version the page tells you to install
All six parts in one commit, because no ordering buys anything and part 4
without parts 1-3 is green and wrong. No check reads the words, so writing them
together is the only thing that makes the set atomic.
1 heading "You cannot check a resolution record with the
version this page tells you to install" becomes
"A resolution record now checks ... It did not, for
four releases."
2 consequence sentence 1 "The version npm install serves does not export it"
becomes "serves exports it. For four releases it
did not."
3 consequence sentence 2 the reader-cannot-check sentence becomes the record
of who could and who could not: the 122 signed
resolution records were always rebuildable by
someone who installed @rc, and were not by someone
who followed the instruction on this page.
4 marker, both sites latest-does-not-export -> latest-exports
5 "What to do about it" it told a reader to accept the gap and said the gap
closes when latest moves past the band. It has. The
paragraph now says nothing needs doing and why the
page stays pinned to latest regardless.
6 npmLatest spans 1.0.0-rc.12 -> 1.0.0-rc.21, both
THE FOUR-RELEASE GAP IS RECORDED, NOT DELETED. The section stays because a
section that vanishes when a defect clears leaves a reader unable to tell a page
that was always right from a page that was corrected. resolutionPayload was
still absent from rc.9 through rc.12 and the withdrawal still appears in no
changelog; what changed is which release a reader receives.
Verified in the INDEX before committing, per CONVENTIONS 18: all six present in
the staged bytes, the old heading gone, and zero occurrences of the old marker
or the old span value.
---
verify.html | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/verify.html b/verify.html
index c9718ee..6454ac7 100644
--- a/verify.html
+++ b/verify.html
@@ -352,12 +352,12 @@ There is no verifier for a PolicyEvaluationCredent
-
You cannot check a resolution record with the version this page tells you to install.
-
An approval resolution is the record of a human decision, signed at both ends: the routing and the outcome. Rebuilding its signed bytes needs resolutionPayload. The version npm install serves does not export it.
-
Read out of every published tarball rather than out of a release note: resolutionPayload was exported at 1.0.0-rc.8, withdrawn across 4 consecutive releases, 1.0.0-rc.9 through 1.0.0-rc.12, and restored at 1.0.0-rc.13. npm's latest tag sits inside that band, at 1.0.0-rc.12, which is the version this page documents and the version the install line above gives you.
-
So a reader following this page's own instructions cannot check a resolution, and until now the page did not say so. That is worse than a missing feature: the instruction was complete, it ran, and it produced a package silently missing one constructor. We hold 122 signed resolution records that a reader on 1.0.0-rc.12 has no route to.
+
A resolution record now checks with the version this page tells you to install. It did not, for four releases.
+
An approval resolution is the record of a human decision, signed at both ends: the routing and the outcome. Rebuilding its signed bytes needs resolutionPayload. The version npm install serves exports it. For four releases it did not.
+
Read out of every published tarball rather than out of a release note: resolutionPayload was exported at 1.0.0-rc.8, withdrawn across 4 consecutive releases, 1.0.0-rc.9 through 1.0.0-rc.12, and restored at 1.0.0-rc.13. npm's latest tag sits inside that band, at 1.0.0-rc.21, which is the version this page documents and the version the install line above gives you.
+
The 122 signed resolution records this estate holds were always rebuildable by someone who installed @rc; what they were not was rebuildable by someone who followed the instruction on this page. That is worse than a missing feature: the instruction was complete, it ran, and it produced a package silently missing one constructor. We hold 122 signed resolution records that a reader on 1.0.0-rc.21 has no route to.
The withdrawal appears in no changelog. The package's own release notes record neither the removal nor the restoration, so nothing but the tarballs could have told you. That is why the figures in this paragraph are read from a measurement in results/ and compared against npm on every build, instead of being typed here where nothing could contradict them.
-
What to do about it. Installing @rc gets you a version that exports it, and that is a different package from the one this site documents, so we are not going to tell you it is the same thing. This site stays pinned to what latest serves, because a page that documents a version nobody receives is the failure this whole surface is written against. The gap closes when latest moves past the band.
+
What to do about it. Nothing. The version this page documents and the version its install line gives you both rebuild a resolution record, because npm’s latest tag now points past the withdrawal band rather than inside it. This site stays pinned to what latest serves, for the same reason it did while that was the broken version: a page that documents a version nobody receives is the failure this whole surface is written against.
From b2c5c910fad6dbfcc91db3c36aa10798be4f75de Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 16:44:37 -0600
Subject: [PATCH 4/7] The six parts were not all of them: two sentences and a
figure declaration
The held plan enumerated six parts and named the two dependents that must change
with them. IT MISSED TWO SENTENCES, ON TWO PAGES, AND ONE OF THEM WAS INSIDE THE
SECTION BEING SWAPPED.
verify.html "We hold 122 signed resolution records that a reader on
1.0.0-rc.21 has no route to."
That is the failure the whole mechanism was built to prevent, in the one place
it was supposed to be watched: the data-measured SPAN inside the sentence
updated itself to rc.21 while the SENTENCE inverted in meaning and did not.
Part 6 moved the span. Nothing moved the words around it.
index.html "A further 122 resolution records are rebuildable at some
published version but not at the one npm install serves."
Both rewritten. The count is now 0 and both pages say so in the past tense, with
the four-release history kept rather than deleted.
RE-MEASURED signed-record-coverage against the stores, which are reachable:
resolution.rebuildableAtNpmLatest false -> true
notRebuildableAtNpmLatest kinds [resolution] count 122
-> kinds [] count 0
neverRebuildable UNCHANGED, instructed and report, 3089,
27.0%. No route at ANY version, so the tag
move cannot touch it.
AND THE CHECK CAUGHT ME DROPPING A DECLARED FIGURE. Rewriting index.html removed
the kinds span, and check-measured-figures refused: "Dropping a measured claim
must not be something an edit does quietly." Correct. 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 this commit with that reason recorded, which is what the
check's message asks for.
The count declaration is KEPT AT ZERO rather than dropped: a page that stops
citing a figure when it clears cannot show a reader that it ever was not zero.
---
index.html | 2 +-
results/signed-record-coverage.json | 20 +++++++++-----------
scripts/measured-figures.json | 16 +++++++---------
verify.html | 2 +-
4 files changed, 18 insertions(+), 22 deletions(-)
diff --git a/index.html b/index.html
index a5f0c5b..eafc6cf 100644
--- a/index.html
+++ b/index.html
@@ -1147,7 +1147,7 @@ The register, stated plainly.
Signed records an outside party can rebuild
-
Of
11,432 signed records across
13 record stores we hold, written between
2026-08-05 and
2026-08-15,
3,089 of them (27.0%) cannot be checked by anyone. They are
instructed, report records, and
no version of the engine ever published rebuilds their signed bytes, 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.
Principle 04 below says the evidence is portable or it isn't evidence. For better than a quarter of what we sign, it isn't. A further
122 resolution records are rebuildable at some published version but not at the one
npm install serves; section 04 of
/verify carries that. Every figure in this row is read from
results/ and fails the build if the copy and the measurement disagree.
+
Of 11,432 signed records across 13 record stores we hold, written between 2026-08-05 and 2026-08-15, 3,089 of them (27.0%) cannot be checked by anyone. They are instructed, report records, and no version of the engine ever published rebuilds their signed bytes, 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. Principle 04 below says the evidence is portable or it isn't evidence. 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 npm install served, for four releases. That count is now 0; section 02 of /verify carries what happened. Every figure in this row is read from results/ and fails the build if the copy and the measurement disagree.
27.0% unverifiable
diff --git a/results/signed-record-coverage.json b/results/signed-record-coverage.json
index 98d6857..d05ad1f 100644
--- a/results/signed-record-coverage.json
+++ b/results/signed-record-coverage.json
@@ -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": [
@@ -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."
@@ -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",
@@ -102,7 +102,7 @@
"total": 122,
"rebuildRoute": "resolutionPayload",
"rebuildableEver": true,
- "rebuildableAtNpmLatest": false
+ "rebuildableAtNpmLatest": true
}
],
"headline": {
@@ -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": [
diff --git a/scripts/measured-figures.json b/scripts/measured-figures.json
index e0c3cac..fa92744 100644
--- a/scripts/measured-figures.json
+++ b/scripts/measured-figures.json
@@ -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": [
{
@@ -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",
@@ -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": [
diff --git a/verify.html b/verify.html
index 6454ac7..3365ac6 100644
--- a/verify.html
+++ b/verify.html
@@ -355,7 +355,7 @@ There is no verifier for a PolicyEvaluationCredent
A resolution record now checks with the version this page tells you to install. It did not, for four releases.
An approval resolution is the record of a human decision, signed at both ends: the routing and the outcome. Rebuilding its signed bytes needs resolutionPayload. The version npm install serves exports it. For four releases it did not.
Read out of every published tarball rather than out of a release note: resolutionPayload was exported at 1.0.0-rc.8, withdrawn across 4 consecutive releases, 1.0.0-rc.9 through 1.0.0-rc.12, and restored at 1.0.0-rc.13. npm's latest tag sits inside that band, at 1.0.0-rc.21, which is the version this page documents and the version the install line above gives you.
- The 122 signed resolution records this estate holds were always rebuildable by someone who installed @rc; what they were not was rebuildable by someone who followed the instruction on this page. That is worse than a missing feature: the instruction was complete, it ran, and it produced a package silently missing one constructor. We hold 122 signed resolution records that a reader on 1.0.0-rc.21 has no route to.
+ The 122 signed resolution records this estate holds were always rebuildable by someone who installed @rc; what they were not was rebuildable by someone who followed the instruction on this page. That was worse than a missing feature: the instruction was complete, it ran, and it produced a package silently missing one constructor. That number is now 0: every signed resolution record this estate holds is rebuildable at the version npm install serves. It was 122 for as long as latest pointed inside the band.
The withdrawal appears in no changelog. The package's own release notes record neither the removal nor the restoration, so nothing but the tarballs could have told you. That is why the figures in this paragraph are read from a measurement in results/ and compared against npm on every build, instead of being typed here where nothing could contradict them.
What to do about it. Nothing. The version this page documents and the version its install line gives you both rebuild a resolution record, because npm’s latest tag now points past the withdrawal band rather than inside it. This site stays pinned to what latest serves, for the same reason it did while that was the broken version: a page that documents a version nobody receives is the failure this whole surface is written against.
From b3752fec019ad1579287b9f25cbf26aea7037304 Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 16:51:00 -0600
Subject: [PATCH 5/7] A third missed sentence, and a malformed tag that has
been live since #85
BOTH FOUND BY THE NEW INSTRUMENT, not by reading.
sentences-at-risk.mjs, run against the pre-move baseline, enumerated three
sentences containing a span whose value the tag move changed. Two were the ones
already fixed. The third was live on this branch and false:
"npm's latest tag SITS INSIDE THAT BAND, at 1.0.0-rc.21"
rc.21 is not inside rc.9 through rc.12. The span updated itself to rc.21 and the
sentence around it did not, which is the exact class this branch already
corrected twice and missed a third time. Now reads "no longer points inside that
band".
THE INSTRUMENT FOUND IN ONE RUN WHAT THREE READINGS OF THE SAME SECTION DID NOT.
AND A MALFORMED TAG, ON MASTER, SINCE #85:
The closing angle bracket is missing. #85 added the marker by replacing '
'
with '
'. The sibling
marker was
added by a different substitution and is well formed, which is why one is broken
and one is not.
It has been on the served page since #85 merged. NOTHING CAUGHT IT: no check in
this repository parses HTML, so a broken tag is invisible to all fourteen. A
browser recovers from it, which is why it is invisible to a reader too.
Found only because the sentence beside it was being read for a different reason.
That is not a method.
---
scripts/sentences-at-risk.mjs | 119 ++++++++++++++++++++++++++++++++++
verify.html | 4 +-
2 files changed, 121 insertions(+), 2 deletions(-)
create mode 100644 scripts/sentences-at-risk.mjs
diff --git a/scripts/sentences-at-risk.mjs b/scripts/sentences-at-risk.mjs
new file mode 100644
index 0000000..11b3ca1
--- /dev/null
+++ b/scripts/sentences-at-risk.mjs
@@ -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
+ *
+ * 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 ');
+ 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 = /]*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(' ${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.');
diff --git a/verify.html b/verify.html
index 3365ac6..fa779a5 100644
--- a/verify.html
+++ b/verify.html
@@ -354,8 +354,8 @@
There is no verifier for a PolicyEvaluationCredent
A resolution record now checks with the version this page tells you to install. It did not, for four releases.
An approval resolution is the record of a human decision, signed at both ends: the routing and the outcome. Rebuilding its signed bytes needs resolutionPayload. The version npm install serves exports it. For four releases it did not.
-
Read out of every published tarball rather than out of a release note: resolutionPayload was exported at 1.0.0-rc.8, withdrawn across 4 consecutive releases, 1.0.0-rc.9 through 1.0.0-rc.12, and restored at 1.0.0-rc.13. npm's latest tag sits inside that band, at 1.0.0-rc.21, which is the version this page documents and the version the install line above gives you.
-
The 122 signed resolution records this estate holds were always rebuildable by someone who installed @rc; what they were not was rebuildable by someone who followed the instruction on this page. That was worse than a missing feature: the instruction was complete, it ran, and it produced a package silently missing one constructor. That number is now 0: every signed resolution record this estate holds is rebuildable at the version npm install serves. It was 122 for as long as latest pointed inside the band.
+
Read out of every published tarball rather than out of a release note: resolutionPayload was exported at 1.0.0-rc.8, withdrawn across 4 consecutive releases, 1.0.0-rc.9 through 1.0.0-rc.12, and restored at 1.0.0-rc.13. npm's latest tag no longer points inside that band, at 1.0.0-rc.21, which is the version this page documents and the version the install line above gives you.
+
The 122 signed resolution records this estate holds were always rebuildable by someone who installed @rc; what they were not was rebuildable by someone who followed the instruction on this page. That was worse than a missing feature: the instruction was complete, it ran, and it produced a package silently missing one constructor. That number is now 0: every signed resolution record this estate holds is rebuildable at the version npm install serves. It was 122 for as long as latest pointed inside the band.
The withdrawal appears in no changelog. The package's own release notes record neither the removal nor the restoration, so nothing but the tarballs could have told you. That is why the figures in this paragraph are read from a measurement in results/ and compared against npm on every build, instead of being typed here where nothing could contradict them.
What to do about it. Nothing. The version this page documents and the version its install line gives you both rebuild a resolution record, because npm’s latest tag now points past the withdrawal band rather than inside it. This site stays pinned to what latest serves, for the same reason it did while that was the broken version: a page that documents a version nobody receives is the failure this whole surface is written against.
From c4dafb22c8af4e75bc2a25a6426b418aaf1dcecc Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 17:52:40 -0600
Subject: [PATCH 6/7] The pin moves to rc.21, and the four Group 2 expectations
move with it
ONE COMMIT, because they cannot land separately. Measured, not assumed: with the
expectations changed and the lockfile still at rc.12, verify-published-credentials
returns 1, because rc.12 cannot verify the four. An expectation is a statement
about the version being run, so it moves when the version does.
THE FOUR
verify-samples/ppp-verdict-released.json
verify-samples/ppp-verdict-denied-ceiling.json
verify-samples/ppp-probe-a-verdict-release-above-escalation.json
verify-samples/ppp-probe-b-verdict-release-above-ceiling.json
before allow: false
reasonContains "(op.evaluation.verdict.v3); the record states
construction op.evaluation.verdict.v4"
knownIssue VERDICT-PAYLOAD-VERSION-PIN
after allow: true
reasonContains "signature verifies over the payload this build
rebuilds (op.evaluation.verdict.v4)"
knownIssue removed
THE EXPECTATIONS CHANGED BECAUSE THE VERSION AT latest CHANGED, NOT BECAUSE THE
ENGINE CHANGED. The records are signed under op.evaluation.verdict.v4 and were
always sound. rc.12 rebuilds v3 and cannot verify them; rc.21 rebuilds v4 and
does. Nothing about the artifacts moved.
The reasonContains strings named both version tokens deliberately so the pin
could not move in either direction without turning this check red. It moved, the
check went red, and this is the answer to it: the control working, not a finding
about the engine.
POPULATION SEARCHED: every tracked file with dist/ excluded, grepped for the
package name adjacent to a semver, plus every data-engine-version="current" span
in tracked HTML.
scripts/package.json 1.0.0-rc.12 -> 1.0.0-rc.21
scripts/package-lock.json 1.0.0-rc.12 -> 1.0.0-rc.21
eight "current" spans verify.html 3, index.html 2, docs.html 2,
check.html 1
EIGHT, NOT THE FIVE THE EARLIER SURVEY FOUND. That survey excluded verify.html's
three as parked; they are on master now, so the population grew by exactly those
three. The difference is not a correction to the earlier count, it is the same
method over a population that changed underneath it.
A NINTH THING, WHICH NO INSTRUMENT FOUND. verify-samples/README.md said "What the
pin does, and what it does not: scripts/package.json still pins 1.0.0-rc.12 ...
and credential-expectations.json still records the four allow:false outcomes."
This commit falsifies that paragraph. Rewritten here.
sentences-at-risk.mjs was run before committing and found NOTHING, correctly and
uselessly: it keys off results/ values and this change moves a pin and eight
spans, touching no results/ file. The population it derives is not the population
this change touches, and the README paragraph is in Markdown, which it does not
read at all. Its scope is one class of change, and this is a different one.
---
check.html | 2 +-
docs.html | 4 ++--
index.html | 4 ++--
scripts/credential-expectations.json | 22 +++++++++-------------
scripts/package-lock.json | 8 ++++----
scripts/package.json | 2 +-
verify-samples/README.md | 11 ++++++-----
verify.html | 6 +++---
8 files changed, 28 insertions(+), 31 deletions(-)
diff --git a/check.html b/check.html
index 339da96..02af63e 100644
--- a/check.html
+++ b/check.html
@@ -214,7 +214,7 @@ What this runs
signed payload is rebuilt from the record's own fields, under the construction the record itself states rather than
whichever version this page ships, and the signature is checked against the key the record names.
Both are the constructions @observer-protocol/policy-engine exports at
- 1.0.0-rc.12, the version npm's latest tag serves a reader today.
+ 1.0.0-rc.21, the version npm's latest tag serves a reader today.
This page loads nothing, so it cannot import the package; it carries its own copy of those constructions and CI asserts,
on every build, that the bytes it produces are identical to the package's own over every refusal this repository publishes.
A divergence turns the build red rather than turning a verdict here wrong.
diff --git a/docs.html b/docs.html
index 1253795..59f8df9 100644
--- a/docs.html
+++ b/docs.html
@@ -218,7 +218,7 @@
Choose your path.
📦
- NPM INSTALL · v1.0.0-rc.12
+ NPM INSTALL · v1.0.0-rc.21
Verify a credential
npm install @observer-protocol/policy-engine
Verify a delegation credential yourself — issuer, structure, validity window, eddsa-jcs-2022 proof against the issuer DID document, and revocation. No API key, no token, no Observer Protocol API in the path. It fetches the issuer's DID document and the revocation status list over ordinary HTTPS from the origins the credential names; point offline.didDocumentPath at a local copy and it makes no network call at all. The hosted verifier is a separate deployment running a different engine version; see the SDK section.
@@ -447,7 +447,7 @@ @observer-protocol/policy-engine
unauthenticated POST /v1/verify returns 200 with a signed result.
It is a different build, though. The hosted service reports
- engine.running: "0.3.3"; the package above is 1.0.0-rc.12. Re-measured against
+ engine.running: "0.3.3"; the package above is 1.0.0-rc.21. Re-measured against
rc.10 on 9 August 2026: they agree on 7 of the 8 artifacts this site publishes. The eighth is
never evaluated by the hosted engine at all — it is refused at that deployment's issuer
allowlist, which does not carry the testbed issuer. So the agreement is on samples rather
diff --git a/index.html b/index.html
index a5f0c5b..d9f62af 100644
--- a/index.html
+++ b/index.html
@@ -983,7 +983,7 @@ It fails closed, and it will refuse us too. onUnreachable: 'cache-then-deny' is the only accepted value: if the revocation list cannot be fetched, a cached answer is used and then the credential is denied. A status list hosted on an origin other than the pinned issuer's is refused until you allowlist it, and Observer Protocol's own clause-zero revocation demonstration is exactly such a pair, so it does not verify out of the box. That limit is published in the package.
-
package · @observer-protocol/policy-engine 1.0.0-rc.12
+
package · @observer-protocol/policy-engine 1.0.0-rc.21
licence · MIT · self-hostable · no account
@@ -1105,7 +1105,7 @@ The register, stated plainly.
Offline verification with nothing from us
-
@observer-protocol/policy-engine 1.0.0-rc.12 on npm, MIT. Section 05 is a transcript of it running against a credential served from this domain.
+
@observer-protocol/policy-engine 1.0.0-rc.21 on npm, MIT. Section 05 is a transcript of it running against a credential served from this domain.
Live
diff --git a/scripts/credential-expectations.json b/scripts/credential-expectations.json
index a650e31..fd8a686 100644
--- a/scripts/credential-expectations.json
+++ b/scripts/credential-expectations.json
@@ -26,7 +26,7 @@
"engine": "@observer-protocol/policy-engine",
"$reMeasured": "All 8 expectations re-confirmed against 1.0.0-rc.10 on 2026-08-09, BEFORE the lockfile bump landed rather than after. Output was byte-identical to rc.6, allow verdicts and reasonContains both. The bump happened because the site documented rc.6 while npm's latest dist-tag served rc.10: sync-engine-version compared the site to the lockfile and called internal consistency currency. Separately re-ran the hosted-service comparison at rc.10, because rewriting a 'current' marker inside a sentence that states a MEASURED agreement would have restated an untested pair: 7 of 8 agree, and the 8th is refused at the hosted deployment's issuer allowlist so its engine never sees it. Prior note, kept: all 8 were re-confirmed against rc.6 on 2026-08-08 after it superseded rc.5 mid-pass.",
"$pppCorpus": "The 8 verify-samples/ppp-* artifacts were added 2026-08-16 and measured against 1.0.0-rc.12 on that date. They are store records copied verbatim from a run of op-ppp-cases against a local enforcement deployment on 2026-08-15; the JSON is re-indented and nothing else is touched, which is safe because every one of these signatures is over bytes the verifier REBUILDS rather than over the file. Their expectations were not chosen: each reason string below was copied out of a failing run of this checker, which is why four of them record a failure. See verify-samples/README.md for the case behind each.",
- "$pppVerdictsFailOnTheVersionPIN": "THE FOUR op.evaluation.verdict EXPECTATIONS BELOW SAY allow:false AND THE RECORDS ARE SOUND. Measured 2026-08-16 against these exact eight files at both published versions: rc.12 (npm `latest`, what this repository pins and what a reader following this site's install instruction gets) rebuilds op.evaluation.verdict.v3 and the four signatures do not verify; rc.18 (npm `rc`) rebuilds v4 and all four verify. The records state v4 in their own `construction` field and nothing in either package consults it. NOT FIXED BY BUMPING THE PIN: sync-engine-version.mjs --check asserts the site states what npm serves a READER, and rc.18 is not on `latest`, so bumping would make this file agree with an engine the site does not tell anyone to install. The reasonContains strings name BOTH versions deliberately, so the pin cannot move in either direction without turning this check red. Contrast the two op.enforcement.refusal entries, which verify under both: signableFromRefusal rebuilds under the payloadType the RECORD carries. One design decision, applied to one signed class and not the other.",
+ "$pppVerdictsFailOnTheVersionPIN": "RESOLVED 2026-08-23 by the dist-tag move, not by a fix. These four op.evaluation.verdict expectations said allow:false for as long as npm's `latest` was 1.0.0-rc.12, which rebuilds op.evaluation.verdict.v3 while the records state v4. The pin is now 1.0.0-rc.21, which rebuilds v4, so all four verify. THE RECORDS DID NOT CHANGE AND WERE ALWAYS SOUND; what changed is which release this repository pins. The reasonContains strings named both version tokens deliberately so the pin could not move in either direction without turning this check red. It did, and that is the control working rather than a finding about the engine. Kept rather than deleted: a note that vanishes when a condition clears leaves a reader unable to tell a repository that was always right from one that was corrected. The contrast that was always the point is unchanged: the two op.enforcement.refusal entries verify under BOTH versions, because signableFromRefusal rebuilds under the payloadType the RECORD carries and evaluationVerdictPayload has no equivalent. One design decision, applied to one signed class and not the other.",
"measuredOn": "2026-08-09",
"artifacts": [
{
@@ -124,20 +124,18 @@
"path": "verify-samples/ppp-verdict-released.json",
"expect": {
"construction": "op.evaluation.verdict",
- "allow": false,
- "reasonContains": "(op.evaluation.verdict.v3); the record states construction op.evaluation.verdict.v4"
+ "allow": true,
+ "reasonContains": "signature verifies over the payload this build rebuilds (op.evaluation.verdict.v4)"
},
- "knownIssue": "VERDICT-PAYLOAD-VERSION-PIN",
"case": "BE-0001 under 85 FR 20811, $105,922.00. Payroll exactly 3/4 of principal, so the 25% non-payroll cap is exactly at its boundary and does not bind; all three terms equal the principal. Submitted 2026-08-15T19:11:31.955Z, third attempt, 200 instructed as res-0001."
},
{
"path": "verify-samples/ppp-verdict-denied-ceiling.json",
"expect": {
"construction": "op.evaluation.verdict",
- "allow": false,
- "reasonContains": "(op.evaluation.verdict.v3); the record states construction op.evaluation.verdict.v4"
+ "allow": true,
+ "reasonContains": "signature verifies over the payload this build rebuilds (op.evaluation.verdict.v4)"
},
- "knownIssue": "VERDICT-PAYLOAD-VERSION-PIN",
"case": "ID-0077 under 85 FR 20811, $1,156,884.63, principal binds under both rules. Above the mandate's 700,000 USDC per-transaction ceiling, so the verdict is a deny naming per_transaction_ceiling. Submitted 2026-08-15T19:10:06.718Z, second attempt, 422 CEILING_EXCEEDED."
},
{
@@ -153,10 +151,9 @@
"path": "verify-samples/ppp-probe-a-verdict-release-above-escalation.json",
"expect": {
"construction": "op.evaluation.verdict",
- "allow": false,
- "reasonContains": "(op.evaluation.verdict.v3); the record states construction op.evaluation.verdict.v4"
+ "allow": true,
+ "reasonContains": "signature verifies over the payload this build rebuilds (op.evaluation.verdict.v4)"
},
- "knownIssue": "VERDICT-PAYLOAD-VERSION-PIN",
"case": "PROBE A. DV-0004 under 85 FR 20811, $500,589.98, chosen as the corpus payment nearest the midpoint of the escalation band so neither threshold can explain the result. A deliberately non-compliant verdict: decision release at an amount above the mandate's displayed 300,000 USDC escalation threshold. Pre-registered before it was sent."
},
{
@@ -173,10 +170,9 @@
"path": "verify-samples/ppp-probe-b-verdict-release-above-ceiling.json",
"expect": {
"construction": "op.evaluation.verdict",
- "allow": false,
- "reasonContains": "(op.evaluation.verdict.v3); the record states construction op.evaluation.verdict.v4"
+ "allow": true,
+ "reasonContains": "signature verifies over the payload this build rebuilds (op.evaluation.verdict.v4)"
},
- "knownIssue": "VERDICT-PAYLOAD-VERSION-PIN",
"case": "PROBE B. BO-0190 under 85 FR 36308, $2,339,635.21, 3.3x the mandate's ceiling. A deliberately non-compliant verdict: decision release at an amount the mandate cannot permit. Being a release it carries no breachedConstraint and no denialDetail, so per_transaction_ceiling occurs nowhere in the request."
},
{
diff --git a/scripts/package-lock.json b/scripts/package-lock.json
index 4ef64af..6778d21 100644
--- a/scripts/package-lock.json
+++ b/scripts/package-lock.json
@@ -6,13 +6,13 @@
"": {
"name": "op-website-credential-check",
"dependencies": {
- "@observer-protocol/policy-engine": "1.0.0-rc.12"
+ "@observer-protocol/policy-engine": "1.0.0-rc.21"
}
},
"node_modules/@observer-protocol/policy-engine": {
- "version": "1.0.0-rc.12",
- "resolved": "https://registry.npmjs.org/@observer-protocol/policy-engine/-/policy-engine-1.0.0-rc.12.tgz",
- "integrity": "sha512-dm6lheerAZQESwmnGbfeKLoEkAsti9IZ9bzjRZLwnv2wSJpfT3Yfc9T0YRHBQngmQrIhB6JmyHmSpFe1vtUREQ==",
+ "version": "1.0.0-rc.21",
+ "resolved": "https://registry.npmjs.org/@observer-protocol/policy-engine/-/policy-engine-1.0.0-rc.21.tgz",
+ "integrity": "sha512-OFFRtmDMs+DWW1pyAWWwRcoXHPPMp/8y48NZ0xyzDKjy310PqFb8S7NPGoW9H7gUWGAm2tEROE3fZq2Y2gOKbA==",
"license": "MIT",
"engines": {
"node": ">=18"
diff --git a/scripts/package.json b/scripts/package.json
index 56bdda3..d8770ec 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -7,6 +7,6 @@
"verify:credentials": "node ./verify-published-credentials.mjs"
},
"dependencies": {
- "@observer-protocol/policy-engine": "1.0.0-rc.12"
+ "@observer-protocol/policy-engine": "1.0.0-rc.21"
}
}
diff --git a/verify-samples/README.md b/verify-samples/README.md
index f6d6568..7c22f03 100644
--- a/verify-samples/README.md
+++ b/verify-samples/README.md
@@ -156,11 +156,12 @@ So the correct description of these artifacts is **recoverable by trial across a
not fragile. A record whose recovery required guessing an unbounded string, or a nonce, or a
timestamp, would be fragile. This one requires counting.
-**What the pin does, and what it does not.** `scripts/package.json` still pins `1.0.0-rc.12`, so CI
-measures this corpus against rc.12 and `scripts/credential-expectations.json` still records the four
-`allow: false` outcomes. **That file and this page describe different versions on purpose until the
-pin is bumped**, and the expectation strings name both version tokens so the pin cannot move in
-either direction without turning the check red. Bumping it is a separate change from this one.
+**What the pin does.** `scripts/package.json` now pins `1.0.0-rc.21`, the version npm's `latest`
+serves, and `scripts/credential-expectations.json` records the four verdicts as `allow: true`.
+Both moved in the same commit as the pin, because rc.12 cannot verify these four and an
+expectation is a statement about the version being run. The expectation strings named both
+version tokens deliberately, so the pin could not move in either direction without turning the
+check red; it did, and that is what brought the two changes together.
**Why it is not in the filenames.** A file called `must-not-verify-*` is making a permanent claim.
diff --git a/verify.html b/verify.html
index c9718ee..c0ac1be 100644
--- a/verify.html
+++ b/verify.html
@@ -120,7 +120,7 @@ Install, one call, done.
-
verify.mjs@observer-protocol/policy-engine 1.0.0-rc.12 · MIT
+
verify.mjs@observer-protocol/policy-engine 1.0.0-rc.21 · MIT
$ npm install @observer-protocol/policy-engine
$ curl -O https://observerprotocol.org/verify-samples/verifies-delegation-mandate.json
@@ -501,7 +501,7 @@ Convenient, ours, and now the same engine two releases back.
Correction: the two verifiers converged, and this section said otherwise for a week
This section used to argue that the hosted service was a different build. It reported engine.running: "0.3.3" and concluded that any agreement between the two was "agreement observed on samples, not a shared code path". That argument is false and has been since 9 August 2026. Convergence landed that day, in commit d3278efb3cb2, built 2026-08-09, and nothing here recorded it. The page went on making an argument its own subject had stopped supporting.
-
Read from the service's own /version today: engine.running 1.0.0-rc.10, engine.builtAgainst 1.0.0-rc.10, agree: true. It is the published package. The published package is 1.0.0-rc.12, so the honest description is the same engine, two releases behind, and not a separate implementation. What remains true is that a version gap is a real gap: rc.10 and rc.12 are not the same code, and a difference between them is a difference you can meet.
+
Read from the service's own /version today: engine.running 1.0.0-rc.10, engine.builtAgainst 1.0.0-rc.10, agree: true. It is the published package. The published package is 1.0.0-rc.21, so the honest description is the same engine, two releases behind, and not a separate implementation. What remains true is that a version gap is a real gap: rc.10 and rc.12 are not the same code, and a difference between them is a difference you can meet.
The old "agree on 7 of 8" figure is withdrawn, and the denominator was the defect rather than the agreement. Three of those eight artifacts never reach the hosted engine at all, so a ratio over eight was never reproducible by a reader. One is the testbed-issuer demonstration credential, and that deployment refuses it at its issuer allowlist, so the refusal is evidence about the allowlist and not about the engine. The other two are the PolicyEvaluationCredential artifacts, for the reason in the next paragraph. A figure whose denominator counts artifacts that cannot be submitted is not a measurement of agreement; it is a measurement of how many things we tried.
A PolicyEvaluationCredential cannot be submitted here at all. Post one as the request body and the service answers HTTP 400, body must carry agentDid (string) and mandate (object). There is no agentDid to supply: a PEC's credentialSubject carries decision, evaluator, proposal and evaluatedAt, and no id. The endpoint's shape assumes a delegation credential granting authority to a subject, and an evaluation credential has no subject in that sense. This is the same root cause as the gap in section 02, met at the transport rather than in the validator: both the package and the service are built around one credential shape, and the artifact that records a determination does not have it. Wrapping a PEC in an invented agentDid does get a response, and what comes back is a structural refusal about credentialSchema, which is the offline failure repeated rather than a second opinion about it.
The service is also candid about a second limit, in its own /version response: the schema and issuer allowlists it enforces come from environment variables on the host, inRepo: false, so a change to what it accepts "leaves no reviewable record". The offline path has no such property, because the allowlist is the one you wrote.
@@ -544,7 +544,7 @@
Checking a record is not approving a vendor.
From a095b133d5148bd3d0aa2f1d733b927776759e9f Mon Sep 17 00:00:00 2001
From: Boyd Cohen
Date: Sun, 23 Aug 2026 17:53:02 -0600
Subject: [PATCH 7/7] Regenerate the sitemap for the four pages the pin bump
touched
verify.html, index.html, docs.html and check.html all moved their git date with
the version spans. make-sitemap.py is one commit behind by construction, so this
follows rather than rides with them.
Caught by tools/hooks/pre-push, which refused the push. First time that hook has
stopped a real one rather than a probe.
---
sitemap.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/sitemap.xml b/sitemap.xml
index c599e3a..3de80e7 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -22,7 +22,7 @@
https://observerprotocol.org/check
- 2026-08-20
+ 2026-08-23
https://observerprotocol.org/crossrail
@@ -34,7 +34,7 @@
https://observerprotocol.org/docs.html
- 2026-08-22
+ 2026-08-23
https://observerprotocol.org/enforcement