@skyhook-io/radar-app 1.13.4 → 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.
Files changed (230) hide show
  1. package/package.json +8 -8
  2. package/src/App.tsx +60 -24
  3. package/src/RadarApp.tsx +5 -0
  4. package/src/api/client.authRedirect.test.ts +12 -1
  5. package/src/api/client.prometheusStatus.test.ts +24 -0
  6. package/src/api/client.ts +325 -16
  7. package/src/api/diagnose.ts +62 -15
  8. package/src/api/drain-plan.test.ts +86 -0
  9. package/src/api/quotas.test.ts +26 -0
  10. package/src/api/quotas.ts +20 -0
  11. package/src/api/workloadMetrics.test.ts +40 -0
  12. package/src/api/workloadMetrics.ts +68 -0
  13. package/src/components/CloudConnectFlow.tsx +179 -42
  14. package/src/components/CloudFunnelButton.tsx +107 -36
  15. package/src/components/ConnectionErrorView.test.tsx +30 -0
  16. package/src/components/ConnectionErrorView.tsx +31 -19
  17. package/src/components/SyncProgressPanel.tsx +73 -0
  18. package/src/components/audit/AuditView.tsx +2 -0
  19. package/src/components/cloudConnectHandoff.test.ts +140 -0
  20. package/src/components/cloudConnectHandoff.ts +124 -0
  21. package/src/components/diagnose/AgentCase.tsx +131 -0
  22. package/src/components/diagnose/DiagnoseContext.tsx +2 -1
  23. package/src/components/diagnose/DiagnoseSurface.test.tsx +0 -20
  24. package/src/components/diagnose/DiagnoseSurface.tsx +32 -195
  25. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +783 -7
  26. package/src/components/diagnose/InvestigationEvidencePane.tsx +774 -133
  27. package/src/components/diagnose/InvestigationView.tsx +222 -5
  28. package/src/components/diagnose/diagnoseEvidenceTypes.ts +22 -1
  29. package/src/components/diagnose/investigationCase.test.tsx +1568 -0
  30. package/src/components/diagnose/investigationCase.ts +439 -0
  31. package/src/components/diagnose/investigationEvidence/adapters/changes.ts +60 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +573 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +935 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/events.ts +96 -0
  35. package/src/components/diagnose/investigationEvidence/adapters/helm.test.ts +242 -0
  36. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +208 -0
  37. package/src/components/diagnose/investigationEvidence/adapters/issues.ts +172 -0
  38. package/src/components/diagnose/investigationEvidence/adapters/logs.test.ts +283 -0
  39. package/src/components/diagnose/investigationEvidence/adapters/logs.ts +183 -0
  40. package/src/components/diagnose/investigationEvidence/adapters/permissions.test.ts +528 -0
  41. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +292 -0
  42. package/src/components/diagnose/investigationEvidence/adapters/prometheus.test.ts +1641 -0
  43. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +769 -0
  44. package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +54 -0
  45. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +257 -0
  46. package/src/components/diagnose/investigationEvidence/adapters/topology.ts +207 -0
  47. package/src/components/diagnose/investigationEvidence/builder.ts +418 -0
  48. package/src/components/diagnose/investigationEvidence/evidenceFixtures.ts +179 -0
  49. package/src/components/diagnose/investigationEvidence/identity.test.ts +614 -0
  50. package/src/components/diagnose/investigationEvidence/identity.ts +261 -0
  51. package/src/components/diagnose/investigationEvidence/index.ts +55 -0
  52. package/src/components/diagnose/investigationEvidence/observations.ts +954 -0
  53. package/src/components/diagnose/investigationEvidence/parse.ts +467 -0
  54. package/src/components/diagnose/investigationEvidence/projection.test.ts +2703 -0
  55. package/src/components/diagnose/investigationEvidence/types.ts +512 -0
  56. package/src/components/diagnose/investigationEvidenceKinds.ts +218 -0
  57. package/src/components/diagnose/investigationEvidencePresentation.test.ts +31 -0
  58. package/src/components/diagnose/investigationEvidencePresentation.ts +1 -0
  59. package/src/components/diagnose/investigationMetrics.test.ts +712 -0
  60. package/src/components/diagnose/investigationMetrics.ts +393 -0
  61. package/src/components/diagnose/investigationSourceFocus.ts +2 -0
  62. package/src/components/diagnose/investigationState.test.ts +322 -0
  63. package/src/components/diagnose/investigationState.ts +147 -25
  64. package/src/components/diagnose/parts.test.tsx +482 -0
  65. package/src/components/diagnose/parts.tsx +418 -41
  66. package/src/components/diagnose/target.test.ts +19 -0
  67. package/src/components/diagnose/target.ts +3 -2
  68. package/src/components/gitops/GitOpsView.tsx +7 -0
  69. package/src/components/gitops/RemoteDestinationCloudHint.tsx +26 -0
  70. package/src/components/resource/PrometheusCharts.overlays.test.ts +22 -0
  71. package/src/components/resource/PrometheusCharts.tsx +4 -3
  72. package/src/components/resource/PrometheusChartsGrid.render.test.tsx +99 -0
  73. package/src/components/resource/PrometheusChartsGrid.tsx +245 -103
  74. package/src/components/resource/RightsizingStrip.test.ts +38 -0
  75. package/src/components/resource/RightsizingStrip.tsx +15 -3
  76. package/src/components/resource/WorkloadMetricsHelpDialog.test.tsx +79 -0
  77. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +145 -0
  78. package/src/components/resource/WorkloadMetricsSection.render.test.tsx +351 -0
  79. package/src/components/resource/WorkloadMetricsSection.tsx +486 -0
  80. package/src/components/resource/workloadMetricValues.test.ts +65 -0
  81. package/src/components/resource/workloadMetricValues.ts +30 -0
  82. package/src/components/resources/PodFilePreview.test.tsx +131 -0
  83. package/src/components/resources/PodFilePreview.tsx +394 -0
  84. package/src/components/resources/PodFilesystemModal.tsx +157 -67
  85. package/src/components/resources/ResourcesView.tsx +44 -6
  86. package/src/components/resources/renderers/NamespaceRenderer.tsx +7 -1
  87. package/src/components/resources/renderers/PodRenderer.tsx +4 -0
  88. package/src/components/resources/renderers/RolloutRenderer.tsx +15 -1
  89. package/src/components/resources/renderers/WorkloadRenderer.tsx +3 -0
  90. package/src/components/rightsizing/RightsizingScanView.tsx +21 -0
  91. package/src/components/settings/SettingsDialog.tsx +94 -14
  92. package/src/components/traffic/TrafficFlowList.tsx +229 -70
  93. package/src/components/traffic/TrafficGraph.tsx +6 -6
  94. package/src/components/traffic/TrafficView.tsx +10 -9
  95. package/src/components/traffic/trafficFilters.test.ts +28 -1
  96. package/src/components/traffic/trafficFilters.ts +31 -0
  97. package/src/components/workload/WorkloadView.tsx +19 -8
  98. package/src/context/ConnectionContext.tsx +48 -3
  99. package/src/context/DiagnoseCustomization.test.tsx +26 -0
  100. package/src/context/DiagnoseCustomization.tsx +15 -2
  101. package/src/index.css +17 -0
  102. package/src/index.ts +1 -0
  103. package/src/utils/navigation.ts +1 -1
  104. package/src/utils/shell-safe.test.ts +25 -1
  105. package/src/utils/shell-safe.ts +16 -0
  106. package/src/utils/topology-namespace.test.ts +64 -0
  107. package/src/utils/topology-namespace.ts +18 -0
  108. package/src/components/diagnose/investigationEvidence.test.ts +0 -5202
  109. package/src/components/diagnose/investigationEvidence.ts +0 -4784
  110. package/src/components/gitops/GitOpsActions.tsx +0 -1
  111. package/src/components/gitops/GitOpsStatusBadge.tsx +0 -1
  112. package/src/components/gitops/ManagedResourcesList.tsx +0 -1
  113. package/src/components/gitops/SyncCountdown.tsx +0 -1
  114. package/src/components/gitops/index.ts +0 -4
  115. package/src/components/helm/ManifestDiffViewer.tsx +0 -69
  116. package/src/components/logs/JsonLogLine.tsx +0 -1
  117. package/src/components/logs/LogCore.tsx +0 -2
  118. package/src/components/logs/useLogBuffer.ts +0 -2
  119. package/src/components/logs/useLogSearch.ts +0 -1
  120. package/src/components/resource-drawer/ResourceDrawer.tsx +0 -216
  121. package/src/components/resources/drawer-components.tsx +0 -1
  122. package/src/components/resources/renderers/AlertRenderer.tsx +0 -1
  123. package/src/components/resources/renderers/CNPGBackupRenderer.tsx +0 -1
  124. package/src/components/resources/renderers/CNPGPoolerRenderer.tsx +0 -1
  125. package/src/components/resources/renderers/CNPGScheduledBackupRenderer.tsx +0 -1
  126. package/src/components/resources/renderers/CertificateRenderer.tsx +0 -1
  127. package/src/components/resources/renderers/CertificateRequestRenderer.tsx +0 -1
  128. package/src/components/resources/renderers/ChallengeRenderer.tsx +0 -1
  129. package/src/components/resources/renderers/ClusterComplianceReportRenderer.tsx +0 -1
  130. package/src/components/resources/renderers/ClusterExternalSecretRenderer.tsx +0 -1
  131. package/src/components/resources/renderers/ClusterIssuerRenderer.tsx +0 -1
  132. package/src/components/resources/renderers/ConfigAuditReportRenderer.tsx +0 -1
  133. package/src/components/resources/renderers/ConfigMapRenderer.tsx +0 -1
  134. package/src/components/resources/renderers/CronJobRenderer.tsx +0 -1
  135. package/src/components/resources/renderers/CronWorkflowRenderer.tsx +0 -1
  136. package/src/components/resources/renderers/EventRenderer.tsx +0 -1
  137. package/src/components/resources/renderers/ExposedSecretReportRenderer.tsx +0 -1
  138. package/src/components/resources/renderers/ExternalSecretRenderer.tsx +0 -1
  139. package/src/components/resources/renderers/FluxHelmReleaseRenderer.tsx +0 -1
  140. package/src/components/resources/renderers/GRPCRouteRenderer.tsx +0 -1
  141. package/src/components/resources/renderers/GatewayClassRenderer.tsx +0 -1
  142. package/src/components/resources/renderers/GatewayRenderer.tsx +0 -1
  143. package/src/components/resources/renderers/GenericRenderer.tsx +0 -1
  144. package/src/components/resources/renderers/GitRepositoryRenderer.tsx +0 -1
  145. package/src/components/resources/renderers/HTTPRouteRenderer.tsx +0 -1
  146. package/src/components/resources/renderers/HelmRepositoryRenderer.tsx +0 -1
  147. package/src/components/resources/renderers/IngressClassRenderer.tsx +0 -1
  148. package/src/components/resources/renderers/IngressRenderer.tsx +0 -1
  149. package/src/components/resources/renderers/IstioAuthorizationPolicyRenderer.tsx +0 -1
  150. package/src/components/resources/renderers/IstioDestinationRuleRenderer.tsx +0 -1
  151. package/src/components/resources/renderers/IstioGatewayRenderer.tsx +0 -1
  152. package/src/components/resources/renderers/IstioPeerAuthenticationRenderer.tsx +0 -1
  153. package/src/components/resources/renderers/IstioServiceEntryRenderer.tsx +0 -1
  154. package/src/components/resources/renderers/IstioVirtualServiceRenderer.tsx +0 -1
  155. package/src/components/resources/renderers/JobRenderer.tsx +0 -1
  156. package/src/components/resources/renderers/KarpenterEC2NodeClassRenderer.tsx +0 -1
  157. package/src/components/resources/renderers/KarpenterNodeClaimRenderer.tsx +0 -1
  158. package/src/components/resources/renderers/KedaScaledJobRenderer.tsx +0 -1
  159. package/src/components/resources/renderers/KedaScaledObjectRenderer.tsx +0 -1
  160. package/src/components/resources/renderers/KedaTriggerAuthRenderer.tsx +0 -1
  161. package/src/components/resources/renderers/KnativeConfigurationRenderer.tsx +0 -1
  162. package/src/components/resources/renderers/KnativeEventingRenderer.tsx +0 -1
  163. package/src/components/resources/renderers/KnativeFlowRenderer.tsx +0 -1
  164. package/src/components/resources/renderers/KnativeNetworkingRenderer.tsx +0 -1
  165. package/src/components/resources/renderers/KnativeRevisionRenderer.tsx +0 -1
  166. package/src/components/resources/renderers/KnativeRouteRenderer.tsx +0 -1
  167. package/src/components/resources/renderers/KnativeServiceRenderer.tsx +0 -1
  168. package/src/components/resources/renderers/KnativeSourceRenderer.tsx +0 -1
  169. package/src/components/resources/renderers/KustomizationRenderer.tsx +0 -1
  170. package/src/components/resources/renderers/KyvernoPolicyReportRenderer.tsx +0 -1
  171. package/src/components/resources/renderers/LeaseRenderer.tsx +0 -1
  172. package/src/components/resources/renderers/NetworkPolicyRenderer.tsx +0 -1
  173. package/src/components/resources/renderers/OCIRepositoryRenderer.tsx +0 -1
  174. package/src/components/resources/renderers/OrderRenderer.tsx +0 -1
  175. package/src/components/resources/renderers/PersistentVolumeRenderer.tsx +0 -1
  176. package/src/components/resources/renderers/PodDisruptionBudgetRenderer.tsx +0 -1
  177. package/src/components/resources/renderers/PodMonitorRenderer.tsx +0 -1
  178. package/src/components/resources/renderers/PriorityClassRenderer.tsx +0 -1
  179. package/src/components/resources/renderers/PrometheusRuleRenderer.tsx +0 -1
  180. package/src/components/resources/renderers/ReplicaSetRenderer.tsx +0 -1
  181. package/src/components/resources/renderers/RuntimeClassRenderer.tsx +0 -1
  182. package/src/components/resources/renderers/SbomReportRenderer.tsx +0 -1
  183. package/src/components/resources/renderers/SealedSecretRenderer.tsx +0 -1
  184. package/src/components/resources/renderers/SecretRenderer.tsx +0 -1
  185. package/src/components/resources/renderers/SecretStoreRenderer.tsx +0 -1
  186. package/src/components/resources/renderers/ServiceMonitorRenderer.tsx +0 -1
  187. package/src/components/resources/renderers/SimpleRouteRenderer.tsx +0 -1
  188. package/src/components/resources/renderers/StorageClassRenderer.tsx +0 -1
  189. package/src/components/resources/renderers/TraefikIngressRouteRenderer.tsx +0 -1
  190. package/src/components/resources/renderers/VPARenderer.tsx +0 -1
  191. package/src/components/resources/renderers/VeleroScheduleRenderer.tsx +0 -1
  192. package/src/components/resources/renderers/VeleroVSLRenderer.tsx +0 -1
  193. package/src/components/resources/renderers/VulnerabilityReportRenderer.tsx +0 -1
  194. package/src/components/resources/renderers/WebhookConfigRenderer.tsx +0 -1
  195. package/src/components/resources/renderers/WorkflowRenderer.tsx +0 -1
  196. package/src/components/resources/renderers/WorkflowTemplateRenderer.tsx +0 -1
  197. package/src/components/resources/renderers/argo-cells.tsx +0 -1
  198. package/src/components/resources/renderers/certmanager-cells.tsx +0 -1
  199. package/src/components/resources/renderers/cnpg-cells.tsx +0 -1
  200. package/src/components/resources/renderers/eso-cells.tsx +0 -1
  201. package/src/components/resources/renderers/flux-cells.tsx +0 -1
  202. package/src/components/resources/renderers/index.ts +0 -94
  203. package/src/components/resources/renderers/istio-cells.tsx +0 -1
  204. package/src/components/resources/renderers/karpenter-cells.tsx +0 -1
  205. package/src/components/resources/renderers/keda-cells.tsx +0 -1
  206. package/src/components/resources/renderers/knative-cells.tsx +0 -1
  207. package/src/components/resources/renderers/kyverno-cells.tsx +0 -1
  208. package/src/components/resources/renderers/prometheus-cells.tsx +0 -1
  209. package/src/components/resources/renderers/traefik-cells.tsx +0 -1
  210. package/src/components/resources/renderers/trivy-cells.tsx +0 -1
  211. package/src/components/resources/renderers/trivy-shared.tsx +0 -1
  212. package/src/components/resources/renderers/velero-cells.tsx +0 -1
  213. package/src/components/resources/resource-utils-argo.ts +0 -2
  214. package/src/components/resources/resource-utils-certmanager.ts +0 -2
  215. package/src/components/resources/resource-utils-eso.ts +0 -2
  216. package/src/components/resources/resource-utils-flux.ts +0 -2
  217. package/src/components/resources/resource-utils-istio.ts +0 -2
  218. package/src/components/resources/resource-utils-karpenter.ts +0 -2
  219. package/src/components/resources/resource-utils-knative.ts +0 -2
  220. package/src/components/resources/resource-utils-kyverno.ts +0 -2
  221. package/src/components/resources/resource-utils-prometheus.ts +0 -2
  222. package/src/components/resources/resource-utils-traefik.ts +0 -1
  223. package/src/components/resources/resource-utils-trivy.ts +0 -2
  224. package/src/components/resources/resource-utils-velero.ts +0 -2
  225. package/src/components/shared/EditableYamlView.tsx +0 -24
  226. package/src/components/timeline/DiffViewer.tsx +0 -1
  227. package/src/components/traffic/index.ts +0 -3
  228. package/src/components/ui/ForceDeleteConfirmDialog.tsx +0 -1
  229. package/src/components/ui/MetricsChart.tsx +0 -1
  230. package/src/components/ui/ResourceBar.tsx +0 -1
