From 1153be477024af2b5832b2aa2bbf8b8abeafd74e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Mon, 20 Jul 2026 08:19:00 +0100 Subject: [PATCH] Keep agent install handoff on canonical config Contract-Neutral: The first-session agent install handoff keeps the existing API and setup contracts while binding runtime handlers to the Router-owned canonical config through startup and monitor reloads. --- internal/api/host_agent_install_token_test.go | 37 +++++++++++++++++++ internal/api/router.go | 2 + internal/api/router_helpers.go | 1 + 3 files changed, 40 insertions(+) diff --git a/internal/api/host_agent_install_token_test.go b/internal/api/host_agent_install_token_test.go index d513db19d..2d3f298ce 100644 --- a/internal/api/host_agent_install_token_test.go +++ b/internal/api/host_agent_install_token_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/rcourtman/pulse-go-rewrite/internal/config" + "github.com/rcourtman/pulse-go-rewrite/internal/monitoring" ) func decodeHostAgentInstallResponse(t *testing.T, rec *httptest.ResponseRecorder) AgentInstallCommandResponse { @@ -101,3 +102,39 @@ func TestHandleAgentInstallCommand_HostOmitsTokenWhenAuthOptional(t *testing.T) t.Fatalf("expected optional-auth host install to avoid persisting API tokens") } } + +func TestRouterSetupHandoffUsesCanonicalRuntimeConfig(t *testing.T) { + dataDir := t.TempDir() + runtimeConfig := &config.Config{DataPath: dataDir, ConfigPath: dataDir} + monitorConfig := &config.Config{DataPath: dataDir, ConfigPath: dataDir} + + monitor, err := monitoring.New(monitorConfig) + if err != nil { + t.Fatalf("monitoring.New: %v", err) + } + t.Cleanup(monitor.Stop) + + router := NewRouter(runtimeConfig, monitor, nil, nil, func() error { return nil }, "1.0.0") + t.Cleanup(router.shutdownBackgroundWorkers) + + // The first-session security setup updates the Router-owned config after + // routes have already been wired. The agent-install handoff must observe + // that same canonical config instead of the monitor's startup snapshot. + config.Mu.Lock() + runtimeConfig.AuthUser = "admin" + runtimeConfig.AuthPass = "hashed-password" + config.Mu.Unlock() + + body := []byte(`{"type":"host","enableCommands":false}`) + req := httptest.NewRequest(http.MethodPost, "/api/agent-install-command", bytes.NewReader(body)) + rec := httptest.NewRecorder() + router.configHandlers.HandleAgentInstallCommand(rec, req) + + resp := decodeHostAgentInstallResponse(t, rec) + if resp.Token == "" || resp.Record == nil { + t.Fatalf("expected setup handoff to create a scoped install token") + } + if got := router.configHandlers.getConfig(req.Context()); got != runtimeConfig { + t.Fatalf("config handler uses config %#v, want Router config %#v", got, runtimeConfig) + } +} diff --git a/internal/api/router.go b/internal/api/router.go index 61b0e0c2a..befdea999 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -419,6 +419,7 @@ func (r *Router) setupRoutes() { if r.monitor != nil { r.configHandlers.SetMonitor(r.monitor) } + r.configHandlers.SetConfig(r.config) r.configHandlers.SetMockModeChangeHook(r.syncPlatformSupplementalProviders) r.trueNASHandlers = &TrueNASHandlers{ getPersistence: r.configHandlers.getPersistence, @@ -1474,6 +1475,7 @@ func (r *Router) SetMonitor(m *monitoring.Monitor) { } if r.configHandlers != nil { r.configHandlers.SetMonitor(m) + r.configHandlers.SetConfig(r.config) } if r.notificationHandlers != nil { r.notificationHandlers.SetMonitor(NewNotificationMonitorWrapper(m)) diff --git a/internal/api/router_helpers.go b/internal/api/router_helpers.go index 28a644032..afa89efd2 100644 --- a/internal/api/router_helpers.go +++ b/internal/api/router_helpers.go @@ -86,6 +86,7 @@ func (r *Router) SetMultiTenantMonitor(mtm *monitoring.MultiTenantMonitor) { r.mtMonitor = mtm if r.configHandlers != nil { r.configHandlers.SetMultiTenantMonitor(mtm) + r.configHandlers.SetConfig(r.config) } if r.alertHandlers != nil { r.alertHandlers.SetMultiTenantMonitor(mtm)