Align monitored system aliases with latest signal

This commit is contained in:
rcourtman 2026-03-24 09:22:55 +00:00
parent b271fb5b2a
commit 8bb0a99f03
6 changed files with 136 additions and 15 deletions

View file

@ -245,7 +245,9 @@ left only as rollout compatibility fields rather than promises that every
grouped source is healthy at that moment. Lifecycle-adjacent consumers must
not label it with generic single-source health wording, and should use the
canonical object when they need attribution for which grouped surface reported
most recently.
most recently. When those flat fields still appear during rollout, they should
be treated only as aliases for `latest_included_signal.at` and
`latest_included_signal.source`, not as an independent lifecycle signal.
Lifecycle-adjacent workspace copy must also keep the same commercial framing:
infrastructure operations may point operators to Pulse Pro for billing, but it
must describe that boundary in monitored-system, plan-limit, and license-status

View file

@ -259,6 +259,11 @@ identify exactly which included top-level surface reported most recently.
remain rollout compatibility fields only on the raw wire shape; normalized
frontend clients should treat the object as the primary contract and must not
re-export those flat aliases in their public response model.
When those flat fields are still emitted for rollout compatibility, the backend
must derive them directly from the canonical object rather than from a parallel
timestamp/source path, so `latest_included_signal_at` and `last_seen` mirror
`latest_included_signal.at` and `latest_included_signal_source` mirrors
`latest_included_signal.source`.
That client contract must also fail closed when older or partial payloads omit
the nested explanation object: the frontend may normalize missing explanation
fields to empty reasons/surfaces plus a safe default summary, but it must not

View file

@ -384,7 +384,9 @@ left only as rollout compatibility fields rather than universal health
timestamps. Storage- or recovery-adjacent consumers must not present that data
with bare single-source `Last Seen` wording that hides grouped stale/offline
conditions, and should use the canonical object when they need attribution for
which grouped surface most recently reported.
which grouped surface most recently reported. When those flat fields still
appear during rollout, they should be interpreted only as aliases for the
canonical object rather than as separate storage-facing freshness signals.
That same shared `internal/api/` dependency now also assumes self-hosted
commercial counting is canonical at the top-level monitored-system boundary:
shared setup, deploy, entitlement, and API-backed monitoring helpers may not

View file

