diff --git a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md index ed9a5ccd4..b6be08d8a 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -124,6 +124,10 @@ alternate name when policy requires governed handling. That keeps the policy-aware label path inside the same hot-row rendering budget instead of adding a second display branch for redacted fleets, and the proof for that behavior lives in `UnifiedResourceTable.performance.contract.test.tsx`. +The shared infrastructure selector search path now also routes through that +same policy-aware display contract, so governed resources do not reappear via +raw-name search candidates while the selector stays on the same hot-path +budget. The aggregate `/api/charts/workloads-summary` endpoint now also has its own explicit API p95 budget constant, aligned with the per-workload charts budget, and `internal/api/slo_bench_test.go` must fail if that aggregate budget or its diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 90594d0eb..e20ec4f3f 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -382,6 +382,10 @@ that drawer now route through the canonical infrastructure resource filter, so the resource history remains navigable from the history surface instead of being purely descriptive text. +The same infrastructure selector pipeline now also uses the policy-aware +display contract for search candidates, so governed resources do not reappear +through raw-name search forks even though the selector stays on the same +hot-path budget. `ResourceFacetSummary` now consumes the shared `frontend-modern/src/utils/resourceChangePresentation.ts` label helper for canonical change kinds, source types, and adapter provenance, so the chip diff --git a/frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx b/frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx index 96d08f0ca..05836267a 100644 --- a/frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx +++ b/frontend-modern/src/components/Infrastructure/__tests__/UnifiedResourceTable.performance.contract.test.tsx @@ -8,6 +8,7 @@ import { getPreferredResourceDisplayName } from '@/utils/resourceIdentity'; import { buildStatusOptions, filterResources, + matchesSearch, sortResources, groupResources, splitPrimaryAndServiceResources, @@ -179,6 +180,21 @@ describe('UnifiedResourceTable performance contract', () => { expect(filtered[0]?.platformData?.sources).toEqual(['proxmox']); }); + it('keeps governed resource search aligned with the safe display label', () => { + const governedResource = makeResource(9, { + name: 'secret-host-9', + displayName: 'secret-host-9', + policy: { + sensitivity: 'restricted', + routing: { scope: 'local-only', redact: ['hostname'] }, + }, + aiSafeSummary: 'Production Host', + }); + + expect(matchesSearch(governedResource, 'Production')).toBe(true); + expect(matchesSearch(governedResource, 'secret-host-9')).toBe(false); + }); + it('renders facet summary badges without changing the Profile S row budget', async () => { const resources = makeResources(PROFILES.S, (i) => i === 0 diff --git a/frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts b/frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts index 96ceb4efe..3d4fde0f3 100644 --- a/frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts +++ b/frontend-modern/src/components/Infrastructure/__tests__/infrastructureSelectors.test.ts @@ -58,6 +58,21 @@ describe('infrastructureSelectors', () => { expect(matchesSearch(resource, 'NODE-123')).toBe(true); }); + it('matches governed resources by their safe label instead of raw name', () => { + const governedResource = makeResource(2, { + name: 'secret-node-2', + displayName: 'secret-node-2', + policy: { + sensitivity: 'restricted', + routing: { scope: 'local-only', redact: ['hostname'] }, + }, + aiSafeSummary: 'Production Node', + }); + + expect(matchesSearch(governedResource, 'Production')).toBe(true); + expect(matchesSearch(governedResource, 'secret-node-2')).toBe(false); + }); + it('matches by ip and tag and returns false when missing', () => { expect(matchesSearch(resource, '192.168.1.10')).toBe(true); expect(matchesSearch(resource, 'database')).toBe(true); @@ -132,6 +147,7 @@ describe('infrastructureSelectors', () => { }), makeResource(3, { name: 'gamma-node', + displayName: 'Gamma Node', status: 'degraded', tags: ['prod', 'db'], platformData: { sources: ['k8s'] }, diff --git a/frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts b/frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts index 3ceebaa01..c9dd0e851 100644 --- a/frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts +++ b/frontend-modern/src/components/Infrastructure/infrastructureSelectors.ts @@ -130,8 +130,7 @@ export const matchesSearch = (resource: Resource, term: string): boolean => { if (!term) return true; const normalizedTerm = term.toLowerCase(); const candidates: string[] = [ - resource.name, - resource.displayName, + getDisplayName(resource), resource.id, resource.identity?.hostname ?? '', ...(resource.identity?.ips ?? []),