mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Stabilize action plan drift hashing
This commit is contained in:
parent
66002e8ba7
commit
6c2a085bb8
3 changed files with 30 additions and 14 deletions
|
|
@ -108,7 +108,6 @@ func ResourceVersion(resource unified.Resource) string {
|
|||
Status unified.ResourceStatus `json:"status"`
|
||||
AISafeSummary string `json:"aiSafeSummary,omitempty"`
|
||||
Sources []unified.DataSource `json:"sources,omitempty"`
|
||||
Identity normalizedIdentity `json:"identity,omitempty"`
|
||||
ParentID string `json:"parentId,omitempty"`
|
||||
Capabilities []normalizedCapabilityForResourceHash `json:"capabilities,omitempty"`
|
||||
Relationships []normalizedRelationship `json:"relationships,omitempty"`
|
||||
|
|
@ -124,7 +123,6 @@ func ResourceVersion(resource unified.Resource) string {
|
|||
Status: resource.Status,
|
||||
AISafeSummary: strings.TrimSpace(resource.AISafeSummary),
|
||||
Sources: normalizeDataSources(resource.Sources),
|
||||
Identity: normalizeIdentity(resource.Identity),
|
||||
Capabilities: normalizeCapabilitiesForResourceHash(resource.Capabilities),
|
||||
Relationships: normalizeRelationships(unified.ResourceRelationshipsWithCanonicalParent(resource)),
|
||||
RecentChanges: normalizeChanges(resource.RecentChanges),
|
||||
|
|
@ -605,9 +603,8 @@ type storageIncidentHashFields struct {
|
|||
}
|
||||
|
||||
type sourceStatusHash struct {
|
||||
Status string `json:"status"`
|
||||
LastSeen time.Time `json:"lastSeen,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func normalizeDataSources(sources []unified.DataSource) []unified.DataSource {
|
||||
|
|
@ -750,9 +747,8 @@ func normalizeSourceStatus(status map[unified.DataSource]unified.SourceStatus) m
|
|||
continue
|
||||
}
|
||||
out[normalizedSource] = sourceStatusHash{
|
||||
Status: strings.TrimSpace(sourceStatus.Status),
|
||||
LastSeen: sourceStatus.LastSeen.UTC(),
|
||||
Error: strings.TrimSpace(sourceStatus.Error),
|
||||
Status: strings.TrimSpace(sourceStatus.Status),
|
||||
Error: strings.TrimSpace(sourceStatus.Error),
|
||||
}
|
||||
}
|
||||
return out
|
||||
|
|
|
|||
|
|
@ -175,10 +175,30 @@ func TestResourceVersionIgnoresObservationOnlyTimestamps(t *testing.T) {
|
|||
Status: unified.StatusOnline,
|
||||
LastSeen: time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC),
|
||||
Identity: unified.ResourceIdentity{
|
||||
Hostnames: []string{"web-42"},
|
||||
IPAddresses: []string{"192.0.2.10"},
|
||||
},
|
||||
SourceStatus: map[unified.DataSource]unified.SourceStatus{
|
||||
unified.SourceProxmox: {
|
||||
Status: "online",
|
||||
LastSeen: time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC),
|
||||
},
|
||||
},
|
||||
}
|
||||
refreshed := base
|
||||
refreshed.LastSeen = base.LastSeen.Add(time.Minute)
|
||||
refreshed.UpdatedAt = base.UpdatedAt.Add(time.Minute)
|
||||
refreshed.Identity = unified.ResourceIdentity{
|
||||
Hostnames: []string{"web-42", "web-42.local"},
|
||||
IPAddresses: []string{"192.0.2.10", "192.0.2.11"},
|
||||
}
|
||||
refreshed.SourceStatus = map[unified.DataSource]unified.SourceStatus{
|
||||
unified.SourceProxmox: {
|
||||
Status: "online",
|
||||
LastSeen: base.SourceStatus[unified.SourceProxmox].LastSeen.Add(time.Minute),
|
||||
},
|
||||
}
|
||||
|
||||
if got, want := ResourceVersion(refreshed), ResourceVersion(base); got != want {
|
||||
t.Fatalf("ResourceVersion changed for observation-only timestamp drift: got %q want %q", got, want)
|
||||
|
|
|
|||
|
|
@ -12712,7 +12712,7 @@ func TestContract_ActionPlanJSONSnapshot(t *testing.T) {
|
|||
}
|
||||
|
||||
const want = `{
|
||||
"actionId":"act_371d0bcde73818752f83bb759e1fa523",
|
||||
"actionId":"act_8f428171be2762cec97fd4546a291d5f",
|
||||
"requestId":"agent-run-123",
|
||||
"allowed":true,
|
||||
"requiresApproval":true,
|
||||
|
|
@ -12722,9 +12722,9 @@ func TestContract_ActionPlanJSONSnapshot(t *testing.T) {
|
|||
"message":"Plan created for restart on web-42. Execution requires admin approval and is not performed by this endpoint.",
|
||||
"plannedAt":"2026-05-03T10:00:00Z",
|
||||
"expiresAt":"2026-05-03T10:05:00Z",
|
||||
"resourceVersion":"resource:sha256:3a87c71cf83bd017736bcb25",
|
||||
"resourceVersion":"resource:sha256:54fb6f0264f42e0f2724e513",
|
||||
"policyVersion":"policy:sha256:0bce3cd2df181ace685598eb",
|
||||
"planHash":"sha256:38e8a016794bae597cd6129e65506556048bcac88d6a5a1b59e1337a2acc5a05",
|
||||
"planHash":"sha256:69631faa9da67496a8b5953de2bc2dceb7b2aab5b582be3d1536e5d67683791b",
|
||||
"preflight":{
|
||||
"target":"vm:42",
|
||||
"currentState":"web-42 is warning",
|
||||
|
|
@ -12739,7 +12739,7 @@ func TestContract_ActionPlanJSONSnapshot(t *testing.T) {
|
|||
],
|
||||
"verificationSteps":[
|
||||
"Refresh the resource and confirm the expected state after execution.",
|
||||
"Review /api/audit/actions/act_371d0bcde73818752f83bb759e1fa523/events for lifecycle evidence."
|
||||
"Review /api/audit/actions/act_8f428171be2762cec97fd4546a291d5f/events for lifecycle evidence."
|
||||
],
|
||||
"generatedAt":"2026-05-03T10:00:00Z"
|
||||
}
|
||||
|
|
@ -12844,13 +12844,13 @@ func TestContract_ActionPlanAuditLifecycleSnapshot(t *testing.T) {
|
|||
}
|
||||
const want = `{
|
||||
"audit":{
|
||||
"id":"act_f464b1a69ce73d27a986b445104ad708",
|
||||
"id":"act_7eed41cdc58507f340151d2497707eca",
|
||||
"state":"pending_approval",
|
||||
"resourceId":"vm:42",
|
||||
"requestId":"agent-run-123",
|
||||
"requestedBy":"agent:oncall-helper",
|
||||
"approvalPolicy":"admin",
|
||||
"planHash":"sha256:e2998e7fe1b9b97c8180102bd474ece7411146db70bbb6299cdc47582de7bd1d",
|
||||
"planHash":"sha256:f60417e39f967eb0803af0a3f2e2abd70f20d8a77a09c2e36976bdda34b6dbaf",
|
||||
"preflightSummary":"No provider-supported dry run is advertised for this capability."
|
||||
},
|
||||
"events":[
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue