From bdaa2bd525be02ec71d70e9fb3f54cb38d123204 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 19 Mar 2026 14:30:45 +0000 Subject: [PATCH] Normalize relationship terminology --- .../v6/internal/subsystems/ai-runtime.md | 4 +-- .../v6/internal/subsystems/alerts.md | 7 ++++++ .../subsystems/patrol-intelligence.md | 10 ++++---- .../internal/subsystems/storage-recovery.md | 2 +- .../internal/subsystems/unified-resources.md | 6 ++--- internal/ai/correlation/rootcause.go | 4 +-- internal/ai/coverage_increase_test.go | 19 +++++++++++--- internal/ai/service.go | 2 +- internal/alerts/specs/types.go | 2 +- internal/alerts/specs/types_test.go | 25 +++++++++++++++++++ .../unifiedresources/code_standards_test.go | 1 + 11 files changed, 63 insertions(+), 19 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 7614aea5e..83a387de3 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -253,11 +253,11 @@ prefixing, and change-type labels without re-implementing the markdown shape. Those unified-resource action and export audit records are now also exposed through the enterprise audit read surface so operators can inspect the execution trail without reaching into storage internals. -AI resource and incident context now also surfaces a canonical resource-graph +AI resource and incident context now also surfaces a canonical relationship 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 graph section is now rendered by the shared +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. diff --git a/docs/release-control/v6/internal/subsystems/alerts.md b/docs/release-control/v6/internal/subsystems/alerts.md index 37a91daba..292b3106e 100644 --- a/docs/release-control/v6/internal/subsystems/alerts.md +++ b/docs/release-control/v6/internal/subsystems/alerts.md @@ -75,6 +75,13 @@ list of services, labels, descriptions, and mention-copy metadata. The WebhookConfig editor now imports the shared webhook template API type directly so it does not retain a local duplicate shape for chooser metadata. +Alert spec validation still accepts the explicit migration-bridge resource +types (`node`, `agent-disk`, `docker-host`, `backup-subject`, +`proxmox-disk`), but any other non-canonical type string is rejected before +it can reach alert persistence. That keeps alert routing aligned with the +canonical unified resource model instead of silently normalizing legacy type +aliases inside the alert layer. + Frontend alert surfaces and backend alert-support files now require explicit registry path-policy coverage, so new alert-owned runtime files must be mapped to a concrete proof route instead of silently inheriting subsystem-default diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index 4e00c8112..1f5a02d21 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -96,17 +96,17 @@ fallback paths. helper, so the detector-owned summary API and the Patrol fallback prompt path stay aligned on the same markdown shape. Those same Patrol-owned prompt contexts now also surface a canonical -resource-graph section from unified-resource relationships, so edge labels, -directionality, and provenance stay aligned with the shared graph model +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 graph section is now rendered by the shared +That relationship section is now rendered by the shared `internal/unifiedresources.FormatResourceGraphContext` helper, so the Patrol -runtime only resolves the canonical resource graph rather than formatting the +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 -changes and resource graph data instead of being pulled from the detector +changes and relationship data instead of being pulled from the detector directly in each caller. The Patrol seed context and AI runtime prompt path now also share the same correlation summary formatter from `internal/ai/correlation`, so learned-edge diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index 91f8fdf43..1620cb790 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -733,7 +733,7 @@ locally, so storage and recovery views see the same policy posture the API publishes. The same hook and the resource-identity helpers it depends on now share the canonical trimmed-string utility instead of each surface rebuilding its own whitespace cleanup, so storage and recovery identity checks stay -aligned with the other resource-graph consumers. That same boundary now also +aligned with the other unified-resource consumers. That same boundary now also owns the backend facet-bundle route for timeline history and related change counts, so storage and recovery surfaces must continue to consume the shared bundle rather than issuing separate local resource-detail fetches. diff --git a/docs/release-control/v6/internal/subsystems/unified-resources.md b/docs/release-control/v6/internal/subsystems/unified-resources.md index f89075e60..cc85f0265 100644 --- a/docs/release-control/v6/internal/subsystems/unified-resources.md +++ b/docs/release-control/v6/internal/subsystems/unified-resources.md @@ -146,7 +146,7 @@ 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 -graph section header, ordering, or freshness wording locally. +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 @@ -441,8 +441,8 @@ instead of `"myserver"`. The infrastructure summary surfaces now use the shared normalized identity lookup helper for these matches, so dotted hostnames such as `tower.example.local` collapse to the same canonical lookup variants as the -resource table and resource graph surfaces instead of each view inventing its -own comparison rule. +resource table and resource detail surfaces instead of each view inventing +its own comparison rule. The same identity surfaces also share the trimmed-string helper from `frontend-modern/src/utils/stringUtils.ts` so resource-id, hostname, and linked-node normalization keep the same fail-closed whitespace trimming rules diff --git a/internal/ai/correlation/rootcause.go b/internal/ai/correlation/rootcause.go index a668e145c..4cfafcd09 100644 --- a/internal/ai/correlation/rootcause.go +++ b/internal/ai/correlation/rootcause.go @@ -14,8 +14,8 @@ import ( ) // RelationshipType and ResourceRelationship are aliases to the canonical -// unified-resource graph model so root-cause correlation consumes the same -// edge vocabulary as the rest of the platform. +// unified-resource relationship model so root-cause correlation consumes the +// same edge vocabulary as the rest of the platform. type RelationshipType = unifiedresources.RelationshipType type ResourceRelationship = unifiedresources.ResourceRelationship diff --git a/internal/ai/coverage_increase_test.go b/internal/ai/coverage_increase_test.go index 8cdd12244..2e5f86a07 100644 --- a/internal/ai/coverage_increase_test.go +++ b/internal/ai/coverage_increase_test.go @@ -479,7 +479,7 @@ func TestService_BuildIncidentContext(t *testing.T) { } } -func TestService_BuildResourceGraphContext_UsesCanonicalReadState(t *testing.T) { +func TestService_BuildRelationshipContext_UsesCanonicalReadState(t *testing.T) { now := time.Now() s := NewService(nil, nil) ps := NewPatrolService(nil, nil) @@ -534,9 +534,20 @@ func TestService_BuildResourceGraphContext_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) + } + if !strings.Contains(graphContext, "Runs on") { + t.Fatalf("expected canonical relationship context to include relationship label, got %q", graphContext) + } + if !strings.Contains(graphContext, "metadata present") { + t.Fatalf("expected canonical relationship context to include shared metadata marker, got %q", graphContext) + } + resourceCtx := s.buildEnrichedResourceContext(resourceID, "", nil) if !strings.Contains(resourceCtx, "Resource Graph") { - t.Fatalf("expected enriched resource context to include graph section, got %q", resourceCtx) + t.Fatalf("expected enriched resource context to include relationship section, got %q", resourceCtx) } if !strings.Contains(resourceCtx, "Runs on") { t.Fatalf("expected enriched resource context to include canonical relationship label, got %q", resourceCtx) @@ -545,7 +556,7 @@ func TestService_BuildResourceGraphContext_UsesCanonicalReadState(t *testing.T) t.Fatalf("expected enriched resource context to include provenance, got %q", resourceCtx) } if !strings.Contains(resourceCtx, "metadata present") { - t.Fatalf("expected enriched resource context to include shared graph metadata marker, got %q", resourceCtx) + t.Fatalf("expected enriched resource context to include shared relationship metadata marker, got %q", resourceCtx) } if !strings.Contains(resourceCtx, "Resource Correlations") { t.Fatalf("expected enriched resource context to include correlation section, got %q", resourceCtx) @@ -556,7 +567,7 @@ func TestService_BuildResourceGraphContext_UsesCanonicalReadState(t *testing.T) incidentCtx := s.buildIncidentContext(resourceID, "") if !strings.Contains(incidentCtx, "Resource Graph") { - t.Fatalf("expected incident context to include graph section, got %q", incidentCtx) + t.Fatalf("expected incident context to include relationship section, got %q", incidentCtx) } if !strings.Contains(incidentCtx, "Runs on") { t.Fatalf("expected incident context to include canonical relationship label, got %q", incidentCtx) diff --git a/internal/ai/service.go b/internal/ai/service.go index b02279d2e..9bb127206 100644 --- a/internal/ai/service.go +++ b/internal/ai/service.go @@ -4822,7 +4822,7 @@ func (s *Service) buildEnrichedResourceContext(resourceID, _ string, currentMetr sections = append(sections, recentChanges) } - // Get canonical resource graph context from unified resources. + // Get canonical relationship context from unified resources. if graphContext := s.buildResourceGraphContext(resourceID); graphContext != "" { sections = append(sections, graphContext) } diff --git a/internal/alerts/specs/types.go b/internal/alerts/specs/types.go index 05444db88..6aa6a9f75 100644 --- a/internal/alerts/specs/types.go +++ b/internal/alerts/specs/types.go @@ -1081,7 +1081,7 @@ func isKnownResourceType(rt unifiedresources.ResourceType) bool { return false } // "node", "agent-disk", "docker-host", "backup-subject", and "proxmox-disk" remain migration bridges while - // live alerts are still keyed separately from the canonical unified resource graph. + // live alerts are still keyed separately from the canonical unified resource model. if rt != unifiedresources.ResourceType("node") && rt != unifiedresources.ResourceType("agent-disk") && rt != unifiedresources.ResourceType("docker-host") && diff --git a/internal/alerts/specs/types_test.go b/internal/alerts/specs/types_test.go index c4f22e36e..b28343aee 100644 --- a/internal/alerts/specs/types_test.go +++ b/internal/alerts/specs/types_test.go @@ -339,6 +339,31 @@ func TestResourceAlertSpecValidateAllowsProxmoxDiskMigrationBridgeType(t *testin } } +func TestResourceAlertSpecValidateRejectsNonCanonicalAlias(t *testing.T) { + t.Parallel() + + spec := ResourceAlertSpec{ + ID: "node-pve1-cpu", + ResourceID: "node/pve-1", + ResourceType: unifiedresources.ResourceType("Node"), + Kind: AlertSpecKindMetricThreshold, + Severity: AlertSeverityWarning, + MetricThreshold: &MetricThresholdSpec{ + Metric: "cpu", + Direction: ThresholdDirectionAbove, + Trigger: 85, + }, + } + + err := spec.Validate() + if err == nil { + t.Fatal("expected validation error") + } + if !strings.Contains(err.Error(), "canonical unified resource type") { + t.Fatalf("unexpected error: %v", err) + } +} + func TestResourceAlertSpecValidateRejectsPayloadKindMismatch(t *testing.T) { t.Parallel() diff --git a/internal/unifiedresources/code_standards_test.go b/internal/unifiedresources/code_standards_test.go index 93291b102..646c0dd4f 100644 --- a/internal/unifiedresources/code_standards_test.go +++ b/internal/unifiedresources/code_standards_test.go @@ -652,6 +652,7 @@ func TestResourceGraphContextUsesCanonicalRelationshipPresentation(t *testing.T) requiredSnippets := []string{ "func (s *Service) buildResourceGraphContext(resourceID string) string", "if graphContext := s.buildResourceGraphContext(resourceID); graphContext != \"\" {", + "Get canonical relationship context from unified resources.", "unifiedresources.FormatResourceGraphContext(resource, 3)", "unifiedresources.FormatResourceRecentChangesContext(changes, false, \"###\")", "type canonicalResourceGetter interface {",