mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-08 09:53:25 +00:00
Align monitored system aliases with latest signal
This commit is contained in:
parent
b271fb5b2a
commit
8bb0a99f03
6 changed files with 136 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue