carlos / platform Public

provision: adoption dry-run reports environment, not just channel

Review fix: the adoption branch's dry-run block printed a fixed "would
adopt ... converging channel X -> Y" line and skipped convergeOwned
entirely, so an operator backfilling a fleet whose records carry
Environment never learned that column was about to change too -- a
material omission in the report this mitigation exists to let someone
read before trusting it. It also claimed a channel convergence would
happen even when the route was already on the record's channel.

Fixed by falling through to convergeOwned(reg, existing, rec, true)
instead of hand-rolling the report: it already knows which of the two
columns actually differ, and its own dryRun guard (added in the prior
commit specifically so a shared function can't be called unsafely from
a caller that forgot to guard) makes the call write-free.

Also added the positive control the review flagged: re-running the
same TestReconcileDryRunWritesNothing fixture with DryRun false now
asserts the registry DID change, so the dry-run half's "unchanged"
assertion can't pass vacuously if a future change ever caused a branch
to silently stop being exercised.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Paul Campbell 7e577abcee05306b2fe90e1a37fefbfc50749e2c parent 2460962
2 files changed, +38 −4
  • internal/provision/reconcile.go +11 −2
  • internal/provision/reconcile_test.go +27 −2
diff --git a/internal/provision/reconcile.go b/internal/provision/reconcile.go
index 649636d7d..2f68aba87 100644
--- a/internal/provision/reconcile.go
+++ b/internal/provision/reconcile.go
@@ -469,8 +469,17 @@ func Reconcile(ctx context.Context, s release.Store, reg *registry.Registry, cfg
continue
}
if cfg.DryRun {
- log.Printf("provision: dry-run: would adopt existing route %s (app %q acct %q), converging channel %s -> %s",
- rec.Host, rec.App, rec.Account, existing.Channel, rec.Channel)
+ log.Printf("provision: dry-run: would adopt existing route %s (app %q acct %q) — this record would own its channel and environment",
+ rec.Host, rec.App, rec.Account)
+ // convergeOwned reports channel and environment together,
+ // and only the columns that actually differ (design §4.1) —
+ // a fixed "converging channel X -> Y" line here would both
+ // omit environment (the defect this fixes) and claim a
+ // convergence that would not happen when the route is
+ // already on the record's channel. Sites 1 and 2 (SetChannel,
+ // SetEnvironment) are already guarded inside convergeOwned,
+ // so this call writes nothing.
+ convergeOwned(reg, existing, rec, true)
continue
}
if err := reg.SetProvisioned(rec.Host, true); err != nil {
diff --git a/internal/provision/reconcile_test.go b/internal/provision/reconcile_test.go
index 00e0965d2..03bba08b3 100644
--- a/internal/provision/reconcile_test.go
+++ b/internal/provision/reconcile_test.go
@@ -1251,9 +1251,12 @@ func TestReconcileDryRunWritesNothing(t *testing.T) {
}
// Adopt: a hand-added route matching the record's shape, on a
- // different channel — adoption would converge it in the same pass.
+ // different channel AND environment — adoption would converge both in
+ // the same pass. Two differing columns, deliberately: a dry-run report
+ // that only ever mentioned channel here would leave environment's
+ // change invisible to the operator reading it (design §4.3 review).
adoptRec := Record{Schema: 1, Host: "adopt.go.tito.io", Slug: "adopt", App: "tito",
- Account: sqid, Pool: "tito", Region: "ie", Channel: "edge", CreatedAt: time.Now()}
+ Account: sqid, Pool: "tito", Region: "ie", Channel: "edge", Environment: "staging", CreatedAt: time.Now()}
if err := CreateRecord(ctx, s, adoptRec); err != nil {
t.Fatal(err)
}
@@ -1303,6 +1306,28 @@ func TestReconcileDryRunWritesNothing(t *testing.T) {
if rt, err := reg.Get("acme.go.tito.io"); err != nil || rt != nil {
t.Errorf("dry-run minted a route: %+v %v", rt, err)
}
+
+ // Positive control (reviewer-flagged): the assertion above — total
+ // state unchanged — would also pass if this fixture simply never
+ // exercised the mint/adopt/converge/remove branches at all, e.g. if a
+ // future validation change caused ownedRec to be skipped and
+ // protect-ed, silently dropping the converge branch from coverage while
+ // the test still reported green. Re-running the identical fixture for
+ // real proves the fixture DOES drive every branch: if state came out
+ // unchanged here too, the dry-run assertion above would have been
+ // vacuous, not a proof of anything.
+ cfg.DryRun = false
+ if err := Reconcile(ctx, s, reg, cfg); err != nil {
+ t.Fatal(err)
+ }
+ live, err := reg.List()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if reflect.DeepEqual(before, live) {
+ t.Fatal("a real pass over the identical fixture left the registry unchanged — " +
+ "the dry-run assertion above proves nothing if nothing was ever going to write")
+ }
}
// The other side of that coin, and the one that matters more: a route no