diff --git a/internal/unifiedresources/canonical_identity.go b/internal/unifiedresources/canonical_identity.go index e5e96b8d2..e86011b9a 100644 --- a/internal/unifiedresources/canonical_identity.go +++ b/internal/unifiedresources/canonical_identity.go @@ -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, diff --git a/internal/unifiedresources/canonical_identity_test.go b/internal/unifiedresources/canonical_identity_test.go index f2b2164d3..cb97dd6c6 100644 --- a/internal/unifiedresources/canonical_identity_test.go +++ b/internal/unifiedresources/canonical_identity_test.go @@ -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) diff --git a/internal/unifiedresources/registry_test.go b/internal/unifiedresources/registry_test.go index e2b53863a..b6a1f3bb5 100644 --- a/internal/unifiedresources/registry_test.go +++ b/internal/unifiedresources/registry_test.go @@ -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) {