Skip to content

Commit ca666b8

Browse files
committed
feat(proof): public per-repo proof summary, endpoint and README badge (#9569)
The shareable, unauthenticated twin of the in-app trust panel. One composition serves both, so the public page and #9193's panel cannot disagree about a figure -- which is the property the page exists to demonstrate. THE PRIVACY BOUNDARY IS STRUCTURAL. Every field is built by NAMING it, never by filtering a wider object. A blocklist has to anticipate every field a future upstream type might grow and silently leaks the one it did not; an allowlisted shape cannot leak a field nobody wrote down. Tested by feeding hostile records carrying hotkey/wallet/reward/trust-score/private- rank and asserting none of it reaches the serialized page -- while the named fields do, so the test proves allowlisting rather than an empty object. NEVER A BARE SCALAR. Any accuracy figure carries its coverage and a Wilson interval; below a 20-decision floor there is no rate at all, only an explicit insufficient_data state that still publishes the count. A perfect record over 19 decisions must not render as 100%. Wilson rather than Wald because a gate metric lives near p->1, exactly where Wald claims impossible certainty. HONEST BOUNDARY STATES. An empty ledger is `empty`, not `verified` -- different claims. A failed read is `unavailable`, not `broken`, which would accuse the operator of tampering. A FAILED anchor attempt is not an anchor: the public attempt log is where failures are legible, and presenting one here would claim corroboration that does not exist. The verification-contract boundary statement travels IN the payload, so a screenshot or embed cannot shed it the way a footer caption can. The badge reports the LEDGER's state rather than an accuracy percentage: a badge is a one-glance claim, and an accuracy number without the interval that makes it honest does not fit in one. Disabled and errored both render a neutral SVG -- a broken image in a README is worse than an honest "unavailable". DECISION (requirement 6), recorded beside the code that implements it: the page is opt-OUT per repo, default ON once the operator's fleet-wide flag (default OFF) is on. Every figure is already publicly fetchable through the ledger-verify / anchors / decision-record endpoints, so gating a page over it would add friction without privacy. The per-repo switch still exists because a page is a different artifact from an API -- discoverable, linkable, and it markets a repo's numbers whether or not the maintainer wants that. A repo can opt out but cannot opt IN when the operator has not, which keeps the fleet switch a real switch. Found and fixed while testing: `DB.prepare()` throws SYNCHRONOUSLY on a driver-level failure, so the `.catch()` chain never ran and a D1 outage would have 503'd the whole public page instead of degrading. Each section is now a real try/catch, which is the difference between the fail-safe-per-section contract being documented and being true. Backend half of #9569; the /proof/:owner/:repo UI route renders this payload and lands separately.
1 parent 1819298 commit ca666b8

8 files changed

Lines changed: 835 additions & 0 deletions

File tree

apps/loopover-ui/public/openapi.json

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26252,6 +26252,82 @@
2625226252
}
2625326253
}
2625426254
}
26255+
},
26256+
"/v1/public/repos/{owner}/{repo}/proof": {
26257+
"get": {
26258+
"operationId": "getPublicRepoProof",
26259+
"tags": [
26260+
"Public"
26261+
],
26262+
"summary": "Public proof summary for one repo — ledger status, anchor, calibration with coverage and interval, sample records",
26263+
"parameters": [
26264+
{
26265+
"schema": {
26266+
"type": "string"
26267+
},
26268+
"required": true,
26269+
"name": "owner",
26270+
"in": "path"
26271+
},
26272+
{
26273+
"schema": {
26274+
"type": "string"
26275+
},
26276+
"required": true,
26277+
"name": "repo",
26278+
"in": "path"
26279+
}
26280+
],
26281+
"responses": {
26282+
"200": {
26283+
"description": "ProofSummary. Any accuracy figure carries its coverage AND a Wilson confidence interval; below the sample floor it is an explicit `insufficient_data` state, never a bare percentage. Carries the verification-boundary statement in the payload"
26284+
},
26285+
"404": {
26286+
"description": "The proof page is disabled fleet-wide, or this repo has opted out"
26287+
},
26288+
"503": {
26289+
"description": "Composition failed — no partial or fabricated summary is served"
26290+
}
26291+
}
26292+
}
26293+
},
26294+
"/v1/public/repos/{owner}/{repo}/proof-badge.svg": {
26295+
"get": {
26296+
"operationId": "getPublicRepoProofBadge",
26297+
"tags": [
26298+
"Public"
26299+
],
26300+
"summary": "README badge for the proof page — reports the ledger's state, never a bare accuracy percentage",
26301+
"parameters": [
26302+
{
26303+
"schema": {
26304+
"type": "string"
26305+
},
26306+
"required": true,
26307+
"name": "owner",
26308+
"in": "path"
26309+
},
26310+
{
26311+
"schema": {
26312+
"type": "string"
26313+
},
26314+
"required": true,
26315+
"name": "repo",
26316+
"in": "path"
26317+
}
26318+
],
26319+
"responses": {
26320+
"200": {
26321+
"description": "SVG badge"
26322+
},
26323+
"404": {
26324+
"description": "Disabled or opted out — still an SVG (a neutral 'unavailable' badge), so a README never shows a broken image"
26325+
},
26326+
"503": {
26327+
"description": "Same neutral SVG on an internal error"
26328+
}
26329+
}
26330+
}
2625526331
}
2625626332
},
2625726333
"servers": [

src/api/proof-badge.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// README badge for the public proof page (#9569). Renders through the SAME flat-badge primitive and the
2+
// SAME XML escaping as the repo-quality badge (`./badge.ts`), so an unauthenticated, embeddable surface
3+
// cannot become an injection vector and the two badges cannot drift apart visually.
4+
import { escapeXml } from "./badge";
5+
import { buildProofBadgeColor, buildProofBadgeMessage, type ProofSummary } from "../review/proof-summary";
6+
7+
export const PROOF_BADGE_LABEL = "loopover proof";
8+
const UNAVAILABLE_COLOR = "#9e9e9e";
9+
10+
/** `null` renders the neutral unavailable badge — used for both the flag-off 404 and the error 503, since
11+
* from a README's point of view those are the same thing: no claim is being made right now. */
12+
export function renderProofBadgeSvg(summary: ProofSummary | null): string {
13+
const message = summary ? buildProofBadgeMessage(summary) : "unavailable";
14+
const color = summary ? buildProofBadgeColor(summary) : UNAVAILABLE_COLOR;
15+
return renderFlatBadge(PROOF_BADGE_LABEL, message, color);
16+
}
17+
18+
function renderFlatBadge(label: string, message: string, color: string): string {
19+
const labelText = escapeXml(label);
20+
const messageText = escapeXml(message);
21+
const labelWidth = textWidth(label);
22+
const messageWidth = textWidth(message);
23+
const totalWidth = labelWidth + messageWidth;
24+
return [
25+
`<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="20" role="img" aria-label="${labelText}: ${messageText}">`,
26+
`<title>${labelText}: ${messageText}</title>`,
27+
`<rect width="${totalWidth}" height="20" rx="3" fill="#fff"/>`,
28+
`<rect width="${labelWidth}" height="20" rx="3" fill="#24292f"/>`,
29+
`<rect x="${labelWidth}" width="${messageWidth}" height="20" rx="3" fill="${escapeXml(color)}"/>`,
30+
`<g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">`,
31+
`<text x="${labelWidth / 2}" y="14">${labelText}</text>`,
32+
`<text x="${labelWidth + messageWidth / 2}" y="14">${messageText}</text>`,
33+
`</g></svg>`,
34+
].join("");
35+
}
36+
37+
function textWidth(text: string): number {
38+
return Math.max(40, Math.round(text.length * 6.5) + 10);
39+
}

src/api/routes.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ import { isRagEnabled } from "../review/rag-wire";
308308
import { loadDecisionLedgerTip, loadPublicDecisionRecord, loadPublicLedgerRow, verifyDecisionLedger } from "../review/decision-record";
309309
import { buildEvalScoreRecordsFromRulePrecision, filterEvalScoreRecords } from "../review/eval-score-records";
310310
import { anchorSigningInput, buildLedgerAnchorPayload, currentAnchorKey, parseAnchorPublicKeys, signLedgerAnchorPayload } from "../review/ledger-anchor";
311+
import { isProofPageEnabledForRepo, loadProofSummary } from "../review/proof-summary";
312+
import { renderProofBadgeSvg } from "./proof-badge";
311313
import { ingestBittensorAnchorReport, parseBittensorAnchorReport } from "../review/ledger-anchor-bittensor";
312314
import { loadPublicLedgerAnchors } from "../review/ledger-anchor-persistence";
313315
import { getPublicStats, isPublicStatsEnabled, resolvePublicStatsManifestOverride } from "../review/public-stats";
@@ -1352,6 +1354,47 @@ export function createApp() {
13521354
return c.json(result);
13531355
});
13541356

1357+
// #9569: the public proof page's data, and its README badge. Read-only over the SAME public sources the
1358+
// standalone endpoints already serve -- no new verification mechanism and no new SQL surface. Two gates
1359+
// must allow (see isProofPageEnabledForRepo's recorded opt-out decision): the operator's fleet-wide flag,
1360+
// default OFF like every sibling public surface, and the repo's own opt-out.
1361+
app.get("/v1/public/repos/:owner/:repo/proof", async (c) => {
1362+
if (!isProofPageEnabledForRepo(c.env)) return c.json({ error: "not_found" }, 404);
1363+
const repoFullName = `${c.req.param("owner")}/${c.req.param("repo")}`;
1364+
try {
1365+
const summary = await loadProofSummary(c.env, repoFullName, {
1366+
verifyLedger: (env) => verifyDecisionLedger(env),
1367+
loadAnchors: (env) => loadPublicLedgerAnchors(env, { limit: 20 }),
1368+
});
1369+
c.header("Cache-Control", "public, max-age=60, stale-while-revalidate=300");
1370+
return c.json(summary);
1371+
} catch {
1372+
return c.json({ error: "unavailable" }, 503);
1373+
}
1374+
});
1375+
1376+
// The badge deliberately reports the LEDGER's state rather than an accuracy percentage: a badge is a
1377+
// one-glance claim, and an accuracy number without the interval that makes it honest (which does not fit
1378+
// in a badge) is exactly the bare scalar the proof summary refuses to publish.
1379+
app.get("/v1/public/repos/:owner/:repo/proof-badge.svg", async (c) => {
1380+
c.header("Content-Type", "image/svg+xml; charset=utf-8");
1381+
if (!isProofPageEnabledForRepo(c.env)) {
1382+
c.header("Cache-Control", "public, max-age=300");
1383+
return c.body(renderProofBadgeSvg(null), 404);
1384+
}
1385+
try {
1386+
const summary = await loadProofSummary(c.env, `${c.req.param("owner")}/${c.req.param("repo")}`, {
1387+
verifyLedger: (env) => verifyDecisionLedger(env),
1388+
loadAnchors: (env) => loadPublicLedgerAnchors(env, { limit: 20 }),
1389+
});
1390+
c.header("Cache-Control", "public, max-age=600, stale-while-revalidate=86400");
1391+
return c.body(renderProofBadgeSvg(summary));
1392+
} catch {
1393+
c.header("Cache-Control", "public, max-age=300");
1394+
return c.body(renderProofBadgeSvg(null), 503);
1395+
}
1396+
});
1397+
13551398
// #9277 (epic #9267): the current tip's SIGNED checkpoint, for the operator's off-Worker Bittensor
13561399
// commitment submitter to fetch and commit on-chain (sha256 of `signingInput` is the exact 32 bytes
13571400
// `Data::Sha256` holds). Unauthenticated like every /v1/public/* sibling: it is the same payload the

