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 840f2a30e..e1d8b93c6 100644 --- a/docs/release-control/v6/internal/subsystems/performance-and-scalability.md +++ b/docs/release-control/v6/internal/subsystems/performance-and-scalability.md @@ -393,6 +393,10 @@ share the canonical cluster-name helpers in the shared agent-resource layer, so route labels, pod grouping, and cluster-name fetch keys keep using the same source of truth instead of rebuilding the `clusterName`/`context`/ `clusterId` prefix locally. +The infrastructure host-table hot path now also suppresses the default +`Internal` + `Cloud Summary` policy pair in row chrome. That baseline posture +still belongs to the canonical policy contract, but repeating it on every host +burns row-density budget without adding operator-grade signal. The shared node adapter also uses that same cluster-name helper for the infrastructure summary surface, so Proxmox node projections stay aligned with the same canonical cluster label instead of carrying a raw adapter-local diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index d8f1a3c4e..b2fc5bd74 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -199,6 +199,12 @@ The same policy presenter now also owns the routing-scope labels used across AI-facing policy surfaces, while the resource detail drawer stays on per-resource policy lines instead of reconstructing a separate `Allowed`/`Blocked` row or `Cloud Summary` decision row locally. +The infrastructure host-table shell now treats the default +`Internal` + `Cloud Summary` posture as canonical policy metadata that should +stay available in the drawer and AI/governance surfaces without being promoted +to always-on row chrome. Inline row badges are reserved for non-default policy +states so the canonical resource surface does not imply that every host carries +an operator-actionable governance exception. The shared routing policy itself now stays intentionally minimal: it carries only the routing scope and the redaction hints derived from canonical sensitivity, and the cloud-summary decision is derived from that scope diff --git a/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx b/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx index 8d99ad8d5..ebe564d6d 100644 --- a/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx +++ b/frontend-modern/src/components/Infrastructure/UnifiedResourceHostTableCard.tsx @@ -25,7 +25,7 @@ import { import { getAgentStatusIndicator } from '@/utils/status'; import { getPreferredResourceDisplayName } from '@/utils/resourceIdentity'; import { - getResourcePolicyBadges, + getResourcePolicyTableBadges, shouldShowResourceAlternateName, } from '@/utils/resourcePolicyPresentation'; import { ResourceDetailDrawer } from './ResourceDetailDrawer'; @@ -267,7 +267,7 @@ export const UnifiedResourceHostTableCard: Component unifiedSourceBadges().length > 0, ); const policyBadges = createMemo(() => - getResourcePolicyBadges(resource.policy), + getResourcePolicyTableBadges(resource.policy), ); const workloadsHref = createMemo(() => buildWorkloadsHref(resource)); 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 28cd9590f..8593b8f98 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 @@ -249,6 +249,49 @@ describe('UnifiedResourceTable performance contract', () => { expect(matchesSearch(governedResource, 'secret-host-9')).toBe(false); }); + it('suppresses the default policy posture badges in host-table rows while preserving exceptional policy badges', async () => { + const resources = [ + makeResource(0, { + name: 'default-policy-host', + displayName: 'Default Policy Host', + policy: { + sensitivity: 'internal', + routing: { scope: 'cloud-summary' }, + }, + }), + makeResource(1, { + name: 'governed-host', + displayName: 'Governed Host', + policy: { + sensitivity: 'sensitive', + routing: { scope: 'local-first', redact: ['hostname'] }, + }, + }), + ]; + + const { container } = render(() => ( + + )); + + await waitFor(() => { + expect(container.querySelector('table')).toBeInTheDocument(); + }); + + const body = container.querySelector('tbody'); + expect(body).not.toBeNull(); + const bodyQueries = within(body as HTMLElement); + + expect(bodyQueries.queryByText('Internal')).not.toBeInTheDocument(); + expect(bodyQueries.queryByText('Cloud Summary')).not.toBeInTheDocument(); + expect(bodyQueries.getByText('Sensitive')).toBeInTheDocument(); + expect(bodyQueries.getByText('Local First')).toBeInTheDocument(); + }); + 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/utils/__tests__/resourcePolicyPresentation.test.ts b/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts index 439807e09..0adf3b53c 100644 --- a/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts +++ b/frontend-modern/src/utils/__tests__/resourcePolicyPresentation.test.ts @@ -4,6 +4,7 @@ import { RESOURCE_POLICY_REDACTION_ORDER, RESOURCE_POLICY_ROUTING_ORDER, RESOURCE_POLICY_SENSITIVITY_ORDER, + getResourcePolicyTableBadges, getResourcePolicyDisplayLabel, getResourcePolicyRedactionSummaries, getResourcePolicyRoutingSummaries, @@ -34,6 +35,27 @@ describe('resourcePolicyPresentation utils', () => { ).toEqual(['Hostname', 'IP Address']); }); + it('suppresses the default internal cloud-summary posture in table rows', () => { + expect( + getResourcePolicyTableBadges({ + sensitivity: 'internal', + routing: { + scope: 'cloud-summary', + }, + }), + ).toEqual([]); + + expect( + getResourcePolicyTableBadges({ + sensitivity: 'sensitive', + routing: { + scope: 'local-first', + redact: ['hostname'], + }, + }).map((badge) => badge.label), + ).toEqual(['Sensitive', 'Local First']); + }); + it('uses the governed aiSafeSummary for redacted resources', () => { expect( getResourcePolicyDisplayLabel({ diff --git a/frontend-modern/src/utils/resourcePolicyPresentation.ts b/frontend-modern/src/utils/resourcePolicyPresentation.ts index e1e3caeca..ea0ce305a 100644 --- a/frontend-modern/src/utils/resourcePolicyPresentation.ts +++ b/frontend-modern/src/utils/resourcePolicyPresentation.ts @@ -108,6 +108,21 @@ export const getResourcePolicyBadges = (policy?: ResourcePolicy): PolicyBadgePre return [sensitivityPresentation[policy.sensitivity], routingPresentation[policy.routing.scope]]; }; +export const getResourcePolicyTableBadges = (policy?: ResourcePolicy): PolicyBadgePresentation[] => { + if (!policy) return []; + + const hasDefaultPolicyPosture = + policy.sensitivity === 'internal' && + policy.routing.scope === 'cloud-summary' && + (policy.routing.redact?.length ?? 0) === 0; + + if (hasDefaultPolicyPosture) { + return []; + } + + return getResourcePolicyBadges(policy); +}; + export const getResourceSensitivityLabel = (sensitivity?: ResourceSensitivity): string => sensitivity ? sensitivityPresentation[sensitivity].label : 'Unclassified';