Files
zeekay bae63692f0 the corporate name is Lux Industries Inc
Lux Partners Limited is the Isle of Man company and does not appear on a
public surface. Lux Industries Inc, a Delaware C corporation, is the one
these copyright lines, citations and terms name.

A bare "Lux Partners" is left alone unless the line says who owns
something — elsewhere it is the name of a site, not a party.
2026-08-28 16:43:59 -07:00

147 lines
4.9 KiB
Go

// Copyright 2024-2026 Lux Industries Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.
package screen
import (
"sort"
"sync"
"time"
)
// Readiness holds the outcome of the most recent load of each publisher's list.
//
// Store.Ready answers whether screening can be relied on at all. This answers
// which publisher is behind it, how many designations it last carried, and when —
// because "screening is not ready" without a source and a date is not something
// an operator can act on.
//
// It exists because the expensive failure in screening is silent. A list that has
// stopped loading produces no matches, and no matches is what a clean party also
// produces, so a retired endpoint or a changed schema is invisible from the
// outside. Two of these four sources had been returning nothing for months.
type Readiness struct {
mu sync.RWMutex
state map[string]Fitness
}
// Fitness is the readiness of one publisher's list.
type Fitness struct {
// Source is the publisher.
Source string `json:"source"`
// Designations is how many entries the last successful load carried.
Designations int `json:"designations"`
// LoadedAt is when that load happened. Zero means this list has never loaded,
// which is not the same as a publisher who designates nobody.
LoadedAt time.Time `json:"loaded_at"`
// Digest is the digest of the payload that produced Designations, so a refresh
// that changed nothing can be told from a refresh that did not run.
Digest string `json:"digest,omitempty"`
// Err is why the last attempt failed, empty if it succeeded.
Err string `json:"error,omitempty"`
// AttemptedAt is when the last attempt happened, successful or not.
AttemptedAt time.Time `json:"attempted_at"`
}
// Fit reports whether this list is fit to screen against as of now.
//
// A list that has never loaded is not fit whatever its last attempt said, and
// neither is one that loaded zero designations — no publisher's list is empty, so
// an empty load means the fetch or the parse is wrong. A list whose latest attempt
// failed stays fit while what it did load is inside maxStale, because yesterday's
// designations still beat none; it stops being fit when that ages out rather than
// staying fit forever on one old success.
func (f Fitness) Fit(now time.Time, maxStale time.Duration) bool {
if f.LoadedAt.IsZero() || f.Designations == 0 {
return false
}
if maxStale <= 0 {
return true
}
return now.Sub(f.LoadedAt) <= maxStale
}
// Age is how long since this list last loaded.
func (f Fitness) Age(now time.Time) time.Duration {
if f.LoadedAt.IsZero() {
return 0
}
return now.Sub(f.LoadedAt)
}
// NewReadiness builds a Readiness over the named sources, each recorded as never
// loaded — so a list that has not once succeeded is reported as unfit rather than
// being absent from the report entirely.
func NewReadiness(sources []string) *Readiness {
r := &Readiness{state: make(map[string]Fitness, len(sources))}
for _, s := range sources {
r.state[s] = Fitness{Source: s}
}
return r
}
// Record folds the results of a refresh in.
//
// A failed attempt keeps the previous successful load's count and date: that data
// is still what screening runs against, and forgetting it would report the list as
// empty when it is only stale.
func (r *Readiness) Record(results []Result, at time.Time) {
r.mu.Lock()
defer r.mu.Unlock()
for _, res := range results {
f := r.state[res.Source]
f.Source = res.Source
f.AttemptedAt = at
if res.Err != nil {
f.Err = res.Err.Error()
} else {
f.Err = ""
f.Designations = len(res.Entries)
f.Digest = res.Digest
f.LoadedAt = at
}
r.state[res.Source] = f
}
}
// Lists returns the readiness of every configured source, in a stable order.
func (r *Readiness) Lists() []Fitness {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]Fitness, 0, len(r.state))
for _, f := range r.state {
out = append(out, f)
}
sort.Slice(out, func(i, j int) bool { return out[i].Source < out[j].Source })
return out
}
// Unfit names the sources not fit to screen against.
//
// Fitness is all-or-nothing across the set by design, and the caller should treat
// a non-empty result as "cannot screen" rather than as partial coverage: screening
// against three of four lists is not three-quarters of a control, because a party
// designated only on the missing list passes completely and the answer is
// indistinguishable from a clean one.
func (r *Readiness) Unfit(now time.Time, maxStale time.Duration) []string {
var unfit []string
for _, f := range r.Lists() {
if !f.Fit(now, maxStale) {
unfit = append(unfit, f.Source)
}
}
return unfit
}
// Designations is the total across every list that is currently loaded.
func (r *Readiness) Designations() int {
r.mu.RLock()
defer r.mu.RUnlock()
total := 0
for _, f := range r.state {
total += f.Designations
}
return total
}