diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 5a509cfdd..56496682c 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -598,6 +598,11 @@ runtime owner separate: `AgentProfilesPanel.tsx` is the surface shell, while `useAgentProfilesPanelState.ts` owns license gating, AI availability, profile load/save mutations, assignment resync, and modal form lifecycle so the panel does not carry a second inline controller. +That same connected profile-assignment surface must also preserve canonical +local operator identity for monitored systems. When governed resources such as +PBS or PMG appear in the assignment list, the panel must keep the local +instance label for ordering and row display instead of substituting governed +summary text, so profile assignment remains instance-specific. Canonical Proxmox auto-register must also preserve the legacy DHCP continuity contract: when a node reruns registration from a new IP but presents the same canonical node name and deterministic Pulse-managed token identity, Pulse diff --git a/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx b/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx index 6d12e10e3..ecb472574 100644 --- a/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx +++ b/frontend-modern/src/components/Settings/__tests__/AgentProfilesPanel.test.tsx @@ -562,6 +562,54 @@ describe('AgentProfilesPanel V6 agent ID handling', () => { }); }); + it('keeps governed infrastructure assignments on local operator identity', async () => { + mockResources = [ + makeAgentResource({ + id: 'pbs-resource-id', + type: 'pbs', + name: 'redacted-pbs', + displayName: 'PBS Main', + platformId: 'pbs-main', + platformType: 'proxmox-pbs', + sourceType: 'api', + identity: { hostname: 'pbs.local' }, + agent: { agentId: 'pbs-agent-1' }, + policy: { + display: { + mode: 'governed', + summary: 'backup server resource; status online; sources pbs', + }, + } as Record, + }), + ]; + mockWsStore.state.connectedInfrastructure = [ + makeHostInfrastructureItem({ + id: 'pbs-resource-id', + name: 'PBS Main', + displayName: 'PBS Main', + hostname: 'pbs.local', + scopeAgentId: 'pbs-agent-1', + surfaces: [ + { + id: 'agent:pbs-agent-1', + kind: 'agent', + label: 'PBS data', + controlId: 'pbs-agent-1', + }, + ], + }), + ]; + + render(() => ); + + await waitFor(() => { + expect(screen.getByText('PBS Main')).toBeInTheDocument(); + }); + expect( + screen.queryByText('backup server resource; status online; sources pbs'), + ).not.toBeInTheDocument(); + }); + it('surfaces malformed profile list responses instead of failing open to an empty state', async () => { listProfilesMock.mockRejectedValueOnce(new Error('Invalid agent profile list response from Pulse.')); diff --git a/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts b/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts index 8639e15a6..203450f45 100644 --- a/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts +++ b/frontend-modern/src/components/Settings/useAgentProfilesPanelState.ts @@ -38,6 +38,7 @@ import { isAgentProfileAssignableResource, } from '@/utils/agentResources'; import { + getPreferredInfrastructureDisplayName, getPreferredNamedEntityLabel, getPreferredResourceDisplayName, getPreferredResourceHostname, @@ -140,8 +141,8 @@ export const useAgentProfilesPanelState = () => { .sort((a, b) => { const byPriority = resourcePriority(a.resource) - resourcePriority(b.resource); if (byPriority !== 0) return byPriority; - const aName = getPreferredResourceDisplayName(a.resource); - const bName = getPreferredResourceDisplayName(b.resource); + const aName = getPreferredInfrastructureDisplayName(a.resource); + const bName = getPreferredInfrastructureDisplayName(b.resource); return aName.localeCompare(bName); }); @@ -156,7 +157,7 @@ export const useAgentProfilesPanelState = () => { id: assignmentId, assignmentId, hostname: getPreferredResourceHostname(resource) || 'Unknown', - displayName: resource.displayName, + displayName: getPreferredInfrastructureDisplayName(resource), status: resource.status || 'unknown', lastSeen: resource.lastSeen, }))