Hide default policy badges in infra rows

This commit is contained in:
rcourtman 2026-03-23 09:49:51 +00:00
parent 3f0ea4ec82
commit bb35c8eb7c
6 changed files with 92 additions and 2 deletions

View file

@ -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

View file

@ -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

View file

@ -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<UnifiedResourceHostTableCar
() => unifiedSourceBadges().length > 0,
);
const policyBadges = createMemo(() =>
getResourcePolicyBadges(resource.policy),
getResourcePolicyTableBadges(resource.policy),
);
const workloadsHref = createMemo(() => buildWorkloadsHref(resource));

View file

@ -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(() => (
<UnifiedResourceTable
resources={resources}
expandedResourceId={null}
onExpandedResourceChange={vi.fn()}
groupingMode="flat"
/>
));
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

View file

@ -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({

View file

@ -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';