@skyhook-io/radar-app 1.14.0 → 1.14.2
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 +2 -2
- package/src/App.tsx +6 -4
- package/src/api/client.ts +3 -0
- package/src/api/diagnose.ts +31 -1
- package/src/components/CloudConnectFlow.tsx +173 -13
- package/src/components/CloudFunnelButton.tsx +4 -2
- package/src/components/ConnectionErrorView.tsx +9 -10
- package/src/components/DebugOverlay.tsx +10 -6
- package/src/components/cloudConnectHandoff.test.ts +109 -4
- package/src/components/cloudConnectHandoff.ts +154 -2
- package/src/components/curl/ServiceCurlButton.tsx +19 -26
- package/src/components/diagnose/AgentCase.tsx +18 -5
- package/src/components/diagnose/AnalysisStory.test.tsx +308 -0
- package/src/components/diagnose/AnalysisStory.tsx +564 -0
- package/src/components/diagnose/DiagnoseContext.test.ts +10 -0
- package/src/components/diagnose/DiagnoseContext.tsx +25 -8
- package/src/components/diagnose/DiagnoseSurface.tsx +20 -14
- package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +771 -0
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +8 -33
- package/src/components/diagnose/InvestigationEvidencePane.tsx +809 -168
- package/src/components/diagnose/InvestigationResourceEvidence.test.tsx +30 -0
- package/src/components/diagnose/InvestigationResourceEvidence.tsx +20 -0
- package/src/components/diagnose/InvestigationView.tsx +493 -529
- package/src/components/diagnose/investigationCase.test.tsx +267 -129
- package/src/components/diagnose/investigationCase.ts +122 -77
- package/src/components/diagnose/investigationEvidence/adapters/resource.test.ts +27 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +28 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +11 -2
- package/src/components/diagnose/investigationEvidence/identity.test.ts +25 -7
- package/src/components/diagnose/investigationEvidence/identity.ts +4 -4
- package/src/components/diagnose/investigationEvidence/observations.ts +24 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +36 -3
- package/src/components/diagnose/investigationEvidence/types.ts +2 -0
- package/src/components/diagnose/investigationEvidencePresentation.test.ts +2 -0
- package/src/components/diagnose/investigationEvidencePresentation.ts +3 -0
- package/src/components/diagnose/investigationState.test.ts +316 -58
- package/src/components/diagnose/investigationState.ts +257 -44
- package/src/components/diagnose/investigationStory.test.ts +161 -0
- package/src/components/diagnose/investigationStory.ts +207 -0
- package/src/components/diagnose/parts.test.tsx +9 -6
- package/src/components/diagnose/parts.tsx +977 -193
- package/src/components/diagnose/storyParts.test.tsx +426 -0
- package/src/components/diagnose/toolCallLabel.test.ts +51 -0
- package/src/components/diagnose/toolCallLabel.ts +135 -0
- package/src/components/diagnose/useDisclosureReveal.ts +10 -11
- package/src/components/helm/HelmCompareRoute.tsx +18 -4
- package/src/components/helm/HelmReleaseDrawer.tsx +11 -26
- package/src/components/helm/InstallWizard.tsx +8 -3
- package/src/components/home/MCPSetupDialog.tsx +15 -9
- package/src/components/portforward/PortForwardManager.tsx +6 -13
- package/src/components/resource/PrometheusChartsGrid.tsx +3 -3
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +3 -3
- package/src/components/resource/WorkloadMetricsSection.tsx +15 -21
- package/src/components/resources/PodFilePreview.tsx +3 -3
- package/src/components/resources/renderers/ServiceRenderer.tsx +2 -1
- package/src/components/settings/SettingsDialog.tsx +4 -2
- package/src/components/ui/CommandPalette.tsx +10 -6
- package/src/components/ui/DiagnosticsOverlay.tsx +10 -6
- package/src/components/ui/Disclosure.tsx +47 -0
- package/src/components/ui/Markdown.tsx +23 -11
- package/src/components/ui/ShortcutHelpOverlay.tsx +3 -1
- package/src/components/ui/UpdateNotification.tsx +18 -2
- package/src/index.css +19 -6
|
@@ -18,9 +18,6 @@ import {
|
|
|
18
18
|
import {
|
|
19
19
|
resolveInvestigationCase,
|
|
20
20
|
type InvestigationCaseResolution,
|
|
21
|
-
investigationCaseItemsStillRendered,
|
|
22
|
-
mergeInvestigationCases,
|
|
23
|
-
type InvestigationCaseItem,
|
|
24
21
|
} from "./investigationCase";
|
|
25
22
|
import { AssessmentSources, ResultCard, assessmentSourceRows } from "./parts";
|
|
26
23
|
import type { Diagnosis, DiagnosisEvidenceItem } from "../../api/diagnose";
|
|
@@ -145,6 +142,7 @@ const diagnoseBundle = {
|
|
|
145
142
|
},
|
|
146
143
|
},
|
|
147
144
|
pods: 1,
|
|
145
|
+
podNames: ["api-abc"],
|
|
148
146
|
relatedIssues: [criticalIssue],
|
|
149
147
|
logsCurrent: [
|
|
150
148
|
{ pod: "api-abc", container: "api", logs: logs(["ERROR auth failed"]) },
|
|
@@ -188,6 +186,263 @@ describe("agent case placement (D-1, D-1b)", () => {
|
|
|
188
186
|
expect(html).not.toContain("data-agent-claim");
|
|
189
187
|
});
|
|
190
188
|
|
|
189
|
+
it("forgives a group on a core kind and a named entry on a listing", () => {
|
|
190
|
+
const listRef = evidenceRef("c", "d");
|
|
191
|
+
const withListing = project(
|
|
192
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
193
|
+
tool(
|
|
194
|
+
"cms",
|
|
195
|
+
"list_resources",
|
|
196
|
+
[{ kind: "ConfigMap", name: "kube-root-ca.crt" }],
|
|
197
|
+
{
|
|
198
|
+
evidenceRef: listRef,
|
|
199
|
+
summary: JSON.stringify({ kind: "configmaps", namespace: "shop" }),
|
|
200
|
+
},
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
const resolved = resolveInvestigationCase(
|
|
204
|
+
withListing,
|
|
205
|
+
{
|
|
206
|
+
evidence: [
|
|
207
|
+
linked(ref, "cause", "The previous log names the error.", {
|
|
208
|
+
group: "apps",
|
|
209
|
+
kind: "Pod",
|
|
210
|
+
namespace: "shop",
|
|
211
|
+
name: "api-abc",
|
|
212
|
+
container: "api",
|
|
213
|
+
stream: "previous",
|
|
214
|
+
observation: "logs",
|
|
215
|
+
}),
|
|
216
|
+
linked(listRef, "context", "Only the CA bundle exists.", {
|
|
217
|
+
kind: "ConfigMap",
|
|
218
|
+
namespace: "shop",
|
|
219
|
+
name: "kube-root-ca.crt",
|
|
220
|
+
observation: "resource",
|
|
221
|
+
}),
|
|
222
|
+
linked(listRef, "rules_out", "No nginx config lives here.", {
|
|
223
|
+
kind: "ConfigMap",
|
|
224
|
+
namespace: "shop",
|
|
225
|
+
observation: "resource",
|
|
226
|
+
}),
|
|
227
|
+
linked(ref, "symptom", "BackOff keeps firing on the pod.", {
|
|
228
|
+
kind: "Pod",
|
|
229
|
+
namespace: "shop",
|
|
230
|
+
name: "api-abc",
|
|
231
|
+
observation: "events",
|
|
232
|
+
}),
|
|
233
|
+
],
|
|
234
|
+
},
|
|
235
|
+
0,
|
|
236
|
+
);
|
|
237
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
238
|
+
"card",
|
|
239
|
+
"card",
|
|
240
|
+
"card",
|
|
241
|
+
"card",
|
|
242
|
+
]);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("binds a Pod subject to events only for the target's own pod or a pod an event names whole", () => {
|
|
246
|
+
const workerRef = evidenceRef("e", "f");
|
|
247
|
+
const canaryBundle = {
|
|
248
|
+
...diagnoseBundle,
|
|
249
|
+
events: [
|
|
250
|
+
{
|
|
251
|
+
reason: "BackOff",
|
|
252
|
+
message: "Back-off restarting failed container in pod api-abc-canary",
|
|
253
|
+
type: "Warning",
|
|
254
|
+
count: 4,
|
|
255
|
+
lastTimestamp: "2026-09-02T10:00:00Z",
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
};
|
|
259
|
+
const withWorker = project(
|
|
260
|
+
tool("diag", "diagnose", canaryBundle, { evidenceRef: ref }),
|
|
261
|
+
tool(
|
|
262
|
+
"ev",
|
|
263
|
+
"get_events",
|
|
264
|
+
{ events: [] },
|
|
265
|
+
{
|
|
266
|
+
evidenceRef: workerRef,
|
|
267
|
+
summary: JSON.stringify({
|
|
268
|
+
kind: "Deployment",
|
|
269
|
+
namespace: "shop",
|
|
270
|
+
name: "worker",
|
|
271
|
+
}),
|
|
272
|
+
},
|
|
273
|
+
),
|
|
274
|
+
);
|
|
275
|
+
const podEvents = (name: string, cite = ref) =>
|
|
276
|
+
linked(cite, "symptom", `${name} keeps backing off.`, {
|
|
277
|
+
kind: "Pod",
|
|
278
|
+
namespace: "shop",
|
|
279
|
+
name,
|
|
280
|
+
observation: "events",
|
|
281
|
+
});
|
|
282
|
+
const resolved = resolveInvestigationCase(
|
|
283
|
+
withWorker,
|
|
284
|
+
{
|
|
285
|
+
evidence: [
|
|
286
|
+
podEvents("api-abc"),
|
|
287
|
+
podEvents("api-abc-canary"),
|
|
288
|
+
podEvents("abc-canary"),
|
|
289
|
+
podEvents("api-zzz"),
|
|
290
|
+
podEvents("api-abc", workerRef),
|
|
291
|
+
],
|
|
292
|
+
},
|
|
293
|
+
0,
|
|
294
|
+
);
|
|
295
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
296
|
+
"card",
|
|
297
|
+
"card",
|
|
298
|
+
"source",
|
|
299
|
+
"source",
|
|
300
|
+
"source",
|
|
301
|
+
]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("binds an alert citation to the rule it names when one result holds several rules", () => {
|
|
305
|
+
const rulesRef = evidenceRef("i", "j");
|
|
306
|
+
const rule = (name: string) => ({
|
|
307
|
+
group: "kubernetes-apps",
|
|
308
|
+
type: "alerting",
|
|
309
|
+
name,
|
|
310
|
+
query: "vector(1)",
|
|
311
|
+
state: "firing",
|
|
312
|
+
health: "ok",
|
|
313
|
+
labels: { severity: "warning" },
|
|
314
|
+
annotations: { summary: name },
|
|
315
|
+
alerts: [
|
|
316
|
+
{
|
|
317
|
+
state: "firing",
|
|
318
|
+
activeAt: "2026-09-07T07:58:00Z",
|
|
319
|
+
value: "1e+00",
|
|
320
|
+
labels: {
|
|
321
|
+
alertname: name,
|
|
322
|
+
namespace: "shop",
|
|
323
|
+
pod: "api-abc",
|
|
324
|
+
container: "api",
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
});
|
|
329
|
+
const withRules = project(
|
|
330
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
331
|
+
tool(
|
|
332
|
+
"rules",
|
|
333
|
+
"get_prometheus_rules",
|
|
334
|
+
{
|
|
335
|
+
count: 2,
|
|
336
|
+
rules: [
|
|
337
|
+
rule("KubePodCrashLooping"),
|
|
338
|
+
rule("KubeDeploymentReplicasMismatch"),
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
{ evidenceRef: rulesRef, summary: JSON.stringify({ state: "firing" }) },
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
const cite = (name?: string) =>
|
|
345
|
+
linked(rulesRef, "symptom", "The rule is firing for this pod.", {
|
|
346
|
+
kind: "PrometheusRule",
|
|
347
|
+
namespace: "shop",
|
|
348
|
+
...(name ? { name } : {}),
|
|
349
|
+
observation: "alerts",
|
|
350
|
+
});
|
|
351
|
+
const resolved = resolveInvestigationCase(
|
|
352
|
+
withRules,
|
|
353
|
+
{
|
|
354
|
+
evidence: [
|
|
355
|
+
cite("KubeDeploymentReplicasMismatch"),
|
|
356
|
+
cite("KubePodCrashLooping"),
|
|
357
|
+
cite(),
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
0,
|
|
361
|
+
);
|
|
362
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
363
|
+
"card",
|
|
364
|
+
"card",
|
|
365
|
+
"source",
|
|
366
|
+
]);
|
|
367
|
+
expect(resolved.items[0].observation?.title).toContain(
|
|
368
|
+
"KubeDeploymentReplicasMismatch",
|
|
369
|
+
);
|
|
370
|
+
const wrongCase = resolveInvestigationCase(
|
|
371
|
+
withRules,
|
|
372
|
+
{ evidence: [cite("kubepodcrashlooping")] },
|
|
373
|
+
0,
|
|
374
|
+
);
|
|
375
|
+
expect(wrongCase.items.map((item) => item.placement)).toEqual(["source"]);
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
it("still binds a named entry the listing does not hold to the listing", () => {
|
|
379
|
+
const listRef = evidenceRef("c", "d");
|
|
380
|
+
const withListing = project(
|
|
381
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
382
|
+
tool(
|
|
383
|
+
"cms",
|
|
384
|
+
"list_resources",
|
|
385
|
+
[{ kind: "ConfigMap", name: "kube-root-ca.crt" }],
|
|
386
|
+
{
|
|
387
|
+
evidenceRef: listRef,
|
|
388
|
+
summary: JSON.stringify({ kind: "configmaps", namespace: "shop" }),
|
|
389
|
+
},
|
|
390
|
+
),
|
|
391
|
+
);
|
|
392
|
+
const resolved = resolveInvestigationCase(
|
|
393
|
+
withListing,
|
|
394
|
+
{
|
|
395
|
+
evidence: [
|
|
396
|
+
linked(listRef, "context", "The nginx config is mounted from here.", {
|
|
397
|
+
kind: "ConfigMap",
|
|
398
|
+
namespace: "shop",
|
|
399
|
+
name: "nginx-config",
|
|
400
|
+
observation: "resource",
|
|
401
|
+
}),
|
|
402
|
+
],
|
|
403
|
+
},
|
|
404
|
+
0,
|
|
405
|
+
);
|
|
406
|
+
expect(resolved.items.map((item) => item.placement)).toEqual(["card"]);
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("places a Namespace citation on a namespace listing, whose call names no kind", () => {
|
|
410
|
+
const nsRef = evidenceRef("g", "h");
|
|
411
|
+
const withNamespaces = project(
|
|
412
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
413
|
+
tool(
|
|
414
|
+
"ns",
|
|
415
|
+
"list_namespaces",
|
|
416
|
+
[
|
|
417
|
+
{ name: "shop", status: "Active" },
|
|
418
|
+
{ name: "capi-system", status: "Terminating" },
|
|
419
|
+
],
|
|
420
|
+
{ evidenceRef: nsRef, summary: "{}" },
|
|
421
|
+
),
|
|
422
|
+
);
|
|
423
|
+
const resolved = resolveInvestigationCase(
|
|
424
|
+
withNamespaces,
|
|
425
|
+
{
|
|
426
|
+
evidence: [
|
|
427
|
+
linked(nsRef, "context", "Only two namespaces exist.", {
|
|
428
|
+
kind: "Namespace",
|
|
429
|
+
observation: "resource",
|
|
430
|
+
}),
|
|
431
|
+
linked(nsRef, "rules_out", "shop is not terminating.", {
|
|
432
|
+
kind: "Namespace",
|
|
433
|
+
name: "shop",
|
|
434
|
+
observation: "resource",
|
|
435
|
+
}),
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
0,
|
|
439
|
+
);
|
|
440
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
441
|
+
"card",
|
|
442
|
+
"card",
|
|
443
|
+
]);
|
|
444
|
+
});
|
|
445
|
+
|
|
191
446
|
it("pins a subject-bearing item to exactly the observation it names", () => {
|
|
192
447
|
const resolved = resolveInvestigationCase(
|
|
193
448
|
projection,
|
|
@@ -210,6 +465,11 @@ describe("agent case placement (D-1, D-1b)", () => {
|
|
|
210
465
|
name: "api",
|
|
211
466
|
observation: "events",
|
|
212
467
|
}),
|
|
468
|
+
linked(ref, "context", "Namespace-named events land too.", {
|
|
469
|
+
kind: "Namespace",
|
|
470
|
+
name: "shop",
|
|
471
|
+
observation: "events",
|
|
472
|
+
}),
|
|
213
473
|
linked(
|
|
214
474
|
ref,
|
|
215
475
|
"context",
|
|
@@ -240,6 +500,7 @@ describe("agent case placement (D-1, D-1b)", () => {
|
|
|
240
500
|
kind: "resource",
|
|
241
501
|
},
|
|
242
502
|
"Events only repeat the back-off.": { placement: "card", kind: "events" },
|
|
503
|
+
"Namespace-named events land too.": { placement: "card", kind: "events" },
|
|
243
504
|
"Ambiguous: two observations share this subject.": {
|
|
244
505
|
placement: "source",
|
|
245
506
|
kind: undefined,
|
|
@@ -1069,130 +1330,6 @@ describe("follow-up answers that cite evidence", () => {
|
|
|
1069
1330
|
});
|
|
1070
1331
|
});
|
|
1071
1332
|
|
|
1072
|
-
describe("carrying earlier assessments' card notes", () => {
|
|
1073
|
-
const item = (
|
|
1074
|
-
index: number,
|
|
1075
|
-
groupId: string | undefined,
|
|
1076
|
-
placement: "card" | "revision" | "source",
|
|
1077
|
-
role: "cause" | "context" | "demoted" = "context",
|
|
1078
|
-
) =>
|
|
1079
|
-
({
|
|
1080
|
-
index,
|
|
1081
|
-
role,
|
|
1082
|
-
claim: `claim ${index}`,
|
|
1083
|
-
source: { id: `s${index}` },
|
|
1084
|
-
placement,
|
|
1085
|
-
groupId,
|
|
1086
|
-
}) as unknown as InvestigationCaseItem;
|
|
1087
|
-
|
|
1088
|
-
it("keeps an earlier turn's note on a card the live turn did not address", () => {
|
|
1089
|
-
const live = {
|
|
1090
|
-
items: [item(0, "chart", "card")],
|
|
1091
|
-
ruledOut: [],
|
|
1092
|
-
} as unknown as InvestigationCaseResolution;
|
|
1093
|
-
const first = {
|
|
1094
|
-
items: [
|
|
1095
|
-
item(0, "logs", "card", "demoted"),
|
|
1096
|
-
item(1, "chart", "card", "cause"),
|
|
1097
|
-
item(2, undefined, "source"),
|
|
1098
|
-
item(3, "deploy", "revision"),
|
|
1099
|
-
],
|
|
1100
|
-
ruledOut: [{ hypothesis: "x" }],
|
|
1101
|
-
} as unknown as InvestigationCaseResolution;
|
|
1102
|
-
const merged = mergeInvestigationCases(live, [first]);
|
|
1103
|
-
expect(merged?.items.map((entry) => [entry.groupId, entry.role])).toEqual([
|
|
1104
|
-
["chart", "context"],
|
|
1105
|
-
["logs", "demoted"],
|
|
1106
|
-
["deploy", "context"],
|
|
1107
|
-
]);
|
|
1108
|
-
expect(merged?.ruledOut).toEqual([]);
|
|
1109
|
-
});
|
|
1110
|
-
|
|
1111
|
-
it("lets the newer earlier turn win a card and leaves a lone live case untouched", () => {
|
|
1112
|
-
const live = {
|
|
1113
|
-
items: [],
|
|
1114
|
-
ruledOut: [],
|
|
1115
|
-
} as unknown as InvestigationCaseResolution;
|
|
1116
|
-
const newer = {
|
|
1117
|
-
items: [item(0, "logs", "card", "context")],
|
|
1118
|
-
ruledOut: [],
|
|
1119
|
-
} as unknown as InvestigationCaseResolution;
|
|
1120
|
-
const older = {
|
|
1121
|
-
items: [item(0, "logs", "card", "demoted"), item(1, "pod", "card")],
|
|
1122
|
-
ruledOut: [],
|
|
1123
|
-
} as unknown as InvestigationCaseResolution;
|
|
1124
|
-
const merged = mergeInvestigationCases(live, [newer, older]);
|
|
1125
|
-
expect(merged?.items.map((entry) => [entry.groupId, entry.role])).toEqual([
|
|
1126
|
-
["logs", "context"],
|
|
1127
|
-
["pod", "context"],
|
|
1128
|
-
]);
|
|
1129
|
-
expect(mergeInvestigationCases(live, [])).toBe(live);
|
|
1130
|
-
expect(mergeInvestigationCases(undefined, [])).toBeUndefined();
|
|
1131
|
-
});
|
|
1132
|
-
|
|
1133
|
-
it("recognises its own surviving notes across a re-resolved merge", () => {
|
|
1134
|
-
// The pane feeds the merge freshly resolved copies of every turn, so the
|
|
1135
|
-
// assessment's own items arrive equal but not identical. An identity test
|
|
1136
|
-
// reported them as gone, which silently retired the conflict banner's
|
|
1137
|
-
// reframing whenever any unrelated follow-up arrived.
|
|
1138
|
-
const assessmentItems = [
|
|
1139
|
-
item(0, "logs", "card", "demoted"),
|
|
1140
|
-
item(1, "crash", "card", "demoted"),
|
|
1141
|
-
];
|
|
1142
|
-
const reResolved = assessmentItems.map(
|
|
1143
|
-
(entry) => ({ ...entry }) as InvestigationCaseItem,
|
|
1144
|
-
);
|
|
1145
|
-
expect(reResolved[0]).not.toBe(assessmentItems[0]);
|
|
1146
|
-
expect(
|
|
1147
|
-
investigationCaseItemsStillRendered(assessmentItems, reResolved).map(
|
|
1148
|
-
(entry) => entry.groupId,
|
|
1149
|
-
),
|
|
1150
|
-
).toEqual(["logs", "crash"]);
|
|
1151
|
-
// A note the merge dropped is correctly reported as gone.
|
|
1152
|
-
expect(
|
|
1153
|
-
investigationCaseItemsStillRendered(assessmentItems, [reResolved[0]]).map(
|
|
1154
|
-
(entry) => entry.groupId,
|
|
1155
|
-
),
|
|
1156
|
-
).toEqual(["logs"]);
|
|
1157
|
-
expect(investigationCaseItemsStillRendered(assessmentItems, [])).toEqual(
|
|
1158
|
-
[],
|
|
1159
|
-
);
|
|
1160
|
-
});
|
|
1161
|
-
|
|
1162
|
-
it("carries every note the winning assessment left on a card, not just the first", () => {
|
|
1163
|
-
// A card note and a note pinned to a superseded read are two readings of
|
|
1164
|
-
// one group by the same assessment. Taking coverage per item dropped the
|
|
1165
|
-
// second, so a visible Cause note could vanish behind a revision note.
|
|
1166
|
-
const live = {
|
|
1167
|
-
items: [item(0, "other", "card", "context")],
|
|
1168
|
-
ruledOut: [],
|
|
1169
|
-
} as unknown as InvestigationCaseResolution;
|
|
1170
|
-
const winner = {
|
|
1171
|
-
items: [
|
|
1172
|
-
item(1, "logs", "revision", "context"),
|
|
1173
|
-
item(2, "logs", "card", "cause"),
|
|
1174
|
-
],
|
|
1175
|
-
ruledOut: [],
|
|
1176
|
-
} as unknown as InvestigationCaseResolution;
|
|
1177
|
-
const older = {
|
|
1178
|
-
items: [item(3, "logs", "card", "demoted")],
|
|
1179
|
-
ruledOut: [],
|
|
1180
|
-
} as unknown as InvestigationCaseResolution;
|
|
1181
|
-
const merged = mergeInvestigationCases(live, [winner, older]);
|
|
1182
|
-
expect(
|
|
1183
|
-
merged?.items.map((entry) => [
|
|
1184
|
-
entry.groupId,
|
|
1185
|
-
entry.placement,
|
|
1186
|
-
entry.role,
|
|
1187
|
-
]),
|
|
1188
|
-
).toEqual([
|
|
1189
|
-
["other", "card", "context"],
|
|
1190
|
-
["logs", "revision", "context"],
|
|
1191
|
-
["logs", "card", "cause"],
|
|
1192
|
-
]);
|
|
1193
|
-
});
|
|
1194
|
-
});
|
|
1195
|
-
|
|
1196
1333
|
describe("agent case robustness", () => {
|
|
1197
1334
|
const ref = evidenceRef("a", "b");
|
|
1198
1335
|
|
|
@@ -1294,7 +1431,7 @@ describe("agent case robustness", () => {
|
|
|
1294
1431
|
);
|
|
1295
1432
|
});
|
|
1296
1433
|
|
|
1297
|
-
it("keeps valid items when siblings are unlinked
|
|
1434
|
+
it("keeps valid items when siblings are unlinked or malformed, and places an earlier turn's read", () => {
|
|
1298
1435
|
const stale = evidenceRef("a", "z");
|
|
1299
1436
|
const projection = projectInvestigationEvidence(
|
|
1300
1437
|
[
|
|
@@ -1346,6 +1483,7 @@ describe("agent case robustness", () => {
|
|
|
1346
1483
|
);
|
|
1347
1484
|
expect(resolved.items.map((item) => [item.index, item.placement])).toEqual([
|
|
1348
1485
|
[1, "card"],
|
|
1486
|
+
[4, "revision"],
|
|
1349
1487
|
[5, "source"],
|
|
1350
1488
|
]);
|
|
1351
1489
|
expect(resolved.ruledOut.map((entry) => entry.hypothesis)).toEqual([
|
|
@@ -1553,7 +1691,7 @@ describe("the agent's contribution is one attributed row", () => {
|
|
|
1553
1691
|
|
|
1554
1692
|
it("labels the role that says an adverse result is not a live problem", () => {
|
|
1555
1693
|
const html = renderToStaticMarkup(<AgentRoleChip role="benign" />);
|
|
1556
|
-
expect(html).toContain("Not a problem");
|
|
1694
|
+
expect(html).toContain("Not a live problem");
|
|
1557
1695
|
});
|
|
1558
1696
|
|
|
1559
1697
|
it("still shows an attributed role when the agent left no sentence", () => {
|