fix(alerts): resolve intent overrides for agents merged with Proxmox nodes

A pulse-agent installed on a Proxmox node merges into one canonical
resource whose primary ID is node:{sourceID}, so the agent:{hostID}
reference the alert evaluator uses (hostResourceID) matched nothing in
the registry. Per-resource grace overrides and operator-state lookups
then silently resolved to factory policy: alerts fired with no grace
while the policy preview, which receives the canonical ID directly,
showed the override working. Index the prefixed agent ref as a
canonical identity alias so runtime resolution lands on the same
resource the override was saved against.

Refs #1497

Contract-Neutral: behavioral fix #1497: index agent: ref as canonical alias, no public contract shape change
This commit is contained in:
rcourtman
2026-08-07 10:52:42 +01:00
parent 4885c378aa
commit dd008f1c2d
3 changed files with 56 additions and 0 deletions
@@ -103,6 +103,13 @@ func canonicalAliases(resource Resource, primaryID, platformID, hostname string)
strings.TrimSpace(resource.ID),
)
values = append(values, resource.Identity.Hostnames...)
// Alert evaluation references host-agent resources as "agent:{hostID}"
// (see alerts.hostResourceID). A merged PVE-node + pulse-agent resource
// takes "node:..." as its primary ID, so the prefixed agent ref must be
// indexed explicitly or per-resource policy lookups miss (#1497).
if agentID := strings.TrimSpace(canonicalAgentID(resource)); agentID != "" {
values = append(values, "agent:"+agentID)
}
if resource.Proxmox != nil {
values = append(
values,
@@ -65,6 +65,7 @@ func TestRefreshCanonicalIdentityPrefersTargetsAndCanonicalHostData(t *testing.T
"tower.local",
"machine-1",
"agent-1",
"agent:host-1",
}
if len(resource.Canonical.Aliases) != len(wantAliases) {
t.Fatalf("aliases len = %d, want %d (%v)", len(resource.Canonical.Aliases), len(wantAliases), resource.Canonical.Aliases)
@@ -435,6 +436,7 @@ func TestRefreshCanonicalIdentityCanonicalizesTargetResourceTypeAliases(t *testi
"docker-runtime-1",
"Tower",
"agent-1",
"agent:host-1",
}
if len(resource.Canonical.Aliases) != len(wantAliases) {
t.Fatalf("aliases len = %d, want %d (%v)", len(resource.Canonical.Aliases), len(wantAliases), resource.Canonical.Aliases)
@@ -194,6 +194,53 @@ func TestResourceRegistry_GetByReferenceResolvesSourceIDAndCanonicalAlias(t *tes
if resolvedID != "agent-host-1" || resource.ID != "agent-host-1" {
t.Fatalf("alias reference resolved to id=%q resource=%q, want agent-host-1", resolvedID, resource.ID)
}
resource, resolvedID, ok = rr.GetByReference("agent:machine-1")
if !ok {
t.Fatal("expected prefixed agent reference to resolve")
}
if resolvedID != "agent-host-1" || resource.ID != "agent-host-1" {
t.Fatalf("agent reference resolved to id=%q resource=%q, want agent-host-1", resolvedID, resource.ID)
}
}
// A pulse-agent installed on a Proxmox node merges into one canonical
// resource whose primary ID is "node:{sourceID}", not "agent:{agentID}".
// Alert evaluation still references the host as "agent:{hostID}"
// (alerts.hostResourceID), and per-resource intent policies are keyed by the
// canonical registry ID, so this reference must resolve or grace overrides
// silently fall back to factory (#1497).
func TestResourceRegistry_GetByReferenceResolvesAgentRefOnMergedProxmoxHost(t *testing.T) {
rr := NewRegistry(nil)
rr.IngestResources([]Resource{{
ID: "agent-abcdef123456",
Type: ResourceTypeAgent,
Name: "proxmox2",
Agent: &AgentData{
AgentID: "machine-2",
Hostname: "proxmox2.local",
},
Proxmox: &ProxmoxData{
SourceID: "node/proxmox2",
NodeName: "proxmox2",
},
}})
merged, ok := rr.Get("agent-abcdef123456")
if !ok || merged.Canonical == nil {
t.Fatalf("merged host missing canonical identity: ok=%v resource=%+v", ok, merged)
}
if merged.Canonical.PrimaryID != "node:node/proxmox2" {
t.Fatalf("merged host primary ID = %q, want node:node/proxmox2", merged.Canonical.PrimaryID)
}
resource, resolvedID, ok := rr.GetByReference("agent:machine-2")
if !ok {
t.Fatal("expected agent reference to resolve on merged Proxmox host")
}
if resolvedID != "agent-abcdef123456" || resource.ID != "agent-abcdef123456" {
t.Fatalf("agent reference resolved to id=%q resource=%q, want agent-abcdef123456", resolvedID, resource.ID)
}
}
func TestMonitorAdapterKeepsSameNamedProxmoxProvidersDistinct(t *testing.T) {