Skip to content

fix(selfhost,observability): close three durable-wrong-state gaps, label the latency histogram, probe the browser, and add memory to host pressure (#9544, #9545) - #9547

Merged
loopover-orb[bot] merged 1 commit into
mainfrom
fix/ops-hardening
Jul 28, 2026

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

The repo-code half of the live edge-nl-01 inspection: three gaps that turn a transient event into a durable wrong state, and three observability gaps where a real signal was either unattributable or absent entirely.

Closes #9544
Closes #9545

#9544 — durable wrong states

An empty secret file silently degraded to "unset". A missing or unreadable <NAME>_FILE correctly throws (#6284), but a zero-byte or whitespace-only one set env[NAME] = "" — which every downstream nonBlank() reads as unconfigured, and preflight.ts deliberately skips absent values. A truncated GITHUB_WEBHOOK_SECRET file therefore booted an instance that rejected every webhook, healthily, indefinitely. Directly adjacent to the known rotation footgun on this host, where the file is rewritten in place: the window in which it is momentarily empty is exactly when a container restart reads it.

The check sits outside the existing try/catch on purpose — throwing inside it would be caught and re-reported as "unreadable", collapsing a bad path and a truncated write into one misleading message and the wrong log event. They are different operator problems.

The crash handler could stall exit behind a telemetry flush. It awaited flush() with no deadline before exit(1), so a wedged PostHog egress delayed — or entirely prevented — the very restart the module exists to guarantee, leaving a process that had already logged a fatal alive and serving from a state it declared unsound. Now Promise.race against a 3s deadline (the timer unref()'d, so it can never itself extend the lifetime it bounds). Losing a few telemetry events is unambiguously the cheaper failure.

Half-written blobs were served forever. Keys are input-hash-addressed, so a truncated PNG from a mid-write kill is never retried or overwritten — the next lookup for that input finds a file and serves it as a permanently "valid" cache hit. Now tmp+rename with a UUID temp name (so concurrent puts of one key cannot publish each other's partial file) and cleanup on the failure path.

#9545 — observability

The latency histogram had no route label, so LoopoverRequestLatencySLOBreach firing at p95 = 9.75s named nothing: sum by (le, route) (...) returned a single unlabelled series. The reason it had no label is the constraint the fix had to respect — a raw path is unbounded cardinality, which takes a Prometheus down rather than merely misinforming it.

httpRouteGroup is therefore an allowlist, not a path sanitizer. A "replace ids with :param" normalizer still trusts the path's shape, and one unmatched pattern reintroduces unbounded cardinality silently; an allowlist can only ever be wrong in the safe direction. Every unknown path — including every 404 and every attacker probe — collapses to one other bucket, pinned by a 500-iteration fuzz sweep asserting no value outside the fixed set ever appears.

BROWSER_WS_ENDPOINT had no readiness probe — the only dependency the review path needs that didn't. A browserless outage was invisible in /ready while silently changing gate outcomes (#9464 stopped that closing PRs; this makes the outage itself visible rather than inferred afterwards). Registered only when configured, probes the cheap HTTP sibling rather than launching Chromium, drops any ?token= so it can't reach a log, fails closed on a configured-but-unparseable endpoint, and caches/single-flights so /ready polling can't itself load a struggling backend.

Host pressure watched CPU only. On this box the realistic killer is memory — Ollama at ~9.9 GiB beside browserless at ~1.5 GiB — and nothing observed it, so the OOM killer decided instead, taking the container and every in-flight job rather than deferring one maintenance job. hostMemoryUsedFraction joins load as a peer signal with the identical fail-open null means "skip" contract, gates the drain escape too, and leaves host_load_high outranking it so existing alerts keep their reason string.

Scope

The config-only items from the parent — the 8-day-stale LoopoverBackupMissing, the latency alert's minimum-rate gate, and the 44% false-positive secret_leak gate — are not here. They live in alertmanager rules and the private per-repo .loopover.yml on edge-nl-01, not in this repo, and they are tracked in #9546 so neither half blocks the other. I have not touched production config.

Validation

  • npx tsc --noEmit, selfhost:env-reference:check (166 vars, picked up the new knob), db:migrations:check, git diff --check — all clean
  • 535 passed, including the full sqlite/pg queue suites as blast radius
  • Patch coverage against this diff: 0 uncovered changed lines

Regressions, each verified to fail against the unfixed code: an empty secret file throws and leaves the target unset; the process exits when flush() never settles; the blob store writes via tmp+rename; an unknown path always groups to other; memory pressure defers with its own reason.

Invariants: the empty and unreadable secret failures stay distinguishable; a whitespace-padded secret still loads trimmed and an explicit env value still wins without reading the file; a flush that settles first is still fully awaited and both fatal events are bounded identically; no temp leftovers after either outcome and concurrent puts yield an intact object; the browser probe is absent when unconfigured, maps wss:https:, and re-probes once its TTL lapses; memory is strictly-greater (not >=), skips on null, and CPU still outranks it.

Two arms are annotated rather than tested, with reasons: the blob cleanup's own failure (unreachable without mocking fs, and mocking it would test the mock), and a ?? "" that String.split makes unreachable.

The blob-store crash window — a SIGKILL between first and last byte — cannot be simulated in-process. Rather than dress up tests that pass either way, the tmp+rename mechanism is pinned by a source assertion, and the accompanying tests state honestly what they do cover.

…bel the latency histogram, probe the browser, and add memory to host pressure (#9544, #9545)
@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 07:54:44 UTC

20 files · 1 AI reviewer · no blockers · readiness 80/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
This PR closes #9544/#9545, fixing three durable-wrong-state bugs (empty secret files reading as unconfigured, unbounded flush blocking process exit, half-written blob cache entries) and adding three observability gaps (browserless readiness probe, route-labelled latency histogram via a bounded allowlist, and host memory pressure feeding maintenance admission). Each change traces cleanly to its stated incident, is wired end-to-end (schema/config/tests all updated together, e.g. hostMemoryUsedFraction threaded through both sqlite-queue.ts and pg-queue.ts and their mocks), and the httpRouteGroup allowlist correctly bounds cardinality by construction rather than sanitizing paths. Tests are substantive (REGRESSION/INVARIANT-labeled) and actually exercise the real code paths, not fabricated scenarios.

Nits — 5 non-blocking
  • src/selfhost/sqlite-queue.ts's maintenancePressureSignals return object has the new `hostMemoryUsedFraction: hostMemoryUsedFraction()` line indented two extra spaces relative to its siblings — cosmetic, but worth a quick fix for consistency.
  • src/selfhost/blob-store.ts's put() no longer performs a single write call, so a caller relying on the prior signature/behavior of writeFile failing synchronously mid-stream should double check nothing else assumed a direct write (defensive note only, no observed regression).
  • Fix the stray indentation on the new hostMemoryUsedFraction line in src/selfhost/sqlite-queue.ts:1634.
  • Consider extracting the 0.92 default and 3_000ms flush deadline into named exported constants if other modules will ever need to reference them (currently fine as local consts with good comments).
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

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 #9544, #9545
Related work ⚠️ 1 scoped overlap Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (2 linked issues).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 326 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 326 issue(s).
Improvement ✅ Minor risk: clean · value: minor · LLM: significant
Linked issue satisfaction

Addressed
The diff implements all three fixes the issue asks for: load-file-secrets.ts now throws distinctly on empty vs unreadable and leaves the target unset, process-lifecycle.ts races flush() against a 3s unref'd deadline before exit, and blob-store.ts writes to a UUID temp path then renames with cleanup on failure, all with matching unit tests covering the stated acceptance criteria.

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 326 issue(s).
  • Related work: Titles/paths share 3 meaningful terms. (issue #9545, issue #642)
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 3 steps 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 &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; 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.

🟩 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

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 8ee87ad Commit Preview URL

Branch Preview URL
Jul 28 2026, 07:37 AM

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Bundle Report

Changes will increase total bundle size by 77 bytes (0.0%) ⬆️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
loopover-ui 7.65MB 77 bytes (0.0%) ⬆️

Affected Assets, Files, and Routes:

view changes for bundle: loopover-ui

Assets Changed:

Asset Name Size Change Total Size Change (%)
assets/add-scalar-classes-BiJ2EYZC.js (New) 2.16MB 2.16MB 100.0% 🚀
assets/tanstack-vendor-Dpm0HCBI.js (New) 862.81kB 862.81kB 100.0% 🚀
assets/docs.fumadocs-spike-api-reference-DUTRBWoc.js (New) 443.45kB 443.45kB 100.0% 🚀
assets/AgentScalarChatInterface.vue-D_KUTMH_.js (New) 201.7kB 201.7kB 100.0% 🚀
assets/modal-D8ojZG0Z.js (New) 184.5kB 184.5kB 100.0% 🚀
assets/client-CdVzAeF0.js (New) 151.47kB 151.47kB 100.0% 🚀
assets/self-hosting-configuration-1r6dwu38.js (New) 102.08kB 102.08kB 100.0% 🚀
assets/maintainer-panel-CcVXeWvS.js (New) 78.99kB 78.99kB 100.0% 🚀
assets/routes-Cb5UhhF2.js (New) 35.96kB 35.96kB 100.0% 🚀
assets/owner-panel-H4JtG5ux.js (New) 27.92kB 27.92kB 100.0% 🚀
assets/app-kkE4Uzym.js (New) 25.78kB 25.78kB 100.0% 🚀
assets/ui-vendor-DTGFF7e-.js (New) 24.57kB 24.57kB 100.0% 🚀
assets/miner-panel-98QT1fcP.js (New) 20.24kB 20.24kB 100.0% 🚀
assets/app.runs-DeKF1Hkv.js (New) 20.22kB 20.22kB 100.0% 🚀
assets/api._op-DHqdj6kA.js (New) 17.57kB 17.57kB 100.0% 🚀
assets/self-hosting-docs-audit-C-rttmRE.js (New) 16.6kB 16.6kB 100.0% 🚀
assets/docs._slug-CUKmfdSY.js (New) 15.52kB 15.52kB 100.0% 🚀
assets/playground-panel-BEoutzN5.js (New) 14.42kB 14.42kB 100.0% 🚀
assets/fairness-Bt0_E8CI.js (New) 10.73kB 10.73kB 100.0% 🚀
assets/app.audit-7v4ddyoe.js (New) 10.08kB 10.08kB 100.0% 🚀
assets/app.config-generator-jHnv_uSI.js (New) 10.06kB 10.06kB 100.0% 🚀
assets/maintainers-CB5I-QrG.js (New) 8.06kB 8.06kB 100.0% 🚀
assets/miners-CESH99Ak.js (New) 7.91kB 7.91kB 100.0% 🚀
assets/agents-i9uTPDSA.js (New) 7.74kB 7.74kB 100.0% 🚀
assets/commands-panel-DI88r7z8.js (New) 6.65kB 6.65kB 100.0% 🚀
assets/maintainer-workflow-BfrONh82.js (New) 6.52kB 6.52kB 100.0% 🚀
assets/digest-panel-jWy_lPgK.js (New) 6.15kB 6.15kB 100.0% 🚀
assets/repos._owner._repo.quality-DjcAmw8J.js (New) 6.14kB 6.14kB 100.0% 🚀
assets/docs-nav-WgNF_aYq.js (New) 6.01kB 6.01kB 100.0% 🚀
assets/docs.index-BGs3wGLm.js (New) 5.95kB 5.95kB 100.0% 🚀
assets/api.index-DBIfA7F8.js (New) 4.7kB 4.7kB 100.0% 🚀
assets/docs-DQi29QIB.js (New) 2.7kB 2.7kB 100.0% 🚀
assets/api-D9aBn0vT.js (New) 2.69kB 2.69kB 100.0% 🚀
assets/docs-page-DLtkrv5r.js (New) 2.1kB 2.1kB 100.0% 🚀
assets/table-6OpAQym5.js (New) 1.75kB 1.75kB 100.0% 🚀
assets/app.workbench-Cqn-hZiK.js (New) 1.58kB 1.58kB 100.0% 🚀
assets/tabs-otYfnD8m.js (New) 1.39kB 1.39kB 100.0% 🚀
assets/app.repos-RShTrhm0.js (New) 1.07kB 1.07kB 100.0% 🚀
assets/input-CDawofeV.js (New) 796 bytes 796 bytes 100.0% 🚀
assets/file-cog-EKi7iFBk.js (New) 758 bytes 758 bytes 100.0% 🚀
assets/app.maintainer-P5iPo5Zm.js (New) 502 bytes 502 bytes 100.0% 🚀
assets/app.owner-BBN0xN5d.js (New) 474 bytes 474 bytes 100.0% 🚀
assets/app.commands-Brwnzxt7.js (New) 455 bytes 455 bytes 100.0% 🚀
assets/app.playground-CXrsBKTZ.js (New) 442 bytes 442 bytes 100.0% 🚀
assets/index-Bg-MmAyw.js (New) 438 bytes 438 bytes 100.0% 🚀
assets/app.digest-CjqCiMUR.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/eye-off-BRRrLyaa.js (New) 430 bytes 430 bytes 100.0% 🚀
assets/app.miner-CqtdDevR.js (New) 422 bytes 422 bytes 100.0% 🚀
assets/key-round-BdgzGKiy.js (New) 355 bytes 355 bytes 100.0% 🚀
assets/bot-CaJHUw_j.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/trash-2-3h8Vdfud.js (New) 328 bytes 328 bytes 100.0% 🚀
assets/save-Cbo9i_w6.js (New) 327 bytes 327 bytes 100.0% 🚀
assets/git-pull-request-arrow-DPCly8mS.js (New) 321 bytes 321 bytes 100.0% 🚀
assets/list-checks-BdWIzW7A.js (New) 279 bytes 279 bytes 100.0% 🚀
assets/compass-uUbixOap.js (New) 251 bytes 251 bytes 100.0% 🚀
assets/history-fwVrLVrW.js (New) 237 bytes 237 bytes 100.0% 🚀
assets/message-square-BcjN1cpj.js (New) 233 bytes 233 bytes 100.0% 🚀
assets/lock-CGZYIjFk.js (New) 206 bytes 206 bytes 100.0% 🚀
assets/rotate-cw-DVHv5LOW.js (New) 201 bytes 201 bytes 100.0% 🚀
assets/play-Cl5Typ5W.js (New) 190 bytes 190 bytes 100.0% 🚀
assets/circle-check-CzAbGLel.js (New) 178 bytes 178 bytes 100.0% 🚀
assets/search-BIkmAtvM.js (New) 174 bytes 174 bytes 100.0% 🚀
assets/add-scalar-classes-BGiofTdr.js (Deleted) -2.16MB 0 bytes -100.0% 🗑️
assets/tanstack-vendor-CAmKk_82.js (Deleted) -862.81kB 0 bytes -100.0% 🗑️
assets/docs.fumadocs-spike-api-reference-Dmod4yBh.js (Deleted) -443.45kB 0 bytes -100.0% 🗑️
assets/AgentScalarChatInterface.vue-Dj7Q2DiK.js (Deleted) -201.7kB 0 bytes -100.0% 🗑️
assets/modal-Doejy3VB.js (Deleted) -184.5kB 0 bytes -100.0% 🗑️
assets/client-vtNzLSGX.js (Deleted) -151.47kB 0 bytes -100.0% 🗑️
assets/self-hosting-configuration-_CVv2NWT.js (Deleted) -102.0kB 0 bytes -100.0% 🗑️
assets/maintainer-panel-BynErEAP.js (Deleted) -78.99kB 0 bytes -100.0% 🗑️
assets/routes-DIvliUWU.js (Deleted) -35.96kB 0 bytes -100.0% 🗑️
assets/owner-panel-DHhGASuz.js (Deleted) -27.92kB 0 bytes -100.0% 🗑️
assets/app-Csp1kIDG.js (Deleted) -25.78kB 0 bytes -100.0% 🗑️
assets/ui-vendor-D9njWRPf.js (Deleted) -24.57kB 0 bytes -100.0% 🗑️
assets/miner-panel-DsAdeD5e.js (Deleted) -20.24kB 0 bytes -100.0% 🗑️
assets/app.runs-4Sqg3_i_.js (Deleted) -20.22kB 0 bytes -100.0% 🗑️
assets/api._op-Cp33mRsF.js (Deleted) -17.57kB 0 bytes -100.0% 🗑️
assets/self-hosting-docs-audit-Bhrv9lDK.js (Deleted) -16.6kB 0 bytes -100.0% 🗑️
assets/docs._slug-D3X1GCXP.js (Deleted) -15.52kB 0 bytes -100.0% 🗑️
assets/playground-panel-BrqVOiYd.js (Deleted) -14.42kB 0 bytes -100.0% 🗑️
assets/fairness-Dpmw9k5n.js (Deleted) -10.73kB 0 bytes -100.0% 🗑️
assets/app.audit-GW9gKYAJ.js (Deleted) -10.08kB 0 bytes -100.0% 🗑️
assets/app.config-generator-DC8AbSuF.js (Deleted) -10.06kB 0 bytes -100.0% 🗑️
assets/maintainers-Dxk-Hwsw.js (Deleted) -8.06kB 0 bytes -100.0% 🗑️
assets/miners-htdj6Z3s.js (Deleted) -7.91kB 0 bytes -100.0% 🗑️
assets/agents-BYhzYEAB.js (Deleted) -7.74kB 0 bytes -100.0% 🗑️
assets/commands-panel-E-g3In8l.js (Deleted) -6.65kB 0 bytes -100.0% 🗑️
assets/maintainer-workflow-Ct95GWYK.js (Deleted) -6.52kB 0 bytes -100.0% 🗑️
assets/digest-panel-drQYUe_u.js (Deleted) -6.15kB 0 bytes -100.0% 🗑️
assets/repos._owner._repo.quality-DPCq9qZo.js (Deleted) -6.14kB 0 bytes -100.0% 🗑️
assets/docs-nav-DREoKjnw.js (Deleted) -6.01kB 0 bytes -100.0% 🗑️
assets/docs.index-CJJ8kSy3.js (Deleted) -5.95kB 0 bytes -100.0% 🗑️
assets/api.index-93WROv_E.js (Deleted) -4.7kB 0 bytes -100.0% 🗑️
assets/docs-DKJ-4vrm.js (Deleted) -2.7kB 0 bytes -100.0% 🗑️
assets/api-NlQDydLq.js (Deleted) -2.69kB 0 bytes -100.0% 🗑️
assets/docs-page-CV_nBSTi.js (Deleted) -2.1kB 0 bytes -100.0% 🗑️
assets/table-nVA-JpZ1.js (Deleted) -1.75kB 0 bytes -100.0% 🗑️
assets/app.workbench-CS-SxAXG.js (Deleted) -1.58kB 0 bytes -100.0% 🗑️
assets/tabs-DtSZ7CrU.js (Deleted) -1.39kB 0 bytes -100.0% 🗑️
assets/app.repos-Ds6pX53R.js (Deleted) -1.07kB 0 bytes -100.0% 🗑️
assets/input-IgQiJZfC.js (Deleted) -796 bytes 0 bytes -100.0% 🗑️
assets/file-cog-gGSlcPEt.js (Deleted) -758 bytes 0 bytes -100.0% 🗑️
assets/app.maintainer-BAS9Puxu.js (Deleted) -502 bytes 0 bytes -100.0% 🗑️
assets/app.owner-DbjJt0jc.js (Deleted) -474 bytes 0 bytes -100.0% 🗑️
assets/app.commands-BhIX-qm4.js (Deleted) -455 bytes 0 bytes -100.0% 🗑️
assets/app.playground-CCLyDHM1.js (Deleted) -442 bytes 0 bytes -100.0% 🗑️
assets/index-YIDK8-pr.js (Deleted) -438 bytes 0 bytes -100.0% 🗑️
assets/app.digest-D9cDkUi1.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/eye-off-yqKuI2xr.js (Deleted) -430 bytes 0 bytes -100.0% 🗑️
assets/app.miner-Ds_lJyBq.js (Deleted) -422 bytes 0 bytes -100.0% 🗑️
assets/key-round-GNLdbEUM.js (Deleted) -355 bytes 0 bytes -100.0% 🗑️
assets/bot-CwTf8nCc.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/trash-2-DPlaYVKD.js (Deleted) -328 bytes 0 bytes -100.0% 🗑️
assets/save-kL4mAkUc.js (Deleted) -327 bytes 0 bytes -100.0% 🗑️
assets/git-pull-request-arrow-BFnjx5EL.js (Deleted) -321 bytes 0 bytes -100.0% 🗑️
assets/list-checks-QvGLYCB-.js (Deleted) -279 bytes 0 bytes -100.0% 🗑️
assets/compass-DdFcJhYH.js (Deleted) -251 bytes 0 bytes -100.0% 🗑️
assets/history-BBNJmGOS.js (Deleted) -237 bytes 0 bytes -100.0% 🗑️
assets/message-square--V06LDcW.js (Deleted) -233 bytes 0 bytes -100.0% 🗑️
assets/lock-BTMX7uPx.js (Deleted) -206 bytes 0 bytes -100.0% 🗑️
assets/rotate-cw-CoTDnnUW.js (Deleted) -201 bytes 0 bytes -100.0% 🗑️
assets/play-4194I6xQ.js (Deleted) -190 bytes 0 bytes -100.0% 🗑️
assets/circle-check-zf9xFlpf.js (Deleted) -178 bytes 0 bytes -100.0% 🗑️
assets/search-BPyJza0S.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 88.68%. Comparing base (47f9ae4) to head (8ee87ad).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9547      +/-   ##
==========================================
- Coverage   89.58%   88.68%   -0.90%     
==========================================
  Files         846      846              
  Lines      110332   110384      +52     
  Branches    26277    26295      +18     
==========================================
- Hits        98837    97898     -939     
- Misses      10231    11513    +1282     
+ Partials     1264      973     -291     
Flag Coverage Δ
backend 93.66% <100.00%> (-1.63%) ⬇️

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

Files with missing lines Coverage Δ
src/selfhost/blob-store.ts 92.30% <100.00%> (+1.39%) ⬆️
src/selfhost/health.ts 100.00% <100.00%> (ø)
src/selfhost/host-pressure.ts 100.00% <100.00%> (ø)
src/selfhost/load-file-secrets.ts 100.00% <100.00%> (ø)
src/selfhost/maintenance-admission.ts 100.00% <100.00%> (ø)
src/selfhost/metrics.ts 100.00% <100.00%> (ø)
src/selfhost/process-lifecycle.ts 100.00% <100.00%> (ø)
src/selfhost/sqlite-queue.ts 99.63% <ø> (ø)

... and 3 files with indirect coverage changes

@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 8751b9a into main Jul 28, 2026
11 checks passed
@loopover-orb
loopover-orb Bot deleted the fix/ops-hardening branch July 28, 2026 07:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant