Loading
feat(remote): upstream health failure counter (S13 Step 14, part 2/3)
📦 What this MR does
Part 2 of 3 of S13 Step 14 (health monitoring): the consecutive-probe-failure counter and every test fake the health monitor (part 3) exercises.
FailureCounteris the health monitor's seam over the S05-A atomic counter, keyed by the remote repository's parentrepositories.idUUID. The plan anticipated a forwarding adapter; none is needed —counter.Counter's method set matches the seam exactly, pinned by a compile-time assertion (plan drift, called out per the drift carve-out).NewFailureCounteris the single production constructor: it bakes in the counter name (upstream_health_failures) and the sliding expiry (4 sweep intervals, per the S05-A sizing rule), so a wiring that reached forcounter.Newdirectly cannot drop the TTL and leave abandoned streak keys behind. It returns an error on a non-positiveScheduledInterval: that value sizes the whole expiry, andcounter.WithTTLreads a non-positive duration as no expiry — the never-expiring counter the constructor exists to prevent.FailureCounterTTLMultipleis exported so the integration test bounds the observed expiry against the same constant the constructor applies.- The integration test proves the production value against real Redis through the cache-purpose client: streak accumulation, the
ar:counter:<name>:<uuid>key grammar (a deliberate cross-package pin, the counter suite's own discipline), the baked-in sliding TTL, and reset-to-absent. It ridestest:integrationrather than the Redis topology matrix: the job already runs both backends, the single-key counter behavior is topology-covered byinternal/cache/counter's own matrix suite, and this package's coming cache-fill integration tests need the PostgreSQL the topology jobs lack.internal/remoteis listed recursively to match the job's recursive changes rule. testutil.StartIsolatedRedisClientcentralizes the Redis integration-test bootstrap (provision, construct through the caller'sredisclientconstructor, start, cleanup); this test is its first caller.remotetestgainsFakeFailureCounter(error injection leaves state untouched),FakeHealthAuditSink(the canonical capturing sink steps 15/S17 will reuse), andSetHealthStatuscall recording onFakeHealthStatusStore, so part 3's probe tests can assert the durable write happens on every probe. TheHealthAuditSinkseam the fake stands in for ships here too, besideHealthTransitionEventinaudit.go, so the fake is pinned to it at compile time from the start; part 3 drops its own copy of the interface on its next rebase. The fakes live inremotetestrather than beside the counter — operator decision, matching the package's canonical-fakes convention.
⚙️ Why stacked
Step 14's full diff is ~1.8k reviewable LoC, so it ships as 3 stacked MRs, reviewed and merged bottom-up. Part 1 (composition-root wiring) is below this MR; part 3 (the health monitor consuming this seam and these fakes) sits above.
🔗 References
- Plan:
docs/plans/2026-07-16-s13-virtual-remote-foundation.md— Step 14 - Spec:
docs/specs/S13-virtual-remote-foundation.md— failure counter; S05-A for the counter contract and TTL sizing
🔬 e2e scenarios
No scenario added or affected: the counter is service-layer plumbing with no reachable request path until steps 11/12 wire resolution and step 15 schedules the sweep.
🚧 Follow-ups
- Migrate the two pre-existing per-file Redis integration-test bootstraps onto
testutil.StartIsolatedRedisClient(touching those files is outside this step's scope).
Related to #335 (closed)
📚 Stacked MRs (review/merge bottom-up)
Edited by David Fernandez