@@ -30,6 +30,8 @@ import {
30
30
  investigationEvidenceInputsEqual,
31
31
  investigationEvidenceCoverageLimited,
32
32
  investigationEvidenceConflictsWithHealthy,
33
+ investigationHealthConflictExplainedBy,
34
+ investigationLiveCaseTurnIndex,
33
35
  investigationEndedBeforeConclusion,
34
36
  type InvestigationHistoryUnavailableState,
35
37
  investigationHistoryUnavailablePresentation,
@@ -78,6 +80,7 @@ import {
78
80
  AssessmentSources,
79
81
  ApplyDialog,
80
82
  appendThinking,
83
+ mergeStartupSignal,
81
84
  upsertTool,
82
85
  type Turn,
83
86
  } from "./parts";
@@ -88,6 +91,12 @@ import {
88
91
  projectInvestigationEvidence,
89
92
  resolveInvestigationRootCauseEvidence,
90
93
  } from "./investigationEvidence";
94
+ import {
95
+ resolveInvestigationCase,
96
+ investigationCaseItemsStillRendered,
97
+ mergeInvestigationCases,
98
+ type InvestigationCaseResolution,
99
+ } from "./investigationCase";
91
100
  import {
92
101
  InvestigationEvidencePane,
93
102
  partitionInvestigationEvidence,
@@ -477,6 +486,14 @@ export function InvestigationView({
477
486
  },
478
487
  ]);
479
488
  break;
489
+ case "phase":
490
+ // Startup phases only feed the pending status line; a replayed
491
+ // finished turn is not running, so nothing shows for it.
492
+ updateLast((t) => {
493
+ const startup = mergeStartupSignal(t.startup, ev);
494
+ return startup === t.startup ? t : { ...t, startup };
495
+ });
496
+ break;
480
497
  case "thinking":
481
498
  if (ev.token) {
482
499
  if (live) {
@@ -967,15 +984,114 @@ export function InvestigationView({
967
984
  : undefined,
968
985
  [currentAssessment, currentAssessmentIdx, projection],
969
986
  );
987
+ // The agent's case binds for every assessment, healthy and inconclusive
988
+ // included, so it is resolved independently of the root cause.
989
+ const investigationCase = useMemo(
990
+ () =>
991
+ currentAssessment?.diagnosis
992
+ ? resolveInvestigationCase(
993
+ projection,
994
+ currentAssessment.diagnosis,
995
+ currentAssessmentIdx,
996
+ )
997
+ : undefined,
998
+ [currentAssessment, currentAssessmentIdx, projection],
999
+ );
1000
+ // A follow-up answer that cites evidence takes over the pane's case; the
1001
+ // assessment's own items are then listed read-only under it.
1002
+ const liveCaseTurnIdx = investigationLiveCaseTurnIndex(
1003
+ turns,
1004
+ currentAssessmentIdx,
1005
+ );
1006
+ const liveCaseIsCurrentAssessment = liveCaseTurnIdx === currentAssessmentIdx;
1007
+ // The qualification banner and the "Used for assessment" markers describe the
1008
+ // assessment on screen, so they always read its own resolution. A later turn's
1009
+ // citations only widen the selection (below): replacing the resolution would
1010
+ // drop the assessment's qualification and push its cited evidence back into
1011
+ // the withheld set.
1012
+ const paneResolution = rootCauseEvidenceResolution;
1013
+ // The live turn's own resolution stays available for the live turn's own
1014
+ // receipt: its sources are its, not the assessment's.
1015
+ const liveTurnResolution = useMemo(() => {
1016
+ if (liveCaseIsCurrentAssessment) return rootCauseEvidenceResolution;
1017
+ const diagnosis = turns[liveCaseTurnIdx]?.diagnosis;
1018
+ return diagnosis?.rootCause
1019
+ ? resolveInvestigationRootCauseEvidence(
1020
+ projection,
1021
+ diagnosis.rootCauseEvidence,
1022
+ liveCaseTurnIdx,
1023
+ )
1024
+ : undefined;
1025
+ }, [
1026
+ liveCaseIsCurrentAssessment,
1027
+ rootCauseEvidenceResolution,
1028
+ turns,
1029
+ liveCaseTurnIdx,
1030
+ projection,
1031
+ ]);
1032
+ const followUpSelectedGroupIds = useMemo(() => {
1033
+ if (liveCaseIsCurrentAssessment) return undefined;
1034
+ if (liveTurnResolution?.status !== "linked") return undefined;
1035
+ return liveTurnResolution.links.flatMap((link) =>
1036
+ link.originalGroupId
1037
+ ? [{ groupId: link.originalGroupId, source: link.source }]
1038
+ : [],
1039
+ );
1040
+ }, [liveCaseIsCurrentAssessment, liveTurnResolution]);
1041
+ // The live turn's own case, before anything is carried onto it. Its source
1042
+ // receipt must list what IT cited, not what the merge brought along.
1043
+ const liveTurnCase = useMemo(
1044
+ () =>
1045
+ liveCaseIsCurrentAssessment
1046
+ ? investigationCase
1047
+ : resolveInvestigationCase(
1048
+ projection,
1049
+ turns[liveCaseTurnIdx]?.diagnosis,
1050
+ liveCaseTurnIdx,
1051
+ ),
1052
+ [
1053
+ liveCaseIsCurrentAssessment,
1054
+ investigationCase,
1055
+ projection,
1056
+ turns,
1057
+ liveCaseTurnIdx,
1058
+ ],
1059
+ );
1060
+ const paneCase = useMemo(() => {
1061
+ const live = liveTurnCase;
1062
+ const earlier: InvestigationCaseResolution[] = [];
1063
+ for (let i = turns.length - 1; i >= 0; i -= 1) {
1064
+ const diagnosis = turns[i]?.diagnosis;
1065
+ if (i === liveCaseTurnIdx || !diagnosis) continue;
1066
+ // Reuse the assessment's own resolution rather than resolving it a
1067
+ // second time: same projection, same result, and it keeps the items the
1068
+ // conflict banner reasons about the ones the merge received.
1069
+ earlier.push(
1070
+ i === currentAssessmentIdx && investigationCase
1071
+ ? investigationCase
1072
+ : resolveInvestigationCase(projection, diagnosis, i),
1073
+ );
1074
+ }
1075
+ return mergeInvestigationCases(live, earlier);
1076
+ }, [
1077
+ liveTurnCase,
1078
+ investigationCase,
1079
+ currentAssessmentIdx,
1080
+ projection,
1081
+ turns,
1082
+ liveCaseTurnIdx,
1083
+ ]);
970
1084
  const visibleEvidenceGroupIds = useMemo(
971
1085
  () =>
972
1086
  new Set(
973
1087
  partitionInvestigationEvidence(
974
1088
  projection.groups,
975
- rootCauseEvidenceResolution,
1089
+ paneResolution,
1090
+ paneCase,
1091
+ followUpSelectedGroupIds,
976
1092
  ).collectionByGroup.keys(),
977
1093
  ),
978
- [projection.groups, rootCauseEvidenceResolution],
1094
+ [projection.groups, paneResolution, paneCase, followUpSelectedGroupIds],
979
1095
  );
980
1096
  const evidenceStepIdsByTurn = useMemo(
981
1097
  () =>
@@ -1273,6 +1389,32 @@ export function InvestigationView({
1273
1389
  const currentAssessmentEvidenceConflict =
1274
1390
  currentAssessment?.diagnosis?.healthy === true &&
1275
1391
  investigationEvidenceConflictsWithHealthy(projection);
1392
+ // The banner qualifies THIS assessment, so only this assessment's own case
1393
+ // may reframe it. `paneCase` also carries a later answer's items and notes
1394
+ // inherited from superseded assessments; neither of those spoke about this
1395
+ // verdict, and letting them soften it would let an unrelated follow-up
1396
+ // retire a warning the reader still needs.
1397
+ const currentAssessmentEvidenceConflictExplainedBy = useMemo(() => {
1398
+ if (!currentAssessmentEvidenceConflict) return undefined;
1399
+ // Two conditions, and both are required. The note must belong to THIS
1400
+ // assessment — a later answer or a superseded assessment did not speak
1401
+ // about this verdict. And it must survive into the case the pane actually
1402
+ // renders: the merge lets a later turn take over a group, and a banner
1403
+ // that points at a note the reader cannot find is worse than no banner.
1404
+ const qualifying = investigationCaseItemsStillRendered(
1405
+ investigationCase?.items,
1406
+ paneCase?.items,
1407
+ );
1408
+ return (
1409
+ investigationHealthConflictExplainedBy(projection, qualifying) ??
1410
+ undefined
1411
+ );
1412
+ }, [
1413
+ currentAssessmentEvidenceConflict,
1414
+ projection,
1415
+ investigationCase,
1416
+ paneCase,
1417
+ ]);
1276
1418
  const hasEvidenceCollectedAfterAssessment =
1277
1419
  currentAssessmentIdx >= 0 &&
1278
1420
  projection.sources.some(
@@ -1680,6 +1822,7 @@ export function InvestigationView({
1680
1822
  <Fragment key={index}>
1681
1823
  <TurnView
1682
1824
  turn={turn}
1825
+ agentLabel={agentLabel}
1683
1826
  turnIndex={index}
1684
1827
  evidenceStepIds={evidenceStepIdsByTurn.get(index)}
1685
1828
  onViewEvidence={viewEvidenceSource}
@@ -1704,6 +1847,49 @@ export function InvestigationView({
1704
1847
  : undefined
1705
1848
  }
1706
1849
  hideConclusion={assessmentIndexes.includes(index)}
1850
+ assessmentSources={(() => {
1851
+ // Answer turns show what they cited; the live
1852
+ // one also drives Findings, earlier ones are
1853
+ // read-only.
1854
+ if (
1855
+ !turn.question ||
1856
+ turn.verify ||
1857
+ turn.apply ||
1858
+ turn.status !== "done" ||
1859
+ !turn.diagnosis
1860
+ )
1861
+ return undefined;
1862
+ const answerCase =
1863
+ index === liveCaseTurnIdx
1864
+ ? liveTurnCase
1865
+ : resolveInvestigationCase(
1866
+ projection,
1867
+ turn.diagnosis,
1868
+ index,
1869
+ );
1870
+ const answerResolution =
1871
+ index === liveCaseTurnIdx
1872
+ ? liveTurnResolution
1873
+ : undefined;
1874
+ return answerCase?.items.length ||
1875
+ answerResolution?.links.length ||
1876
+ turn.diagnosis?.unlinkedEvidence ||
1877
+ turn.diagnosis?.evidenceMalformed ? (
1878
+ <AssessmentSources
1879
+ renderedGroupIds={visibleEvidenceGroupIds}
1880
+ resolution={answerResolution}
1881
+ investigationCase={answerCase}
1882
+ unlinkedEvidence={
1883
+ turn.diagnosis?.unlinkedEvidence
1884
+ }
1885
+ evidenceMalformed={
1886
+ turn.diagnosis?.evidenceMalformed
1887
+ }
1888
+ readOnly={index !== liveCaseTurnIdx}
1889
+ onViewSource={viewActivitySource}
1890
+ />
1891
+ ) : undefined;
1892
+ })()}
1707
1893
  />
1708
1894
  {showSplitWorkspace &&
1709
1895
  index === currentAssessmentIdx &&
@@ -1942,9 +2128,24 @@ export function InvestigationView({
1942
2128
  }
1943
2129
  diagnosis={currentAssessment.diagnosis}
1944
2130
  assessmentSources={
1945
- rootCauseEvidenceResolution?.links.length ? (
2131
+ // An assessment whose every note was rejected has no
2132
+ // items and no links, and the loss is the only thing
2133
+ // there is to say about it.
2134
+ rootCauseEvidenceResolution?.links.length ||
2135
+ investigationCase?.items.length ||
2136
+ currentAssessment.diagnosis.unlinkedEvidence ||
2137
+ currentAssessment.diagnosis.evidenceMalformed ? (
1946
2138
  <AssessmentSources
2139
+ renderedGroupIds={visibleEvidenceGroupIds}
1947
2140
  resolution={rootCauseEvidenceResolution}
2141
+ investigationCase={investigationCase}
2142
+ unlinkedEvidence={
2143
+ currentAssessment.diagnosis?.unlinkedEvidence
2144
+ }
2145
+ evidenceMalformed={
2146
+ currentAssessment.diagnosis?.evidenceMalformed
2147
+ }
2148
+ readOnly={!liveCaseIsCurrentAssessment}
1948
2149
  onViewSource={viewActivitySource}
1949
2150
  />
1950
2151
  ) : undefined
@@ -1983,6 +2184,9 @@ export function InvestigationView({
1983
2184
  showDisclaimer={false}
1984
2185
  coverageLimited={currentAssessmentCoverageLimited}
1985
2186
  evidenceConflict={currentAssessmentEvidenceConflict}
2187
+ evidenceConflictExplainedBy={
2188
+ currentAssessmentEvidenceConflictExplainedBy
2189
+ }
1986
2190
  />
1987
2191
  ) : (
1988
2192
  <div className="mt-2 flex items-center gap-2 rounded-md bg-theme-surface/60 px-2.5 py-2 text-xs text-theme-text-tertiary">
@@ -2046,15 +2250,26 @@ export function InvestigationView({
2046
2250
  diagnosis: turns[index].diagnosis!,
2047
2251
  explanation: explanationFor(turns[index]),
2048
2252
  sources: (() => {
2253
+ // Each earlier assessment owns its own items; they
2254
+ // render read-only here and never annotate cards.
2049
2255
  const resolution =
2050
2256
  resolveInvestigationRootCauseEvidence(
2051
2257
  projection,
2052
2258
  turns[index].diagnosis!.rootCauseEvidence,
2053
2259
  index,
2054
2260
  );
2055
- return resolution.links.length ? (
2261
+ const earlierCase = resolveInvestigationCase(
2262
+ projection,
2263
+ turns[index].diagnosis,
2264
+ index,
2265
+ );
2266
+ return resolution.links.length ||
2267
+ earlierCase.items.length ? (
2056
2268
  <AssessmentSources
2269
+ renderedGroupIds={visibleEvidenceGroupIds}
2057
2270
  resolution={resolution}
2271
+ investigationCase={earlierCase}
2272
+ readOnly
2058
2273
  onViewSource={viewActivitySource}
2059
2274
  />
2060
2275
  ) : undefined;
@@ -2064,7 +2279,9 @@ export function InvestigationView({
2064
2279
 
2065
2280
  <InvestigationEvidencePane
2066
2281
  projection={projection}
2067
- rootCauseEvidence={rootCauseEvidenceResolution}
2282
+ rootCauseEvidence={paneResolution}
2283
+ alsoSelectedGroupIds={followUpSelectedGroupIds}
2284
+ investigationCase={paneCase}
2068
2285
  collecting={
2069
2286
  explanationRequest?.status !== "running" &&
2070
2287
  (requestPending ||
@@ -3,6 +3,8 @@
3
3
  * investigation projection. These deliberately exclude any presentation or
4
4
  * transport envelope so the live Diagnose UI is not coupled to another route.
5
5
  */
6
+ import type { HPADiagnosisView } from "@skyhook-io/k8s-ui";
7
+
6
8
  export interface DiagnosisFilteredLogs {
7
9
  lines: string[] | null;
8
10
  totalLines: number;
@@ -53,6 +55,25 @@ export interface DiagnosisResourceRef {
53
55
  name: string;
54
56
  }
55
57
 
58
+ /** The HPA diagnosis Radar computes for the scaler itself, attached to the workload it scales. */
59
+ export interface DiagnosisHPASummary extends HPADiagnosisView {
60
+ target?: DiagnosisResourceRef;
61
+ metrics?: Array<{
62
+ type: string;
63
+ name: string;
64
+ current?: string;
65
+ target?: string;
66
+ status: string;
67
+ }>;
68
+ }
69
+
70
+ export interface DiagnosisScalerRef extends DiagnosisResourceRef {
71
+ /** The KEDA ScaledObject that owns this HPA, when KEDA created it. */
72
+ managedBy?: DiagnosisResourceRef;
73
+ /** Present only for an HPA the caller may read; KEDA scalers carry the ref alone. */
74
+ hpaSummary?: DiagnosisHPASummary;
75
+ }
76
+
56
77
  export interface DiagnosisResourceContext {
57
78
  tier: "basic" | "diagnostic";
58
79
  owner?: DiagnosisResourceRef;
@@ -71,7 +92,7 @@ export interface DiagnosisResourceContext {
71
92
  pvcs?: DiagnosisResourceRef[];
72
93
  };
73
94
  runsOn?: DiagnosisResourceRef;
74
- scaledBy?: DiagnosisResourceRef[];
95
+ scaledBy?: DiagnosisScalerRef[];
75
96
  statusSummary?: {
76
97
  phase?: string;
77
98
  conditions?: Array<{