@skyhook-io/radar-app 1.13.5 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -8
- package/src/App.tsx +60 -24
- package/src/api/client.authRedirect.test.ts +12 -1
- package/src/api/client.prometheusStatus.test.ts +24 -0
- package/src/api/client.ts +321 -16
- package/src/api/diagnose.ts +1 -0
- package/src/api/drain-plan.test.ts +86 -0
- package/src/api/quotas.test.ts +26 -0
- package/src/api/quotas.ts +20 -0
- package/src/api/workloadMetrics.test.ts +40 -0
- package/src/api/workloadMetrics.ts +68 -0
- package/src/components/CloudConnectFlow.tsx +179 -42
- package/src/components/CloudFunnelButton.tsx +107 -36
- package/src/components/SyncProgressPanel.tsx +73 -0
- package/src/components/audit/AuditView.tsx +2 -0
- package/src/components/cloudConnectHandoff.test.ts +140 -0
- package/src/components/cloudConnectHandoff.ts +124 -0
- package/src/components/diagnose/DiagnoseContext.tsx +2 -1
- package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
- package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
- package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
- package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
- package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
- package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
- package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
- package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
- package/src/components/diagnose/investigationEvidence/index.ts +55 -0
- package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
- package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
- package/src/components/diagnose/investigationEvidence/types.ts +512 -0
- package/src/components/diagnose/target.test.ts +19 -0
- package/src/components/diagnose/target.ts +3 -2
- package/src/components/gitops/GitOpsView.tsx +7 -0
- package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
- package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
- package/src/components/resource/PrometheusCharts.tsx +4 -3
- package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +75 -35
- package/src/components/resource/RightsizingStrip.test.ts +38 -0
- package/src/components/resource/RightsizingStrip.tsx +15 -3
- package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
- package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
- package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
- package/src/components/resource/workloadMetricValues.test.ts +65 -0
- package/src/components/resource/workloadMetricValues.ts +30 -0
- package/src/components/resources/ResourcesView.tsx +44 -6
- package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
- package/src/components/resources/renderers/PodRenderer.tsx +4 -0
- package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
- package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
- package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
- package/src/components/settings/SettingsDialog.tsx +94 -14
- package/src/components/traffic/TrafficFlowList.tsx +229 -70
- package/src/components/traffic/TrafficGraph.tsx +6 -6
- package/src/components/traffic/TrafficView.tsx +10 -9
- package/src/components/traffic/trafficFilters.test.ts +28 -1
- package/src/components/traffic/trafficFilters.ts +31 -0
- package/src/components/workload/WorkloadView.tsx +19 -8
- package/src/context/ConnectionContext.tsx +48 -3
- package/src/context/DiagnoseCustomization.tsx +1 -0
- package/src/index.css +17 -0
- package/src/utils/navigation.ts +1 -1
- package/src/utils/topology-namespace.test.ts +64 -0
- package/src/utils/topology-namespace.ts +18 -0
- package/src/components/diagnose/investigationEvidence.test.ts +0 -6617
- package/src/components/diagnose/investigationEvidence.ts +0 -5572
- package/src/components/gitops/GitOpsActions.tsx +0 -1
- package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
- package/src/components/gitops/ManagedResourcesList.tsx +0 -1
- package/src/components/gitops/SyncCountdown.tsx +0 -1
- package/src/components/gitops/index.ts +0 -4
- package/src/components/helm/ManifestDiffViewer.tsx +0 -69
- package/src/components/logs/JsonLogLine.tsx +0 -1
- package/src/components/logs/LogCore.tsx +0 -2
- package/src/components/logs/useLogBuffer.ts +0 -2
- package/src/components/logs/useLogSearch.ts +0 -1
- package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
- package/src/components/resources/drawer-components.tsx +0 -1
- package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
- package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
- package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
- package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/EventRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
- package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
- package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/JobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
- package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
- package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
- package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
- package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
- package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
- package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
- package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
- package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
- package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
- package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
- package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
- package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
- package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
- package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
- package/src/components/resources/renderers/VPARenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
- package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
- package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
- package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
- package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
- package/src/components/resources/renderers/argo-cells.tsx +0 -1
- package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
- package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
- package/src/components/resources/renderers/eso-cells.tsx +0 -1
- package/src/components/resources/renderers/flux-cells.tsx +0 -1
- package/src/components/resources/renderers/index.ts +0 -94
- package/src/components/resources/renderers/istio-cells.tsx +0 -1
- package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
- package/src/components/resources/renderers/keda-cells.tsx +0 -1
- package/src/components/resources/renderers/knative-cells.tsx +0 -1
- package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
- package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
- package/src/components/resources/renderers/traefik-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-cells.tsx +0 -1
- package/src/components/resources/renderers/trivy-shared.tsx +0 -1
- package/src/components/resources/renderers/velero-cells.tsx +0 -1
- package/src/components/resources/resource-utils-argo.ts +0 -2
- package/src/components/resources/resource-utils-certmanager.ts +0 -2
- package/src/components/resources/resource-utils-eso.ts +0 -2
- package/src/components/resources/resource-utils-flux.ts +0 -2
- package/src/components/resources/resource-utils-istio.ts +0 -2
- package/src/components/resources/resource-utils-karpenter.ts +0 -2
- package/src/components/resources/resource-utils-knative.ts +0 -2
- package/src/components/resources/resource-utils-kyverno.ts +0 -2
- package/src/components/resources/resource-utils-prometheus.ts +0 -2
- package/src/components/resources/resource-utils-traefik.ts +0 -1
- package/src/components/resources/resource-utils-trivy.ts +0 -2
- package/src/components/resources/resource-utils-velero.ts +0 -2
- package/src/components/shared/EditableYamlView.tsx +0 -24
- package/src/components/timeline/DiffViewer.tsx +0 -1
- package/src/components/traffic/index.ts +0 -3
- package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
- package/src/components/ui/MetricsChart.tsx +0 -1
- package/src/components/ui/ResourceBar.tsx +0 -1
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { CORE_RESOURCES } from "@skyhook-io/k8s-ui";
|
|
2
|
+
import { fnv1a32 } from "@skyhook-io/k8s-ui/utils/structure-hash";
|
|
3
|
+
import { apiVersionToGroup } from "../../../utils/navigation";
|
|
4
|
+
import { type DiagnosisResourceRef } from "../diagnoseEvidenceTypes";
|
|
5
|
+
import { type RootCauseEvidence } from "../../../api/diagnose";
|
|
6
|
+
import { nonEmptyString, parseJSON, record } from "./parse";
|
|
7
|
+
import {
|
|
8
|
+
type InvestigationEvidenceData,
|
|
9
|
+
type InvestigationEvidenceProjection,
|
|
10
|
+
type InvestigationEvidenceSource,
|
|
11
|
+
type InvestigationRootCauseEvidenceLink,
|
|
12
|
+
type InvestigationRootCauseEvidenceResolution,
|
|
13
|
+
} from "./types";
|
|
14
|
+
|
|
15
|
+
function exactDiagnosisResourceRef(ref: {
|
|
16
|
+
kind?: unknown;
|
|
17
|
+
group?: unknown;
|
|
18
|
+
namespace?: unknown;
|
|
19
|
+
name?: unknown;
|
|
20
|
+
}): DiagnosisResourceRef | undefined {
|
|
21
|
+
const { kind, name } = ref;
|
|
22
|
+
if (
|
|
23
|
+
!nonEmptyString(kind) ||
|
|
24
|
+
kind.trim() !== kind ||
|
|
25
|
+
!nonEmptyString(name) ||
|
|
26
|
+
name.trim() !== name ||
|
|
27
|
+
(ref.group !== undefined &&
|
|
28
|
+
(typeof ref.group !== "string" || ref.group.trim() !== ref.group)) ||
|
|
29
|
+
(ref.namespace !== undefined &&
|
|
30
|
+
(typeof ref.namespace !== "string" ||
|
|
31
|
+
ref.namespace.trim() !== ref.namespace))
|
|
32
|
+
) {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const group = ref.group || undefined;
|
|
36
|
+
const namespace = ref.namespace || undefined;
|
|
37
|
+
const knownKinds = CORE_RESOURCES.filter(
|
|
38
|
+
(resource) =>
|
|
39
|
+
resource.kind.toLowerCase() === kind.toLowerCase() &&
|
|
40
|
+
(group === undefined || resource.group === group),
|
|
41
|
+
);
|
|
42
|
+
const knownScopes = new Set(
|
|
43
|
+
knownKinds.map((resource) => resource.namespaced),
|
|
44
|
+
);
|
|
45
|
+
// This is deliberately only a negative guard. Unknown kinds/groups may be
|
|
46
|
+
// cluster-scoped CRDs, so suppress the link only when Radar's existing
|
|
47
|
+
// resource metadata identifies the kind's scope without ambiguity.
|
|
48
|
+
if (knownScopes.size === 1) {
|
|
49
|
+
if (knownScopes.has(true) && !namespace) return undefined;
|
|
50
|
+
if (knownScopes.has(false) && namespace) return undefined;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
kind,
|
|
54
|
+
name,
|
|
55
|
+
...(group ? { group } : {}),
|
|
56
|
+
...(namespace ? { namespace } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The Kubernetes resource an evidence item is about, when the producer payload
|
|
61
|
+
* states one unambiguously. Pod-shaped evidence resolves only through the
|
|
62
|
+
* namespace its producing check actually read; the investigation target's
|
|
63
|
+
* namespace is deliberately never borrowed.
|
|
64
|
+
*/
|
|
65
|
+
export function investigationEvidenceSubjectRef(
|
|
66
|
+
data: InvestigationEvidenceData,
|
|
67
|
+
): DiagnosisResourceRef | undefined {
|
|
68
|
+
const ref = ((): DiagnosisResourceRef | undefined => {
|
|
69
|
+
switch (data.type) {
|
|
70
|
+
case "issue":
|
|
71
|
+
return {
|
|
72
|
+
kind: data.issue.kind,
|
|
73
|
+
group: data.issue.group,
|
|
74
|
+
namespace: data.issue.namespace,
|
|
75
|
+
name: data.issue.name,
|
|
76
|
+
};
|
|
77
|
+
case "startup":
|
|
78
|
+
return data.subject;
|
|
79
|
+
case "resource": {
|
|
80
|
+
const apiVersion = data.resource.apiVersion;
|
|
81
|
+
const group = apiVersionToGroup(apiVersion);
|
|
82
|
+
return {
|
|
83
|
+
kind: data.resource.kind,
|
|
84
|
+
group: group || undefined,
|
|
85
|
+
namespace: data.resource.metadata.namespace,
|
|
86
|
+
name: data.resource.metadata.name,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
case "logs":
|
|
90
|
+
if (!data.namespace) return undefined;
|
|
91
|
+
return { kind: "Pod", namespace: data.namespace, name: data.pod };
|
|
92
|
+
case "crash":
|
|
93
|
+
if (data.crash.pods.length !== 1 || !data.namespace) return undefined;
|
|
94
|
+
return {
|
|
95
|
+
kind: "Pod",
|
|
96
|
+
namespace: data.namespace,
|
|
97
|
+
name: data.crash.pods[0],
|
|
98
|
+
};
|
|
99
|
+
case "network":
|
|
100
|
+
return data.network.subject;
|
|
101
|
+
case "relationships":
|
|
102
|
+
return data.root;
|
|
103
|
+
case "helm":
|
|
104
|
+
// A resource ref cannot carry the storage namespace, and opening the
|
|
105
|
+
// release by name alone would read the wrong storage.
|
|
106
|
+
if (
|
|
107
|
+
data.release.storageNamespace !== undefined &&
|
|
108
|
+
data.release.storageNamespace !== data.release.namespace
|
|
109
|
+
) {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
kind: "HelmRelease",
|
|
114
|
+
group: "helm.sh",
|
|
115
|
+
namespace: data.release.namespace,
|
|
116
|
+
name: data.release.name,
|
|
117
|
+
};
|
|
118
|
+
case "permissions":
|
|
119
|
+
// Users and Groups are principals, not Kubernetes objects.
|
|
120
|
+
if (data.subject.kind !== "ServiceAccount") return undefined;
|
|
121
|
+
return {
|
|
122
|
+
kind: "ServiceAccount",
|
|
123
|
+
namespace: data.subject.namespace,
|
|
124
|
+
name: data.subject.name,
|
|
125
|
+
};
|
|
126
|
+
case "metrics":
|
|
127
|
+
return data.subject;
|
|
128
|
+
default:
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
})();
|
|
132
|
+
return ref ? exactDiagnosisResourceRef(ref) : undefined;
|
|
133
|
+
}
|
|
134
|
+
function domToken(value: string): string {
|
|
135
|
+
// Underscore is reserved as the escape delimiter, so raw input can never
|
|
136
|
+
// impersonate an encoded code point ("/" vs. the literal "_x2f_"). The
|
|
137
|
+
// empty sentinel is safe for the same reason: a literal "_empty_" is escaped.
|
|
138
|
+
if (!value) return "_empty_";
|
|
139
|
+
return Array.from(value, (character) =>
|
|
140
|
+
/[A-Za-z0-9-]/.test(character)
|
|
141
|
+
? character
|
|
142
|
+
: `_x${character.codePointAt(0)!.toString(16)}_`,
|
|
143
|
+
).join("");
|
|
144
|
+
}
|
|
145
|
+
export function investigationEvidenceSourceId(
|
|
146
|
+
turnIndex: number,
|
|
147
|
+
stepId: string,
|
|
148
|
+
): string {
|
|
149
|
+
return `turn-${turnIndex}-step-${domToken(stepId)}`;
|
|
150
|
+
}
|
|
151
|
+
export function investigationActivitySourceDomId(sourceId: string): string {
|
|
152
|
+
return `investigation-activity-${sourceId}`;
|
|
153
|
+
}
|
|
154
|
+
export function investigationEvidenceSourceDomId(sourceId: string): string {
|
|
155
|
+
return `investigation-evidence-${sourceId}`;
|
|
156
|
+
}
|
|
157
|
+
export const investigationEvidenceRefRe =
|
|
158
|
+
/^ev_[a-z2-7]{26,128}_[a-z2-7]{26,128}$/;
|
|
159
|
+
export function isInvestigationEvidenceRef(value: string): boolean {
|
|
160
|
+
return investigationEvidenceRefRe.test(value);
|
|
161
|
+
}
|
|
162
|
+
export function investigationSourceArgs(
|
|
163
|
+
source: InvestigationEvidenceSource,
|
|
164
|
+
): Record<string, unknown> | undefined {
|
|
165
|
+
return record(source.args ? parseJSON(source.args) : undefined);
|
|
166
|
+
}
|
|
167
|
+
export function resolveInvestigationRootCauseEvidence(
|
|
168
|
+
projection: InvestigationEvidenceProjection,
|
|
169
|
+
evidence: RootCauseEvidence | undefined,
|
|
170
|
+
assessmentTurnIndex: number,
|
|
171
|
+
): InvestigationRootCauseEvidenceResolution {
|
|
172
|
+
if (!evidence || evidence.status === "missing") {
|
|
173
|
+
return { status: "missing", links: [] };
|
|
174
|
+
}
|
|
175
|
+
if (evidence.status !== "linked") {
|
|
176
|
+
return { status: "invalid", links: [] };
|
|
177
|
+
}
|
|
178
|
+
const refs = evidence.refs;
|
|
179
|
+
if (
|
|
180
|
+
!refs ||
|
|
181
|
+
refs.length < 1 ||
|
|
182
|
+
refs.length > 3 ||
|
|
183
|
+
new Set(refs).size !== refs.length ||
|
|
184
|
+
refs.some((ref) => !investigationEvidenceRefRe.test(ref)) ||
|
|
185
|
+
refs.some((ref) => ref.split("_")[1] !== refs[0].split("_")[1])
|
|
186
|
+
) {
|
|
187
|
+
return { status: "invalid", links: [] };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const byRef = new Map<string, InvestigationEvidenceSource[]>();
|
|
191
|
+
for (const source of projection.evidenceRefSources) {
|
|
192
|
+
if (source.turnIndex !== assessmentTurnIndex) continue;
|
|
193
|
+
if (!source.evidenceRef) continue;
|
|
194
|
+
const matches = byRef.get(source.evidenceRef) ?? [];
|
|
195
|
+
matches.push(source);
|
|
196
|
+
byRef.set(source.evidenceRef, matches);
|
|
197
|
+
}
|
|
198
|
+
const citableSourceIds = new Set(
|
|
199
|
+
projection.citableSources
|
|
200
|
+
.filter((source) => source.turnIndex === assessmentTurnIndex)
|
|
201
|
+
.map((source) => source.id),
|
|
202
|
+
);
|
|
203
|
+
const links: InvestigationRootCauseEvidenceLink[] = [];
|
|
204
|
+
for (const ref of refs) {
|
|
205
|
+
const matches = byRef.get(ref);
|
|
206
|
+
// Match the server's fail-closed binding: every current-turn occurrence
|
|
207
|
+
// counts before success/completeness eligibility is considered.
|
|
208
|
+
if (matches?.length !== 1 || !citableSourceIds.has(matches[0].id)) {
|
|
209
|
+
return { status: "invalid", links: [] };
|
|
210
|
+
}
|
|
211
|
+
const source = matches[0];
|
|
212
|
+
const originalGroup = source.primaryGroupId
|
|
213
|
+
? projection.groups.find((group) => group.id === source.primaryGroupId)
|
|
214
|
+
: undefined;
|
|
215
|
+
links.push({
|
|
216
|
+
source,
|
|
217
|
+
originalGroupId: originalGroup?.observations.some(
|
|
218
|
+
(observation) => observation.source.id === source.id,
|
|
219
|
+
)
|
|
220
|
+
? originalGroup.id
|
|
221
|
+
: undefined,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return { status: "linked", links };
|
|
225
|
+
}
|
|
226
|
+
export function investigationEvidenceStepIdsByTurn(
|
|
227
|
+
projection: InvestigationEvidenceProjection,
|
|
228
|
+
visibleGroupIds?: ReadonlySet<string>,
|
|
229
|
+
): Map<number, Set<string>> {
|
|
230
|
+
const byTurn = new Map<number, Set<string>>();
|
|
231
|
+
const linkedSourceIds = new Set<string>();
|
|
232
|
+
for (const group of projection.groups) {
|
|
233
|
+
if (visibleGroupIds && !visibleGroupIds.has(group.id)) continue;
|
|
234
|
+
for (const observation of group.observations) {
|
|
235
|
+
if (visibleGroupIds && observation.source.primaryGroupId !== group.id)
|
|
236
|
+
continue;
|
|
237
|
+
linkedSourceIds.add(observation.source.id);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const limitation of projection.limitations) {
|
|
241
|
+
for (const source of limitation.sources) linkedSourceIds.add(source.id);
|
|
242
|
+
}
|
|
243
|
+
const navigableSources = new Map(
|
|
244
|
+
[...projection.sources, ...projection.citableSources].map((source) => [
|
|
245
|
+
source.id,
|
|
246
|
+
source,
|
|
247
|
+
]),
|
|
248
|
+
);
|
|
249
|
+
for (const source of navigableSources.values()) {
|
|
250
|
+
if (!linkedSourceIds.has(source.id)) continue;
|
|
251
|
+
const stepIds = byTurn.get(source.turnIndex) ?? new Set<string>();
|
|
252
|
+
stepIds.add(source.stepId);
|
|
253
|
+
byTurn.set(source.turnIndex, stepIds);
|
|
254
|
+
}
|
|
255
|
+
return byTurn;
|
|
256
|
+
}
|
|
257
|
+
export function stableHash(value: string): string {
|
|
258
|
+
// FNV-1a keeps long resource/log identities out of DOM IDs. The raw identity
|
|
259
|
+
// remains the Map key, so a hash collision can never merge evidence.
|
|
260
|
+
return fnv1a32(value).toString(36);
|
|
261
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// The module's public surface. Importers name the directory, never a file
|
|
2
|
+
// inside it, so what this file re-exports is the whole contract. Anything
|
|
3
|
+
// absent here is internal: exported from its own file only so a sibling can
|
|
4
|
+
// reach it, and free to move between files without affecting a caller.
|
|
5
|
+
|
|
6
|
+
export { metricsScope } from "./adapters/prometheus";
|
|
7
|
+
export { projectInvestigationEvidence } from "./builder";
|
|
8
|
+
export {
|
|
9
|
+
investigationActivitySourceDomId,
|
|
10
|
+
investigationEvidenceSourceDomId,
|
|
11
|
+
investigationEvidenceSourceId,
|
|
12
|
+
investigationEvidenceStepIdsByTurn,
|
|
13
|
+
investigationEvidenceSubjectRef,
|
|
14
|
+
investigationSourceArgs,
|
|
15
|
+
isInvestigationEvidenceRef,
|
|
16
|
+
resolveInvestigationRootCauseEvidence,
|
|
17
|
+
} from "./identity";
|
|
18
|
+
export { evidenceSemanticSnapshot } from "./observations";
|
|
19
|
+
export type {
|
|
20
|
+
InvestigationAccessCheck,
|
|
21
|
+
InvestigationAlertInstance,
|
|
22
|
+
InvestigationAlertRule,
|
|
23
|
+
InvestigationEventEvidence,
|
|
24
|
+
InvestigationEvidenceCoverage,
|
|
25
|
+
InvestigationEvidenceData,
|
|
26
|
+
InvestigationEvidenceGroup,
|
|
27
|
+
InvestigationEvidenceKind,
|
|
28
|
+
InvestigationEvidenceLimitation,
|
|
29
|
+
InvestigationEvidenceObservation,
|
|
30
|
+
InvestigationEvidencePhase,
|
|
31
|
+
InvestigationEvidenceProjection,
|
|
32
|
+
InvestigationEvidenceRelevance,
|
|
33
|
+
InvestigationEvidenceSource,
|
|
34
|
+
InvestigationEvidenceTarget,
|
|
35
|
+
InvestigationEvidenceTier,
|
|
36
|
+
InvestigationEvidenceTimelineItem,
|
|
37
|
+
InvestigationEvidenceTurn,
|
|
38
|
+
InvestigationGitOpsDiagnosis,
|
|
39
|
+
InvestigationHelmOperation,
|
|
40
|
+
InvestigationHelmOwnedResource,
|
|
41
|
+
InvestigationHelmRelease,
|
|
42
|
+
InvestigationKubernetesResource,
|
|
43
|
+
InvestigationMetricsEvidence,
|
|
44
|
+
InvestigationMetricsSelector,
|
|
45
|
+
InvestigationNetworkEvidence,
|
|
46
|
+
InvestigationNetworkRoute,
|
|
47
|
+
InvestigationPermissionBinding,
|
|
48
|
+
InvestigationPermissionSubject,
|
|
49
|
+
InvestigationResourceContext,
|
|
50
|
+
InvestigationResourceSummary,
|
|
51
|
+
InvestigationRootCauseEvidenceLink,
|
|
52
|
+
InvestigationRootCauseEvidenceResolution,
|
|
53
|
+
InvestigationTopologyEdge,
|
|
54
|
+
InvestigationTopologyNode,
|
|
55
|
+
} from "./types";
|