fix(webapp): scan every run-ops store for the batches list (#4806)
## Summary Batches created on a run-ops store other than the two the list reads were missing from the Batches page. No error, nothing logged: the page just showed fewer batches than exist. This is only reachable once additional run-ops stores are configured, so nothing changes for anyone today. ## Fix The list scanned exactly two databases and merged them by keyset. It now covers one leg per configured store, in ascending precedence order, all issued together. The existing keyset merge generalises without change. Every leg runs the same query, with the same cursor predicate, ordering and over-fetch, so a row's rank within its own leg is never worse than its global rank, and the merged first page is still the true first page. That argument holds for any number of legs, not just two. The empty-state check keeps its existing sequential pair, since a project with no batches is the common case for that path, then issues the remaining checks in a single round trip. A store that declares itself an alias of another shares its client by reference, so it contributes no leg. Scanning it would query the same database twice for rows the other leg already returned. This matches how the routing store and the boot checks treat an alias. The fan-out deliberately fails the page if any store is unreachable, rather than returning a short page. A tolerant merge would recreate the same silent absence this change removes, with a wider blast radius. ## Verification Covered by container tests against real databases: gen-1, legacy and additional stores merged into one ordered page, paging forward and back across a boundary that spans stores, and the empty-state check. Also verified end to end against a live environment with a real corpus: the missing rows reproduce with the new leg removed and appear correctly with it present, ordering interleaves across stores as expected, paging across a store boundary loses and repeats nothing, and the page is byte-identical to before when no extra store is configured. Merge precedence is pinned by its own test: one id seeded on two stores, asserting the higher-authority copy is the one shown. Verified by mutation, since a union-only test passes regardless of leg order. ## Boot interlocks Two related boot checks changed alongside the read path, since configuring an extra store is what makes them reachable. A store configured while split reads are disabled is dropped in silence: no client is built, no leg is added, and rows already resident there disappear from every list with no error. The other two ways the split ends up disabled already refuse to start; this closes the one that did not, and names the stores it is refusing. A store that declares itself an alias of another owns no database, so it is exempt. The distinct-database probe fails closed, which meant one store being briefly unreachable collapsed the deployment to single-DB and then refused the boot entirely. Each target now gets a bounded number of attempts with a short backoff before the probe gives up. Failing closed is unchanged once that budget is exhausted, and a genuine duplicate is still a final answer that is never retried.
D
Daniel Sutton committed
63b8e6e1f57316f05687eb8cb1969a08f7028590
Parent: 2e24c01
Committed by GitHub <noreply@github.com>
on 8/28/2026, 2:35:23 PM