src/auth/route-auth.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ export function requiresApiToken(path: string): boolean {
4848
if (path === "/v1/public/decision-ledger/anchor-key") return false;
4949
// #9271: the public anchor-attempt listing, added in the SAME PR as its route.
5050
if (path === "/v1/public/decision-ledger/anchors") return false;
51+
// #9569: the public proof page's data and its README badge. Unauthenticated by design -- every figure
52+
// they render is already served unauthenticated by the ledger-verify / anchors / decision-record routes
53+
// above; this is a composition, not a new disclosure. Added in the SAME PR as the routes, per #9120.
54+
if (/^\/v1\/public\/repos\/[^/]+\/[^/]+\/proof$/.test(path)) return false;
55+
if (/^\/v1\/public\/repos\/[^/]+\/[^/]+\/proof-badge\.svg$/.test(path)) return false;
5156
// #9277: the current tip's signed checkpoint, for the operator's off-Worker Bittensor submitter (and
5257
// anyone else — it is the same payload the Rekor/git backends already publish externally). Added in the
5358
// SAME PR as its route, per the #9120 lesson.

src/env.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,10 @@ declare global {
601601
* worker is byte-identical to today. Exposes review-disposition counts + a reversal-grounded accuracy
602602
* percentage + an estimated-time-saved figure ONLY — never PR content, authors, scores, or reward internals.
603603
* See review/public-stats.ts. */
604+
/** #9569: fleet-wide switch for the public proof page (`/proof/:owner/:repo`) and its badge. Default
605+
* OFF like every sibling public surface. A repo can opt OUT via its own manifest, but cannot opt IN
606+
* when this is off -- see isProofPageEnabledForRepo's recorded decision in review/proof-summary.ts. */
607+
LOOPOVER_PUBLIC_PROOF?: string;
604608
LOOPOVER_PUBLIC_STATS?: string;
605609
/** Proof of Power (#1059): comma-separated allowlist of repo full-names ("owner/repo") whose OWN historical
606610
* review ledger (audit_events "published a review surface" + pull_requests terminal state) counts toward

src/openapi/spec.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,6 +1906,32 @@ export function buildOpenApiSpec() {
19061906
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" },
19071907
},
19081908
});
1909+
registry.registerPath({
1910+
method: "get",
1911+
path: "/v1/public/repos/{owner}/{repo}/proof",
1912+
operationId: "getPublicRepoProof",
1913+
tags: ["Public"],
1914+
summary: "Public proof summary for one repo — ledger status, anchor, calibration with coverage and interval, sample records",
1915+
request: { params: z.object({ owner: z.string(), repo: z.string() }) },
1916+
responses: {
1917+
200: { description: "ProofSummary. Any accuracy figure carries its coverage AND a Wilson confidence interval; below the sample floor it is an explicit `insufficient_data` state, never a bare percentage. Carries the verification-boundary statement in the payload" },
1918+
404: { description: "The proof page is disabled fleet-wide, or this repo has opted out" },
1919+
503: { description: "Composition failed — no partial or fabricated summary is served" },
1920+
},
1921+
});
1922+
registry.registerPath({
1923+
method: "get",
1924+
path: "/v1/public/repos/{owner}/{repo}/proof-badge.svg",
1925+
operationId: "getPublicRepoProofBadge",
1926+
tags: ["Public"],
1927+
summary: "README badge for the proof page — reports the ledger's state, never a bare accuracy percentage",
1928+
request: { params: z.object({ owner: z.string(), repo: z.string() }) },
1929+
responses: {
1930+
200: { description: "SVG badge" },
1931+
404: { description: "Disabled or opted out — still an SVG (a neutral 'unavailable' badge), so a README never shows a broken image" },
1932+
503: { description: "Same neutral SVG on an internal error" },
1933+
},
1934+
});
19091935
registry.registerPath({
19101936
method: "get",
19111937
path: "/v1/public/decision-ledger/anchor-payload",

0 commit comments

Comments
 (0)