From d85007929bfd302701711f7f533b31d5a6cf09c8 Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Tue, 28 Jul 2026 14:14:25 -0700
Subject: [PATCH 1/2] =?UTF-8?q?feat(ledger):=20Bittensor=20on-chain=20anch?=
=?UTF-8?q?or=20backend=20=E2=80=94=20repo-side=20glue=20for=20the=20off-W?=
=?UTF-8?q?orker=20submitter=20(#9277)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The fourth, optional, Gittensor/SN74-audience anchoring backend. The
on-chain SUBMISSION never runs in this Worker: a process on the
operator's own node infrastructure (where its dedicated anchor-only
hotkey lives as an operational secret, never in this repo) fetches the
signed checkpoint, commits sha256(signingInput) via the commitments
pallet's set_commitment(netuid, Data::Sha256), and reports the outcome
back. This lands the validation boundary that report crosses:
- GET /v1/public/decision-ledger/anchor-payload: the current tip's
freshly signed checkpoint + the exact signingInput bytes to hash.
Unauthenticated (it is the same payload Rekor/git already publish
externally); no-store, since 'at' is minted per call.
- POST /v1/decision-ledger/anchor-attempts: bearer-gated
(LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN, fails closed unset -- the
isAuthorizedIngest posture). Authentication alone cannot forge
corroboration: an ok report must verify against a PUBLISHED anchor
key AND its (seq, rowHash) must match the LIVE chain row; a failed
report records without those checks, because a broken submitter is
exactly what the public attempt log exists to make visible.
- decision_ledger_anchors backend CHECK widened to 'bittensor'
(migration 0201 rebuild); the public listing filter follows. Rows are
byte-identical in shape to Rekor/git attempts.
- backendRef carries the FULL historical-retrieval reference
{netuid, blockNumber, blockHash, hotkey}: CommitmentOf is overwritten
in place on-chain, so a verifier reads archive state at that block,
never current chain state -- now documented step-by-step in
what-you-can-verify.mdx, still clearly scoped as optional
corroboration outside the default two-backend claim.
Closes #9277
---
.../content/docs/what-you-can-verify.mdx | 27 +-
migrations/0201_ledger_anchor_bittensor.sql | 26 ++
src/api/routes.ts | 49 +++-
src/auth/route-auth.ts | 7 +
src/env.d.ts | 6 +
src/review/ledger-anchor-bittensor.ts | 137 ++++++++++
src/review/ledger-anchor-persistence.ts | 2 +-
test/unit/ledger-anchor-bittensor.test.ts | 244 ++++++++++++++++++
8 files changed, 489 insertions(+), 9 deletions(-)
create mode 100644 migrations/0201_ledger_anchor_bittensor.sql
create mode 100644 src/review/ledger-anchor-bittensor.ts
create mode 100644 test/unit/ledger-anchor-bittensor.test.ts
diff --git a/apps/loopover-ui/content/docs/what-you-can-verify.mdx b/apps/loopover-ui/content/docs/what-you-can-verify.mdx
index a419e28e3..3df962efc 100644
--- a/apps/loopover-ui/content/docs/what-you-can-verify.mdx
+++ b/apps/loopover-ui/content/docs/what-you-can-verify.mdx
@@ -127,11 +127,28 @@ asking LoopOver anything.
**Bittensor on-chain anchoring is optional, separate corroboration — not part of this default
- check.** A third, Gittensor/SN74-audience-specific backend (tracked, not yet
- shipped — [#9277](https://github.com/JSONbored/loopover/issues/9277)) publishes the same checkpoint
- as an on-chain commitment, signed by a dedicated hotkey run on the operator's own infrastructure.
- It's additive corroboration for that specific audience, never folded into the default two-backend
- claim every verifier above is told to check.
+ check.** A third, Gittensor/SN74-audience-specific
+ backend ([#9277](https://github.com/JSONbored/loopover/issues/9277)) publishes the same signed
+ checkpoint as an on-chain commitment. A submitter on the operator's own node infrastructure — never
+ this Worker, and its dedicated anchor-only hotkey never leaves that infrastructure — fetches
+ `GET /v1/public/decision-ledger/anchor-payload` and commits `sha256(signingInput)` via the
+ commitments pallet's `set_commitment(netuid, Data::Sha256)`, then reports the attempt (success
+ *and* failure, like every other backend) back into the public attempt log, where it appears as
+ `backend: "bittensor"`. It's additive corroboration for that specific audience, never folded into
+ the default two-backend claim every verifier above is told to check.
+
+
+
+ **Historical retrieval — read the block, not chain state.** The commitments pallet's
+ `CommitmentOf` map is **overwritten in place**: only the *latest* commitment per (netuid, account)
+ survives in current chain state. To verify an older anchor, use its `backendRef` from the attempt
+ log — `{netuid, blockNumber, blockHash, hotkey}` — and query **archive state at that block**
+ (`state_getStorage` at `blockHash`, or the block's events), not the current tip. Then check the
+ stored commitment's `Sha256` bytes equal `sha256(signingInput)` of the anchor's own
+ `payload_json`, and verify the payload's signature against the published anchor keys exactly as in
+ step (c) above. Any Bittensor archive node can answer this; the operator's own archive is merely
+ the convenient one — the same trust posture as the git backend's "GitHub hosts it, the mirror
+ cross-checks it."
### 2. Decision-record authenticity
diff --git a/migrations/0201_ledger_anchor_bittensor.sql b/migrations/0201_ledger_anchor_bittensor.sql
new file mode 100644
index 000000000..67dd367db
--- /dev/null
+++ b/migrations/0201_ledger_anchor_bittensor.sql
@@ -0,0 +1,26 @@
+-- #9277 (epic #9267): widen decision_ledger_anchors' backend CHECK to admit 'bittensor' — the optional,
+-- Gittensor/SN74-audience on-chain commitment backend. The submission itself runs on the operator's own node
+-- infrastructure (never this Worker); this table records its reported attempts, success AND failure, exactly
+-- like the Rekor/git rows (#9271's whole design: a backend that can fail silently is a backend an operator
+-- can silently disable). SQLite cannot ALTER a CHECK constraint, so this is the standard rebuild-and-rename.
+CREATE TABLE decision_ledger_anchors_new (
+ id TEXT PRIMARY KEY,
+ seq INTEGER NOT NULL,
+ row_hash TEXT NOT NULL,
+ payload_json TEXT NOT NULL,
+ signature TEXT NOT NULL,
+ key_id TEXT NOT NULL,
+ backend TEXT NOT NULL CHECK (backend IN ('rekor', 'git', 'ots', 'bittensor')),
+ -- For bittensor: {netuid, blockNumber, blockHash, hotkey} — deliberately the FULL historical-retrieval
+ -- reference: CommitmentOf is overwritten in place on-chain, so a verifier needs the block, not chain state.
+ backend_ref TEXT,
+ proof_r2_key TEXT,
+ status TEXT NOT NULL CHECK (status IN ('ok', 'failed')),
+ error TEXT,
+ created_at TEXT NOT NULL
+);
+INSERT INTO decision_ledger_anchors_new SELECT * FROM decision_ledger_anchors;
+DROP TABLE decision_ledger_anchors;
+ALTER TABLE decision_ledger_anchors_new RENAME TO decision_ledger_anchors;
+CREATE INDEX IF NOT EXISTS decision_ledger_anchors_created_at ON decision_ledger_anchors (created_at DESC);
+CREATE INDEX IF NOT EXISTS decision_ledger_anchors_backend_created_at ON decision_ledger_anchors (backend, created_at DESC);
diff --git a/src/api/routes.ts b/src/api/routes.ts
index 125c4f9fc..d555c395a 100644
--- a/src/api/routes.ts
+++ b/src/api/routes.ts
@@ -305,9 +305,10 @@ import { getContributorTrustProfile } from "../review/contributor-trust-profile"
import { backfillContributorGateHistory } from "../review/contributor-gate-history-backfill";
import { isFairnessAnalyticsEnabled, resolveFairnessAnalyticsManifestOverride } from "../review/contributor-trust-profile-wire";
import { isRagEnabled } from "../review/rag-wire";
-import { loadPublicDecisionRecord, loadPublicLedgerRow, verifyDecisionLedger } from "../review/decision-record";
+import { loadDecisionLedgerTip, loadPublicDecisionRecord, loadPublicLedgerRow, verifyDecisionLedger } from "../review/decision-record";
import { buildEvalScoreRecordsFromRulePrecision, filterEvalScoreRecords } from "../review/eval-score-records";
-import { currentAnchorKey, parseAnchorPublicKeys } from "../review/ledger-anchor";
+import { anchorSigningInput, buildLedgerAnchorPayload, currentAnchorKey, parseAnchorPublicKeys, signLedgerAnchorPayload } from "../review/ledger-anchor";
+import { ingestBittensorAnchorReport, parseBittensorAnchorReport } from "../review/ledger-anchor-bittensor";
import { loadPublicLedgerAnchors } from "../review/ledger-anchor-persistence";
import { getPublicStats, isPublicStatsEnabled, resolvePublicStatsManifestOverride } from "../review/public-stats";
import { loadPublicAccuracyTrend } from "../services/public-accuracy-trend";
@@ -1339,7 +1340,7 @@ export function createApp() {
// Built with spreads, not literal undefined-valued keys: exactOptionalPropertyTypes means an optional
// filter field must be OMITTED to mean "no filter", not present-with-value-undefined.
const backendParam = c.req.query("backend");
- const backend = backendParam === "rekor" || backendParam === "git" || backendParam === "ots" ? backendParam : undefined;
+ const backend = backendParam === "rekor" || backendParam === "git" || backendParam === "ots" || backendParam === "bittensor" ? backendParam : undefined;
const before = c.req.query("before");
const limit = Number(c.req.query("limit")) || undefined;
const result = await loadPublicLedgerAnchors(c.env, {
@@ -1351,6 +1352,48 @@ export function createApp() {
return c.json(result);
});
+ // #9277 (epic #9267): the current tip's SIGNED checkpoint, for the operator's off-Worker Bittensor
+ // commitment submitter to fetch and commit on-chain (sha256 of `signingInput` is the exact 32 bytes
+ // `Data::Sha256` holds). Unauthenticated like every /v1/public/* sibling: it is the same payload the
+ // Rekor/git backends already publish externally on every checkpoint — hashes, a seq, a timestamp and a
+ // key id, nothing else. `no-store`: `at` is minted per call, so a cached copy would just make two
+ // submitters commit two different payload hashes for the same tip for no reason.
+ app.get("/v1/public/decision-ledger/anchor-payload", async (c) => {
+ const keys = parseAnchorPublicKeys(c.env.LOOPOVER_LEDGER_ANCHOR_KEYS);
+ const current = currentAnchorKey(keys);
+ if (!current || !c.env.LOOPOVER_LEDGER_ANCHOR_PRIVATE_KEY) return c.json({ error: "anchor_signing_unconfigured" }, 404);
+ const tip = await loadDecisionLedgerTip(c.env);
+ if (tip.seq === 0) return c.json({ error: "empty_ledger" }, 404);
+ const payload = buildLedgerAnchorPayload(tip, nowIso());
+ const signed = await signLedgerAnchorPayload(payload, c.env.LOOPOVER_LEDGER_ANCHOR_PRIVATE_KEY, current.keyId);
+ c.header("Cache-Control", "no-store");
+ return c.json({ signed, signingInput: anchorSigningInput(payload) });
+ });
+
+ // #9277 (epic #9267): the operator's off-Worker Bittensor submitter reports each on-chain anchor attempt
+ // (success AND failure) back into #9271's public attempt log. Bearer-gated, FAILS CLOSED when the token is
+ // unset (isAuthorizedIngest, same posture as /v1/orb/ingest). Authentication alone is deliberately not
+ // enough for an `ok` row: the report's signed payload must verify against a PUBLISHED anchor key and its
+ // (seq, rowHash) must match the LIVE chain row — the public log asserting on-chain corroboration that a
+ // buggy submitter never actually anchored would be worse than no log at all. A `failed` report skips those
+ // checks: "the submitter is broken" is exactly what the attempt log exists to make publicly visible.
+ app.post("/v1/decision-ledger/anchor-attempts", async (c) => {
+ if (!(await isAuthorizedIngest(c.env.LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN, extractBearerToken(c.req.header("authorization"))))) return c.json({ error: "unauthorized" }, 401);
+ const body = await readOrbIngestBody(c.req.raw, c.req.header("content-length"));
+ if (body === null) return c.json({ error: "payload_too_large" }, 413);
+ let raw: unknown;
+ try {
+ raw = JSON.parse(body || "");
+ } catch {
+ return c.json({ error: "invalid_json" }, 400);
+ }
+ const parsed = parseBittensorAnchorReport(raw);
+ if ("error" in parsed) return c.json({ error: "invalid_report", detail: parsed.error }, 400);
+ const outcome = await ingestBittensorAnchorReport(c.env, parsed.report);
+ if (!outcome.recorded) return c.json({ error: outcome.reason }, 422);
+ return c.json({ recorded: true, status: outcome.status }, 200);
+ });
+
// #9123: the decision record itself was persisted (decision_records) but never published anywhere a
// contributor or a third party could fetch the full body — the only prior public surface was
// renderDecisionRecordSection's bounded review-comment summary (12-char digest prefixes, and it omits
diff --git a/src/auth/route-auth.ts b/src/auth/route-auth.ts
index 538675885..429036fea 100644
--- a/src/auth/route-auth.ts
+++ b/src/auth/route-auth.ts
@@ -48,6 +48,13 @@ export function requiresApiToken(path: string): boolean {
if (path === "/v1/public/decision-ledger/anchor-key") return false;
// #9271: the public anchor-attempt listing, added in the SAME PR as its route.
if (path === "/v1/public/decision-ledger/anchors") return false;
+ // #9277: the current tip's signed checkpoint, for the operator's off-Worker Bittensor submitter (and
+ // anyone else — it is the same payload the Rekor/git backends already publish externally). Added in the
+ // SAME PR as its route, per the #9120 lesson.
+ if (path === "/v1/public/decision-ledger/anchor-payload") return false;
+ // #9277: the submitter's report route carries its OWN bearer gate (isAuthorizedIngest against
+ // LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN, fails closed when unset) — same posture as /v1/orb/ingest below.
+ if (path === "/v1/decision-ledger/anchor-attempts") return false;
// #9123: the new public decision-record read route — same unauthenticated posture as its ledger-verify
// sibling immediately above, added in the SAME PR so the two can never drift apart the way #9120 did. The
// pull segment matches any non-slash text (not just digits): an invalid pull number is the ROUTE's 400 to
diff --git a/src/env.d.ts b/src/env.d.ts
index 94148c0c3..0a59667e9 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -652,6 +652,12 @@ declare global {
* chokepoint every other GitHub write in this engine goes through). Unset (alongside the owner/repo
* pair above) means the git backend does not run this tick; Rekor is unaffected. */
LOOPOVER_LEDGER_ANCHOR_GIT_INSTALLATION_ID?: string;
+ /** External ledger anchoring (#9277, epic #9267): bearer token the operator's OFF-Worker Bittensor
+ * commitment submitter presents to `POST /v1/decision-ledger/anchor-attempts` when reporting an
+ * on-chain anchor attempt back into the public attempt log. FAILS CLOSED when unset (the route
+ * rejects everything, same isAuthorizedIngest posture as ORB_INGEST_TOKEN) — the submitter itself and
+ * its hotkey live entirely on the operator's node infrastructure, never in this repo or this Worker. */
+ LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN?: string;
/** Convergence (port): public OAuth draft-submission flow ported from reviewbot. When truthy, the
* /v1/drafts endpoints accept a contributor draft -> GitHub OAuth -> fork PR against the content repo.
* Default OFF — unset/false makes every draft endpoint 404 and writes nothing (byte-identical worker). */
diff --git a/src/review/ledger-anchor-bittensor.ts b/src/review/ledger-anchor-bittensor.ts
new file mode 100644
index 000000000..4ba27a4c1
--- /dev/null
+++ b/src/review/ledger-anchor-bittensor.ts
@@ -0,0 +1,137 @@
+// Bittensor on-chain commitment backend, repo-side glue (#9277, epic #9267).
+//
+// Unlike the Rekor (#9272) and git-commit (#9273) backends, the SUBMISSION never runs here: a small process
+// on the operator's own node infrastructure fetches the current signed checkpoint from
+// `GET /v1/public/decision-ledger/anchor-payload`, commits sha256(signingInput) on-chain via the commitments
+// pallet's `set_commitment(netuid, Data::Sha256)` using a dedicated hotkey (an operational secret on that
+// infrastructure, never in this repo), and then reports the outcome back through
+// `POST /v1/decision-ledger/anchor-attempts`. This module is the validation boundary for that report.
+//
+// Why the report must be VERIFIED and not merely authenticated: the bearer token proves "the operator's
+// submitter", but the attempt log is a PUBLIC trust surface (#9271's whole point). A buggy or compromised
+// submitter must not be able to inject an anchor row for a payload this engine never signed, nor for a
+// (seq, rowHash) pair that is not this live chain's — either would let the log claim corroboration that
+// does not exist. So a report is accepted only when:
+// 1. its signed payload verifies against a PUBLISHED anchor key (the same check any third party runs), and
+// 2. its (seq, rowHash) matches the live ledger row at that seq (the same bind-to-chain step #9269 exists
+// for) — with the one honest exception that a FAILED attempt is recorded even when signature/row checks
+// would fail, because "the submitter is broken" is exactly what the public attempt log must show.
+//
+// Scope (#9277's own framing): optional, Gittensor/SN74-audience corroboration. Rekor + git remain the
+// default every verifier is told to check; nothing here is a required verification step.
+import type { LedgerAnchorPayload, SignedLedgerAnchor } from "./ledger-anchor";
+import { anchorKeyById, parseAnchorPublicKeys, verifyLedgerAnchorSignature } from "./ledger-anchor";
+import { LEDGER_ANCHOR_LEDGER_ID, LEDGER_ANCHOR_PAYLOAD_VERSION } from "./ledger-anchor";
+import { loadPublicLedgerRow } from "./decision-record";
+import { recordLedgerAnchorAttempt } from "./ledger-anchor-persistence";
+
+/** The on-chain reference an `ok` report must carry — the full set a Gittensor-audience verifier needs to
+ * find the commitment WITHOUT trusting current chain state: `CommitmentOf` is overwritten in place, so a
+ * historical commitment is only reachable by querying archive state at (or the events of) this block. */
+export type BittensorAnchorRef = {
+ netuid: number;
+ blockNumber: number;
+ /** 0x-prefixed 32-byte block hash — the archive-state query key for historical retrieval. */
+ blockHash: string;
+ /** ss58 account of the dedicated anchor hotkey (public on-chain identity, never key material). */
+ hotkey: string;
+};
+
+export type BittensorAnchorReport = {
+ signed: SignedLedgerAnchor;
+} & ({ status: "ok"; backendRef: BittensorAnchorRef } | { status: "failed"; error: string });
+
+const HEX_32_BYTES = /^0x[0-9a-f]{64}$/i;
+const ROW_HASH = /^[0-9a-f]{64}$/i;
+
+/** Parse + bound one submitter report. Returns a typed report or a NAMED rejection reason — every arm names
+ * the exact field it refused so a submitter bug is diagnosable from the 400 body alone. PURE. */
+export function parseBittensorAnchorReport(raw: unknown): { report: BittensorAnchorReport } | { error: string } {
+ if (!raw || typeof raw !== "object") return { error: "body must be a JSON object" };
+ const o = raw as Record;
+
+ const signedRaw = o.signed;
+ if (!signedRaw || typeof signedRaw !== "object") return { error: "signed: missing — fetch it from /v1/public/decision-ledger/anchor-payload" };
+ const s = signedRaw as Record;
+ const payloadRaw = s.payload;
+ if (!payloadRaw || typeof payloadRaw !== "object") return { error: "signed.payload: missing" };
+ const p = payloadRaw as Record;
+ if (p.v !== LEDGER_ANCHOR_PAYLOAD_VERSION) return { error: `signed.payload.v: expected ${LEDGER_ANCHOR_PAYLOAD_VERSION}` };
+ if (p.ledger !== LEDGER_ANCHOR_LEDGER_ID) return { error: `signed.payload.ledger: expected ${LEDGER_ANCHOR_LEDGER_ID}` };
+ if (typeof p.seq !== "number" || !Number.isInteger(p.seq) || p.seq <= 0) return { error: "signed.payload.seq: expected a positive integer" };
+ if (typeof p.rowHash !== "string" || !ROW_HASH.test(p.rowHash)) return { error: "signed.payload.rowHash: expected 64 hex chars" };
+ if (typeof p.totalCount !== "number" || !Number.isInteger(p.totalCount) || p.totalCount <= 0) return { error: "signed.payload.totalCount: expected a positive integer" };
+ if (typeof p.at !== "string" || !p.at || p.at.length > 40) return { error: "signed.payload.at: expected a timestamp string" };
+ const payload: LedgerAnchorPayload = { v: LEDGER_ANCHOR_PAYLOAD_VERSION, ledger: LEDGER_ANCHOR_LEDGER_ID, seq: p.seq, rowHash: p.rowHash, totalCount: p.totalCount, at: p.at };
+
+ if (typeof s.keyId !== "string" || !s.keyId || s.keyId.length > 64) return { error: "signed.keyId: expected a short key id" };
+ if (typeof s.signature !== "string" || !s.signature || s.signature.length > 512) return { error: "signed.signature: expected base64 (<=512 chars)" };
+ const signed: SignedLedgerAnchor = { payload, keyId: s.keyId, signature: s.signature };
+
+ if (o.status === "failed") {
+ if (typeof o.error !== "string" || !o.error.trim()) return { error: "error: required on a failed report" };
+ return { report: { signed, status: "failed", error: o.error.trim().slice(0, 500) } };
+ }
+ if (o.status !== "ok") return { error: 'status: expected "ok" or "failed"' };
+
+ const refRaw = o.backendRef;
+ if (!refRaw || typeof refRaw !== "object") return { error: "backendRef: required on an ok report" };
+ const r = refRaw as Record;
+ if (typeof r.netuid !== "number" || !Number.isInteger(r.netuid) || r.netuid < 0 || r.netuid > 65535) return { error: "backendRef.netuid: expected an integer in [0, 65535]" };
+ if (typeof r.blockNumber !== "number" || !Number.isInteger(r.blockNumber) || r.blockNumber <= 0) return { error: "backendRef.blockNumber: expected a positive integer" };
+ if (typeof r.blockHash !== "string" || !HEX_32_BYTES.test(r.blockHash)) return { error: "backendRef.blockHash: expected 0x + 64 hex chars" };
+ if (typeof r.hotkey !== "string" || !r.hotkey.trim() || r.hotkey.length > 64) return { error: "backendRef.hotkey: expected an ss58 address (<=64 chars)" };
+ return {
+ report: {
+ signed,
+ status: "ok",
+ backendRef: { netuid: r.netuid, blockNumber: r.blockNumber, blockHash: r.blockHash.toLowerCase(), hotkey: r.hotkey.trim() },
+ },
+ };
+}
+
+export type BittensorReportOutcome =
+ | { recorded: true; status: "ok" | "failed" }
+ | { recorded: false; reason: "unknown_key" | "bad_signature" | "row_not_found" | "row_hash_mismatch" };
+
+/**
+ * Verify one parsed report against the published keys and the LIVE chain, then record it in #9271's public
+ * attempt log with backend `bittensor` — the exact same row shape Rekor/git attempts get.
+ *
+ * A FAILED report skips the signature/row checks deliberately: it records that the submitter could not
+ * anchor (chain down, rate-limited, hotkey deregistered), and the payload it carries is what the submitter
+ * ATTEMPTED — refusing to log a failure because its evidence is imperfect would recreate exactly the
+ * silent-failure hole the public attempt log exists to close. An OK report asserts a public corroboration
+ * claim, so it must clear every check before the log will say so.
+ */
+export async function ingestBittensorAnchorReport(env: Env, report: BittensorAnchorReport): Promise {
+ if (report.status === "ok") {
+ const keys = parseAnchorPublicKeys(env.LOOPOVER_LEDGER_ANCHOR_KEYS);
+ const key = anchorKeyById(keys, report.signed.keyId);
+ if (!key) return { recorded: false, reason: "unknown_key" };
+ if (!(await verifyLedgerAnchorSignature(report.signed, key.publicKeySpki))) return { recorded: false, reason: "bad_signature" };
+ const row = await loadPublicLedgerRow(env, report.signed.payload.seq);
+ if (!row) return { recorded: false, reason: "row_not_found" };
+ if (row.rowHash !== report.signed.payload.rowHash) return { recorded: false, reason: "row_hash_mismatch" };
+ await recordLedgerAnchorAttempt(env, {
+ payload: report.signed.payload,
+ signature: report.signed.signature,
+ keyId: report.signed.keyId,
+ backend: "bittensor",
+ status: "ok",
+ backendRef: report.backendRef,
+ // The on-chain commitment IS the proof; there is no separate blob to store (same as git's null).
+ proofR2Key: null,
+ });
+ return { recorded: true, status: "ok" };
+ }
+ await recordLedgerAnchorAttempt(env, {
+ payload: report.signed.payload,
+ signature: report.signed.signature,
+ keyId: report.signed.keyId,
+ backend: "bittensor",
+ status: "failed",
+ error: report.error,
+ });
+ return { recorded: true, status: "failed" };
+}
diff --git a/src/review/ledger-anchor-persistence.ts b/src/review/ledger-anchor-persistence.ts
index 544337ca2..fadcad634 100644
--- a/src/review/ledger-anchor-persistence.ts
+++ b/src/review/ledger-anchor-persistence.ts
@@ -7,7 +7,7 @@
import { nowIso, errorMessage } from "../utils/json";
import type { LedgerAnchorPayload } from "./ledger-anchor";
-export type LedgerAnchorBackend = "rekor" | "git" | "ots";
+export type LedgerAnchorBackend = "rekor" | "git" | "ots" | "bittensor";
/** What a backend passes in to record ONE attempt -- success or failure, same shape either way. */
export type LedgerAnchorAttemptInput = {
diff --git a/test/unit/ledger-anchor-bittensor.test.ts b/test/unit/ledger-anchor-bittensor.test.ts
new file mode 100644
index 000000000..a44e6776b
--- /dev/null
+++ b/test/unit/ledger-anchor-bittensor.test.ts
@@ -0,0 +1,244 @@
+import { describe, expect, it } from "vitest";
+import {
+ ingestBittensorAnchorReport,
+ parseBittensorAnchorReport,
+ type BittensorAnchorReport,
+} from "../../src/review/ledger-anchor-bittensor";
+import {
+ buildLedgerAnchorPayload,
+ computeAnchorKeyId,
+ signLedgerAnchorPayload,
+ LEDGER_ANCHOR_LEDGER_ID,
+ LEDGER_ANCHOR_PAYLOAD_VERSION,
+ type SignedLedgerAnchor,
+} from "../../src/review/ledger-anchor";
+import { appendDecisionLedger, loadDecisionLedgerTip } from "../../src/review/decision-record";
+import { loadPublicLedgerAnchors } from "../../src/review/ledger-anchor-persistence";
+import { createTestEnv } from "../helpers/d1";
+import { createApp } from "../../src/api/routes";
+import { verifyLedgerAnchorSignature, anchorKeyById, parseAnchorPublicKeys } from "../../src/review/ledger-anchor";
+
+// #9277 (epic #9267): the Bittensor commitment backend's repo-side glue. The on-chain SUBMISSION runs on the
+// operator's node infrastructure and is deliberately not in this repo; what IS here — and what these tests
+// pin — is the validation boundary: a report only lands in the PUBLIC attempt log as `ok` when its signed
+// payload verifies against a published key AND its (seq, rowHash) matches the live chain. Real ECDSA keys,
+// real signatures, real D1 rows — same discipline as every sibling anchor suite.
+
+function bytesToBase64(bytes: Uint8Array): string {
+ let binary = "";
+ for (const byte of bytes) binary += String.fromCharCode(byte);
+ return btoa(binary);
+}
+
+function toPem(base64: string, label: string): string {
+ return `-----BEGIN ${label}-----\n${(base64.match(/.{1,64}/g) ?? []).join("\n")}\n-----END ${label}-----`;
+}
+
+async function generateKeypair(): Promise<{ privateKeyPem: string; publicKeySpki: string; keyId: string }> {
+ const pair = (await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"])) as CryptoKeyPair;
+ const pkcs8 = bytesToBase64(new Uint8Array((await crypto.subtle.exportKey("pkcs8", pair.privateKey)) as ArrayBuffer));
+ const publicKeySpki = bytesToBase64(new Uint8Array((await crypto.subtle.exportKey("spki", pair.publicKey)) as ArrayBuffer));
+ return { privateKeyPem: toPem(pkcs8, "PRIVATE KEY"), publicKeySpki, keyId: await computeAnchorKeyId(publicKeySpki) };
+}
+
+const REF = { netuid: 74, blockNumber: 5_000_001, blockHash: `0x${"ab".repeat(32)}`, hotkey: "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty" };
+
+/** A ledger with one real row, a published key, and a correctly signed payload for that live tip. */
+async function anchoredFixture() {
+ const { privateKeyPem, publicKeySpki, keyId } = await generateKeypair();
+ const env = createTestEnv({
+ LOOPOVER_LEDGER_ANCHOR_KEYS: JSON.stringify([{ keyId, publicKeySpki, notBefore: "2026-01-01T00:00:00.000Z", notAfter: null }]),
+ });
+ await appendDecisionLedger(env, "record:o/r#1@sha1", "d".repeat(64));
+ const tip = await loadDecisionLedgerTip(env);
+ const signed = await signLedgerAnchorPayload(buildLedgerAnchorPayload(tip, "2026-07-28T00:00:00.000Z"), privateKeyPem, keyId);
+ return { env, signed, privateKeyPem, keyId };
+}
+
+function okBody(signed: SignedLedgerAnchor): Record {
+ return { signed, status: "ok", backendRef: { ...REF } };
+}
+
+describe("parseBittensorAnchorReport (#9277)", () => {
+ it("accepts a complete ok report and normalizes the block hash to lowercase", async () => {
+ const { signed } = await anchoredFixture();
+ const parsed = parseBittensorAnchorReport({ signed, status: "ok", backendRef: { ...REF, blockHash: REF.blockHash.toUpperCase().replace("0X", "0x") } });
+ if ("error" in parsed) throw new Error(parsed.error);
+ expect(parsed.report.status).toBe("ok");
+ if (parsed.report.status === "ok") expect(parsed.report.backendRef.blockHash).toBe(REF.blockHash);
+ });
+
+ it("accepts a failed report with just the signed payload and an error string (bounded to 500 chars)", async () => {
+ const { signed } = await anchoredFixture();
+ const parsed = parseBittensorAnchorReport({ signed, status: "failed", error: ` ${"e".repeat(600)} ` });
+ if ("error" in parsed) throw new Error(parsed.error);
+ expect(parsed.report.status).toBe("failed");
+ if (parsed.report.status === "failed") expect(parsed.report.error).toHaveLength(500);
+ });
+
+ it("names the exact refused field for every malformed shape — a submitter bug is diagnosable from the 400 alone", async () => {
+ const { signed } = await anchoredFixture();
+ const p = signed.payload;
+ const cases: Array<[unknown, string]> = [
+ [null, "JSON object"],
+ ["x", "JSON object"],
+ [{ status: "ok" }, "signed: missing"],
+ [{ signed: "x", status: "ok" }, "signed: missing"],
+ [{ signed: {}, status: "ok" }, "signed.payload: missing"],
+ [{ signed: { payload: { ...p, v: 99 } }, status: "ok" }, "signed.payload.v"],
+ [{ signed: { payload: { ...p, ledger: "other" } }, status: "ok" }, "signed.payload.ledger"],
+ [{ signed: { payload: { ...p, seq: 0 } }, status: "ok" }, "signed.payload.seq"],
+ [{ signed: { payload: { ...p, seq: 1.5 } }, status: "ok" }, "signed.payload.seq"],
+ [{ signed: { payload: { ...p, rowHash: "xyz" } }, status: "ok" }, "signed.payload.rowHash"],
+ [{ signed: { payload: { ...p, totalCount: 0 } }, status: "ok" }, "signed.payload.totalCount"],
+ [{ signed: { payload: { ...p, at: "" } }, status: "ok" }, "signed.payload.at"],
+ [{ signed: { payload: p, keyId: "" }, status: "ok" }, "signed.keyId"],
+ [{ signed: { payload: p, keyId: "k", signature: "x".repeat(513) }, status: "ok" }, "signed.signature"],
+ [{ signed: { payload: p, keyId: "k", signature: "sig" }, status: "maybe" }, "status"],
+ [{ signed: { payload: p, keyId: "k", signature: "sig" }, status: "failed" }, "error: required"],
+ [{ signed: { payload: p, keyId: "k", signature: "sig" }, status: "failed", error: " " }, "error: required"],
+ [{ signed: { payload: p, keyId: "k", signature: "sig" }, status: "ok" }, "backendRef: required"],
+ [{ ...okBody(signed), backendRef: { ...REF, netuid: -1 } }, "backendRef.netuid"],
+ [{ ...okBody(signed), backendRef: { ...REF, netuid: 65536 } }, "backendRef.netuid"],
+ [{ ...okBody(signed), backendRef: { ...REF, blockNumber: 0 } }, "backendRef.blockNumber"],
+ [{ ...okBody(signed), backendRef: { ...REF, blockHash: "0x123" } }, "backendRef.blockHash"],
+ [{ ...okBody(signed), backendRef: { ...REF, hotkey: "" } }, "backendRef.hotkey"],
+ [{ ...okBody(signed), backendRef: { ...REF, hotkey: "h".repeat(65) } }, "backendRef.hotkey"],
+ ];
+ for (const [raw, expected] of cases) {
+ const parsed = parseBittensorAnchorReport(raw);
+ expect("error" in parsed, JSON.stringify(raw).slice(0, 80)).toBe(true);
+ if ("error" in parsed) expect(parsed.error).toContain(expected);
+ }
+ });
+
+ it("netuid 0 (the root subnet) is a valid netuid — the floor is 0, not 1", async () => {
+ const { signed } = await anchoredFixture();
+ const parsed = parseBittensorAnchorReport({ ...okBody(signed), backendRef: { ...REF, netuid: 0 } });
+ expect("report" in parsed).toBe(true);
+ });
+});
+
+describe("ingestBittensorAnchorReport (#9277) — the verification boundary the public log sits behind", () => {
+ it("records a fully verified ok report, and it appears in the PUBLIC attempt log as backend=bittensor", async () => {
+ const { env, signed } = await anchoredFixture();
+ const report: BittensorAnchorReport = { signed, status: "ok", backendRef: { ...REF } };
+ expect(await ingestBittensorAnchorReport(env, report)).toEqual({ recorded: true, status: "ok" });
+ const listed = await loadPublicLedgerAnchors(env, { backend: "bittensor" });
+ expect(listed.anchors).toHaveLength(1);
+ expect(listed.anchors[0]).toMatchObject({ backend: "bittensor", status: "ok", seq: signed.payload.seq, rowHash: signed.payload.rowHash, backendRef: REF });
+ });
+
+ it("REGRESSION: an ok report signed by an UNPUBLISHED key is refused — a bearer token alone cannot forge corroboration", async () => {
+ const { env, signed } = await anchoredFixture();
+ const rogue = await generateKeypair();
+ const forged = await signLedgerAnchorPayload(signed.payload, rogue.privateKeyPem, rogue.keyId);
+ expect(await ingestBittensorAnchorReport(env, { signed: forged, status: "ok", backendRef: { ...REF } })).toEqual({ recorded: false, reason: "unknown_key" });
+ // Claiming the PUBLISHED keyId over a rogue signature fails the signature check instead.
+ const impersonating = { ...forged, keyId: signed.keyId };
+ expect(await ingestBittensorAnchorReport(env, { signed: impersonating, status: "ok", backendRef: { ...REF } })).toEqual({ recorded: false, reason: "bad_signature" });
+ expect((await loadPublicLedgerAnchors(env, { backend: "bittensor" })).anchors).toHaveLength(0);
+ });
+
+ it("REGRESSION: an ok report is bound to the LIVE chain — a wrong seq or a re-chained rowHash is refused", async () => {
+ const { env, signed, privateKeyPem, keyId } = await anchoredFixture();
+ // Seq beyond the live tip: no row to bind to.
+ const beyond = await signLedgerAnchorPayload({ ...signed.payload, seq: 999 }, privateKeyPem, keyId);
+ expect(await ingestBittensorAnchorReport(env, { signed: beyond, status: "ok", backendRef: { ...REF } })).toEqual({ recorded: false, reason: "row_not_found" });
+ // Right seq, wrong hash: exactly what anchoring exists to catch — a validly signed payload for a hash
+ // that is not THIS chain's row must never enter the log as corroboration.
+ const rechained = await signLedgerAnchorPayload({ ...signed.payload, rowHash: "b".repeat(64) }, privateKeyPem, keyId);
+ expect(await ingestBittensorAnchorReport(env, { signed: rechained, status: "ok", backendRef: { ...REF } })).toEqual({ recorded: false, reason: "row_hash_mismatch" });
+ });
+
+ it("INVARIANT: a FAILED report records without signature/row verification — a broken submitter must be publicly visible, not silently unloggable", async () => {
+ const { env, signed } = await anchoredFixture();
+ const rogue = await generateKeypair();
+ const unverifiable = await signLedgerAnchorPayload(signed.payload, rogue.privateKeyPem, rogue.keyId);
+ expect(await ingestBittensorAnchorReport(env, { signed: unverifiable, status: "failed", error: "subtensor RPC timeout after 3 attempts" })).toEqual({ recorded: true, status: "failed" });
+ const listed = await loadPublicLedgerAnchors(env, { backend: "bittensor" });
+ expect(listed.anchors).toHaveLength(1);
+ expect(listed.anchors[0]).toMatchObject({ backend: "bittensor", status: "failed", error: "subtensor RPC timeout after 3 attempts", backendRef: null });
+ });
+
+ it("payload constants round-trip: the fixture's payload is exactly the shape the parser demands", async () => {
+ const { signed } = await anchoredFixture();
+ expect(signed.payload.v).toBe(LEDGER_ANCHOR_PAYLOAD_VERSION);
+ expect(signed.payload.ledger).toBe(LEDGER_ANCHOR_LEDGER_ID);
+ const parsed = parseBittensorAnchorReport(okBody(signed));
+ expect("report" in parsed).toBe(true);
+ });
+});
+
+describe("#9277 routes — the submitter's two HTTP touchpoints", () => {
+ it("GET anchor-payload serves a freshly signed checkpoint an outside verifier can check, and degrades honestly", async () => {
+ const app = createApp();
+ const { privateKeyPem, publicKeySpki, keyId } = await generateKeypair();
+ // Unconfigured signing: honest 404, not a crash and not an unsigned payload.
+ const bare = createTestEnv();
+ expect((await app.request("/v1/public/decision-ledger/anchor-payload", {}, bare)).status).toBe(404);
+ // Configured but EMPTY ledger: nothing to anchor yet.
+ const env = createTestEnv({
+ LOOPOVER_LEDGER_ANCHOR_KEYS: JSON.stringify([{ keyId, publicKeySpki, notBefore: "2026-01-01T00:00:00.000Z", notAfter: null }]),
+ LOOPOVER_LEDGER_ANCHOR_PRIVATE_KEY: privateKeyPem,
+ });
+ expect((await app.request("/v1/public/decision-ledger/anchor-payload", {}, env)).status).toBe(404);
+ // With a real row: unauthenticated 200 whose signature verifies against the PUBLISHED key — the exact
+ // check the off-Worker submitter (or anyone) runs before committing the hash on-chain.
+ await appendDecisionLedger(env, "record:o/r#1@sha1", "d".repeat(64));
+ const res = await app.request("/v1/public/decision-ledger/anchor-payload", {}, env);
+ expect(res.status).toBe(200);
+ expect(res.headers.get("cache-control")).toBe("no-store");
+ const body = (await res.json()) as { signed: SignedLedgerAnchor; signingInput: string };
+ expect(body.signed.payload.seq).toBe(1);
+ expect(body.signingInput).toContain(LEDGER_ANCHOR_LEDGER_ID);
+ const key = anchorKeyById(parseAnchorPublicKeys(env.LOOPOVER_LEDGER_ANCHOR_KEYS), body.signed.keyId);
+ expect(key && (await verifyLedgerAnchorSignature(body.signed, key.publicKeySpki))).toBe(true);
+ });
+
+ it("POST anchor-attempts FAILS CLOSED with no token configured, rejects a wrong bearer, and accepts a verified report end-to-end", async () => {
+ const app = createApp();
+ const { env, signed } = await anchoredFixture();
+ const post = (target: Env, headers: Record, body: unknown) =>
+ app.request("/v1/decision-ledger/anchor-attempts", { method: "POST", body: JSON.stringify(body), headers: { "content-type": "application/json", ...headers } }, target);
+ // Unset token: rejected — an unconfigured collector never accepts anonymous writes.
+ expect((await post(env, {}, okBody(signed))).status).toBe(401);
+ (env as { LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN?: string }).LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN = "s3cret";
+ expect((await post(env, { authorization: "Bearer nope" }, okBody(signed))).status).toBe(401);
+ // An oversize body is refused at the byte ceiling before any parsing.
+ const oversize = await app.request(
+ "/v1/decision-ledger/anchor-attempts",
+ { method: "POST", body: "x", headers: { "content-type": "application/json", authorization: "Bearer s3cret", "content-length": "2097152" } },
+ env,
+ );
+ expect(oversize.status).toBe(413);
+ // Malformed JSON and malformed reports are the route's own 400s, with the parser's named reason.
+ const badJson = await app.request("/v1/decision-ledger/anchor-attempts", { method: "POST", body: "{nope", headers: { "content-type": "application/json", authorization: "Bearer s3cret" } }, env);
+ expect(badJson.status).toBe(400);
+ // An EMPTY body is invalid JSON too, not a crash on undefined.
+ const emptyBody = await app.request("/v1/decision-ledger/anchor-attempts", { method: "POST", headers: { authorization: "Bearer s3cret" } }, env);
+ expect(emptyBody.status).toBe(400);
+ const badReport = await post(env, { authorization: "Bearer s3cret" }, { status: "ok" });
+ expect(badReport.status).toBe(400);
+ expect(((await badReport.json()) as { detail: string }).detail).toContain("signed: missing");
+ // An authenticated but UNVERIFIABLE ok report is 422 with the named reason — never recorded.
+ const rogue = await generateKeypair();
+ const forged = await signLedgerAnchorPayload(signed.payload, rogue.privateKeyPem, rogue.keyId);
+ const refused = await post(env, { authorization: "Bearer s3cret" }, okBody(forged));
+ expect(refused.status).toBe(422);
+ expect(((await refused.json()) as { error: string }).error).toBe("unknown_key");
+ // The genuine report lands, and the PUBLIC listing (filter widened to bittensor) serves it.
+ const accepted = await post(env, { authorization: "Bearer s3cret" }, okBody(signed));
+ expect(accepted.status).toBe(200);
+ expect(await accepted.json()).toEqual({ recorded: true, status: "ok" });
+ const listing = await app.request("/v1/public/decision-ledger/anchors?backend=bittensor", {}, env);
+ const listed = (await listing.json()) as { anchors: Array<{ backend: string; status: string }> };
+ expect(listed.anchors).toHaveLength(1);
+ expect(listed.anchors[0]).toMatchObject({ backend: "bittensor", status: "ok" });
+ // The widened filter keeps every sibling arm intact: named backends still filter, junk still means "all".
+ for (const [query, expected] of [["?backend=rekor", 0], ["?backend=git", 0], ["?backend=ots", 0], ["?backend=nonsense", 1], ["", 1]] as const) {
+ const page = await app.request(`/v1/public/decision-ledger/anchors${query}`, {}, env);
+ expect(((await page.json()) as { anchors: unknown[] }).anchors).toHaveLength(expected);
+ }
+ });
+});
From ec748353b0fe60da50edc3713da76392f8984f95 Mon Sep 17 00:00:00 2001
From: JSONbored <49853598+JSONbored@users.noreply.github.com>
Date: Tue, 28 Jul 2026 14:50:53 -0700
Subject: [PATCH 2/2] feat(openapi): describe the two #9277 anchoring routes
and widen the backend filter
The route/spec ratchet caught both new routes as undescribed -- correctly:
they exist in createApp() with no OpenAPI operation, which is exactly what
that check is for.
- GET /v1/public/decision-ledger/anchor-payload: the signed checkpoint an
off-Worker submitter commits, with the 404 arms (signing unconfigured,
empty ledger) described as the honest degrades they are.
- POST /v1/decision-ledger/anchor-attempts: the report ingest, with every
refusal documented -- notably the 401 covering an unconfigured token
(fails closed) and the 422 naming all four unverifiable outcomes, since
those are what a submitter integrates against.
- The anchors listing's `backend` filter gains 'bittensor', matching the
widened CHECK constraint.
---
apps/loopover-ui/public/openapi.json | 46 +++++++++++++++++++++++++++-
src/openapi/spec.ts | 27 +++++++++++++++-
2 files changed, 71 insertions(+), 2 deletions(-)
diff --git a/apps/loopover-ui/public/openapi.json b/apps/loopover-ui/public/openapi.json
index 4db371b43..a84890ed1 100644
--- a/apps/loopover-ui/public/openapi.json
+++ b/apps/loopover-ui/public/openapi.json
@@ -20766,7 +20766,8 @@
"enum": [
"rekor",
"git",
- "ots"
+ "ots",
+ "bittensor"
]
},
"required": false,
@@ -26208,6 +26209,49 @@
}
}
}
+ },
+ "/v1/public/decision-ledger/anchor-payload": {
+ "get": {
+ "operationId": "getPublicDecisionLedgerAnchorPayload",
+ "tags": [
+ "Public"
+ ],
+ "summary": "The current ledger tip as a freshly signed checkpoint, for an external anchoring submitter to commit",
+ "responses": {
+ "200": {
+ "description": "{ signed: { payload, keyId, signature }, signingInput } — `sha256(signingInput)` is the exact 32 bytes an on-chain commitment holds. Never cached: `payload.at` is minted per call"
+ },
+ "404": {
+ "description": "Anchor signing is not configured, or the ledger is empty — nothing is claimed to be anchorable yet"
+ }
+ }
+ }
+ },
+ "/v1/decision-ledger/anchor-attempts": {
+ "post": {
+ "operationId": "reportDecisionLedgerAnchorAttempt",
+ "tags": [
+ "Public"
+ ],
+ "summary": "Report one off-Worker anchoring attempt (success or failure) into the public attempt log",
+ "responses": {
+ "200": {
+ "description": "{ recorded: true, status: 'ok' | 'failed' }"
+ },
+ "400": {
+ "description": "Unparseable body, or a report whose named field failed validation"
+ },
+ "401": {
+ "description": "Missing or wrong bearer token; also returned when no report token is configured (fails closed)"
+ },
+ "413": {
+ "description": "Body exceeded the ingest ceiling"
+ },
+ "422": {
+ "description": "Authenticated but unverifiable: unknown_key, bad_signature, row_not_found, or row_hash_mismatch — an `ok` report must verify against a published key AND match the live chain row"
+ }
+ }
+ }
}
},
"servers": [
diff --git a/src/openapi/spec.ts b/src/openapi/spec.ts
index c2c08cb89..83f03ed76 100644
--- a/src/openapi/spec.ts
+++ b/src/openapi/spec.ts
@@ -1901,11 +1901,36 @@ export function buildOpenApiSpec() {
operationId: "listPublicDecisionLedgerAnchors",
tags: ["Public"],
summary: "Every external anchoring attempt, success and failure, paginated newest-first — anchoring's own health as a public fact",
- request: { query: z.object({ backend: z.enum(["rekor", "git", "ots"]).optional(), before: z.string().optional(), limit: z.string().optional() }) },
+ request: { query: z.object({ backend: z.enum(["rekor", "git", "ots", "bittensor"]).optional(), before: z.string().optional(), limit: z.string().optional() }) },
responses: {
200: { description: "{ anchors: [{ id, seq, rowHash, keyId, backend, backendRef, status, error, createdAt }], nextBefore } — a failed attempt is returned identically to a successful one, never filtered out or reshaped" },
},
});
+ registry.registerPath({
+ method: "get",
+ path: "/v1/public/decision-ledger/anchor-payload",
+ operationId: "getPublicDecisionLedgerAnchorPayload",
+ tags: ["Public"],
+ summary: "The current ledger tip as a freshly signed checkpoint, for an external anchoring submitter to commit",
+ responses: {
+ 200: { description: "{ signed: { payload, keyId, signature }, signingInput } — `sha256(signingInput)` is the exact 32 bytes an on-chain commitment holds. Never cached: `payload.at` is minted per call" },
+ 404: { description: "Anchor signing is not configured, or the ledger is empty — nothing is claimed to be anchorable yet" },
+ },
+ });
+ registry.registerPath({
+ method: "post",
+ path: "/v1/decision-ledger/anchor-attempts",
+ operationId: "reportDecisionLedgerAnchorAttempt",
+ tags: ["Public"],
+ summary: "Report one off-Worker anchoring attempt (success or failure) into the public attempt log",
+ responses: {
+ 200: { description: "{ recorded: true, status: 'ok' | 'failed' }" },
+ 400: { description: "Unparseable body, or a report whose named field failed validation" },
+ 401: { description: "Missing or wrong bearer token; also returned when no report token is configured (fails closed)" },
+ 413: { description: "Body exceeded the ingest ceiling" },
+ 422: { description: "Authenticated but unverifiable: unknown_key, bad_signature, row_not_found, or row_hash_mismatch — an `ok` report must verify against a published key AND match the live chain row" },
+ },
+ });
registry.registerPath({
method: "get",
path: "/v1/public/decision-records/{owner}/{repo}/{pull}",