carlos / platform Public

settings-3 task 3: update_settings hashes into the snapshot only when set

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 09a40e5c48d3d2e6c2b5423170a2573cb499b47e parent 5cf6fd2
4 files changed, +42 −12
  • internal/registry/hold_test.go +1 −1
  • internal/registry/registry.go +12 −10
  • internal/registry/registry_test.go +1 −1
  • internal/registry/updatesettings_test.go +28 −0
diff --git a/internal/registry/hold_test.go b/internal/registry/hold_test.go
index 496f9b2cb..473a98762 100644
--- a/internal/registry/hold_test.go
+++ b/internal/registry/hold_test.go
@@ -424,7 +424,7 @@ func TestFingerprintsUnchangedForUnheldRows(t *testing.T) {
t.Fatal(err)
}
const wantF = "2:f1013971d496e9401cd6a9928a1f4e18669deda40089d2c2a8b84263b32f34c6"
- const wantS = "2:178f6dc7e16d6e4226bf8f24acf88396dee2a2faf85b0306853d1c9ebf81a1b4"
+ const wantS = "2:149e8f668771c7493bdb7d4245fdabe03e957b0909c4daa1d037484b11064b80"
if got, _ := u.Fingerprint(); got != wantF {
t.Fatalf("Fingerprint = %s, want %s", got, wantF)
}
diff --git a/internal/registry/registry.go b/internal/registry/registry.go
index a92bbcebe..fe3457f65 100644
--- a/internal/registry/registry.go
+++ b/internal/registry/registry.go
@@ -1028,8 +1028,8 @@ func (r *Registry) Delete(host string) error {
// SetEnvironment (`carlos route -environment -`), SetCaps (`carlos route
// -revoke`), SetTunnel (`carlos route -tunnel -`), SetUnit (`carlos route
// -unit -`), SetReplication (`carlos route -replication -`), SetSeed,
-// SetFleetBox, SetArgs, SetWorkerLimits, and for `provisioned` the row's
-// own deletion.
+// SetFleetBox, SetArgs, SetWorkerLimits, SetUpdateSettings, and for
+// `provisioned` the row's own deletion.
//
// `replication` is the one column with a SECOND clearing door, and it is not
// an operator's: SetHibernate clears it when a route stops hibernating,
@@ -3141,15 +3141,17 @@ var snapshotClaimCols = map[string]bool{
"claim_row_id": true,
}
-// snapshotWhenSetCols are the good-build columns (zero-downtime spec §8.5).
-// A restore has to reproduce them, so they are hashed, but only when set and
-// tagged, on the hold_op rule: a registry that has never recorded a good
-// build digests exactly as it did before the columns existed, so upgrading
-// does not force a snapshot on every box.
+// snapshotWhenSetCols are the good-build columns (zero-downtime spec §8.5)
+// and the update-settings column. A restore has to reproduce them, so they
+// are hashed, but only when set and tagged, on the hold_op rule: a registry
+// that has never recorded a good build or set update settings digests exactly
+// as it did before the columns existed, so upgrading does not force a snapshot
+// on every box.
var snapshotWhenSetCols = map[string]bool{
- "good_version": true,
- "good_digest": true,
- "good_demand": true,
+ "good_version": true,
+ "good_digest": true,
+ "good_demand": true,
+ "update_settings": true,
}
func (r *Registry) SnapshotFingerprint() (string, error) {
diff --git a/internal/registry/registry_test.go b/internal/registry/registry_test.go
index f8ed1e8c1..ca70219d2 100644
--- a/internal/registry/registry_test.go
+++ b/internal/registry/registry_test.go
@@ -83,7 +83,7 @@ func TestUpsertRoundTripsAllColumns(t *testing.T) {
CreatedAt: 1700000000,
GoodVersion: "good789", GoodDigest: "sha256:good", GoodDemand: "swap-demand",
ServingVersion: "serve012",
- UpdateSettings: "",
+ UpdateSettings: `{"updates":"manual","channel":"stable"}`,
}
if err := reg.Upsert(want); err != nil {
t.Fatal(err)
diff --git a/internal/registry/updatesettings_test.go b/internal/registry/updatesettings_test.go
index ec49b9a98..d397254c8 100644
--- a/internal/registry/updatesettings_test.go
+++ b/internal/registry/updatesettings_test.go
@@ -52,3 +52,31 @@ func TestUpdateSettingsColumn(t *testing.T) {
t.Fatal("an unknown host was not an error")
}
}
+
+func TestUpdateSettingsMovesSnapshotFingerprint(t *testing.T) {
+ reg, err := Open(filepath.Join(t.TempDir(), "r.db"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := reg.Upsert(Route{Host: "s.example.com", Kind: "instance", App: "km"}); err != nil {
+ t.Fatal(err)
+ }
+ // SnapshotFingerprint with unset update_settings
+ before, err := reg.SnapshotFingerprint()
+ if err != nil {
+ t.Fatal(err)
+ }
+ col := `{"updates":"manual","channel":"stable"}`
+ if err := reg.SetUpdateSettings("s.example.com", col); err != nil {
+ t.Fatal(err)
+ }
+ // SnapshotFingerprint with set update_settings must be different
+ // (restores must reproduce the column value)
+ after, err := reg.SnapshotFingerprint()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if after == before {
+ t.Fatal("setting update_settings did not move SnapshotFingerprint — restore would miss the column")
+ }
+}