fix(registry): heartbeat promotes provisioning → online atomically (#2500) #2562

Merged
agent-reviewer merged 1 commits from fix/heartbeat-promote-provisioning-to-online into main 2026-06-11 02:27:51 +00:00
2 changed files with 42 additions and 0 deletions
@@ -702,6 +702,7 @@ func (h *RegistryHandler) Heartbeat(c *gin.Context) {
uptime_seconds = $5,
current_task = $6,
monthly_spend = $7,
status = CASE WHEN status = 'provisioning' THEN 'online' ELSE status END,
updated_at = now()
WHERE id = $1 AND status != 'removed'
`, payload.WorkspaceID, payload.ErrorRate, payload.SampleError,
@@ -716,6 +717,7 @@ func (h *RegistryHandler) Heartbeat(c *gin.Context) {
active_tasks = $4,
uptime_seconds = $5,
current_task = $6,
status = CASE WHEN status = 'provisioning' THEN 'online' ELSE status END,
updated_at = now()
WHERE id = $1 AND status != 'removed'
`, payload.WorkspaceID, payload.ErrorRate, payload.SampleError,
@@ -197,6 +197,7 @@ const registerUpsertSQL = `
const heartbeatUpdateSQL = `
UPDATE workspaces SET
last_heartbeat_at = now(),
status = CASE WHEN status = 'provisioning' THEN 'online' ELSE status END,
updated_at = now()
WHERE id = $1 AND status != 'removed'
`
@@ -285,6 +286,45 @@ func TestIntegration_RegistryRowState_HeartbeatUpdatesLiveWorkspace(t *testing.T
}
}
func TestIntegration_RegistryRowState_HeartbeatPromotesProvisioningToOnline(t *testing.T) {
conn := integrationAuthDB(t)
ctx := context.Background()
id := insertWorkspace(t, conn, "provisioning-ws", "provisioning", "")
if _, err := conn.ExecContext(ctx, heartbeatUpdateSQL, id); err != nil {
t.Fatalf("heartbeat update: %v", err)
}
if got := statusOf(t, conn, id); got != "online" {
t.Fatalf("provisioning workspace not promoted to online by heartbeat: status=%q, want 'online'", got)
}
var hb sql.NullTime
if err := conn.QueryRowContext(ctx,
`SELECT last_heartbeat_at FROM workspaces WHERE id = $1`, id).Scan(&hb); err != nil {
t.Fatalf("read last_heartbeat_at: %v", err)
}
if !hb.Valid {
t.Fatalf("provisioning workspace heartbeat did NOT bump last_heartbeat_at")
}
}
func TestIntegration_RegistryRowState_HeartbeatProvisioningAlreadyOnlineUnchanged(t *testing.T) {
conn := integrationAuthDB(t)
ctx := context.Background()
id := insertWorkspace(t, conn, "online-ws", "online", "")
if _, err := conn.ExecContext(ctx, heartbeatUpdateSQL, id); err != nil {
t.Fatalf("heartbeat update: %v", err)
}
if got := statusOf(t, conn, id); got != "online" {
t.Fatalf("online workspace status changed unexpectedly by heartbeat: status=%q, want 'online'", got)
}
}
// ---------------------------------------------------------------------------
// 2 — wsauth.ValidateToken A↔B binding (the cross-tenant non-leak boundary).
//