carlos / platform Public

settings-3 final review: the control socket reports the route's channel for default settings

An empty settings column (default settings) names no channel, so GET
/updates answered "channel":"" for every untouched instance. The
reader now fills the route's channel when the column names none; a set
column's channel, reconcile's effective copy, still wins. The stored
column is never touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude agent, operated by paul@keymail.dev 9002e8f225104f0c1c86335b252af1e39f24ce96 parent df3def0
2 files changed, +37 −3
  • internal/instanceupdates/control.go +10 −2
  • internal/instanceupdates/control_test.go +27 −1
diff --git a/internal/instanceupdates/control.go b/internal/instanceupdates/control.go
index bb86c5118..a115b9e9b 100644
--- a/internal/instanceupdates/control.go
+++ b/internal/instanceupdates/control.go
@@ -63,8 +63,9 @@ func Mount(mux *http.ServeMux, host string, token func() (string, error), read f
// row for: the verb turns it into a 503, never a 200 with an empty report.
var errNoRoute = errors.New("instanceupdates: no route for host")
-// RegistryReader answers from the box's registry copy: the settings column,
-// the installed build and the serving build. Root's desired build and reason
+// RegistryReader answers from the box's registry copy: the settings column
+// (with the route's channel when the column names none), the installed
+// build and the serving build. Root's desired build and reason
// are added by plan 4's reader, which wraps this one.
func RegistryReader(reg interface {
Get(host string) (*registry.Route, error)
@@ -81,6 +82,13 @@ func RegistryReader(reg interface {
if err != nil {
return Report{}, err
}
+ // Default settings are stored as "" (Column), which names no channel:
+ // the instance follows its route's. A set column is reconcile's
+ // effective copy and its channel wins; one with no channel falls back
+ // to the route's too. Only the report is filled, never the column.
+ if u.Channel == "" {
+ u.Channel = rt.Channel
+ }
return Report{Settings: u, MayChange: u.Control, Installed: rt.Version, Serving: rt.ServingVersion}, nil
}
}
diff --git a/internal/instanceupdates/control_test.go b/internal/instanceupdates/control_test.go
index 81eda8f26..d726686b4 100644
--- a/internal/instanceupdates/control_test.go
+++ b/internal/instanceupdates/control_test.go
@@ -160,11 +160,37 @@ func TestRegistryReaderFillsFromTheRoute(t *testing.T) {
}
}
+// An empty column is an instance with default settings (Column() writes ""
+// for them), and its channel is the route's: the report says which channel
+// the instance follows, and the stored column stays "".
func TestRegistryReaderReadsAnEmptyColumnAsAuto(t *testing.T) {
- rep, err := RegistryReader(fakeReg{rt: &registry.Route{Host: "a.example.com", Version: "v1"}})("a.example.com")
+ rt := &registry.Route{Host: "a.example.com", Version: "v1", Channel: "beta"}
+ rep, err := RegistryReader(fakeReg{rt: rt})("a.example.com")
if err != nil || rep.Settings.Updates != release.UpdatesAuto || rep.Installed != "v1" {
t.Fatalf("%+v, %v", rep, err)
}
+ if rep.Settings.Channel != "beta" {
+ t.Fatalf("channel of an empty column = %q, want the route's %q", rep.Settings.Channel, "beta")
+ }
+ if rt.UpdateSettings != "" {
+ t.Fatalf("the reader wrote the column: %q", rt.UpdateSettings)
+ }
+}
+
+// A set column is reconcile's effective copy and names its own channel, so
+// it wins over the route's; a set column with no channel falls back to the
+// route's.
+func TestRegistryReaderChannelPrefersTheColumn(t *testing.T) {
+ rep, err := RegistryReader(fakeReg{rt: &registry.Route{Host: "a.example.com", Channel: "stable",
+ UpdateSettings: `{"updates":"manual","channel":"beta"}`}})("a.example.com")
+ if err != nil || rep.Settings.Channel != "beta" {
+ t.Fatalf("column beta, route stable: %+v, %v", rep, err)
+ }
+ rep, err = RegistryReader(fakeReg{rt: &registry.Route{Host: "a.example.com", Channel: "stable",
+ UpdateSettings: `{"updates":"manual"}`}})("a.example.com")
+ if err != nil || rep.Settings.Channel != "stable" {
+ t.Fatalf("column with no channel, route stable: %+v, %v", rep, err)
+ }
}
// A host the registry does not know, or cannot read, is an error: the verb