provision: reconcile hands records with a run to RunSeen (run spec §5.2, codex 3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+39
−0
internal/provision/reconcile.go+10 −0internal/provision/reconcile_test.go+29 −0
CI passed — run details
diff --git a/internal/provision/reconcile.go b/internal/provision/reconcile.go| index 70b3b0509..125691e5b 100644 |
| --- a/internal/provision/reconcile.go |
| +++ b/internal/provision/reconcile.go |
| @@ -125,6 +125,13 @@ type ReconcileConfig struct { |
| // stage-requested marker: root re-judges what it staged under the new |
| // hold (registry.SetHold's contract, G10). Nil: nobody is told. |
| HoldWritten func(host string) |
| + // RunSeen is called with every in-scope record of an untrashed app that |
| + // carries a run, once per pass, before the pass acts on the record (run |
| + // spec §5.2: pickup rides reconcile's own record reads, so an idle box |
| + // pays nothing extra). It must not block: the box's runs worker only |
| + // notes the record and acts after the pass, by which time a row this |
| + // pass minted is in the registry. Nil: no one is told. |
| + RunSeen func(rec Record) |
| // beforeHoldWrite is the tests' pause under the host's hold lock, just |
| // before holdExisting's conditional write (TestReconcileHoldWrite...). |
| @@ -1073,6 +1080,9 @@ func Reconcile(ctx context.Context, s release.Store, reg *registry.Registry, cfg |
| if trashedFor(rec.Account, rec.App) { |
| continue |
| } |
| + if cfg.RunSeen != nil && rec.ActiveRun != nil { |
| + cfg.RunSeen(rec) |
| + } |
| // A mint-only fallback ("", the record-already-written-is-truth |
| // case) is fine here even when the manifest is unreadable this |
| // pass — there is no existing seed value a brand new route could |
diff --git a/internal/provision/reconcile_test.go b/internal/provision/reconcile_test.go| index 51e53e7c6..6d6080a95 100644 |
| --- a/internal/provision/reconcile_test.go |
| +++ b/internal/provision/reconcile_test.go |
| @@ -3551,3 +3551,32 @@ func TestWithHostHoldLockIsExclusivePerHost(t *testing.T) { |
| t.Fatal("the lock was not handed on") |
| } |
| } |
| + |
| +// Reconcile's own reads carry the runs to the box (run spec §5.2): every |
| +// in-scope record with a run, including one whose row this very pass |
| +// mints, and none out of scope. |
| +func TestReconcileHandsRunsToRunSeen(t *testing.T) { |
| + s, sqid := setup(t) |
| + withRun := acmeRecord(sqid) |
| + withRun.ActiveRun = &RunState{ID: "run_abcdefghijklmnopqrstuvwxyz", State: RunQueued, Deadline: time.Now().Add(time.Minute)} |
| + plain := acmeRecord(sqid) |
| + plain.Host, plain.Slug = "plain.go.tito.io", "plain" |
| + away := acmeRecord(sqid) |
| + away.Host, away.Slug, away.Region = "away.go.tito.io", "away", "uk" |
| + away.ActiveRun = withRun.ActiveRun |
| + for _, rec := range []Record{withRun, plain, away} { |
| + if err := CreateRecord(ctx, s, rec); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + var seen []string |
| + cfg := ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data", RunSeen: func(rec Record) { |
| + seen = append(seen, rec.Host+":"+rec.ActiveRun.ID) |
| + }} |
| + if err := Reconcile(ctx, s, regWith(t), cfg); err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(seen) != 1 || seen[0] != "acme.go.tito.io:run_abcdefghijklmnopqrstuvwxyz" { |
| + t.Fatalf("RunSeen saw %q", seen) |
| + } |
| +} |