feat(remote): standalone remote request flow (S13 Step 11, part 2/3)

📦 What this MR does

Part 2 of 3 of S13 virtual/remote foundation — Step 11: standalone remote request.

This step is split into 3 stacked MRs to keep each within the review size limit (~800 reviewable LoC). Each part targets the previous one (part 1 targets main); review and merge proceed bottom-up.

This part is the flow itself: Standalone (standalone.go) composes Lookup, the RevalidationFor freshness verdict, the unhealthy-remote gate, and the coalesced Fetch into the kind=2 standalone serve flow, on the taxonomy and classification foundations from part 1/3.

  • A fresh hit serves from the store with no upstream call, recording last_downloaded_at off the request path (fire-and-forget on a detached, bounded context).
  • A stale hit revalidates with its stored ETag; a miss (and a stale row without an ETag) fetches in full.
  • An upstream refusal (any non-2xx except 304) propagates verbatim as UpstreamReportsError; a success-shaped response the fetch rejected is classified as no-usable-response instead.
  • A transport failure serves any cached copy under the S13 cache-fallback rule, reported as ServeFromCacheDegraded (with the served-from-cache-on-error log line) so a caller can tell it from a healthy cache serve, and returns ErrUpstreamUnavailable only when none exists.
  • A leader loss under coalescing (ErrLeaderCanceled, or its race twin, a synchronous follower ErrFillAbandoned) is retried exactly once; a 304 serve re-reads the cache row rather than trusting the caller's pre-fetch snapshot; a vanished blob recovers with exactly one unconditional re-fetch on every path.
  • An unhealthy last_health_status blocks only the fetch paths with ErrRemoteUnhealthy; a health-status read error is inconclusive — logged through logDegradedInfra, the fetch proceeds.

🎯 Spec coverage

This part carries one core acceptance test per spec behavior; the deeper concurrency-choreography and edge-case suite lands in part 3/3.

Spec behavior Test
Fresh hit serves from cache; no upstream call, no health read TestStandalone_Serve_FreshHitServesFromCache
Fresh-hit download record survives client disconnect TestStandalone_Serve_FreshHitBumpSurvivesRequestCancellation
Fresh-hit download-record failure never fails the serve TestStandalone_Serve_FreshHitBumpFailureStillServes
Stale hit revalidates with stored ETag; 304 serves the cached blob TestStandalone_Serve_StaleHitRevalidates304
Stale hit, etag differs: 200 refills the row and stores the new validator TestStandalone_Serve_StaleHitETagDiffersRefills
Stale hit, no stored ETag: re-fetched in full, no validator sent TestStandalone_Serve_StaleHitWithoutStoredETagFetchesFull
Miss streams and fills; standalone issues GET only TestStandalone_Serve_MissStreamsAndFills
Standalone status fidelity: non-2xx propagates verbatim TestStandalone_Serve_UpstreamStatusPropagatesVerbatim
Success-shaped response the fetch rejected is no-usable-response, not a verbatim status TestStandalone_Serve_UnusableSuccessShapedResponseServesFallback, TestStandalone_Serve_UnusableSuccessShapedResponseWithoutCacheEntryFails
Transport failure with a cache entry serves fallback + log line TestStandalone_Serve_TransportFailureServesFallback
Transport failure with no cache entry returns ErrUpstreamUnavailable TestStandalone_Serve_TransportFailureWithoutCacheEntryFails
Direct request to unhealthy remote blocks the fetch paths TestStandalone_Serve_UnhealthyRemoteBlocksFetchPaths
Health-status read error is inconclusive; the fetch proceeds and is logged TestStandalone_Serve_HealthReadErrorDoesNotBlockFetch

📖 References

🧪 E2E scenario catalog

No scenario added or affected: this is service-layer composition with no HTTP endpoint. The format handlers that expose the standalone flow will add the client-visible scenarios.

:bricks: Stacked MRs (review/merge bottom-up)

Related to #332 (closed)

Edited by Dzmitry (Dima) Meshcharakou

Merge request reports

Loading
Loading