feat(ledger): Bittensor on-chain anchor backend — repo-side glue for the off-Worker submitter (#9277) - #9594
Conversation
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-28 22:15:00 UTC
Review summary Nits — 5 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk 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.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. Visual preview
Click any thumbnail to open the full-size screenshot. Before = production · After = this PR's preview deploy. Scroll preview
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.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Deploying with
|
| 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 |
8de17c0 to
d0baa7e
Compare
Bundle ReportChanges will increase total bundle size by 6.38kB (0.08%) ⬆️. This is within the configured threshold ✅ Detailed changes
Affected Assets, Files, and Routes:view changes for bundle: loopover-uiAssets Changed:
|
Codecov Report✅ All modified and coverable lines are covered by tests. 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
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…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.
d1a1df9 to
ec74835
Compare
…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.
…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.
…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.


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'sset_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 exactsigningInputbytes to hash. Unauthenticated (it's the same payload the Rekor/git backends already publish externally on every checkpoint);no-storesinceatis minted per call. Honest degrades: 404 for unconfigured signing or an empty ledger.POST /v1/decision-ledger/anchor-attempts— bearer-gated byLOOPOVER_LEDGER_ANCHOR_REPORT_TOKEN, fails closed when unset (theisAuthorizedIngestposture). Authentication alone cannot forge corroboration: anokreport 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_mismatchare each a named 422 and nothing is recorded. Afailedreport 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_anchorsbackend CHECK widened to'bittensor'(migration 0201 rebuild-and-rename); the public listing filter follows; rows are byte-identical in shape to Rekor/git attempts.backendRefcarries the full historical-retrieval reference{netuid, blockNumber, blockHash, hotkey}—CommitmentOfis 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 inwhat-you-can-verify.mdx, still clearly scoped as additive corroboration outside the default two-backend claim.Tests (11, real ECDSA keys, real D1 rows)
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.Closes #9277 — and with it, every sub-issue of #9267.