carlos / platform Public

moves: the T11 budget refuses negative, NaN and infinite inputs (Task 10 fix round 1)

A negative rate priced a phase below zero and admitted a move. Estimate now
prices any unusable rate, bar or database size at infinity.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 58e6c49ed7144a23658e9a39183a1212283fb0be parent 71026be
2 files changed, +65 −12
  • internal/moves/budget.go +18 −2
  • internal/moves/budget_test.go +47 −10
diff --git a/internal/moves/budget.go b/internal/moves/budget.go
index 17a58eb70..dbd8238c9 100644
--- a/internal/moves/budget.go
+++ b/internal/moves/budget.go
@@ -1,6 +1,9 @@
package moves
-import "time"
+import (
+ "math"
+ "time"
+)
// The T11 cut budget (moves spec §7.3 T11, §7.4): pure arithmetic that
// prices a move's stop-to-open time from measured rates and decides how
@@ -49,8 +52,16 @@ var ExampleRates = Rates{Cross: 10, S3: 250, Disk: 500}
// disk, bar the verification, which reads the disk alone. Each probe
// attempt restores the prepared generation again and pays the admission;
// a retried probe also uploads the whole database as a fresh generation.
-// A zero rate prices its phase at infinity, so the cut is refused.
+// An input that is not a usable measurement (a rate that is not a positive
+// finite number, a negative, NaN or infinite database size or bar) prices
+// the cut at infinity, so it is refused: a negative rate must never price a
+// phase below zero and let a move in.
func Estimate(dbMiB float64, r Rates) CutEstimate {
+ if !usable(r.Cross) || !usable(r.S3) || !usable(r.Disk) ||
+ !finite(dbMiB) || dbMiB < 0 || !finite(r.MirrorBar) || r.MirrorBar < 0 {
+ inf := math.Inf(1)
+ return CutEstimate{Stop: inf, Checkpoint: inf, Mirror: inf, Archive: inf, Render: inf, Open: inf}
+ }
bar := r.MirrorBar
if bar == 0 {
bar = MirrorBarMiB
@@ -115,3 +126,8 @@ func (e CutEstimate) Total(n int) float64 {
func CutDeadline(cutAt time.Time, windowSeconds float64) time.Time {
return cutAt.Add(time.Duration((windowSeconds - OpenBudget) * float64(time.Second)))
}
+
+func finite(x float64) bool { return !math.IsNaN(x) && !math.IsInf(x, 0) }
+
+// usable is a rate that can be divided by: positive and finite.
+func usable(rate float64) bool { return finite(rate) && rate > 0 }
diff --git a/internal/moves/budget_test.go b/internal/moves/budget_test.go
index d1bd77d9a..25491628d 100644
--- a/internal/moves/budget_test.go
+++ b/internal/moves/budget_test.go
@@ -22,7 +22,7 @@ func TestReadinessBudget(t *testing.T) {
t.Fatalf("rev 7.4's formula admits %d attempts for a tiny instance in %.0f s, want 2", n, CutWindowDefault)
}
// the boundary: fixed costs of 70 s leave exactly one attempt; 71 s none
- edge := CutEstimate{Stop: 70 - 5, Open: 5, Probe: ProbeSeconds, Settle: SettleSeconds}
+ edge := CutEstimate{Stop: 70 - OpenBudget, Open: OpenBudget, Probe: ProbeSeconds, Settle: SettleSeconds}
if n := edge.AttemptsAdmitted(CutWindowDefault); n != 1 {
t.Fatalf("fixed costs of 70 s admit %d attempts, want 1", n)
}
@@ -147,15 +147,52 @@ func TestEstimateMirrorBar(t *testing.T) {
}
}
-// A zero or missing rate prices a phase at infinity: refused, never admitted.
-func TestEstimateZeroRateRefuses(t *testing.T) {
- for name, r := range map[string]Rates{
- "cross": {Cross: 0, S3: 250, Disk: 500},
- "s3": {Cross: 10, S3: 0, Disk: 500},
- "disk": {Cross: 10, S3: 250, Disk: 0},
- } {
- if n := Estimate(1, r).AttemptsAdmitted(CutWindowDefault); n != 0 {
- t.Fatalf("%s rate of zero admits %d attempts, want 0", name, n)
+// An unusable input prices the cut at infinity: refused, never admitted. A
+// negative rate would otherwise price a phase below zero and let a move in.
+func TestEstimateInvalidInputsRefuse(t *testing.T) {
+ nan, inf := math.NaN(), math.Inf(1)
+ good := ExampleRates
+ with := func(f func(r *Rates)) Rates { r := good; f(&r); return r }
+ cases := map[string]struct {
+ db float64
+ r Rates
+ }{
+ "cross zero": {1, with(func(r *Rates) { r.Cross = 0 })},
+ "cross negative": {1, with(func(r *Rates) { r.Cross = -10 })},
+ "cross NaN": {1, with(func(r *Rates) { r.Cross = nan })},
+ "cross Inf": {1, with(func(r *Rates) { r.Cross = inf })},
+ "s3 zero": {1, with(func(r *Rates) { r.S3 = 0 })},
+ "s3 negative": {1, with(func(r *Rates) { r.S3 = -250 })},
+ "s3 NaN": {1, with(func(r *Rates) { r.S3 = nan })},
+ "s3 Inf": {1, with(func(r *Rates) { r.S3 = inf })},
+ "disk zero": {1, with(func(r *Rates) { r.Disk = 0 })},
+ "disk negative": {1, with(func(r *Rates) { r.Disk = -500 })},
+ "disk NaN": {1, with(func(r *Rates) { r.Disk = nan })},
+ "disk Inf": {1, with(func(r *Rates) { r.Disk = inf })},
+ "bar negative": {1, with(func(r *Rates) { r.MirrorBar = -16 })},
+ "bar NaN": {1, with(func(r *Rates) { r.MirrorBar = nan })},
+ "bar Inf": {1, with(func(r *Rates) { r.MirrorBar = inf })},
+ "db negative": {-2048, good},
+ "db NaN": {nan, good},
+ "db Inf": {inf, good},
+ // a negative database against a negative rate: the quotient is
+ // positive, and must still be refused
+ "db and s3 negative": {-1, with(func(r *Rates) { r.S3 = -250 })},
+ }
+ for name, c := range cases {
+ e := Estimate(c.db, c.r)
+ if n := e.AttemptsAdmitted(CutWindowDefault); n != 0 {
+ t.Errorf("%s admits %d attempts, want 0", name, n)
}
+ if n := e.AttemptsAdmitted(1e9); n != 0 {
+ t.Errorf("%s admits %d attempts in a huge window, want 0", name, n)
+ }
+ if f := e.Fixed(); !math.IsInf(f, 1) {
+ t.Errorf("%s fixed cost %v, want +Inf", name, f)
+ }
+ }
+ // the boundary the refusals must not touch: an empty database is valid
+ if n := Estimate(0, ExampleRates).AttemptsAdmitted(CutWindowDefault); n != 2 {
+ t.Errorf("an empty database admits %d attempts, want 2", n)
}
}