Rename resource graph context to relationships

This commit is contained in:
rcourtman
2026-03-19 14:32:27 +00:00
parent bdaa2bd525
commit db4a02eced
8 changed files with 40 additions and 39 deletions
@@ -258,9 +258,9 @@ section from unified-resource relationships, so relationship wording and edge
provenance stay aligned with the same shared resource model instead of being
reconstructed from the drawer or prompt helpers.
That relationship section is now rendered by the shared
`internal/unifiedresources.FormatResourceGraphContext` helper, so the service
layer only resolves the canonical resource and does not rebuild the section
format locally.
`internal/unifiedresources.FormatResourceRelationshipContext` helper, so the
service layer only resolves the canonical resource and does not rebuild the
section format locally.
The canonical recent-change sentence formatting also lives in
`internal/unifiedresources.FormatResourceChangeSummary`, so AI runtime prompt
sections and Patrol seed context reuse the same change wording instead of
@@ -100,9 +100,9 @@ relationship section from unified-resource relationships, so edge labels,
directionality, and provenance stay aligned with the shared relationship model
instead of being reconstructed locally.
That relationship section is now rendered by the shared
`internal/unifiedresources.FormatResourceGraphContext` helper, so the Patrol
runtime only resolves the canonical relationship context rather than formatting the
relationship section itself.
`internal/unifiedresources.FormatResourceRelationshipContext` helper, so the
Patrol runtime only resolves the canonical relationship context rather than
formatting the relationship section itself.
Patrol-owned correlation context now also comes through the shared AI
intelligence facade before reaching the detector, so the learned correlation
surface is routed through the same canonical AI ownership boundary as recent
@@ -144,9 +144,10 @@ from `internal/unifiedresources/relationship_presentation.go`, so the correlatio
semantics live with the resource model instead of being duplicated in prompt
helpers or drawer-specific markdown.
That same resource model now also owns the canonical
`FormatResourceGraphContext` helper, so service-layer callers only resolve the
resource and hand the model the relationship list instead of rebuilding the
relationship section header, ordering, or freshness wording locally.
`FormatResourceRelationshipContext` helper, so service-layer callers only
resolve the resource and hand the model the relationship list instead of
rebuilding the relationship section header, ordering, or freshness wording
locally.
The same shared relationship presenter also owns the compact change-timeline
relationship summary used by resource change records, so change `from` and
`to` values stay aligned with the canonical relationship labels instead of
+9 -9
View File
@@ -534,19 +534,19 @@ func TestService_BuildRelationshipContext_UsesCanonicalReadState(t *testing.T) {
})
ps.SetCorrelationDetector(corr)
graphContext := s.buildResourceGraphContext(resourceID)
if !strings.Contains(graphContext, "### Resource Graph") {
t.Fatalf("expected canonical relationship context to include graph heading, got %q", graphContext)
relationshipContext := s.buildResourceRelationshipContext(resourceID)
if !strings.Contains(relationshipContext, "### Resource Relationships") {
t.Fatalf("expected canonical relationship context to include relationship heading, got %q", relationshipContext)
}
if !strings.Contains(graphContext, "Runs on") {
t.Fatalf("expected canonical relationship context to include relationship label, got %q", graphContext)
if !strings.Contains(relationshipContext, "Runs on") {
t.Fatalf("expected canonical relationship context to include relationship label, got %q", relationshipContext)
}
if !strings.Contains(graphContext, "metadata present") {
t.Fatalf("expected canonical relationship context to include shared metadata marker, got %q", graphContext)
if !strings.Contains(relationshipContext, "metadata present") {
t.Fatalf("expected canonical relationship context to include shared metadata marker, got %q", relationshipContext)
}
resourceCtx := s.buildEnrichedResourceContext(resourceID, "", nil)
if !strings.Contains(resourceCtx, "Resource Graph") {
if !strings.Contains(resourceCtx, "Resource Relationships") {
t.Fatalf("expected enriched resource context to include relationship section, got %q", resourceCtx)
}
if !strings.Contains(resourceCtx, "Runs on") {
@@ -566,7 +566,7 @@ func TestService_BuildRelationshipContext_UsesCanonicalReadState(t *testing.T) {
}
incidentCtx := s.buildIncidentContext(resourceID, "")
if !strings.Contains(incidentCtx, "Resource Graph") {
if !strings.Contains(incidentCtx, "Resource Relationships") {
t.Fatalf("expected incident context to include relationship section, got %q", incidentCtx)
}
if !strings.Contains(incidentCtx, "Runs on") {
+6 -6
View File
@@ -4512,8 +4512,8 @@ func (s *Service) buildIncidentContext(resourceID, alertIdentifier string) strin
if recentChanges := s.buildRecentResourceChangesContext(resourceID); recentChanges != "" {
sections = append(sections, recentChanges)
}
if graphContext := s.buildResourceGraphContext(resourceID); graphContext != "" {
sections = append(sections, graphContext)
if relationshipContext := s.buildResourceRelationshipContext(resourceID); relationshipContext != "" {
sections = append(sections, relationshipContext)
}
}
@@ -4614,7 +4614,7 @@ type canonicalResourceGetter interface {
Get(id string) (*unifiedresources.Resource, bool)
}
func (s *Service) buildResourceGraphContext(resourceID string) string {
func (s *Service) buildResourceRelationshipContext(resourceID string) string {
resourceID = strings.TrimSpace(resourceID)
if resourceID == "" {
return ""
@@ -4637,7 +4637,7 @@ func (s *Service) buildResourceGraphContext(resourceID string) string {
if !ok || resource == nil {
return ""
}
return unifiedresources.FormatResourceGraphContext(resource, 3)
return unifiedresources.FormatResourceRelationshipContext(resource, 3)
}
// truncateString truncates a string to maxLen characters.
@@ -4823,8 +4823,8 @@ func (s *Service) buildEnrichedResourceContext(resourceID, _ string, currentMetr
}
// Get canonical relationship context from unified resources.
if graphContext := s.buildResourceGraphContext(resourceID); graphContext != "" {
sections = append(sections, graphContext)
if relationshipContext := s.buildResourceRelationshipContext(resourceID); relationshipContext != "" {
sections = append(sections, relationshipContext)
}
// Get shared correlation context for learned related resources.
@@ -643,24 +643,24 @@ func TestResourceChangePresentationUsesCanonicalLabels(t *testing.T) {
}
}
func TestResourceGraphContextUsesCanonicalRelationshipPresentation(t *testing.T) {
func TestResourceRelationshipContextUsesCanonicalRelationshipPresentation(t *testing.T) {
data, err := os.ReadFile(filepath.Join("..", "ai", "service.go"))
if err != nil {
t.Fatalf("failed to read service.go: %v", err)
}
source := string(data)
requiredSnippets := []string{
"func (s *Service) buildResourceGraphContext(resourceID string) string",
"if graphContext := s.buildResourceGraphContext(resourceID); graphContext != \"\" {",
"func (s *Service) buildResourceRelationshipContext(resourceID string) string",
"if relationshipContext := s.buildResourceRelationshipContext(resourceID); relationshipContext != \"\" {",
"Get canonical relationship context from unified resources.",
"unifiedresources.FormatResourceGraphContext(resource, 3)",
"unifiedresources.FormatResourceRelationshipContext(resource, 3)",
"unifiedresources.FormatResourceRecentChangesContext(changes, false, \"###\")",
"type canonicalResourceGetter interface {",
"intel.FormatCorrelationsContext(resourceID)",
}
for _, snippet := range requiredSnippets {
if !strings.Contains(source, snippet) {
t.Fatalf("internal/ai/service.go must pin canonical resource graph presentation snippet %q", snippet)
t.Fatalf("internal/ai/service.go must pin canonical relationship presentation snippet %q", snippet)
}
}
}
@@ -68,9 +68,9 @@ func DescribeRelationship(rel ResourceRelationship) RelationshipPresentation {
return presentation
}
// FormatResourceGraphContext returns the canonical AI prompt section for a
// FormatResourceRelationshipContext returns the canonical AI prompt section for a
// resource's learned relationships.
func FormatResourceGraphContext(resource *Resource, limit int) string {
func FormatResourceRelationshipContext(resource *Resource, limit int) string {
if resource == nil || limit <= 0 || len(resource.Relationships) == 0 {
return ""
}
@@ -112,5 +112,5 @@ func FormatResourceGraphContext(resource *Resource, limit int) string {
if len(lines) == 0 {
return ""
}
return "\n\n### Resource Graph\n" + strings.Join(lines, "\n")
return "\n\n### Resource Relationships\n" + strings.Join(lines, "\n")
}
@@ -70,7 +70,7 @@ func TestResourceRelationshipSummary(t *testing.T) {
}
}
func TestFormatResourceGraphContext(t *testing.T) {
func TestFormatResourceRelationshipContext(t *testing.T) {
resource := &Resource{
Relationships: []ResourceRelationship{
{
@@ -92,21 +92,21 @@ func TestFormatResourceGraphContext(t *testing.T) {
},
}
ctx := FormatResourceGraphContext(resource, 1)
ctx := FormatResourceRelationshipContext(resource, 1)
if ctx == "" {
t.Fatal("expected graph context")
t.Fatal("expected relationship context")
}
if want := "### Resource Graph"; !contains(ctx, want) {
t.Fatalf("expected %q in graph context, got %q", want, ctx)
if want := "### Resource Relationships"; !contains(ctx, want) {
t.Fatalf("expected %q in relationship context, got %q", want, ctx)
}
if !contains(ctx, "Runs on") {
t.Fatalf("expected canonical relationship label, got %q", ctx)
}
if !contains(ctx, "discoverer docker_adapter") {
t.Fatalf("expected provenance in graph context, got %q", ctx)
t.Fatalf("expected provenance in relationship context, got %q", ctx)
}
if contains(ctx, "Depends on") {
t.Fatalf("expected graph limit to truncate entries, got %q", ctx)
t.Fatalf("expected relationship limit to truncate entries, got %q", ctx)
}
}