feat(api): say why the public anchor list is empty instead of implying health - #9721
Conversation
|
Warning ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-29 06:37:55 UTC
Review summary Nits — 5 non-blocking
Concerns raised — review before merging
📋 Copy for AI agents — paste into your coding agentDecision drivers
Context & advisory signals — never blocks the verdict
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. Decision record
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 | 612452f | Commit Preview URL Branch Preview URL |
Jul 29 2026, 06:23 AM |
Bundle ReportChanges will increase total bundle size by 532 bytes (0.01%) ⬆️. 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 #9721 +/- ##
==========================================
- Coverage 90.27% 89.45% -0.82%
==========================================
Files 904 904
Lines 113177 113185 +8
Branches 26840 26847 +7
==========================================
- Hits 102171 101255 -916
- Misses 9676 10842 +1166
+ Partials 1330 1088 -242
Flags with carried forward coverage won't be shown. Click here to find out more.
|
…g health
`GET /v1/public/decision-ledger/anchors` returned `{"anchors":[]}` for three
completely different situations — anchoring was never configured, there is no
ledger to anchor, or it is configured and simply has not run yet. From outside
they were indistinguishable, and an empty list reads as a healthy one, so a
silently misconfigured deployment looked exactly like a working one.
That is the gap ledger-anchor-persistence.ts's own header set out to close
("an operator whose anchoring silently fails could quietly regress the ledger
back to tamper-evident-only with no visible signal"): the guarantee only held
AFTER the scheduler's two guards passed, and both of those return without
writing anything a reader can see.
The response now carries `status`: anchored | empty_ledger | unconfigured |
pending. The predicate is pure and its guard order deliberately mirrors
runScheduledLedgerAnchor's own — tip first, then signing key — so the published
status always names the same reason the scheduler would act on rather than
offering a second opinion.
Only computed for an unfiltered first page: with a backend/before filter an
empty page means "none matched", which is a different question from "is
anchoring running at all", so `status` is omitted there rather than answering
the wrong one.
Refs #9719
d8117a5 to
612452f
Compare


Summary
GET /v1/public/decision-ledger/anchorsreturns{"anchors":[]}— and has for the entire life of the endpoint — for three completely different situations:LOOPOVER_LEDGER_ANCHOR_KEYSunset; live/anchor-keyreturns{"keys":[],"currentKeyId":null}),/verifyreportstipSeq: 0, totalCount: 0),From outside they are indistinguishable, and an empty list reads as a healthy one — so a silently misconfigured deployment looks exactly like a working one.
That is precisely the gap
ledger-anchor-persistence.ts:1-6set out to close ("an operator whose anchoring silently fails could quietly regress the ledger back to tamper-evident-only with no visible signal"). The guarantee only held after the scheduler's two guards passed, and both return without writing anything a reader can see:ledger-anchor-scheduler.ts:37returnsempty_ledgerwith no row, and:113-121writes only aconsole.log.The response now carries
status:anchored | empty_ledger | unconfigured | pending.publicAnchorStatusis pure, and its guard order deliberately mirrorsrunScheduledLedgerAnchor's own — tip first, then signing key — so the published status always names the same reason the scheduler would act on, never a second opinion.statusis omitted on a filtered page: with?backend=or?before=, an empty result means "none matched", which is a different question from "is anchoring running at all", so answering the second one there would be misleading.This is additive — existing consumers of
anchors/nextBeforeare unaffected — and it does not make anchoring work. Production will reportempty_ledger, which is the honest answer; the two blockers behind that (deployment topology and an unprovisioned keypair) are #9719.Refs #9719
Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateIf any required check was skipped, explain why:
vitest run --coverageoverpublic-ledger-anchors-route.test.ts+ledger-anchor.test.ts, restricted tosrc/review/ledger-anchor.ts— 100% statements / branches / functions / lines (51/51, 25/25, 16/16, 39/39). All four status arms are exercised end-to-end through the route, plus both sides of the filtered/unfiltered branch and both sides of the signing-key predicate (published key with no private half is stillunconfigured).ui:openapi), since the documented response shape changed.ui:lint/ui:typecheck/ui:buildwere not run.test:workers,build:mcp,test:mcp-packuntouched by this diff; left to CI.Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.statusis a four-value enum derived from counts and a boolean — it publishes no key material, no private key state beyond configured-or-not, and no ledger content.unconfiguredis exactly what/v1/public/decision-ledger/anchor-keyalready discloses by returning an empty key list, so this reveals nothing that endpoint does not. The route remains unauthenticated like its/v1/public/*siblings; the existing negative-path suite (public-decision-ledger-routes.test.ts) is unchanged and still passes. No UI file is touched, hence no UI Evidence table.