From 5ddc3ff312f3ca280f635234a7083bb6b2494af9 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 22 Oct 2026 14:13:18 +0200 Subject: [PATCH] =?UTF-8?q?Measure=20a=20repository's=20last=20check=20tim?= =?UTF-8?q?e=20from=20its=20newest=20verdict=20(ADR=200505=20=C2=A74)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cmd/mesh-delivery-gate/adr505_test.go | 26 ++++++++++++++ .../cmd/mesh-delivery-gate/checktime.go | 34 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 modules/mesh-delivery-gate/cmd/mesh-delivery-gate/checktime.go diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr505_test.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr505_test.go index ee8bfdbf..ef3888ef 100644 --- a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr505_test.go +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/adr505_test.go @@ -39,3 +39,29 @@ func TestTheBoundsAreSettingsWithTheirDefaults(t *testing.T) { t.Fatalf("a bound of zero minutes was taken") } } + +// ADR 0505 §4: a repository's last measured check time is its newest verdict's (`took`, as mesh-controller +// cmd/mesh-builder main.go writes it: a duration rounded to the second); none, and the floor alone holds. +func TestTheLastMeasuredCheckTimeIsTheNewestVerdictsOfItsRepository(t *testing.T) { + h := &Holder{} + h.init() + at := time.Date(2026, 10, 22, 10, 0, 0, 0, time.UTC) + h.deliveries["novox/mesh-catalog@a"] = &Delivery{ID: "novox/mesh-catalog@a", Repository: "novox/mesh-catalog", + Check: &Verdict{At: at, Took: "4m0s"}} + h.deliveries["novox/mesh-catalog@b"] = &Delivery{ID: "novox/mesh-catalog@b", Repository: "novox/mesh-catalog", + Check: &Verdict{At: at.Add(time.Hour), Took: "7m30s"}} + h.deliveries["novox/hq@c"] = &Delivery{ID: "novox/hq@c", Repository: "novox/hq", + Check: &Verdict{At: at.Add(2 * time.Hour), Took: "1m0s"}} + if got := h.lastCheckTime("novox/mesh-catalog"); got != 7*time.Minute+30*time.Second { + t.Fatalf("the newer of two: %s", got) + } + if got := h.answeredRefusalBound("novox/mesh-catalog"); got != 15*time.Minute { + t.Fatalf("twice 7m30s: %s", got) + } + if got := h.answeredRefusalBound("novox/hq"); got != 10*time.Minute { + t.Fatalf("twice one minute is under the floor: %s", got) + } + if got := h.answeredRefusalBound("novox/mesh-sdk"); got != 10*time.Minute { + t.Fatalf("no verdict: %s", got) + } +} diff --git a/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/checktime.go b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/checktime.go new file mode 100644 index 00000000..91bc0eec --- /dev/null +++ b/modules/mesh-delivery-gate/cmd/mesh-delivery-gate/checktime.go @@ -0,0 +1,34 @@ +package main + +import "time" + +// lastCheckTime is how long the newest answered check of a repository ran, as its verdict says it (`took`): the +// repository's last measured check time (novox/hq ADR 0505 §4). Zero when no verdict of the repository says it. +// Under the lock. +func (h *Holder) lastCheckTime(repository string) time.Duration { + var newest *Verdict + var took time.Duration + for _, d := range h.deliveries { + v := d.Check + if d.Repository != repository || v == nil || v.Took == "" { + continue + } + t, err := time.ParseDuration(v.Took) + if err != nil || t <= 0 { + continue + } + if newest == nil || v.At.After(newest.At) { + newest, took = v, t + } + } + return took +} + +// answeredRefusalBound is how long a refusal the gate is already answering waits before it is said as stalled: twice +// the repository's last measured check time, at least the floor (ADR 0505 §4). Under the lock. +func (h *Holder) answeredRefusalBound(repository string) time.Duration { + if b := 2 * h.lastCheckTime(repository); b > h.refusalFloor() { + return b + } + return h.refusalFloor() +}