carlos / platform Public

console: /healthz reports the build the PROCESS is running

X-Carlos-Version is stamped by the edge from the route row, so it reports
what adoption intended from the moment the symlink moves — whether or not
anything restarted. systemctl is-active is no better. Only the process can
answer this honestly, and now it does.

'ok' stays the first token: vet and the edge probe read status not body,
but a human eyeballing it should see what they always saw.
Paul Campbell pushed by paul@keymail.dev b88c4cd230ce5c0c6d573bf7fd9ce4cc2682542c parent 5a1538c
2 files changed, +35 −1
  • internal/console/server.go +11 −1
  • internal/console/server_test.go +24 −0
diff --git a/internal/console/server.go b/internal/console/server.go
index ff4d49cd2..04caade06 100644
--- a/internal/console/server.go
+++ b/internal/console/server.go
@@ -37,6 +37,7 @@ import (
"github.com/carlosframework/platform/internal/status"
"github.com/carlosframework/platform/internal/svcrole"
"github.com/carlosframework/platform/internal/usage"
+ "github.com/carlosframework/platform/internal/version"
)
//go:embed templates/*.html
@@ -797,8 +798,17 @@ func NewServer(cfg Config, st *Store) (*Server, error) {
// still land somewhere sensible rather than 404ing.
s.mux.HandleFunc("GET /apps", s.handleLegacyApps)
s.mux.HandleFunc("GET /apps/{app}", s.handleLegacyApp)
+ // "ok <build>" — the build comes from THIS PROCESS (ldflags, at link
+ // time), which is the whole point: X-Carlos-Version is stamped by the
+ // edge from the route row, so it reports the version adoption INTENDED
+ // from the moment the symlink moves, whether or not anything restarted.
+ // `carlos deploy` polls this instead for any app declaring a build probe.
+ //
+ // "ok" stays the first token: internal/vet and the edge health probe read
+ // this endpoint and check status, not body, but keeping the prefix means
+ // a human or a script eyeballing it sees what it always saw.
s.mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, "ok")
+ fmt.Fprintln(w, "ok", version.Build)
})
// Catch up on accounts minted outside this database — `carlos accounts
// create` writes the bucket manifest and the registry row and never opens
diff --git a/internal/console/server_test.go b/internal/console/server_test.go
index e756ca8c3..099cd46d3 100644
--- a/internal/console/server_test.go
+++ b/internal/console/server_test.go
@@ -23,6 +23,7 @@ import (
"github.com/carlosframework/platform/internal/registry"
"github.com/carlosframework/platform/internal/release"
"github.com/carlosframework/platform/internal/status"
+ "github.com/carlosframework/platform/internal/version"
)
// consoleFixture is a whole console: fixture platform (registry + bucket
@@ -302,6 +303,29 @@ func TestConsoleE2E(t *testing.T) {
}
}
+// TestHealthzCarriesTheBuild: the console's own build, from the PROCESS.
+//
+// This is the deploy watch's exit criterion, and it exists because both cheap
+// answers lie. X-Carlos-Version is stamped by the EDGE from the route row
+// (edge.go:1336), so adoption sets it while the old process keeps serving;
+// `systemctl is-active` says "active" for a process started from the previous
+// version just as happily (tito, 2026-08-09, seconds after adopt and restart
+// both reported success). Only the running process can answer this.
+func TestHealthzCarriesTheBuild(t *testing.T) {
+ fx := newConsoleFixture(t)
+ status, _, body := get(t, &http.Client{}, fx.ts.URL+"/healthz")
+ if status != 200 {
+ t.Fatalf("/healthz = %d, want 200", status)
+ }
+ if !strings.Contains(body, "ok") {
+ t.Fatalf("/healthz body = %q, want it to still start with ok (vet and the edge probe read this)", body)
+ }
+ fields := strings.Fields(body)
+ if len(fields) != 2 || fields[1] != version.Build {
+ t.Fatalf("/healthz body = %q, want exactly \"ok %s\" — the watch matches a whitespace-delimited token", body, version.Build)
+ }
+}
+
var confirmLinkRe = regexp.MustCompile(`https?://[^\s"]+/auth/confirm\?token=[A-Za-z0-9_%.~-]+`)
func TestReturningSignInViaConfirmationLink(t *testing.T) {