feat(#76): harvest request-scoped availability and freshness truth

Backend-only re-cut of the #76 availability work onto main, stacked on the
canonical Weekend Context API. Adds request-scoped freshness reporting so
aggregate responses cannot report fresh when a component is stale, plus
local-first driver summary resolution and cache/pacing truth.

The frontend half of #76 is deliberately excluded: it is built on the
Weekend shell that failed owner review, including the full-width Partial
banner treatment. Availability presentation is re-cut with the shell in #89.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 09:14:37 -04:00
parent d03e480e03
commit 77a6b0f2dd
23 changed files with 1587 additions and 137 deletions

View File

@@ -1,6 +1,7 @@
package api
import (
"context"
"net/http"
"sync"
"sync/atomic"
@@ -26,8 +27,12 @@ type requestPacer struct {
// wait blocks until this caller's reserved slot arrives.
func (p *requestPacer) wait() {
_ = p.waitContext(context.Background())
}
func (p *requestPacer) waitContext(ctx context.Context) error {
if p == nil || p.interval <= 0 {
return
return nil
}
p.mu.Lock()
now := time.Now()
@@ -38,8 +43,18 @@ func (p *requestPacer) wait() {
p.next = p.next.Add(p.interval)
p.mu.Unlock()
if sleep > 0 {
time.Sleep(sleep)
timer := time.NewTimer(sleep)
defer timer.Stop()
select {
case <-timer.C:
case <-ctx.Done():
// Keep the unused reservation in the schedule. Blindly reclaiming an
// interval can collide with later callers that already reserved their
// wake times, releasing two requests simultaneously.
return ctx.Err()
}
}
return nil
}
type OpenF1Client struct {
@@ -56,6 +71,26 @@ type OpenF1Client struct {
staleFlag int32
}
// Scoped returns a lightweight request-scoped view of the client. Network,
// pacing and cache resources are shared, while the stale fallback indicator is
// deliberately not shared. Web handlers use this view so a stale fallback in
// one concurrent HTTP request can never mark an unrelated response as stale.
//
// The legacy client-wide stale flag remains available for the TUI, whose loads
// are intentionally aggregated into one navigation-level notice.
func (c *OpenF1Client) Scoped() *OpenF1Client {
if c == nil {
return nil
}
return &OpenF1Client{
url: c.url,
apiKey: c.apiKey,
httpClient: c.httpClient,
cache: c.cache,
pacer: c.pacer,
}
}
func NewOpenF1Client(url string, timeout time.Duration) *OpenF1Client {
return &OpenF1Client{
url: url,