Normalize relationship terminology

This commit is contained in:
rcourtman
2026-03-19 14:30:45 +00:00
parent cc806171dc
commit bdaa2bd525
11 changed files with 63 additions and 19 deletions
@@ -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.
@@ -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
@@ -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
@@ -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.
@@ -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
+2 -2
View File
@@ -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
+15 -4
View File
@@ -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)
+1 -1
View File
@@ -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)
}
+1 -1
View File
@@ -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") &&
+25
View File
@@ -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()
@@ -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 {",