@ -688,6 +688,75 @@ func TestContract_MonitoredSystemLedgerJSONSnapshot(t *testing.T) {
assertJSONSnapshot(t, got, want)
}
func TestContract_MonitoredSystemLedgerCompatibilityAliasesFollowLatestSignal(t *testing.T) {
entry := monitoredSystemLedgerEntry(unifiedresources.MonitoredSystemRecord{
Name: "Tower",
Type: "host",
Status: unifiedresources.StatusWarning,
StatusExplanation: unifiedresources.MonitoredSystemStatusExplanation{
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
Reasons: []unifiedresources.MonitoredSystemStatusReason{},
},
LastSeen: time.Date(2026, 3, 18, 17, 35, 0, 0, time.UTC),
LatestIncludedSignal: unifiedresources.MonitoredSystemLatestSignal{
Name: "tower.local",
Type: "docker-host",
Source: "docker",
At: time.Date(2026, 3, 18, 17, 30, 0, 0, time.UTC),
},
Source: "multiple",
Explanation: unifiedresources.MonitoredSystemGroupingExplanation{
Summary: "Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
Reasons: []unifiedresources.MonitoredSystemGroupingReason{},
Surfaces: []unifiedresources.MonitoredSystemGroupingSurface{},
},
})
payload := MonitoredSystemLedgerResponse{
Systems: []MonitoredSystemLedgerEntry{entry},
Total: 1,
Limit: 5,
}
got, err := json.Marshal(payload)
if err != nil {
t.Fatalf("marshal monitored system ledger response: %v", err)
}
const want = `{
"systems":[
{
"name":"Tower",
"type":"host",
"status":"warning",
"status_explanation":{
"summary":"At least one included source is stale, so Pulse marks this monitored system as warning.",
"reasons":[]
},
"latest_included_signal":{
"name":"tower.local",
"type":"docker-host",
"source":"docker",
"at":"2026-03-18T17:30:00Z"
},
"latest_included_signal_at":"2026-03-18T17:30:00Z",
"latest_included_signal_source":"docker",
"last_seen":"2026-03-18T17:30:00Z",
"source":"multiple",
"explanation":{
"summary":"Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
"reasons":[],
"surfaces":[]
}
}
],
"total":1,
"limit":5
}`
assertJSONSnapshot(t, got, want)
}
func TestContract_ResolveAuthEnvPathUsesCanonicalRuntimeDataDir(t *testing.T) {
envDir := t.TempDir()
t.Setenv("PULSE_DATA_DIR", envDir)

View file

@ -132,19 +132,7 @@ func (r *Router) handleMonitoredSystemLedger(w http.ResponseWriter, req *http.Re
entries := make([]MonitoredSystemLedgerEntry, 0, len(systems))
for _, system := range systems {
status := normalizeStatus(string(system.Status))
entries = append(entries, MonitoredSystemLedgerEntry{
Name: system.Name,
Type: system.Type,
Status: status,
StatusExplanation: monitoredSystemLedgerStatusExplanation(system.StatusExplanation, status),
LatestIncludedSignal: monitoredSystemLedgerLatestSignal(system.LatestIncludedSignal),
LatestIncludedSignalAt: formatLastSeen(system.LastSeen),
LatestIncludedSignalSource: normalizeMonitoredSystemLedgerSource(system.LatestIncludedSignal.Source),
LastSeen: formatLastSeen(system.LastSeen),
Source: system.Source,
Explanation: monitoredSystemLedgerExplanation(system.Explanation),
})
entries = append(entries, monitoredSystemLedgerEntry(system))
}
limit := maxMonitoredSystemsLimitForContext(req.Context())
@ -158,6 +146,23 @@ func (r *Router) handleMonitoredSystemLedger(w http.ResponseWriter, req *http.Re
json.NewEncoder(w).Encode(resp.NormalizeCollections())
}
func monitoredSystemLedgerEntry(system unifiedresources.MonitoredSystemRecord) MonitoredSystemLedgerEntry {
status := normalizeStatus(string(system.Status))
latestIncludedSignal := monitoredSystemLedgerLatestSignal(system.LatestIncludedSignal)
return MonitoredSystemLedgerEntry{
Name: system.Name,
Type: system.Type,
Status: status,
StatusExplanation: monitoredSystemLedgerStatusExplanation(system.StatusExplanation, status),
LatestIncludedSignal: latestIncludedSignal,
LatestIncludedSignalAt: latestIncludedSignal.At,
LatestIncludedSignalSource: latestIncludedSignal.Source,
LastSeen: latestIncludedSignal.At,
Source: system.Source,
Explanation: monitoredSystemLedgerExplanation(system.Explanation),
}
}
// ---------------------------------------------------------------------------
// Status helpers
// ---------------------------------------------------------------------------

View file

@ -136,6 +136,44 @@ func TestMonitoredSystemLedgerStatusExplanation(t *testing.T) {
}
}
func TestMonitoredSystemLedgerEntryUsesLatestIncludedSignalForCompatibilityAliases(t *testing.T) {
got := monitoredSystemLedgerEntry(unifiedresources.MonitoredSystemRecord{
Name: "Tower",
Type: "host",
Status: unifiedresources.StatusWarning,
StatusExplanation: unifiedresources.MonitoredSystemStatusExplanation{
Summary: "At least one included source is stale, so Pulse marks this monitored system as warning.",
Reasons: []unifiedresources.MonitoredSystemStatusReason{},
},
LastSeen: time.Date(2026, 3, 23, 12, 5, 0, 0, time.UTC),
LatestIncludedSignal: unifiedresources.MonitoredSystemLatestSignal{
Name: "tower.local",
Type: "docker-host",
Source: "docker",
At: time.Date(2026, 3, 23, 12, 0, 0, 0, time.UTC),
},
Source: "multiple",
Explanation: unifiedresources.MonitoredSystemGroupingExplanation{
Summary: "Counts as one monitored system because Pulse merged 2 top-level views into one canonical system using shared machine identity.",
Reasons: []unifiedresources.MonitoredSystemGroupingReason{},
Surfaces: []unifiedresources.MonitoredSystemGroupingSurface{},
},
})
if got.LatestIncludedSignal.At != "2026-03-23T12:00:00Z" {
t.Fatalf("expected canonical latest signal timestamp, got %+v", got.LatestIncludedSignal)
}
if got.LatestIncludedSignalAt != got.LatestIncludedSignal.At {
t.Fatalf("expected latest_included_signal_at to mirror latest_included_signal.at, got %+v", got)
}
if got.LastSeen != got.LatestIncludedSignal.At {
t.Fatalf("expected last_seen compatibility alias to mirror latest_included_signal.at, got %+v", got)
}
if got.LatestIncludedSignalSource != got.LatestIncludedSignal.Source {
t.Fatalf("expected latest_included_signal_source to mirror latest_included_signal.source, got %+v", got)
}
}
func TestMonitoredSystemLedgerResponseEmptyState(t *testing.T) {
resp := EmptyMonitoredSystemLedgerResponse()
data, err := json.Marshal(resp)