Skip to content

feat(proof): public per-repo proof summary, endpoint and README badge (#9569) - #9608

Open
JSONbored wants to merge 3 commits into
mainfrom
feat/proof-page-9569
Open

feat(proof): public per-repo proof summary, endpoint and README badge (#9569)#9608
JSONbored wants to merge 3 commits into
mainfrom
feat/proof-page-9569

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

What

The shareable, unauthenticated twin of the in-app trust panel. One composition serves both, so the public page and #9193's panel can't disagree about a figure — which is precisely the property this page exists to demonstrate.

The privacy boundary is structural, not a filter

Every field is built by naming it, never by spreading a wider object. A blocklist has to anticipate every field a future upstream type might grow, and silently leaks the one it didn't; an allowlisted shape can't leak a field nobody wrote down. Tested by feeding hostile records carrying hotkey/walletAddress/rewardTao/trustScore/privateRank and asserting none of it reaches the serialized page — while the named fields do come through, 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 — an explicit insufficient_data state that still publishes the count, because "we have 7 decisions, too few to claim a rate" is more honest than hiding both. 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 doesn't exist. The verification-contract boundary statement travels in the payload, so a screenshot or embed can't shed it the way a footer caption can.

The badge reports the ledger's state, not an accuracy percentage — a badge is a one-glance claim, and an accuracy number without the interval that makes it honest doesn't fit in one. Disabled and errored both render a neutral SVG, because a broken image in a README is worse than an honest "unavailable".

The opt-out decision (requirement 6)

Recorded in the module beside the code that implements it, not only here:

Opt-out per repo, default ON once the operator's fleet-wide flag (default OFF) is on. Every figure is already publicly fetchable through /v1/public/decision-ledger/verify, /…/anchors and /…/decision-records/… — gating a page over data anyone can already curl adds friction without privacy, and makes a verification story look less confident than it is. The per-repo switch still exists because a page is a genuinely different artifact from an API: discoverable, linkable, indexable, 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 hasn't, which keeps the fleet switch a real switch.

A real bug the tests caught

DB.prepare() throws synchronously on a driver-level failure, so my .catch() chain never ran — a D1 outage would have 503'd the entire public page instead of degrading section by section. Each section is now a real try/catch. That's the difference between the fail-safe-per-section contract being documented and being true, and there's a test that breaks prepare outright plus one for a driver returning no results array.

Tests (19, 100% of branches on both new modules)

Accuracy with coverage+interval and the floor in both directions; failed-attempt-is-not-an-anchor and newest-wins regardless of list order; all four ledger states including the unknown-position break; the privacy-boundary regression; sample bounding and the in-payload caveat; every badge message and color including the neutral not-yet-decided case; the flag's truthy parsing and the full opt-out matrix; end-to-end route 404-while-off / 200-when-on with cache headers; per-section degradation; and a real recorded anchor flowing through to anchored.

Auth exemptions and OpenAPI operations are added in this PR alongside the routes, per the #9120 lesson.

Backend half of #9569 — the /proof/:owner/:repo UI route renders this payload and lands separately, so I've left the issue open.

…#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.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 28, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
❌ Deployment failed
View logs
loopover-ui 26f90ca Jul 29 2026, 04:31 AM

@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Important

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏳ LoopOver is waiting…

LoopOver has seen this pull request and is waiting on CI checks to finish before reviewing it. This comment will update once the review runs.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed · 🟨 Waiting

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 3.84kB (0.05%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.8MB 3.84kB (0.05%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-D1tDF7Rr.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-CNWwAv2x.js (New) 914.34kB 914.34kB 100.0% 🚀
openapi.json 2.36kB 715.21kB 0.33%
assets/docs.fumadocs-spike-api-reference-DJy0NhTQ.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-vBgq2Zuz.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-Cj4MxPKE.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-DDHHJFJ2.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-C7GnQuAS.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-BpjfYM0J.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-CEpbkiyr.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-BgHqpQFd.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-CCLo6HHe.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-hN7w8vg_.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-BbpqovTh.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DSLv4XCf.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-C93IoHqJ.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-8hp8iNZG.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-BBn9EGJ6.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-B_dW90mY.js (New) 10.73kB 10.73kB 100.0% 🚀
assets/app.audit-vPGZYw3b.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-CzqwBl1m.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-Cnjr0IiE.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-Cwn0Ebr3.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-D65IXeD_.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-C3hMr1PO.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-BxkFdJ9i.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-BtlLFpNC.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-Cp8MkAyN.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-Bqwyje0Y.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-BX5Q-xCG.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-eCLBckN-.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-DA5i7FFN.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-7jo8c6Vw.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-Ca3vGjKZ.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-GURAES2b.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-CF6B83hU.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-TC-rAJu1.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-FwF2Q0ZE.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-CG8EFThB.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-Cm-EiPk1.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-BHfCtPKz.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-CS-UJtPV.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-4ejLgkcV.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-BgjfX1j7.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-Dw96KjVG.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-D8tRRQcJ.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-CalS8Jak.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-CmBvnKKJ.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-BAkKmm4t.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-C4fz3BDR.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-FXflfVoB.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-CA55-hDW.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-CwG-mJQL.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-DmiKiGnt.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-Dj-kSKwa.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-BS_Rl5Pd.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-CqAgu00b.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-DSpkwav7.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-BgXfrep0.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-BOHTdFk-.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-BfEq5pNg.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-DyHZrfCS.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-C6VUvb6O.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-DvvQWC8e.js (Deleted) -912.86kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-lPsQ1LL2.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-D7Hg_rQe.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-DUw_M3F1.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-DpqqOuFX.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-C_uoN151.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-Ba96T4-l.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-BQh_XmUH.js (Deleted) -27.92kB 0 bytes -100.0% 🗑️
assets/app-a0fOAcmU.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-CWYEqp6N.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-DbIi1SDR.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-C5uFmfbI.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-CeRGL1cS.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-CladSRn4.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-beIuPh91.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-DRrQcEpQ.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-BvmlZGNU.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-CZnDfwCO.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-CnV6VHSl.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-BQu1j4d3.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-Cc_RTMN0.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-CFm9nTwJ.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-DNVKg2Oc.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-AFaBBFG3.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-LTklf-O6.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-eafFwDTf.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-DICgsmy5.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-k8ouxTYV.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-JM-zdjlq.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-DnryJy60.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-DKqsEZ-K.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-DkSTujgi.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-B1V1fHa5.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-Da0cl6qr.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-D7664EKM.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-B98QLrzv.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-CO5WduYr.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-byz_q9OJ.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-7WtF-prZ.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-CN1NaazS.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-BrW6Yi9b.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-BwpNTrNw.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-Blyp1qLl.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-DMR-gWe4.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-BBdqtdU2.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-ADrtnRUJ.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-Qa_YampS.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-BF1bYAYf.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-CcT_8Nvp.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-D1D0ARVf.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-DwRfH1bQ.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-DkJqyivS.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-bqRgfqGW.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-BbDf2fUc.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-ZBr83Nbs.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-CdC_q2mc.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-Z_FhRYaD.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-BDf1wu4a.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-CUxgut0j.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-ZE8miIvD.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.55172% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.14%. Comparing base (3200abc) to head (63251f5).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/api/routes.ts 86.95% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9608      +/-   ##
==========================================
- Coverage   90.24%   89.14%   -1.10%     
==========================================
  Files         900      902       +2     
  Lines      112914   112332     -582     
  Branches    26776    26709      -67     
==========================================
- Hits       101896   100141    -1755     
- Misses       9687    11101    +1414     
+ Partials     1331     1090     -241     
Flag Coverage Δ
backend 94.04% <96.55%> (-1.49%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/api/proof-badge.ts 100.00% <100.00%> (ø)
src/auth/route-auth.ts 100.00% <100.00%> (ø)
src/openapi/spec.ts 99.31% <100.00%> (+<0.01%) ⬆️
src/review/proof-summary.ts 100.00% <100.00%> (ø)
src/api/routes.ts 95.50% <86.95%> (-0.08%) ⬇️

... and 19 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 29, 2026
…d to honor (#9569)

Review caught the real defect: both handlers called
isProofPageEnabledForRepo(c.env) with no second argument, so the
ProofPageRepoOverride documented at length in proof-summary.ts and in the
PR body was never loaded or passed. Every repo was effectively
opt-out-less once the fleet flag was on -- a gate that is described,
typed, and unit-tested as a pure function, but never reachable from the
surface it governs. That is the registered-but-unreachable class, and the
long comment made it worse rather than better by making it look done.

- Adds a real `publicProof:` focus-manifest block (engine parser + toJson
  + loader snapshot), mirroring `publicStats:`/`ops:`. Precedence is
  deliberately the opposite of those two: read from the TARGET repo's
  manifest rather than the operator's self-repo, because the thing being
  opted out of is that repo's own page.
- loadProofPageRepoOverride resolves it, degrading a failed manifest load
  to "no override" -- a broken manifest never takes a page DOWN, which is
  the failure direction worth accepting here and is now stated in the
  doc comment rather than left implicit.
- Both routes load the override BEFORE anything else, so a repo that
  turned its page off does not have its decision records queried to build
  a summary that will be discarded.
- Documents the block in .loopover.yml.example, including the precedence
  and the opt-out default.

Tests that would have caught it: a repo opting out in its manifest now
gets 404 from BOTH routes with the fleet flag on, while a different repo
in the same fleet still serves 200 (the opt-out is per repo, not a kill
switch); explicit opt-in and no-block-at-all both serve; and the resolver
is covered across absent/explicit/failing loads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant