carlos / platform Public

moves: the T11 budget arithmetic, ported from the model (§7.3, §7.4)

Estimate takes the mirror bar through Rates.MirrorBar (zero is MirrorBarMiB) so
Task 11 can pass the env-overridable value (ruling F28(e)).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 430298dbe1fa7e0ce71a5dbed5e01590d97217c1 parent b797a1d
2 files changed, +278 −0
  • internal/moves/budget.go +117 −0
  • internal/moves/budget_test.go +161 −0
diff --git a/internal/moves/budget.go b/internal/moves/budget.go
new file mode 100644
index 000000000..17a58eb70
--- /dev/null
+++ b/internal/moves/budget.go
@@ -0,0 +1,117 @@
+package moves
+
+import "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
+// many probe attempts fit the operator's window. Ported from the model in
+// internal/sim/hostlife/budget_test.go at 9c417aaa; the constants below
+// that duplicate the model's (cutWindowDefault, probeAttemptsMax,
+// probeSeconds, settleSeconds, admitSeconds, mirrorBarMiB) are twins of it
+// and change together. All times are seconds, all sizes and rates MiB.
+
+const (
+ CutWindowDefault = 120.0 // the operator's default cut window
+ OpenBudget = 5.0 // the target's open after the commit (§7.4)
+ ProbeAttemptsMax = 3 // §7.7: never more than three attempts
+ ProbeSeconds = 30.0
+ SettleSeconds = 20.0
+ AdmitSeconds = 2.0 // the admitting CAS, the purge and generation.json
+ MirrorBarMiB = 16.0 // §7.3: the final mirror round is at most this much
+)
+
+// CutEstimate is a cut priced phase by phase.
+type CutEstimate struct {
+ Stop, Checkpoint, Mirror, Archive, Render, Open float64
+ RestoreStaged, SnapshotBuild, Anchor, Verify float64 // target-side build, whole database
+ RestoreAttempt, Admit, FreshUpload float64 // per probe attempt
+ Probe, Settle float64 // one attempt
+}
+
+// Rates are the measured rates T11 reads, in MiB/s: the cross-region copy
+// path, regional S3 (reads and writes from a box in the bucket's region)
+// and the box's disk. MirrorBar is the final mirror round's size bar in
+// MiB; zero means MirrorBarMiB. It is the one knob Task 11 turns
+// (CARLOS_MOVE_READY_DELTA_BYTES, ruling F28(e)).
+type Rates struct {
+ Cross, S3, Disk float64
+ MirrorBar float64
+}
+
+// ExampleRates are the rates the spec's worked example uses (§7.3 T11):
+// 10 MiB/s across regions, 250 MiB/s to regional S3, 500 MiB/s on disk.
+// Tests and documentation only: T11 reads measured rates.
+var ExampleRates = Rates{Cross: 10, S3: 250, Disk: 500}
+
+// Estimate prices a cut for a database of dbMiB at the given rates. The
+// final mirror is within the bar across regions. On the target every
+// build pass moves the whole database once across S3 and once across the
+// 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.
+func Estimate(dbMiB float64, r Rates) CutEstimate {
+ bar := r.MirrorBar
+ if bar == 0 {
+ bar = MirrorBarMiB
+ }
+ return CutEstimate{
+ Stop: 2, Checkpoint: 1, Archive: 2, Render: 1, Open: OpenBudget,
+ Mirror: bar / r.Cross,
+ RestoreStaged: dbMiB/r.S3 + dbMiB/r.Disk,
+ SnapshotBuild: dbMiB/r.Disk + dbMiB/r.S3,
+ Anchor: dbMiB/r.Disk + dbMiB/r.S3,
+ Verify: dbMiB / r.Disk,
+ RestoreAttempt: dbMiB/r.S3 + dbMiB/r.Disk,
+ Admit: AdmitSeconds,
+ FreshUpload: dbMiB/r.Disk + dbMiB/r.S3,
+ Probe: ProbeSeconds, Settle: SettleSeconds,
+ }
+}
+
+// Fixed is the cost of every phase outside the probe attempts.
+func (e CutEstimate) Fixed() float64 {
+ return e.Stop + e.Checkpoint + e.Mirror + e.Archive + e.RestoreStaged + e.SnapshotBuild + e.Anchor + e.Verify + e.Render + e.Open
+}
+
+// Attempt prices the i-th probe attempt (from 1): the restore of the
+// prepared generation and the admission overhead, the probe and its
+// settle; from the second attempt on, the fresh generation's first upload
+// as well.
+func (e CutEstimate) Attempt(i int) float64 {
+ c := e.RestoreAttempt + e.Admit + e.Probe + e.Settle
+ if i > 1 {
+ c += e.FreshUpload
+ }
+ return c
+}
+
+// AttemptsAdmitted is T11: the attempts permitted are those whose summed
+// costs fit the window after the fixed costs, at most ProbeAttemptsMax.
+// The instance is eligible when that is at least one, and the probe loop
+// runs at most that many.
+func (e CutEstimate) AttemptsAdmitted(window float64) int {
+ left := window - e.Fixed()
+ n := 0
+ for n < ProbeAttemptsMax && left >= e.Attempt(n+1) {
+ left -= e.Attempt(n + 1)
+ n++
+ }
+ return n
+}
+
+// Total prices the cut with n attempts.
+func (e CutEstimate) Total(n int) float64 {
+ t := e.Fixed()
+ for i := 1; i <= n; i++ {
+ t += e.Attempt(i)
+ }
+ return t
+}
+
+// CutDeadline is §7.4's one absolute deadline: cut_at + cut_budget -
+// open_budget. Every phase before the commit checks it; the commit CAS is
+// conditional on it.
+func CutDeadline(cutAt time.Time, windowSeconds float64) time.Time {
+ return cutAt.Add(time.Duration((windowSeconds - OpenBudget) * float64(time.Second)))
+}
diff --git a/internal/moves/budget_test.go b/internal/moves/budget_test.go
new file mode 100644
index 000000000..d1bd77d9a
--- /dev/null
+++ b/internal/moves/budget_test.go
@@ -0,0 +1,161 @@
+package moves
+
+import (
+ "math"
+ "testing"
+ "time"
+)
+
+// Ported from internal/sim/hostlife/budget_test.go at 9c417aaa (the model of
+// moves spec §7.3 T11, rev 7.7 with rev 7.11's per-attempt pricing). The
+// model's fitsRev73 helper is dropped: it priced a rule the spec retired.
+
+func TestReadinessBudget(t *testing.T) {
+ // a tiny instance: every fixed cost at the floor of its measured rate
+ tiny := Estimate(1, ExampleRates)
+ // rev 7.3 required three attempts always: 150 s of probing alone, which
+ // the default window never held
+ if old := tiny.Fixed() + ProbeAttemptsMax*(tiny.Probe+tiny.Settle); old <= CutWindowDefault {
+ t.Fatalf("rev 7.3's formula admits a tiny instance in the default window (%.1f s): R3-7 is stale", old)
+ }
+ if n := tiny.AttemptsAdmitted(CutWindowDefault); n != 2 {
+ 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}
+ if n := edge.AttemptsAdmitted(CutWindowDefault); n != 1 {
+ t.Fatalf("fixed costs of 70 s admit %d attempts, want 1", n)
+ }
+ edge.Stop++
+ if n := edge.AttemptsAdmitted(CutWindowDefault); n != 0 {
+ t.Fatalf("fixed costs of 71 s admit %d attempts, want 0 (refused)", n)
+ }
+ // a generous window never permits more than three
+ if n := tiny.AttemptsAdmitted(600); n != ProbeAttemptsMax {
+ t.Fatalf("a 600 s window admits %d attempts, want %d", n, ProbeAttemptsMax)
+ }
+ // R5-5: the spec's example, a 2 GiB database at the example rates. Rev
+ // 7.5's full-snapshot transfer alone takes 205 s and could never fit;
+ // the delta within the bar and the target's build do, with one attempt
+ big := Estimate(2048, ExampleRates)
+ fullTransfer := 2048.0 / ExampleRates.Cross
+ if fullTransfer < CutWindowDefault {
+ t.Fatalf("the example no longer illustrates R5-5")
+ }
+ if n := big.AttemptsAdmitted(CutWindowDefault); n != 1 {
+ t.Fatalf("a 2 GiB database at the example rates admits %d attempts, want 1 (fixed %.1f s, first attempt %.1f s)", n, big.Fixed(), big.Attempt(1))
+ }
+ if math.Abs(big.Fixed()-53.6) > 0.1 {
+ t.Fatalf("the example's fixed costs are %.1f s; the spec says 53.6 s", big.Fixed())
+ }
+ // rev 7.11 (R10-8): the first attempt restores the prepared generation
+ // again and pays the admission: 12.3 + 2 + 30 + 20 = 64.3 s, so the
+ // example runs to 117.9 s and admits one attempt by 2.1 s; a second
+ // attempt would add 76.6 s (a fresh generation's full upload too)
+ if a := big.Attempt(1); math.Abs(a-64.3) > 0.1 {
+ t.Fatalf("the example's first attempt costs %.1f s; the spec says 64.3 s", a)
+ }
+ if tot := big.Fixed() + big.Attempt(1); math.Abs(tot-117.9) > 0.1 {
+ t.Fatalf("the example runs to %.1f s with one attempt; the spec says 117.9 s", tot)
+ }
+ if a := big.Attempt(2); math.Abs(a-76.6) > 0.1 {
+ t.Fatalf("the example's second attempt would cost %.1f s; the spec says 76.6 s", a)
+ }
+ // the review's 2.5 GiB (R10-8): rev 7.10's arithmetic admitted it at
+ // 113.8 s; with the probe's own restore it is 129.2 s, past the window,
+ // and refused
+ bigger := Estimate(2560, ExampleRates)
+ old := bigger.Fixed() + ProbeSeconds + SettleSeconds
+ if math.Abs(old-113.8) > 0.1 {
+ t.Fatalf("rev 7.10's arithmetic for 2.5 GiB is %.1f s; the review says 113.8 s", old)
+ }
+ if n := bigger.AttemptsAdmitted(CutWindowDefault); n != 0 {
+ t.Fatalf("2.5 GiB at the example rates admits %d attempts, want 0 (%.1f s with one attempt)", n, bigger.Fixed()+bigger.Attempt(1))
+ }
+ // and a cross-region snapshot transfer, if a builder did that instead,
+ // is refused by the same arithmetic
+ naive := big
+ naive.Mirror, naive.RestoreStaged, naive.SnapshotBuild = fullTransfer, 0, 0
+ if n := naive.AttemptsAdmitted(CutWindowDefault); n != 0 {
+ t.Fatalf("a full cross-region snapshot transfer admits %d attempts, want 0", n)
+ }
+ // R6-3: rev 7.6 priced three passes at one local rate and then restored
+ // the generation cold a fourth time, unpriced. The review's workload: 3
+ // GiB, 200 MiB/s local, 10 MiB/s across regions, one probe. Three passes
+ // admit one attempt (108.68 s with the probe); the fourth pass takes the
+ // same workload to 124.04 s, past the window
+ r := Rates{Cross: 10, S3: 200, Disk: 200}
+ three := Estimate(3072, r)
+ three.RestoreStaged, three.SnapshotBuild, three.Anchor, three.Verify = 3072/200.0, 3072/200.0, 3072/200.0, 0 // rev 7.6's arithmetic
+ three.RestoreAttempt, three.Admit, three.FreshUpload = 0, 0, 0 // rev 7.6's arithmetic knew no per-attempt restore
+ if n := three.AttemptsAdmitted(CutWindowDefault); n != 1 {
+ t.Fatalf("rev 7.6's three passes admit %d attempts for the review's workload, want 1 (the finding's premise; fixed %.2f s)", n, three.Fixed())
+ }
+ four := three
+ four.Verify = 3072 / 200.0 // the unpriced cold restore, at the same rate
+ if n := four.AttemptsAdmitted(CutWindowDefault); n != 0 || four.Fixed()+ProbeSeconds+SettleSeconds < 124 {
+ t.Fatalf("with the fourth pass priced the review's workload admits %d attempts at %.2f s with one probe, want 0 past 124 s", n, four.Fixed()+ProbeSeconds+SettleSeconds)
+ }
+ // rev 7.7 verifies the built database instead of restoring it again:
+ // the same workload is refused honestly, by its own arithmetic
+ if n := Estimate(3072, r).AttemptsAdmitted(CutWindowDefault); n != 0 {
+ t.Fatalf("rev 7.7 admits %d attempts for the review's workload, want 0 (fixed %.1f s)", n, Estimate(3072, r).Fixed())
+ }
+}
+
+func TestBudgetTotalAndDeadline(t *testing.T) {
+ e := Estimate(2048, ExampleRates)
+ n := e.AttemptsAdmitted(CutWindowDefault)
+ if n != 1 || math.Abs(e.Total(n)-117.9) > 0.1 {
+ t.Fatalf("attempts %d total %.1f", n, e.Total(n))
+ }
+ // cut_deadline = cut_at + cut_budget - open_budget (§7.4).
+ at := time.Date(2026, 10, 6, 9, 0, 0, 0, time.UTC)
+ if d := CutDeadline(at, CutWindowDefault); !d.Equal(at.Add(115 * time.Second)) {
+ t.Fatal(d)
+ }
+}
+
+// The mirror bar is the one input Task 11 overrides
+// (CARLOS_MOVE_READY_DELTA_BYTES, Ruling F28(e)): zero means the default,
+// and the bar scales only the mirror.
+func TestEstimateMirrorBar(t *testing.T) {
+ def := Estimate(2048, ExampleRates)
+ if want := MirrorBarMiB / ExampleRates.Cross; def.Mirror != want {
+ t.Fatalf("default mirror %.2f s, want %.2f s", def.Mirror, want)
+ }
+ explicit := ExampleRates
+ explicit.MirrorBar = MirrorBarMiB
+ if got := Estimate(2048, explicit); got != def {
+ t.Fatalf("an explicit default bar changed the estimate: %+v vs %+v", got, def)
+ }
+ wide := ExampleRates
+ wide.MirrorBar = 64
+ w := Estimate(2048, wide)
+ if math.Abs(w.Mirror-6.4) > 1e-9 {
+ t.Fatalf("a 64 MiB bar at 10 MiB/s mirrors in %.2f s, want 6.4", w.Mirror)
+ }
+ if math.Abs((w.Fixed()-def.Fixed())-(6.4-1.6)) > 1e-9 {
+ t.Fatalf("the bar moved more than the mirror: fixed %.2f vs %.2f", w.Fixed(), def.Fixed())
+ }
+ if w.Attempt(1) != def.Attempt(1) {
+ t.Fatalf("the bar changed a per-attempt cost")
+ }
+ // the open phase is the §7.4 open budget, not a second literal
+ if def.Open != OpenBudget {
+ t.Fatalf("Open %.1f, want OpenBudget %.1f", def.Open, OpenBudget)
+ }
+}
+
+// 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)
+ }
+ }
+}