diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 803ed0af6..0fbf3038e 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -128,6 +128,9 @@ canonical policy wording. The complete governed mention block is also assembled by the shared policy presenter, so chat prefetch only decides when to render it and never rebuilds the summary layout locally. +The chat prefetch path now also calls the shared governed-summary predicate +directly at each mention site, so it no longer carries a local wrapper around +the canonical policy decision or a separate mention-summary trim helper. The same governed-context rule also applies to the main unified AI resource overview: infrastructure, workload, alert-label, and top-consumer summaries must not leak raw resource names, cluster labels, IP addresses, or unresolved diff --git a/internal/ai/chat/context_prefetch.go b/internal/ai/chat/context_prefetch.go index b248539d5..563977585 100644 --- a/internal/ai/chat/context_prefetch.go +++ b/internal/ai/chat/context_prefetch.go @@ -134,7 +134,7 @@ func (p *ContextPrefetcher) Prefetch(ctx context.Context, message string, struct var discoveries []*tools.ResourceDiscoveryInfo if p.discoveryProvider != nil { for _, mention := range mentions { - if mention.requiresGovernedSummary() { + if unifiedresources.ResourcePolicyRequiresGovernedSummary(mention.Policy) { continue } discovery, err := p.getOrTriggerDiscovery(ctx, mention) @@ -193,7 +193,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: vm.Node(), MatchedText: name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), }) } } @@ -219,7 +219,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: ct.Node(), MatchedText: name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), }) } } @@ -277,7 +277,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: hostID, MatchedText: name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), BindMounts: mounts, DockerHostName: loc.DockerHostName, DockerHostType: loc.DockerHostType, @@ -310,7 +310,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: name, MatchedText: name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) } @@ -340,7 +340,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: hostID, MatchedText: hostname, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) } @@ -374,7 +374,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: clusterSourceID, MatchedText: clusterName, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) } @@ -404,7 +404,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: hostID, MatchedText: podName, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) } @@ -434,7 +434,7 @@ func (p *ContextPrefetcher) extractResourceMentions(message string) []ResourceMe TargetID: hostID, MatchedText: deployName, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) } @@ -487,7 +487,7 @@ func (p *ContextPrefetcher) resolveStructuredMentions(structured []StructuredMen TargetID: node, MatchedText: sm.Name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) @@ -505,7 +505,7 @@ func (p *ContextPrefetcher) resolveStructuredMentions(structured []StructuredMen TargetID: node, MatchedText: sm.Name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) @@ -540,7 +540,7 @@ func (p *ContextPrefetcher) resolveStructuredMentions(structured []StructuredMen TargetID: hostID, MatchedText: sm.Name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), BindMounts: mounts, DockerHostName: loc.DockerHostName, DockerHostType: loc.DockerHostType, @@ -557,7 +557,7 @@ func (p *ContextPrefetcher) resolveStructuredMentions(structured []StructuredMen TargetID: sm.Name, MatchedText: sm.Name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) @@ -573,7 +573,7 @@ func (p *ContextPrefetcher) resolveStructuredMentions(structured []StructuredMen TargetID: hostID, MatchedText: sm.Name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) @@ -596,7 +596,7 @@ func (p *ContextPrefetcher) resolveStructuredMentions(structured []StructuredMen TargetID: sm.Node, MatchedText: sm.Name, Policy: unifiedresources.CloneResourcePolicy(resolved.Resource.Policy), - AISafeSummary: mentionAISafeSummary(resolved.Resource), + AISafeSummary: strings.TrimSpace(resolved.Resource.AISafeSummary), TargetHost: loc.TargetHost, }) } @@ -743,7 +743,7 @@ func (p *ContextPrefetcher) formatContextSummary(mentions []ResourceMention, dis } for _, mention := range mentions { - if mention.requiresGovernedSummary() { + if unifiedresources.ResourcePolicyRequiresGovernedSummary(mention.Policy) { sb.WriteString(unifiedresources.FormatResourcePolicyGovernedSummary(mention.AISafeSummary, mention.Policy)) continue } @@ -898,17 +898,6 @@ func (p *ContextPrefetcher) formatContextSummary(mentions []ResourceMention, dis return sb.String() } -func mentionAISafeSummary(resource *unifiedresources.Resource) string { - if resource == nil { - return "" - } - return strings.TrimSpace(resource.AISafeSummary) -} - -func (m ResourceMention) requiresGovernedSummary() bool { - return unifiedresources.ResourcePolicyRequiresGovernedSummary(m.Policy) -} - // extractWords extracts words (3+ characters) from a message for matching func extractWords(message string) []string { // Split on common delimiters and filter short words diff --git a/internal/ai/chat/context_prefetch_additional_test.go b/internal/ai/chat/context_prefetch_additional_test.go index 01bb489d7..12a975813 100644 --- a/internal/ai/chat/context_prefetch_additional_test.go +++ b/internal/ai/chat/context_prefetch_additional_test.go @@ -424,33 +424,6 @@ func TestContextPrefetcher_PrefetchRestrictedMentionSkipsDiscoveryAndPaths(t *te } } -func TestResourceMentionRequiresGovernedSummaryUsesSharedHelper(t *testing.T) { - if (ResourceMention{}).requiresGovernedSummary() { - t.Fatal("expected empty mention to not require governed summary") - } - - if !(ResourceMention{ - Policy: &unifiedresources.ResourcePolicy{ - Routing: unifiedresources.ResourceRoutingPolicy{ - Scope: unifiedresources.ResourceRoutingScopeLocalOnly, - }, - }, - }).requiresGovernedSummary() { - t.Fatal("expected local-only policy to require governed summary") - } - - if !(ResourceMention{ - Policy: &unifiedresources.ResourcePolicy{ - Routing: unifiedresources.ResourceRoutingPolicy{ - Scope: unifiedresources.ResourceRoutingScopeLocalFirst, - Redact: []unifiedresources.ResourceRedactionHint{unifiedresources.ResourceRedactionHostname}, - }, - }, - }).requiresGovernedSummary() { - t.Fatal("expected redacted policy to require governed summary") - } -} - func TestContextPrefetcher_FormatContextSummary_GovernedMention(t *testing.T) { prefetcher := NewContextPrefetcher(newTestReadState(models.StateSnapshot{}), nil)