eleven / messenger Public

tools+web+docs: the registry hash ledger — public tamper-evidence in elevenmessenger/releases (#800)

Each corpus build emits manifest.sha256.json (hash of every published file
under the contributor's prefix, written after the fail-closed verify so it
hashes exactly what publishes; uploaded with the corpus so the bucket is
self-describing). The accrual runbook commits it to the PUBLIC releases repo
at prompt-registry/<slug>.json — that file's git history is the ledger: a few
KB per accrual, and any after-the-fact change to a published transcript shows
as a dated hash change without re-preserving the changed bytes. The page
footer links the ledger history.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell dd1acbfc716694e0a862c0803fb3bfc55cec2576 parent 5be7cff
3 files changed, +34 −1
  • docs/status.md +8 −1
  • tools/prompt-registry.mjs +19 −0
  • web/status/prompts.html +7 −0
diff --git a/docs/status.md b/docs/status.md
index 41d109d..29b90d6 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -411,7 +411,14 @@ Accruing a machine's sessions:
set +a` — or anywhere with equivalent PutObject rights). Sync **only your
own prefix**; the manifest upload is a `cp`, never a sync with `--delete`
at the `prompts/` root, which would erase other contributors.
-3. `prompts.html` itself is a committed viewer page: copy it to the box's
+3. Commit the hash ledger entry (the tool prints the exact command): the
+ corpus's `manifest.sha256.json` goes to the **public**
+ `elevenmessenger/releases` repo at `prompt-registry/<slug>.json` via
+ `gh api -X PUT`. Its git history is the registry's tamper-evidence — a
+ few KB per accrual, and any after-the-fact change to a published
+ transcript shows as a dated hash change. (The manifest also uploads with
+ the corpus, so the bucket is self-describing.)
+4. `prompts.html` itself is a committed viewer page: copy it to the box's
`/opt/lchat/status-pages/` like the other pages; the 5-minute publish
re-uploads it.
diff --git a/tools/prompt-registry.mjs b/tools/prompt-registry.mjs
index f3f6932..02b3e48 100644
--- a/tools/prompt-registry.mjs
+++ b/tools/prompt-registry.mjs
@@ -76,6 +76,7 @@
// prefixes) are the real net for labelled secrets.
import { readFileSync, writeFileSync, readdirSync, existsSync, mkdirSync, rmSync, renameSync, statSync } from "node:fs";
import { execSync } from "node:child_process";
+import { createHash } from "node:crypto";
import { join, basename } from "node:path";
import os from "node:os";
@@ -570,6 +571,19 @@ if (!checkOnly) {
console.error("\n" + bad + " file(s) failed post-redaction scan — corpus NOT published, left in " + tmp);
process.exit(1);
}
+ // The hash ledger entry: sha256 of every published file under this slug,
+ // committed to the PUBLIC elevenmessenger/releases repo (its git history is
+ // the tamper-evidence — see docs/status.md § The prompt registry). Written
+ // after verification so it hashes exactly what publishes; also uploaded
+ // with the corpus so the bucket is self-describing.
+ const hashed = {};
+ for (const f of readdirSync(join(tmp, "prompts", slug)).sort()) {
+ hashed[f] = "sha256:" + createHash("sha256").update(readFileSync(join(tmp, "prompts", slug, f))).digest("hex");
+ }
+ const ledger = { generated_at: index.generated_at, contributor, slug,
+ totals: index.totals, files: hashed };
+ writeFileSync(join(tmp, "prompts", slug, "manifest.sha256.json"), JSON.stringify(ledger, null, 1));
+
rmSync(join(outRoot, "prompts"), { recursive: true, force: true });
mkdirSync(outRoot, { recursive: true });
renameSync(join(tmp, "prompts"), join(outRoot, "prompts"));
@@ -587,4 +601,9 @@ if (!checkOnly) {
(manifestLive
? "\n aws s3 cp " + join(outRoot, "prompts", "contributors.json") + " s3://<status-bucket>/prompts/contributors.json --cache-control max-age=300"
: "\n ⚠ manifest built OFFLINE — do NOT upload contributors.json (it would drop other contributors); re-run online first"));
+ console.log("ledger (public tamper-evidence — commit the hash manifest to elevenmessenger/releases):" +
+ "\n gh api -X PUT repos/elevenmessenger/releases/contents/prompt-registry/" + slug + ".json" +
+ " -f message='prompt-registry: accrue " + slug + "'" +
+ " -f content=\"$(base64 -i " + join(outRoot, "prompts", slug, "manifest.sha256.json") + ")\"" +
+ " -f sha=\"$(gh api repos/elevenmessenger/releases/contents/prompt-registry/" + slug + ".json --jq .sha 2>/dev/null || true)\"");
}
diff --git a/web/status/prompts.html b/web/status/prompts.html
index b9363fd..1934505 100644
--- a/web/status/prompts.html
+++ b/web/status/prompts.html
@@ -156,6 +156,13 @@
<dl class="legend" id="legend"></dl>
</details>
+ <p class="meta">🤖 Verifying the record: each contributor's corpus carries a
+ <code>manifest.sha256.json</code> naming the hash of every published file, and its
+ history in the public
+ <a href="https://github.com/elevenmessenger/releases/commits/main/prompt-registry">releases
+ repo's hash ledger</a> is the tamper-evidence — if a published transcript ever
+ changed after the fact, that history shows when.</p>
+
<div class="card totals" id="totals" aria-live="polite"></div>
<h2>Sessions</h2>