From b0cbc45864701474359641defd65ca63ef04c601 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 19 Mar 2026 02:16:26 +0000 Subject: [PATCH] Canonicalize policy posture ordering --- .../v6/internal/subsystems/ai-runtime.md | 3 +++ .../internal/subsystems/unified-resources.md | 3 +++ internal/ai/resource_context.go | 24 ++++++++++--------- internal/ai/resource_context_test.go | 2 +- .../unifiedresources/code_standards_test.go | 2 ++ .../unifiedresources/policy_presentation.go | 17 +++++++++++++ 6 files changed, 39 insertions(+), 12 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 6985bdafc..0d1cc1264 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -187,6 +187,9 @@ runtime uses for prompt export and context rendering. That posture snapshot must render redaction labels through the canonical unified-resource hint order, not alphabetically, so the AI summary, drawer, and any future policy surfaces all present the same redaction precedence. +Its sensitivity and routing counts must also follow the canonical +unified-resource order, so both the backend summary and the frontend policy +card stay aligned on the same presentation sequence. The resource-intelligence payload used by the resource drawer also carries the same canonical policy posture snapshot, so the detail surface can show governed posture context without inventing a second posture contract. diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index 5e7b9cf2a..a90ca684f 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -404,6 +404,9 @@ re-described independently per surface. Those helpers now own the canonical redaction-hint order and count-to-label projection, so the AI summary and any other backend policy posture surface do not re-sort redaction labels locally. +They also own the canonical sensitivity and routing order used to format +policy-posture counts, so the AI summary and frontend policy card both read +the same presentation sequence from the shared resource model. Canonical resources now carry first-class graph-expansion fields: `Capabilities` (bounded action definitions with approval levels), `Relationships` (typed inter-resource links with direction and confidence), and `RecentChanges` (typed diff --git a/internal/ai/resource_context.go b/internal/ai/resource_context.go index daa68a6da..e13239fb4 100644 --- a/internal/ai/resource_context.go +++ b/internal/ai/resource_context.go @@ -123,17 +123,19 @@ func (s *Service) buildUnifiedResourceContextForModel(destinationModel string) s } if policyPosture != nil && policyPosture.TotalResources > 0 { sections = append(sections, "\n### Data Governance") - sections = append(sections, fmt.Sprintf("- Sensitivity: %d public, %d internal, %d sensitive, %d restricted", - sensitivityCounts[unifiedresources.ResourceSensitivityPublic], - sensitivityCounts[unifiedresources.ResourceSensitivityInternal], - sensitivityCounts[unifiedresources.ResourceSensitivitySensitive], - sensitivityCounts[unifiedresources.ResourceSensitivityRestricted], - )) - sections = append(sections, fmt.Sprintf("- Routing: %d cloud-summary, %d local-first, %d local-only", - routingCounts[unifiedresources.ResourceRoutingScopeCloudSummary], - routingCounts[unifiedresources.ResourceRoutingScopeLocalFirst], - routingCounts[unifiedresources.ResourceRoutingScopeLocalOnly], - )) + sensitivityParts := make([]string, 0, len(unifiedresources.ResourceSensitivityOrder)) + for _, sensitivity := range unifiedresources.ResourceSensitivityOrder { + sensitivityParts = append(sensitivityParts, fmt.Sprintf("%d %s", + sensitivityCounts[sensitivity], sensitivity)) + } + sections = append(sections, fmt.Sprintf("- Sensitivity: %s", strings.Join(sensitivityParts, ", "))) + + routingParts := make([]string, 0, len(unifiedresources.ResourceRoutingScopeOrder)) + for _, scope := range unifiedresources.ResourceRoutingScopeOrder { + routingParts = append(routingParts, fmt.Sprintf("%d %s", + routingCounts[scope], scope)) + } + sections = append(sections, fmt.Sprintf("- Routing: %s", strings.Join(routingParts, ", "))) sections = append(sections, fmt.Sprintf("- Local-only resources: %d", localOnlyCount)) if redactionLabels := policyPostureRedactionLabels(policyPosture); len(redactionLabels) > 0 { sections = append(sections, "\n### Policy Redaction Hints") diff --git a/internal/ai/resource_context_test.go b/internal/ai/resource_context_test.go index dbb68ed40..2a4d907d8 100644 --- a/internal/ai/resource_context_test.go +++ b/internal/ai/resource_context_test.go @@ -225,7 +225,7 @@ func TestBuildUnifiedResourceContext_FullContext(t *testing.T) { assertContains("Sensitivity: 0 public, 5 internal, 6 sensitive, 0 restricted") assertContains("Routing: 5 cloud-summary, 6 local-first, 0 local-only") assertContains("Policy Redaction Hints") - assertContains("Redactions in use: Alias, Hostname, IP Address, Platform ID") + assertContains("Redactions in use: Hostname, IP Address, Platform ID, Alias") assertContains("Proxmox VE Nodes") assertContains("HAS AGENT") assertContains("NO AGENT") diff --git a/internal/unifiedresources/code_standards_test.go b/internal/unifiedresources/code_standards_test.go index fc670dce5..fd13c6be7 100644 --- a/internal/unifiedresources/code_standards_test.go +++ b/internal/unifiedresources/code_standards_test.go @@ -272,6 +272,8 @@ func TestResourcePolicyPresentationUsesCanonicalLabels(t *testing.T) { source := string(data) requiredSnippets := []string{ + "ResourceSensitivityOrder", + "ResourceRoutingScopeOrder", "ResourceRedactionHintOrder", "ResourceSensitivityLabel(", "ResourceRoutingScopeLabel(", diff --git a/internal/unifiedresources/policy_presentation.go b/internal/unifiedresources/policy_presentation.go index 64bab12f4..5432fa0a0 100644 --- a/internal/unifiedresources/policy_presentation.go +++ b/internal/unifiedresources/policy_presentation.go @@ -2,6 +2,23 @@ package unifiedresources import "sort" +// ResourceSensitivityOrder captures the canonical presentation order for +// sensitivity counts across policy surfaces. +var ResourceSensitivityOrder = []ResourceSensitivity{ + ResourceSensitivityPublic, + ResourceSensitivityInternal, + ResourceSensitivitySensitive, + ResourceSensitivityRestricted, +} + +// ResourceRoutingScopeOrder captures the canonical presentation order for +// routing counts across policy surfaces. +var ResourceRoutingScopeOrder = []ResourceRoutingScope{ + ResourceRoutingScopeCloudSummary, + ResourceRoutingScopeLocalFirst, + ResourceRoutingScopeLocalOnly, +} + // ResourceRedactionHintOrder captures the canonical presentation order for // redaction hints across backend and frontend policy surfaces. var ResourceRedactionHintOrder = []ResourceRedactionHint{