fix(selfhost): fail preflight on half-configured ledger anchoring instead of silently skipping it - #9771
Conversation
…tead of silently skipping it
Anchoring has nine LOOPOVER_LEDGER_ANCHOR_* vars, all documented in env.d.ts and
all genuinely read, and not one of them appeared in any self-host preflight or
config-lint. An operator got zero boot-time guidance.
That matters more than a normal missing-config warning because the failure is
completely silent: runScheduledLedgerAnchor logs ledger_anchor_skipped_unconfigured
and returns, while the job keeps firing every couple of minutes doing two queries
and nothing else. A self-host container DOES run the scheduler and DOES have a
populated decision_ledger, so it is genuinely one keypair away from working -- and
nothing tells the operator that.
Anchoring stays OPT-IN: configuring none of it is deliberately not a problem, and
there is an invariant test pinning that so a future edit cannot turn an optional
feature into a boot requirement. What now fails preflight is PARTIAL
configuration, every case of which silently disables anchoring while looking
configured:
- a published key list with no private half, or a private key with nothing
published (anchors would be unverifiable)
- a key list that parses to zero usable entries -- malformed JSON and entries
missing a required field are both dropped silently at runtime
- no entry with notAfter: null, or MORE than one: currentAnchorKey fails closed
on an ambiguous rotation rather than guessing which key signs
- a git owner/repo without an installation id (no write token can be minted, so
job-dispatch resolves submitGit to null), a non-positive-integer id, or half a
git target
The key checks call the real parseAnchorPublicKeys/currentAnchorKey rather than
re-validating the shape locally, so preflight can never disagree with what the
scheduler will actually do -- a second hand-written copy of those rules is exactly
how this drifts back apart.
Regenerating the self-host env reference is a side benefit worth naming: because
preflight now reads these vars under src/selfhost/**, all five appear in the
generated operator-facing env documentation for the first time.
Closes #9769
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-29 07:53:29 UTC
Review summary Nits — 6 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionPartially addressed 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. 🟩 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 | 36d88f0 | Commit Preview URL Branch Preview URL |
Jul 29 2026, 07:36 AM |
Bundle ReportChanges will decrease total bundle size by 20.36kB (-0.26%) ⬇️. 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 #9771 +/- ##
==========================================
- Coverage 90.28% 89.47% -0.82%
==========================================
Files 907 907
Lines 113363 113388 +25
Branches 26892 26902 +10
==========================================
- Hits 102349 101450 -899
- Misses 9683 10849 +1166
+ Partials 1331 1089 -242
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Summary
Anchoring has nine
LOOPOVER_LEDGER_ANCHOR_*vars, all documented inenv.d.tsand all genuinely read — and not one appeared in any self-host preflight or config-lint. An operator got zero boot-time guidance.That matters more than a normal missing-config warning because the failure is completely silent:
runScheduledLedgerAnchorlogsledger_anchor_skipped_unconfiguredand returns (ledger-anchor-scheduler.ts:113-121), while the job keeps firing every ~2 minutes doing two queries and nothing else.And the stakes are higher than they look: a self-host container already runs the anchor scheduler (
src/index.ts:145→src/server.ts:1322-1339→processJobdirectly, bypassing the hosted ack-and-drop) and already has a populateddecision_ledger. It is genuinely one keypair away from real anchoring, and nothing tells the operator that.Closes #9769
What changed
Anchoring stays opt-in. Configuring none of it is deliberately not a problem —
preflightEnv's problems are fatal (assertSelfHostPreflightthrows), and turning an optional feature into a boot requirement would be a worse bug than the one being fixed. There's an invariant test pinning that so a future edit can't quietly do it.What now fails preflight is partial configuration — every case of which silently disables anchoring while looking configured:
parseAnchorPublicKeysnotAfter: null, or more than onecurrentAnchorKeyfails closed on an ambiguous rotation rather than guessing which key signsjob-dispatchresolvessubmitGittonulland the backend is skippedThe key checks call the real
parseAnchorPublicKeys/currentAnchorKeyrather than re-validating the shape locally, so preflight can never disagree with what the scheduler will actually do. A second hand-written copy of those rules is precisely how this drifts apart again.Validation
selfhost-preflightsuitetypecheck·ui:typecheck·selfhost:validate-observability·selfhost:env-reference:check·git diff --checkThe ambiguous-rotation case is worth calling out as a test: two entries with
notAfter: nullis the one an operator is most likely to create during a rotation, and it fails closed at runtime with no message.Regenerated artifact
apps/loopover-ui/src/lib/selfhost-env-reference.ts— required, since preflight now reads these vars undersrc/selfhost/**. Worth naming as a side benefit rather than noise: all five anchor vars now appear in the generated operator-facing env documentation for the first time, which is itself part of the problem #9769 describes.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.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.This reads key metadata only —
keyId,publicKeySpki,notBefore/notAfter— and never the private key's contents, only whether it is set. No key material reaches a preflight message; every message names the variable and the consequence. The only rendered output is the generated env-reference table, which lists variable names and the file that reads them, so there is no UI Evidence table.Notes
Decision-independent by design: this is correct under every option on #9719, including the one where anchoring stays a per-deployment capability and the hosted list stays empty on purpose.