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"`
|
Status unified.ResourceStatus `json:"status"`
|
||||||
AISafeSummary string `json:"aiSafeSummary,omitempty"`
|
AISafeSummary string `json:"aiSafeSummary,omitempty"`
|
||||||
Sources []unified.DataSource `json:"sources,omitempty"`
|
Sources []unified.DataSource `json:"sources,omitempty"`
|
||||||
Identity normalizedIdentity `json:"identity,omitempty"`
|
|
||||||
ParentID string `json:"parentId,omitempty"`
|
ParentID string `json:"parentId,omitempty"`
|
||||||
Capabilities []normalizedCapabilityForResourceHash `json:"capabilities,omitempty"`
|
Capabilities []normalizedCapabilityForResourceHash `json:"capabilities,omitempty"`
|
||||||
Relationships []normalizedRelationship `json:"relationships,omitempty"`
|
Relationships []normalizedRelationship `json:"relationships,omitempty"`
|
||||||
|
|
@ -124,7 +123,6 @@ func ResourceVersion(resource unified.Resource) string {
|
||||||
Status: resource.Status,
|
Status: resource.Status,
|
||||||
AISafeSummary: strings.TrimSpace(resource.AISafeSummary),
|
AISafeSummary: strings.TrimSpace(resource.AISafeSummary),
|
||||||
Sources: normalizeDataSources(resource.Sources),
|
Sources: normalizeDataSources(resource.Sources),
|
||||||
Identity: normalizeIdentity(resource.Identity),
|
|
||||||
Capabilities: normalizeCapabilitiesForResourceHash(resource.Capabilities),
|
Capabilities: normalizeCapabilitiesForResourceHash(resource.Capabilities),
|
||||||
Relationships: normalizeRelationships(unified.ResourceRelationshipsWithCanonicalParent(resource)),
|
Relationships: normalizeRelationships(unified.ResourceRelationshipsWithCanonicalParent(resource)),
|
||||||
RecentChanges: normalizeChanges(resource.RecentChanges),
|
RecentChanges: normalizeChanges(resource.RecentChanges),
|
||||||
|
|
@ -605,9 +603,8 @@ type storageIncidentHashFields struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sourceStatusHash struct {
|
type sourceStatusHash struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
LastSeen time.Time `json:"lastSeen,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
Error string `json:"error,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeDataSources(sources []unified.DataSource) []unified.DataSource {
|
func normalizeDataSources(sources []unified.DataSource) []unified.DataSource {
|
||||||
|
|
@ -750,9 +747,8 @@ func normalizeSourceStatus(status map[unified.DataSource]unified.SourceStatus) m
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out[normalizedSource] = sourceStatusHash{
|
out[normalizedSource] = sourceStatusHash{
|
||||||
Status: strings.TrimSpace(sourceStatus.Status),
|
Status: strings.TrimSpace(sourceStatus.Status),
|
||||||
LastSeen: sourceStatus.LastSeen.UTC(),
|
Error: strings.TrimSpace(sourceStatus.Error),
|
||||||
Error: strings.TrimSpace(sourceStatus.Error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
|
||||||
|
|
@ -175,10 +175,30 @@ func TestResourceVersionIgnoresObservationOnlyTimestamps(t *testing.T) {
|
||||||
Status: unified.StatusOnline,
|
Status: unified.StatusOnline,
|
||||||
LastSeen: time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC),
|
LastSeen: time.Date(2026, 5, 3, 10, 0, 0, 0, time.UTC),
|
||||||
UpdatedAt: 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 := base
|
||||||
refreshed.LastSeen = base.LastSeen.Add(time.Minute)
|
refreshed.LastSeen = base.LastSeen.Add(time.Minute)
|
||||||
refreshed.UpdatedAt = base.UpdatedAt.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 {
|
if got, want := ResourceVersion(refreshed), ResourceVersion(base); got != want {
|
||||||
t.Fatalf("ResourceVersion changed for observation-only timestamp drift: got %q want %q", 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 = `{
|
const want = `{
|
||||||
"actionId":"act_371d0bcde73818752f83bb759e1fa523",
|
"actionId":"act_8f428171be2762cec97fd4546a291d5f",
|
||||||
"requestId":"agent-run-123",
|
"requestId":"agent-run-123",
|
||||||
"allowed":true,
|
"allowed":true,
|
||||||
"requiresApproval":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.",
|
"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",
|
"plannedAt":"2026-05-03T10:00:00Z",
|
||||||
"expiresAt":"2026-05-03T10:05:00Z",
|
"expiresAt":"2026-05-03T10:05:00Z",
|
||||||
"resourceVersion":"resource:sha256:3a87c71cf83bd017736bcb25",
|
"resourceVersion":"resource:sha256:54fb6f0264f42e0f2724e513",
|
||||||
"policyVersion":"policy:sha256:0bce3cd2df181ace685598eb",
|
"policyVersion":"policy:sha256:0bce3cd2df181ace685598eb",
|
||||||
"planHash":"sha256:38e8a016794bae597cd6129e65506556048bcac88d6a5a1b59e1337a2acc5a05",
|
"planHash":"sha256:69631faa9da67496a8b5953de2bc2dceb7b2aab5b582be3d1536e5d67683791b",
|
||||||
"preflight":{
|
"preflight":{
|
||||||
"target":"vm:42",
|
"target":"vm:42",
|
||||||
"currentState":"web-42 is warning",
|
"currentState":"web-42 is warning",
|
||||||
|
|
@ -12739,7 +12739,7 @@ func TestContract_ActionPlanJSONSnapshot(t *testing.T) {
|
||||||
],
|
],
|
||||||
"verificationSteps":[
|
"verificationSteps":[
|
||||||
"Refresh the resource and confirm the expected state after execution.",
|
"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"
|
"generatedAt":"2026-05-03T10:00:00Z"
|
||||||
}
|
}
|
||||||
|
|
@ -12844,13 +12844,13 @@ func TestContract_ActionPlanAuditLifecycleSnapshot(t *testing.T) {
|
||||||
}
|
}
|
||||||
const want = `{
|
const want = `{
|
||||||
"audit":{
|
"audit":{
|
||||||
"id":"act_f464b1a69ce73d27a986b445104ad708",
|
"id":"act_7eed41cdc58507f340151d2497707eca",
|
||||||
"state":"pending_approval",
|
"state":"pending_approval",
|
||||||
"resourceId":"vm:42",
|
"resourceId":"vm:42",
|
||||||
"requestId":"agent-run-123",
|
"requestId":"agent-run-123",
|
||||||
"requestedBy":"agent:oncall-helper",
|
"requestedBy":"agent:oncall-helper",
|
||||||
"approvalPolicy":"admin",
|
"approvalPolicy":"admin",
|
||||||
"planHash":"sha256:e2998e7fe1b9b97c8180102bd474ece7411146db70bbb6299cdc47582de7bd1d",
|
"planHash":"sha256:f60417e39f967eb0803af0a3f2e2abd70f20d8a77a09c2e36976bdda34b6dbaf",
|
||||||
"preflightSummary":"No provider-supported dry run is advertised for this capability."
|
"preflightSummary":"No provider-supported dry run is advertised for this capability."
|
||||||
},
|
},
|
||||||
"events":[
|
"events":[
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue