From dc15e2d04b5d9c32c3f794ab1d8bc096c99e5d8d Mon Sep 17 00:00:00 2001 From: Emil Date: Mon, 3 Aug 2026 16:35:37 +0300 Subject: [PATCH] Warn in the wizard preflight when the installed scimesh version mismatches the binary --- coordinator/internal/agent/check.go | 4 ++- coordinator/internal/agent/setupui/server.go | 26 ++++++++++++++++ .../internal/agent/setupui/server_test.go | 31 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/coordinator/internal/agent/check.go b/coordinator/internal/agent/check.go index 3aa23d7..c4c6d38 100644 --- a/coordinator/internal/agent/check.go +++ b/coordinator/internal/agent/check.go @@ -88,8 +88,10 @@ func CheckEnvironment(ctx context.Context) CheckReport { // execute with. func CheckEnvironmentWithPython(ctx context.Context, python string) CheckReport { report := CheckReport{Agent: Version, Python: CheckItem{Name: "python", OK: true, Detail: python}} + // The version comes from importlib.metadata, so the wizard can compare the + // installed package with the binary version and offer an upgrade. //nolint:gosec // G204: python is a resolved interpreter path, the argument list is constant - cmd := exec.CommandContext(ctx, python, "-c", "import scimesh; print(scimesh.__version__ if hasattr(scimesh, '__version__') else 'installed')") + cmd := exec.CommandContext(ctx, python, "-c", "import importlib.metadata as m; print(m.version('scimesh'))") out, err := cmd.Output() if err != nil { // The worker executes workloads by spawning scimesh's task runner, so diff --git a/coordinator/internal/agent/setupui/server.go b/coordinator/internal/agent/setupui/server.go index 8aff709..0606ceb 100644 --- a/coordinator/internal/agent/setupui/server.go +++ b/coordinator/internal/agent/setupui/server.go @@ -454,6 +454,9 @@ func (s *Server) handleTest(w http.ResponseWriter, r *http.Request) { // checking the bare system python3 would keep reporting scimesh as // missing even though the worker would run with the venv. report := agent.RunCheck(r.Context(), url, s.venvPython(), req.Token, req.WorkerKey, req.UserserviceURL) + if report.Scimesh.OK { + report.Scimesh = ensureMatchingScimeshVersion(report.Scimesh) + } writeJSON(w, http.StatusOK, report) } @@ -628,3 +631,26 @@ func truncate(s string, n int) string { } return s[:n] + "…" } + +// ensureMatchingScimeshVersion flips a green scimesh check to a stale one when +// the installed package does not match the worker-agent's own version: a +// version-locked wheel is the only supported runtime, and a mismatch means the +// workload catalog the worker advertises is not what it executes. The wizard +// UI then offers the Install button again. Dev builds have no release wheel, +// so they skip the comparison. +func ensureMatchingScimeshVersion(item agent.CheckItem) agent.CheckItem { + if agent.Version == "" || agent.Version == "dev" { + return item + } + want := agent.NormalizePEP440(agent.Version) + got := strings.TrimSpace(item.Detail) + if got == "" || got == want { + return item + } + item.OK = false + item.Detail = fmt.Sprintf( + "installed scimesh %s, but this worker-agent (%s) needs %s — press Install to upgrade", + got, agent.Version, want, + ) + return item +} diff --git a/coordinator/internal/agent/setupui/server_test.go b/coordinator/internal/agent/setupui/server_test.go index d76d524..3349e20 100644 --- a/coordinator/internal/agent/setupui/server_test.go +++ b/coordinator/internal/agent/setupui/server_test.go @@ -497,3 +497,34 @@ time=6 level=WARN msg="agent cycle failed" error="boom" t.Errorf("stats = %+v, want registered claimed=2 completed=1 failed=1", stats) } } + +func writeVenvStub(t *testing.T, server *Server, version string) string { + t.Helper() + venvPython := filepath.Join(server.dir, "venv", "bin", "python") + _ = os.MkdirAll(filepath.Dir(venvPython), 0o755) + _ = os.WriteFile(venvPython, []byte("#!/bin/sh\nif [ \"$1\" = \"-c\" ]; then echo "+version+"; exit 0; fi\nexit 0\n"), 0o755) + return venvPython +} + +func testCheckScimeshVersion(t *testing.T, installed, binary string, wantOK bool, wantDetail string) { + t.Helper() + old := agent.Version + agent.Version = binary + t.Cleanup(func() { agent.Version = old }) + item := ensureMatchingScimeshVersion(agent.CheckItem{Name: "scimesh", OK: true, Detail: installed}) + if item.OK != wantOK { + t.Errorf("installed=%s binary=%s: ok=%v, want %v (%s)", installed, binary, item.OK, wantOK, item.Detail) + } + if wantDetail != "" && !strings.Contains(item.Detail, wantDetail) { + t.Errorf("detail = %q, want it to contain %q", item.Detail, wantDetail) + } +} + +func TestEnsureMatchingScimeshVersion(t *testing.T) { + testCheckScimeshVersion(t, "1.1.0a20", "1.1.0-alpha.20", true, "") + testCheckScimeshVersion(t, "1.1.0a17", "1.1.0-alpha.20", false, "press Install to upgrade") + testCheckScimeshVersion(t, "1.1.0a16.dev7+gea0fb8c59.d20260803", "1.1.0-alpha.20", false, "needs 1.1.0a20") + // Dev builds and unknown versions never block. + testCheckScimeshVersion(t, "anything", "dev", true, "") + testCheckScimeshVersion(t, "1.1.0a20", "", true, "") +}