Skip to content

feat(ledger): Bittensor on-chain anchor backend — repo-side glue for the off-Worker submitter (#9277) - #9594

Merged
loopover-orb[bot] merged 2 commits into
mainfrom
feat/bittensor-anchor-9277
Jul 28, 2026
Merged

feat(ledger): Bittensor on-chain anchor backend — repo-side glue for the off-Worker submitter (#9277)#9594
loopover-orb[bot] merged 2 commits into
mainfrom
feat/bittensor-anchor-9277

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

What

The fourth, optional, Gittensor/SN74-audience anchoring backend for the decision ledger (epic #9267's last open sub-issue). Per the issue's own architecture: the on-chain submission never runs in this Worker — a small process on the operator's 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 PR is the validation boundary that report crosses.

  • GET /v1/public/decision-ledger/anchor-payload — the current tip's freshly signed checkpoint plus the exact signingInput bytes to hash. Unauthenticated (it's the same payload the Rekor/git backends already publish externally on every checkpoint); no-store since at is minted per call. Honest degrades: 404 for unconfigured signing or an empty ledger.
  • POST /v1/decision-ledger/anchor-attempts — bearer-gated by LOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN, fails closed when unset (the isAuthorizedIngest posture). Authentication alone cannot forge corroboration: an ok report must (1) verify against a published anchor key and (2) match the live chain row at its seq — unknown_key / bad_signature / row_not_found / row_hash_mismatch are each a named 422 and nothing is recorded. A failed report skips those checks deliberately: a broken submitter is exactly what ledger: anchor persistence + public attempt log (success and failure both visible) #9271's public attempt log exists to make visible.
  • decision_ledger_anchors backend CHECK widened to 'bittensor' (migration 0201 rebuild-and-rename); 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 verifying an old anchor means querying archive state at that block, never current chain state. Documented step-by-step in what-you-can-verify.mdx, still clearly scoped as additive corroboration outside the default two-backend claim.

Tests (11, real ECDSA keys, real D1 rows)

  • Parser: complete ok/failed reports (block-hash lowercasing, 500-char error bound, netuid 0 valid), and a 24-case table where every malformed shape's rejection names the exact refused field.
  • Verification boundary: a verified report lands in the public log as backend: "bittensor"; a rogue-key report is refused (unknown_key), an impersonated keyId fails the signature check (bad_signature); wrong seq / re-chained rowHash are refused (row_not_found / row_hash_mismatch); a failed report records without verification, backendRef: null.
  • Routes end-to-end: payload endpoint 404s honestly then serves a checkpoint whose signature verifies against the published key exactly as an outsider would check it; report endpoint fails closed with no token, rejects a wrong bearer, 413s an oversize body, 400s empty/bad JSON with the parser's named reason, 422s an unverifiable report, accepts the genuine one; the widened listing filter keeps every sibling arm intact.
  • 100% of changed lines and branches.

Closes #9277 — and with it, every sub-issue of #9267.

@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-28 22:15:00 UTC

10 files · 1 AI reviewer · no blockers · readiness 98/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
Adds the repo-side glue for the fourth (optional) decision-ledger anchoring backend — Bittensor on-chain commitments — closing issue #9277 under epic #9267. The diff wires two new routes (unauthenticated signed-checkpoint GET, bearer-gated fails-closed attempt-report POST), a well-reasoned validation boundary (published-key + live-row binding for 'ok' reports, unchecked pass-through for 'failed' reports so a broken submitter stays visible), a CHECK-widening migration via the standard SQLite rebuild-and-rename, and matching route-auth/env.d.ts/docs updates. The test suite exercises real ECDSA signing, D1 rows, and both the parser and route layers with genuine forged-key/wrong-seq/hash-mismatch regression cases rather than fabricated ones.

Nits — 5 non-blocking
  • migrations/0201_ledger_anchor_bittensor.sql uses the same rebuild-and-rename idiom the file's own comment cites as standard for this repo; worth confirming (as with any such migration) that no in-flight deploy briefly reads the old decision_ledger_anchors table mid-migration, though this is a pre-existing pattern, not new risk introduced here.
  • src/review/ledger-anchor-bittensor.ts uses several bare numeric literals for length bounds (500, 512, 65535, 64) — consider named constants for readability, consistent with the review brief's note.
  • src/api/routes.ts's anchor-attempts POST handler doesn't distinguish an oversized-body 413 from a malformed-JSON 400 in the response body shape — minor, but a submitter debugging a failed report gets less detail than the 422 named-reason path gets.
  • routes.ts continues growing (now ~1399 lines per the size check) — not this PR's fault alone, but another data point for eventually splitting the ledger-anchor route registrations into their own module the way ledger-anchor-bittensor.ts itself was split out.
  • Consider extracting the repeated HTTP status literals (404/401/413/400/422) in the two new routes into named constants if this file's convention elsewhere already does so, for consistency with the rest of routes.ts.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9277
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 310 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 310 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The PR implements all core requirements: a repo-side glue module and routes for exposing the anchor payload and ingesting submitter reports with strict signature/row verification, a widened backend enum including 'bittensor' with a migration, env token wiring for the report endpoint, comprehensive tests, and docs describing the optional scoping and precise historical-retrieval procedure via archiv

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is registered but has no active allocation in the current snapshot.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 310 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 1 step in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Visual preview
Route Viewport Before (production) After (this PR's preview) Diff
/ desktop before /
before /
after /
after /
/ mobile before / (mobile)
before / (mobile)
after / (mobile)
after / (mobile)

Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy.

Scroll preview
Route Before (production) After (this PR's preview)
/ before / (scroll)
before / (scroll)
after / (scroll)
after / (scroll)

A short scroll-through clip (desktop) — click either thumbnail to open the full animation. Evidence for scroll-linked behavior a single screenshot can't show.

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


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

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

@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 Preview URL Updated (UTC)
✅ Deployment successful!
View logs
loopover-ui ec74835 Commit Preview URL

Branch Preview URL
Jul 28 2026, 09:57 PM

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bundle Report

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

Detailed changes
Bundle name Size Change
loopover-ui 7.79MB 6.38kB (0.08%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-D8Dj7YyG.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-DgX5HYmn.js (New) 912.86kB 912.86kB 100.0% 🚀
openapi.json 1.83kB 712.86kB 0.26%
assets/docs.fumadocs-spike-api-reference-DHZ63mnT.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-D0XRxWXc.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-CKVqXj52.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-BiUgLQNF.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/maintainer-panel-BE-s2qKp.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/what-you-can-verify-WYKa_oSR.js (New) 54.1kB 54.1kB 100.0% 🚀
assets/routes-BIIqSisu.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-BIwdq47g.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-Bn5KjUMo.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-BqoXHLzp.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-hgWaNYQQ.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-6vZbrege.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DVwCRakh.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-HktHL1uq.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-C_m8qXeX.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-B49R_Yi7.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-VY1DclTZ.js (New) 10.73kB 10.73kB 100.0% 🚀
assets/app.audit-CaFm8Eqs.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-i6cjBVeu.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-DsDK-yPw.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-BMvDsqDe.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-Q1yFlaDD.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-D-8wPXGb.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-CQG8L7mN.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-DuUBSq1O.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-F3oREzhU.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-uW4i6eVC.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-Mag_9Ek5.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-B0sE4AFV.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-DSuqq3FI.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-LvGaCYkk.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-CsRVj76t.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-sKqhVkg8.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-D8wd2Gd0.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-BylaM85l.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-XkgUdRhW.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-BfvmcGjW.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-DxfkU_Qm.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-nUgZiMyM.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-H7TNH-hk.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-Co1kG67d.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-BkgwmzcP.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-CnL2s3_D.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-DwIhNDUN.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-DX6rI0lD.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-_EzNdLDQ.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-DaONLAkf.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-CsaaI2U2.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-CzgYtdg9.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-DbIfNLkM.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-D2fv1DMg.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-1EbutALM.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-CQgBrikq.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-vw6Aln5Z.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-DMcaexhm.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-Ba4wK6Uv.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-lpWDG7jF.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-CrO53Kph.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-CFE2hDxO.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-DjH7H0qh.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-ClD5uxs8.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-DQgq5OSM.js (Deleted) -911.49kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-C0UJXd3W.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-BAkmyz2N.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-Cm8Tzpcj.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-HY28ZfK6.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-CV5l0RVc.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/what-you-can-verify-CiXBz4TS.js (Deleted) -50.92kB 0 bytes -100.0% 🗑️
assets/routes-C5VVDu7X.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-Deyi_wiR.js (Deleted) -27.92kB 0 bytes -100.0% 🗑️
assets/app-D_Dv4YdD.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-Dy1Rnzlr.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-DH5NnRmi.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-7TMmwQ1m.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-DwYtF28W.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-D8C_veIb.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-BOpUB7AR.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-D6GGRjRp.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-CvTa_YZi.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-CbteU9si.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-CXwnYcT2.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-1i1i4UQZ.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-DOe2OszS.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-CociCkFx.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-BVNwV8Zt.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-BVxUEli6.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-CAeX5F4v.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-DMePUsr5.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-DuX9s4h9.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-QjQzfLqm.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-YGOgpX7S.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-Br-kvME0.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-CD9duIYi.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-1ySSUtgu.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-B0UxI15m.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-CCsr-6Fs.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-C7b_HP1D.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-CxnF3aN5.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-BJ_EdN8j.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-NbCElz_t.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BCNJz3OU.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-DVviUgkt.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-BaIBn7R3.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-o5tpA7_S.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-DQw8M9V1.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-7t-KH7QG.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-MH_rerEw.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-w3nx_ZmL.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-DvM_GmWd.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-D6NNYx_T.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-DwuO4vEd.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-CZpxL0JA.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-DP-sV9U1.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-EOxf8X2i.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DDiQU4UF.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-C9TsYZ8e.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square-BZCF6OHX.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-DrZR2-DH.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-DZOb_f-N.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-B__w4wlD.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-Z6QeTmNw.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-DYcunweo.js (Deleted) -174 bytes 0 bytes -100.0% 🗑️

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 89.85%. Comparing base (26071f1) to head (ec74835).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #9594   +/-   ##
=======================================
  Coverage   89.85%   89.85%           
=======================================
  Files         880      881    +1     
  Lines      111175   111245   +70     
  Branches    26473    26506   +33     
=======================================
+ Hits        99891    99961   +70     
  Misses       9992     9992           
  Partials     1292     1292           
Flag Coverage Δ
backend 95.63% <100.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
src/api/routes.ts 95.62% <100.00%> (+0.03%) ⬆️
src/auth/route-auth.ts 100.00% <100.00%> (ø)
src/openapi/spec.ts 99.30% <100.00%> (+<0.01%) ⬆️
src/review/ledger-anchor-bittensor.ts 100.00% <100.00%> (ø)
src/review/ledger-anchor-persistence.ts 100.00% <ø> (ø)

…the off-Worker submitter (#9277)

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
…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.

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 355e029 into main Jul 28, 2026
11 checks passed
@loopover-orb
loopover-orb Bot deleted the feat/bittensor-anchor-9277 branch July 28, 2026 22:15
JSONbored added a commit that referenced this pull request Jul 29, 2026
…can deploy

Cloudflare Workers Builds has failed on every recent commit, which is the
deploy path for loopover-ui — no GitHub Actions workflow runs `wrangler deploy`
(ui-deploy.yml only validates). Production has consequently been serving stale
code: #9574's `fleetAccuracy.basis` and #9594's anchor-payload auth exemption
both landed on main and neither is live.

The build command is `npm run build:cloudflare` -> `npm ci && npm run ui:build`.
`ui:build` runs `turbo run build --filter=@loopover/engine`, then `ui:openapi`,
whose `scripts/write-ui-openapi.ts` reaches `src/openapi/schemas.ts` and imports
`@loopover/contract/dist/public-api.js`. Nothing in that chain builds
`@loopover/contract` — `@loopover/engine` does not depend on it — so on a clean
checkout the import resolves to a directory that was never emitted:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
    '/opt/buildhome/repo/node_modules/@loopover/contract/dist/public-api.js'
    imported from /opt/buildhome/repo/src/openapi/schemas.ts

It passes in `test:ci` only by accident of ordering: `npm run typecheck` runs
first, and the `//#typecheck` turbo task already carries an explicit
`@loopover/contract#build` edge — added for exactly this failure mode, and
documented in turbo.json as "the root package.json dependency does NOT create a
build edge for a root task". Cloudflare runs `ui:build` on its own, with no
typecheck ahead of it, so the incidental ordering never applies.

Adding the same edge to `ui:build`'s own turbo invocation, which is the level
that actually owns the dependency.

Verified against a genuinely clean tree (package dists and .tsbuildinfo files
removed, TURBO_FORCE=1 to defeat the shared worktree cache — the first two
attempts at reproducing this were masked by a stale tsbuildinfo and then by a
turbo cache replay): without this change `ui:build` exits 1 on the error above;
with it, exit 0 and both dist artifacts are emitted.
JSONbored added a commit that referenced this pull request Jul 29, 2026
…checkout can deploy

Cloudflare Workers Builds is the deploy path for BOTH workers — no GitHub
Actions workflow runs `wrangler deploy` (ui-deploy.yml only validates) — and
both have failed on every recent commit. Production has consequently been
serving stale code: #9574's `fleetAccuracy.basis` and #9594's anchor-payload
auth exemption both landed on main and neither is live.

One root cause, two scripts: nothing builds `@loopover/contract` before
something needs its `dist/`. `@loopover/engine` does not depend on it, so
building engine alone leaves it unemitted.

loopover-ui — `npm run ui:build` runs `turbo run build --filter=@loopover/engine`,
then `ui:openapi`, whose `scripts/write-ui-openapi.ts` reaches
`src/openapi/schemas.ts` and imports `@loopover/contract/dist/public-api.js`:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
    '/opt/buildhome/repo/node_modules/@loopover/contract/dist/public-api.js'

loopover-api — `npm run deploy:api` builds only `@loopover/engine`, then
`wrangler deploy` bundles `src/index.ts`, which reaches `@loopover/contract`
from src/mcp/*, src/openapi/schemas.ts and src/orb/broker-client.ts:

  ✘ [ERROR] Build failed with 8 errors:
    ✘ [ERROR] Could not resolve "@loopover/contract"

Both pass in `test:ci` only by accident of ordering: `npm run typecheck` runs
first, and the `//#typecheck` turbo task already carries an explicit
`@loopover/contract#build` edge — added for exactly this failure mode, and
documented in turbo.json as "the root package.json dependency does NOT create a
build edge for a root task". Neither deploy command runs a typecheck ahead of
itself, so the incidental ordering never applies.

Adding the same edge to each deploy path's own turbo invocation, which is the
level that actually owns the dependency. `deploy:api` also moves from a bare
`npm --workspace ... run build` to turbo so both paths express it identically.

Verified against a genuinely clean tree (package dists and .tsbuildinfo files
removed, TURBO_FORCE=1 to defeat the shared worktree cache — the first two
reproduction attempts were masked by a stale tsbuildinfo and then by a turbo
cache replay). ui:build: exit 1 -> exit 0. wrangler deploy --dry-run: 8
unresolved "@loopover/contract" imports -> 0, exit 0.
loopover-orb Bot pushed a commit that referenced this pull request Jul 29, 2026
…checkout can deploy (#9732)

Cloudflare Workers Builds is the deploy path for BOTH workers — no GitHub
Actions workflow runs `wrangler deploy` (ui-deploy.yml only validates) — and
both have failed on every recent commit. Production has consequently been
serving stale code: #9574's `fleetAccuracy.basis` and #9594's anchor-payload
auth exemption both landed on main and neither is live.

One root cause, two scripts: nothing builds `@loopover/contract` before
something needs its `dist/`. `@loopover/engine` does not depend on it, so
building engine alone leaves it unemitted.

loopover-ui — `npm run ui:build` runs `turbo run build --filter=@loopover/engine`,
then `ui:openapi`, whose `scripts/write-ui-openapi.ts` reaches
`src/openapi/schemas.ts` and imports `@loopover/contract/dist/public-api.js`:

  Error [ERR_MODULE_NOT_FOUND]: Cannot find module
    '/opt/buildhome/repo/node_modules/@loopover/contract/dist/public-api.js'

loopover-api — `npm run deploy:api` builds only `@loopover/engine`, then
`wrangler deploy` bundles `src/index.ts`, which reaches `@loopover/contract`
from src/mcp/*, src/openapi/schemas.ts and src/orb/broker-client.ts:

  ✘ [ERROR] Build failed with 8 errors:
    ✘ [ERROR] Could not resolve "@loopover/contract"

Both pass in `test:ci` only by accident of ordering: `npm run typecheck` runs
first, and the `//#typecheck` turbo task already carries an explicit
`@loopover/contract#build` edge — added for exactly this failure mode, and
documented in turbo.json as "the root package.json dependency does NOT create a
build edge for a root task". Neither deploy command runs a typecheck ahead of
itself, so the incidental ordering never applies.

Adding the same edge to each deploy path's own turbo invocation, which is the
level that actually owns the dependency. `deploy:api` also moves from a bare
`npm --workspace ... run build` to turbo so both paths express it identically.

Verified against a genuinely clean tree (package dists and .tsbuildinfo files
removed, TURBO_FORCE=1 to defeat the shared worktree cache — the first two
reproduction attempts were masked by a stale tsbuildinfo and then by a turbo
cache replay). ui:build: exit 1 -> exit 0. wrangler deploy --dry-run: 8
unresolved "@loopover/contract" imports -> 0, exit 0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ledger: Bittensor on-chain commitment backend — optional, Gittensor/SN74-audience corroboration

1 participant