@skyhook-io/radar-app 1.14.2 → 1.14.4
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/api/client.ts +32 -0
- package/src/api/diagnose.ts +7 -1
- package/src/components/CloudConnectFlow.tsx +21 -7
- package/src/components/CloudFunnelButton.tsx +85 -8
- package/src/components/cloudConnectHandoff.test.ts +10 -1
- package/src/components/cloudConnectHandoff.ts +15 -1
- package/src/components/diagnose/AIMarkdown.tsx +87 -0
- package/src/components/diagnose/ActivityTurn.tsx +1244 -0
- package/src/components/diagnose/AgentControls.tsx +603 -0
- package/src/components/diagnose/AnalysisStory.test.tsx +31 -25
- package/src/components/diagnose/AnalysisStory.tsx +13 -18
- package/src/components/diagnose/ApplyDialog.tsx +289 -0
- package/src/components/diagnose/AssessmentCard.tsx +1579 -0
- package/src/components/diagnose/DiagnoseSurface.test.tsx +9 -9
- package/src/components/diagnose/DiagnoseSurface.tsx +5 -1
- package/src/components/diagnose/EvidenceCard.tsx +611 -0
- package/src/components/diagnose/Home.test.tsx +1 -3
- package/src/components/diagnose/Home.tsx +2 -1
- package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +6 -8
- package/src/components/diagnose/InvestigationEvidencePane.test.tsx +5 -3
- package/src/components/diagnose/InvestigationEvidencePane.tsx +31 -3445
- package/src/components/diagnose/InvestigationEvidenceSections.tsx +523 -0
- package/src/components/diagnose/InvestigationView.tsx +39 -51
- package/src/components/diagnose/StoryExcerpt.tsx +118 -0
- package/src/components/diagnose/assessmentCopy.ts +169 -0
- package/src/components/diagnose/investigationCase.test.tsx +658 -2
- package/src/components/diagnose/investigationCase.ts +135 -26
- package/src/components/diagnose/investigationEvidence/adapters/coverage.test.tsx +682 -0
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.test.ts +8 -2
- package/src/components/diagnose/investigationEvidence/adapters/diagnose.ts +1 -1
- package/src/components/diagnose/investigationEvidence/adapters/{helm.test.ts → helm.test.tsx} +63 -0
- package/src/components/diagnose/investigationEvidence/adapters/helm.ts +70 -1
- package/src/components/diagnose/investigationEvidence/adapters/listings.ts +136 -0
- package/src/components/diagnose/investigationEvidence/adapters/{permissions.test.ts → permissions.test.tsx} +50 -0
- package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +18 -0
- package/src/components/diagnose/investigationEvidence/adapters/posture.ts +272 -0
- package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +49 -0
- package/src/components/diagnose/investigationEvidence/adapters/rankings.ts +147 -0
- package/src/components/diagnose/investigationEvidence/adapters/resource.ts +20 -10
- package/src/components/diagnose/investigationEvidence/bodies/index.tsx +127 -0
- package/src/components/diagnose/investigationEvidence/bodies/inventory.tsx +158 -0
- package/src/components/diagnose/investigationEvidence/bodies/metrics.tsx +373 -0
- package/src/components/diagnose/investigationEvidence/bodies/network.tsx +274 -0
- package/src/components/diagnose/investigationEvidence/bodies/platform.tsx +475 -0
- package/src/components/diagnose/investigationEvidence/bodies/posture.tsx +60 -0
- package/src/components/diagnose/investigationEvidence/bodies/ranking.tsx +76 -0
- package/src/components/diagnose/investigationEvidence/bodies/streams.tsx +212 -0
- package/src/components/diagnose/investigationEvidence/bodies/workload.tsx +335 -0
- package/src/components/diagnose/investigationEvidence/builder.ts +15 -0
- package/src/components/diagnose/investigationEvidence/cardParts.tsx +312 -0
- package/src/components/diagnose/investigationEvidence/excerpt.test.ts +105 -0
- package/src/components/diagnose/investigationEvidence/excerpt.ts +92 -0
- package/src/components/diagnose/investigationEvidence/identity.test.ts +5 -7
- package/src/components/diagnose/investigationEvidence/index.ts +1 -1
- package/src/components/diagnose/investigationEvidence/navigation.tsx +26 -0
- package/src/components/diagnose/investigationEvidence/observations.ts +13 -0
- package/src/components/diagnose/investigationEvidence/projection.test.ts +1 -1
- package/src/components/diagnose/investigationEvidence/types.ts +97 -2
- package/src/components/diagnose/investigationEvidenceKinds.ts +22 -0
- package/src/components/diagnose/investigationEvidencePartition.ts +296 -0
- package/src/components/diagnose/investigationState.test.ts +2 -2
- package/src/components/diagnose/investigationState.ts +2 -2
- package/src/components/diagnose/investigationStory.ts +1 -1
- package/src/components/diagnose/parts.test.tsx +15 -1
- package/src/components/diagnose/parts.tsx +26 -3904
- package/src/components/diagnose/target.ts +9 -3
- package/src/components/diagnose/toolCallLabel.test.ts +47 -1
- package/src/components/diagnose/toolCallLabel.ts +41 -2
- package/src/components/helm/HelmReleaseDrawer.tsx +1 -1
- package/src/components/home/MCPSetupDialog.tsx +1 -1
- package/src/components/home/mcpToolCatalog.ts +27 -0
- package/src/components/resource/PrometheusChartsGrid.tsx +1 -1
- package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
- package/src/components/resource/WorkloadMetricsSection.tsx +1 -1
- package/src/components/resources/PodFilePreview.tsx +1 -1
- package/src/components/rightsizing/RightsizingScanView.tsx +20 -1
- package/src/components/ui/CommandPalette.tsx +12 -8
- package/src/components/ui/DiagnosticsOverlay.tsx +7 -5
- package/src/components/ui/Omnibar.tsx +96 -60
- package/src/components/ui/ShortcutHelpOverlay.tsx +8 -5
- package/src/components/workload/WorkloadView.tsx +7 -1
- package/src/components/ui/Disclosure.tsx +0 -47
|
@@ -2,12 +2,12 @@ import { renderToStaticMarkup } from "react-dom/server";
|
|
|
2
2
|
import { AgentClaimNote, AgentRoleChip } from "./AgentCase";
|
|
3
3
|
import { describe, expect, it, vi } from "vitest";
|
|
4
4
|
|
|
5
|
+
import { InvestigationEvidencePane } from "./InvestigationEvidencePane";
|
|
5
6
|
import {
|
|
6
|
-
InvestigationEvidencePane,
|
|
7
7
|
investigationCaseByGroup,
|
|
8
8
|
investigationEvidenceShouldRevealHistory,
|
|
9
9
|
partitionInvestigationEvidence,
|
|
10
|
-
} from "./
|
|
10
|
+
} from "./investigationEvidencePartition";
|
|
11
11
|
import {
|
|
12
12
|
projectInvestigationEvidence,
|
|
13
13
|
resolveInvestigationRootCauseEvidence,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type InvestigationCaseResolution,
|
|
21
21
|
} from "./investigationCase";
|
|
22
22
|
import { AssessmentSources, ResultCard, assessmentSourceRows } from "./parts";
|
|
23
|
+
import { namedInventoryRows } from "./investigationEvidence/bodies/inventory";
|
|
23
24
|
import type { Diagnosis, DiagnosisEvidenceItem } from "../../api/diagnose";
|
|
24
25
|
|
|
25
26
|
const onViewSource = vi.fn();
|
|
@@ -242,6 +243,634 @@ describe("agent case placement (D-1, D-1b)", () => {
|
|
|
242
243
|
]);
|
|
243
244
|
});
|
|
244
245
|
|
|
246
|
+
it("places a resource-shaped result cited as 'resource': a Helm release, a permissions check, a neighborhood, a packages entry", () => {
|
|
247
|
+
const helmRef = evidenceRef("h", "a");
|
|
248
|
+
const permRef = evidenceRef("p", "b");
|
|
249
|
+
const graphRef = evidenceRef("g", "c");
|
|
250
|
+
const pkgRef = evidenceRef("k", "d");
|
|
251
|
+
const projection = project(
|
|
252
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
253
|
+
tool(
|
|
254
|
+
"helm",
|
|
255
|
+
"get_helm_release",
|
|
256
|
+
{
|
|
257
|
+
name: "shop",
|
|
258
|
+
namespace: "shop",
|
|
259
|
+
chart: "shop",
|
|
260
|
+
chartVersion: "1.4.2",
|
|
261
|
+
status: "deployed",
|
|
262
|
+
revision: 7,
|
|
263
|
+
updated: "2026-09-07T07:00:00Z",
|
|
264
|
+
resources: [
|
|
265
|
+
{
|
|
266
|
+
kind: "Deployment",
|
|
267
|
+
apiVersion: "apps/v1",
|
|
268
|
+
name: "api",
|
|
269
|
+
namespace: "shop",
|
|
270
|
+
},
|
|
271
|
+
],
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
evidenceRef: helmRef,
|
|
275
|
+
summary: JSON.stringify({ namespace: "shop", name: "shop" }),
|
|
276
|
+
},
|
|
277
|
+
),
|
|
278
|
+
tool(
|
|
279
|
+
"perm",
|
|
280
|
+
"get_subject_permissions",
|
|
281
|
+
{
|
|
282
|
+
subject: {
|
|
283
|
+
kind: "ServiceAccount",
|
|
284
|
+
namespace: "shop",
|
|
285
|
+
name: "default",
|
|
286
|
+
},
|
|
287
|
+
usedByPods: ["api-abc"],
|
|
288
|
+
bindings: [],
|
|
289
|
+
flatRules: [],
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
evidenceRef: permRef,
|
|
293
|
+
summary: JSON.stringify({
|
|
294
|
+
kind: "ServiceAccount",
|
|
295
|
+
namespace: "shop",
|
|
296
|
+
name: "default",
|
|
297
|
+
}),
|
|
298
|
+
},
|
|
299
|
+
),
|
|
300
|
+
tool(
|
|
301
|
+
"graph",
|
|
302
|
+
"get_neighborhood",
|
|
303
|
+
{
|
|
304
|
+
root: { kind: "Service", group: "", namespace: "shop", name: "api" },
|
|
305
|
+
subgraph: {
|
|
306
|
+
nodes: [
|
|
307
|
+
{
|
|
308
|
+
id: "service/shop/api",
|
|
309
|
+
kind: "Service",
|
|
310
|
+
name: "api",
|
|
311
|
+
data: { namespace: "shop" },
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
id: "deployment/shop/api",
|
|
315
|
+
kind: "Deployment",
|
|
316
|
+
name: "api",
|
|
317
|
+
data: { namespace: "shop" },
|
|
318
|
+
},
|
|
319
|
+
],
|
|
320
|
+
edges: [
|
|
321
|
+
{
|
|
322
|
+
source: "service/shop/api",
|
|
323
|
+
target: "deployment/shop/api",
|
|
324
|
+
type: "exposes",
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
truncated: false,
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
evidenceRef: graphRef,
|
|
332
|
+
summary: JSON.stringify({
|
|
333
|
+
kind: "Service",
|
|
334
|
+
namespace: "shop",
|
|
335
|
+
name: "api",
|
|
336
|
+
}),
|
|
337
|
+
},
|
|
338
|
+
),
|
|
339
|
+
tool(
|
|
340
|
+
"pkgs",
|
|
341
|
+
"list_packages",
|
|
342
|
+
{
|
|
343
|
+
packages: [
|
|
344
|
+
{
|
|
345
|
+
chart: "shop",
|
|
346
|
+
namespace: "shop",
|
|
347
|
+
releaseName: "shop",
|
|
348
|
+
version: "1.4.2",
|
|
349
|
+
health: { status: "healthy" },
|
|
350
|
+
sources: ["H", "F"],
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
sourceLegend: { H: "Helm", F: "Flux" },
|
|
354
|
+
},
|
|
355
|
+
{ evidenceRef: pkgRef, summary: JSON.stringify({}) },
|
|
356
|
+
),
|
|
357
|
+
);
|
|
358
|
+
const resolved = resolveInvestigationCase(
|
|
359
|
+
projection,
|
|
360
|
+
{
|
|
361
|
+
evidence: [
|
|
362
|
+
linked(helmRef, "context", "Deployed at revision 7.", {
|
|
363
|
+
kind: "HelmRelease",
|
|
364
|
+
namespace: "shop",
|
|
365
|
+
name: "shop",
|
|
366
|
+
observation: "resource",
|
|
367
|
+
}),
|
|
368
|
+
linked(permRef, "context", "Only implicit discovery grants.", {
|
|
369
|
+
kind: "ServiceAccount",
|
|
370
|
+
namespace: "shop",
|
|
371
|
+
name: "default",
|
|
372
|
+
observation: "resource",
|
|
373
|
+
}),
|
|
374
|
+
linked(graphRef, "context", "The Service is the only neighbour.", {
|
|
375
|
+
kind: "Service",
|
|
376
|
+
namespace: "shop",
|
|
377
|
+
name: "api",
|
|
378
|
+
observation: "resource",
|
|
379
|
+
}),
|
|
380
|
+
linked(
|
|
381
|
+
pkgRef,
|
|
382
|
+
"context",
|
|
383
|
+
"The package is healthy from every source.",
|
|
384
|
+
{
|
|
385
|
+
group: "helm.toolkit.fluxcd.io",
|
|
386
|
+
kind: "HelmRelease",
|
|
387
|
+
namespace: "shop",
|
|
388
|
+
name: "shop",
|
|
389
|
+
observation: "resource",
|
|
390
|
+
},
|
|
391
|
+
),
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
0,
|
|
395
|
+
);
|
|
396
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
397
|
+
"card",
|
|
398
|
+
"card",
|
|
399
|
+
"card",
|
|
400
|
+
"card",
|
|
401
|
+
]);
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("does not hold the evidence word against a call whose only observation is a receipt", () => {
|
|
405
|
+
const metricsRef = evidenceRef("m", "a");
|
|
406
|
+
const upgradeRef = evidenceRef("u", "b");
|
|
407
|
+
const projection = project(
|
|
408
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
409
|
+
tool(
|
|
410
|
+
"names",
|
|
411
|
+
"discover_metrics",
|
|
412
|
+
{
|
|
413
|
+
match: "container_restarts",
|
|
414
|
+
count: 0,
|
|
415
|
+
metrics: [],
|
|
416
|
+
truncated: false,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
evidenceRef: metricsRef,
|
|
420
|
+
summary: JSON.stringify({ match: "container_restarts" }),
|
|
421
|
+
},
|
|
422
|
+
),
|
|
423
|
+
tool(
|
|
424
|
+
"upgrade",
|
|
425
|
+
"get_cluster_upgrade_readiness",
|
|
426
|
+
{
|
|
427
|
+
currentVersion: "1.35.7",
|
|
428
|
+
targetVersion: "1.36",
|
|
429
|
+
check: {
|
|
430
|
+
id: "webhooks",
|
|
431
|
+
title: "Admission webhooks",
|
|
432
|
+
category: "api",
|
|
433
|
+
status: "fail",
|
|
434
|
+
findings: [
|
|
435
|
+
{
|
|
436
|
+
title: "Webhook has no failure policy",
|
|
437
|
+
level: "blocker",
|
|
438
|
+
resource: {
|
|
439
|
+
kind: "ValidatingWebhookConfiguration",
|
|
440
|
+
name: "kyverno",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
],
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
evidenceRef: upgradeRef,
|
|
448
|
+
summary: JSON.stringify({ targetVersion: "1.36" }),
|
|
449
|
+
},
|
|
450
|
+
),
|
|
451
|
+
);
|
|
452
|
+
const resolved = resolveInvestigationCase(
|
|
453
|
+
projection,
|
|
454
|
+
{
|
|
455
|
+
evidence: [
|
|
456
|
+
linked(
|
|
457
|
+
metricsRef,
|
|
458
|
+
"context",
|
|
459
|
+
"No metric name contains container_restarts.",
|
|
460
|
+
{
|
|
461
|
+
kind: "Pod",
|
|
462
|
+
namespace: "shop",
|
|
463
|
+
name: "api-abc",
|
|
464
|
+
observation: "metrics",
|
|
465
|
+
},
|
|
466
|
+
),
|
|
467
|
+
linked(
|
|
468
|
+
upgradeRef,
|
|
469
|
+
"context",
|
|
470
|
+
"None of the findings concern this workload.",
|
|
471
|
+
{
|
|
472
|
+
group: "apps",
|
|
473
|
+
kind: "Deployment",
|
|
474
|
+
namespace: "shop",
|
|
475
|
+
name: "api",
|
|
476
|
+
observation: "resource",
|
|
477
|
+
},
|
|
478
|
+
),
|
|
479
|
+
linked(ref, "symptom", "The bundle's events.", {
|
|
480
|
+
kind: "Pod",
|
|
481
|
+
namespace: "shop",
|
|
482
|
+
name: "api-abc",
|
|
483
|
+
observation: "startup",
|
|
484
|
+
}),
|
|
485
|
+
],
|
|
486
|
+
},
|
|
487
|
+
0,
|
|
488
|
+
);
|
|
489
|
+
// The bundle yields many observations, so a word it cannot satisfy still
|
|
490
|
+
// leaves that item at its source.
|
|
491
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
492
|
+
"card",
|
|
493
|
+
"card",
|
|
494
|
+
"source",
|
|
495
|
+
]);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("holds a bare read to the evidence word: 'logs' does not name a resource card", () => {
|
|
499
|
+
const readRef = evidenceRef("r", "a");
|
|
500
|
+
const projection = project(
|
|
501
|
+
tool("read", "get_resource", deployment, {
|
|
502
|
+
evidenceRef: readRef,
|
|
503
|
+
summary: JSON.stringify({
|
|
504
|
+
kind: "deployment",
|
|
505
|
+
namespace: "shop",
|
|
506
|
+
name: "api",
|
|
507
|
+
}),
|
|
508
|
+
}),
|
|
509
|
+
);
|
|
510
|
+
const resolved = resolveInvestigationCase(
|
|
511
|
+
projection,
|
|
512
|
+
{
|
|
513
|
+
evidence: [
|
|
514
|
+
linked(readRef, "context", "Zero ready.", {
|
|
515
|
+
group: "apps",
|
|
516
|
+
kind: "Deployment",
|
|
517
|
+
namespace: "shop",
|
|
518
|
+
name: "api",
|
|
519
|
+
observation: "logs",
|
|
520
|
+
}),
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
0,
|
|
524
|
+
);
|
|
525
|
+
expect(resolved.items[0].placement).toBe("source");
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
it("leads a cited package listing with one row when two aliases name it, and holds a Package subject to its namespace", () => {
|
|
529
|
+
const pkgRef = evidenceRef("k", "e");
|
|
530
|
+
const projection = project(
|
|
531
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
532
|
+
tool(
|
|
533
|
+
"pkgs",
|
|
534
|
+
"list_packages",
|
|
535
|
+
{
|
|
536
|
+
packages: [
|
|
537
|
+
{
|
|
538
|
+
chart: "podinfo",
|
|
539
|
+
namespace: "prod",
|
|
540
|
+
releaseName: "podinfo",
|
|
541
|
+
version: "6.15.0",
|
|
542
|
+
health: { status: "healthy" },
|
|
543
|
+
sources: ["H"],
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
chart: "redis",
|
|
547
|
+
namespace: "prod",
|
|
548
|
+
releaseName: "redis",
|
|
549
|
+
version: "1.0.0",
|
|
550
|
+
health: { status: "healthy" },
|
|
551
|
+
sources: ["H"],
|
|
552
|
+
},
|
|
553
|
+
],
|
|
554
|
+
sourceLegend: { H: "Helm" },
|
|
555
|
+
},
|
|
556
|
+
{ evidenceRef: pkgRef, summary: JSON.stringify({}) },
|
|
557
|
+
),
|
|
558
|
+
);
|
|
559
|
+
const resolved = resolveInvestigationCase(
|
|
560
|
+
projection,
|
|
561
|
+
{
|
|
562
|
+
evidence: [
|
|
563
|
+
linked(pkgRef, "context", "Declared by Flux.", {
|
|
564
|
+
group: "helm.toolkit.fluxcd.io",
|
|
565
|
+
kind: "HelmRelease",
|
|
566
|
+
namespace: "flux-system",
|
|
567
|
+
name: "podinfo",
|
|
568
|
+
observation: "resource",
|
|
569
|
+
}),
|
|
570
|
+
linked(pkgRef, "context", "Installed by Helm.", {
|
|
571
|
+
kind: "HelmRelease",
|
|
572
|
+
namespace: "staging",
|
|
573
|
+
name: "podinfo",
|
|
574
|
+
observation: "resource",
|
|
575
|
+
}),
|
|
576
|
+
linked(pkgRef, "context", "Another namespace's package.", {
|
|
577
|
+
kind: "Package",
|
|
578
|
+
namespace: "staging",
|
|
579
|
+
name: "podinfo",
|
|
580
|
+
observation: "resource",
|
|
581
|
+
}),
|
|
582
|
+
],
|
|
583
|
+
},
|
|
584
|
+
0,
|
|
585
|
+
);
|
|
586
|
+
// All three bind to the listing; only the two declaring-object aliases
|
|
587
|
+
// name the prod row, and it leads the list once.
|
|
588
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
589
|
+
"card",
|
|
590
|
+
"card",
|
|
591
|
+
"card",
|
|
592
|
+
]);
|
|
593
|
+
const listing = projection.groups.find(
|
|
594
|
+
(group) => group.latest.data.type === "inventory",
|
|
595
|
+
)!.latest.data;
|
|
596
|
+
if (listing.type !== "inventory") throw new Error("expected inventory");
|
|
597
|
+
expect(
|
|
598
|
+
namedInventoryRows(listing.resources, resolved.items).map(
|
|
599
|
+
(named) => named.matches,
|
|
600
|
+
),
|
|
601
|
+
).toEqual([1, 1, 0]);
|
|
602
|
+
expect(
|
|
603
|
+
namedInventoryRows(listing.resources, [...resolved.items].reverse()).map(
|
|
604
|
+
(named) => named.matches,
|
|
605
|
+
),
|
|
606
|
+
).toEqual([0, 1, 1]);
|
|
607
|
+
const html = render(projection, resolved);
|
|
608
|
+
const cited = html.match(/data-inventory-row="cited"/g) ?? [];
|
|
609
|
+
expect(cited).toHaveLength(1);
|
|
610
|
+
const marker = html.indexOf('data-inventory-row="cited"');
|
|
611
|
+
expect(marker).toBeGreaterThan(-1);
|
|
612
|
+
expect(marker).toBeLessThan(html.indexOf("prod/redis"));
|
|
613
|
+
expect(html.match(/prod\/podinfo/g)).toHaveLength(1);
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it("reaches a posture finding through the workload that owns its resource, and holds a mixed-kind card to the names it holds", () => {
|
|
617
|
+
const upgradeRef = evidenceRef("u", "c");
|
|
618
|
+
const projection = project(
|
|
619
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
620
|
+
tool(
|
|
621
|
+
"upgrade",
|
|
622
|
+
"get_cluster_upgrade_readiness",
|
|
623
|
+
{
|
|
624
|
+
currentVersion: "1.35.7",
|
|
625
|
+
targetVersion: "1.36",
|
|
626
|
+
check: {
|
|
627
|
+
id: "removed-apis",
|
|
628
|
+
title: "Removed APIs",
|
|
629
|
+
category: "api",
|
|
630
|
+
status: "fail",
|
|
631
|
+
findings: [
|
|
632
|
+
{
|
|
633
|
+
title: "Uses autoscaling/v2beta2",
|
|
634
|
+
level: "blocker",
|
|
635
|
+
resource: {
|
|
636
|
+
kind: "HorizontalPodAutoscaler",
|
|
637
|
+
namespace: "shop",
|
|
638
|
+
name: "api-hpa",
|
|
639
|
+
},
|
|
640
|
+
managedBy: {
|
|
641
|
+
kind: "Deployment",
|
|
642
|
+
group: "apps",
|
|
643
|
+
namespace: "shop",
|
|
644
|
+
name: "api",
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
title: "Uses networking/v1beta1",
|
|
649
|
+
level: "warning",
|
|
650
|
+
resource: { kind: "Ingress", namespace: "shop", name: "api" },
|
|
651
|
+
managedBy: {
|
|
652
|
+
kind: "Deployment",
|
|
653
|
+
group: "apps",
|
|
654
|
+
namespace: "shop",
|
|
655
|
+
name: "api",
|
|
656
|
+
},
|
|
657
|
+
},
|
|
658
|
+
],
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
evidenceRef: upgradeRef,
|
|
663
|
+
summary: JSON.stringify({
|
|
664
|
+
targetVersion: "1.36",
|
|
665
|
+
check: "removed-apis",
|
|
666
|
+
}),
|
|
667
|
+
},
|
|
668
|
+
),
|
|
669
|
+
);
|
|
670
|
+
const resolved = resolveInvestigationCase(
|
|
671
|
+
projection,
|
|
672
|
+
{
|
|
673
|
+
evidence: [
|
|
674
|
+
linked(
|
|
675
|
+
upgradeRef,
|
|
676
|
+
"context",
|
|
677
|
+
"The HPA and the Ingress need new APIs.",
|
|
678
|
+
{
|
|
679
|
+
group: "apps",
|
|
680
|
+
kind: "Deployment",
|
|
681
|
+
namespace: "shop",
|
|
682
|
+
name: "api",
|
|
683
|
+
observation: "resource",
|
|
684
|
+
},
|
|
685
|
+
),
|
|
686
|
+
linked(upgradeRef, "context", "Names nothing on the card.", {
|
|
687
|
+
kind: "Service",
|
|
688
|
+
namespace: "shop",
|
|
689
|
+
name: "api",
|
|
690
|
+
observation: "resource",
|
|
691
|
+
}),
|
|
692
|
+
],
|
|
693
|
+
},
|
|
694
|
+
0,
|
|
695
|
+
);
|
|
696
|
+
expect(resolved.items.map((item) => item.placement)).toEqual([
|
|
697
|
+
"card",
|
|
698
|
+
"source",
|
|
699
|
+
]);
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it("reaches a pod ranking through the workload its marked pods belong to", () => {
|
|
703
|
+
const rankRef = evidenceRef("t", "b");
|
|
704
|
+
const projection = project(
|
|
705
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
706
|
+
tool(
|
|
707
|
+
"top",
|
|
708
|
+
"top_resources",
|
|
709
|
+
{
|
|
710
|
+
kind: "pods",
|
|
711
|
+
sort: "memory",
|
|
712
|
+
metricsAvailable: true,
|
|
713
|
+
items: [
|
|
714
|
+
{
|
|
715
|
+
kind: "Pod",
|
|
716
|
+
namespace: "shop",
|
|
717
|
+
name: "worker-1",
|
|
718
|
+
cpuMilli: 9,
|
|
719
|
+
memoryMi: 400,
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
kind: "Pod",
|
|
723
|
+
namespace: "shop",
|
|
724
|
+
name: "api-abc",
|
|
725
|
+
owner: { group: "apps", kind: "Deployment", name: "api" },
|
|
726
|
+
cpuMilli: 5,
|
|
727
|
+
memoryMi: 300,
|
|
728
|
+
},
|
|
729
|
+
],
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
evidenceRef: rankRef,
|
|
733
|
+
summary: JSON.stringify({
|
|
734
|
+
kind: "pods",
|
|
735
|
+
namespace: "shop",
|
|
736
|
+
sort: "memory",
|
|
737
|
+
}),
|
|
738
|
+
},
|
|
739
|
+
),
|
|
740
|
+
);
|
|
741
|
+
const resolved = resolveInvestigationCase(
|
|
742
|
+
projection,
|
|
743
|
+
{
|
|
744
|
+
evidence: [
|
|
745
|
+
linked(rankRef, "context", "The API pod is second by memory.", {
|
|
746
|
+
group: "apps",
|
|
747
|
+
kind: "Deployment",
|
|
748
|
+
namespace: "shop",
|
|
749
|
+
name: "api",
|
|
750
|
+
observation: "resource",
|
|
751
|
+
}),
|
|
752
|
+
],
|
|
753
|
+
},
|
|
754
|
+
0,
|
|
755
|
+
);
|
|
756
|
+
expect(resolved.items[0].placement).toBe("card");
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
it("marks the investigated workload's own row in a workload ranking", () => {
|
|
760
|
+
const rankRef = evidenceRef("t", "a");
|
|
761
|
+
const projection = project(
|
|
762
|
+
tool(
|
|
763
|
+
"top",
|
|
764
|
+
"top_resources",
|
|
765
|
+
{
|
|
766
|
+
kind: "workloads",
|
|
767
|
+
sort: "cpu",
|
|
768
|
+
namespace: "shop",
|
|
769
|
+
metricsAvailable: true,
|
|
770
|
+
workloads: [
|
|
771
|
+
{
|
|
772
|
+
kind: "Deployment",
|
|
773
|
+
namespace: "shop",
|
|
774
|
+
name: "web",
|
|
775
|
+
cpuMilli: 9,
|
|
776
|
+
memoryMi: 40,
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
kind: "Deployment",
|
|
780
|
+
namespace: "shop",
|
|
781
|
+
name: "api",
|
|
782
|
+
cpuMilli: 5,
|
|
783
|
+
memoryMi: 30,
|
|
784
|
+
},
|
|
785
|
+
],
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
evidenceRef: rankRef,
|
|
789
|
+
summary: JSON.stringify({ kind: "workloads", namespace: "shop" }),
|
|
790
|
+
},
|
|
791
|
+
),
|
|
792
|
+
);
|
|
793
|
+
const group = projection.groups.find(
|
|
794
|
+
(g) => g.latest.data.type === "ranking",
|
|
795
|
+
)!;
|
|
796
|
+
const data = group.latest.data;
|
|
797
|
+
if (data.type !== "ranking") throw new Error("expected ranking");
|
|
798
|
+
expect(data.rows.map((row) => row.target)).toEqual([false, true]);
|
|
799
|
+
expect(group.latest.relevance).toBe("producer-related");
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it("places a declaring-object citation on a package listing scoped to the workload's namespace", () => {
|
|
803
|
+
const pkgRef = evidenceRef("k", "f");
|
|
804
|
+
const projection = project(
|
|
805
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
806
|
+
tool(
|
|
807
|
+
"pkgs",
|
|
808
|
+
"list_packages",
|
|
809
|
+
{
|
|
810
|
+
packages: [
|
|
811
|
+
{
|
|
812
|
+
chart: "shop",
|
|
813
|
+
namespace: "shop",
|
|
814
|
+
releaseName: "shop",
|
|
815
|
+
version: "1.4.2",
|
|
816
|
+
health: "healthy",
|
|
817
|
+
sources: ["F"],
|
|
818
|
+
},
|
|
819
|
+
],
|
|
820
|
+
sourceLegend: { F: "Flux" },
|
|
821
|
+
},
|
|
822
|
+
{ evidenceRef: pkgRef, summary: JSON.stringify({ namespace: "shop" }) },
|
|
823
|
+
),
|
|
824
|
+
);
|
|
825
|
+
const resolved = resolveInvestigationCase(
|
|
826
|
+
projection,
|
|
827
|
+
{
|
|
828
|
+
evidence: [
|
|
829
|
+
linked(pkgRef, "context", "Declared by Flux.", {
|
|
830
|
+
group: "helm.toolkit.fluxcd.io",
|
|
831
|
+
kind: "HelmRelease",
|
|
832
|
+
namespace: "flux-system",
|
|
833
|
+
name: "shop",
|
|
834
|
+
observation: "resource",
|
|
835
|
+
}),
|
|
836
|
+
],
|
|
837
|
+
},
|
|
838
|
+
0,
|
|
839
|
+
);
|
|
840
|
+
expect(resolved.items[0].placement).toBe("card");
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
it("keeps an ordinary listing's held entry to the listing's namespace", () => {
|
|
844
|
+
const listRef = evidenceRef("c", "e");
|
|
845
|
+
const projection = project(
|
|
846
|
+
tool("diag", "diagnose", diagnoseBundle, { evidenceRef: ref }),
|
|
847
|
+
tool(
|
|
848
|
+
"cms",
|
|
849
|
+
"list_resources",
|
|
850
|
+
[{ kind: "ConfigMap", name: "kube-root-ca.crt" }],
|
|
851
|
+
{
|
|
852
|
+
evidenceRef: listRef,
|
|
853
|
+
summary: JSON.stringify({ kind: "configmaps", namespace: "shop" }),
|
|
854
|
+
},
|
|
855
|
+
),
|
|
856
|
+
);
|
|
857
|
+
const resolved = resolveInvestigationCase(
|
|
858
|
+
projection,
|
|
859
|
+
{
|
|
860
|
+
evidence: [
|
|
861
|
+
linked(listRef, "context", "Another namespace's CA bundle.", {
|
|
862
|
+
kind: "ConfigMap",
|
|
863
|
+
namespace: "prod",
|
|
864
|
+
name: "kube-root-ca.crt",
|
|
865
|
+
observation: "resource",
|
|
866
|
+
}),
|
|
867
|
+
],
|
|
868
|
+
},
|
|
869
|
+
0,
|
|
870
|
+
);
|
|
871
|
+
expect(resolved.items[0].placement).toBe("source");
|
|
872
|
+
});
|
|
873
|
+
|
|
245
874
|
it("binds a Pod subject to events only for the target's own pod or a pod an event names whole", () => {
|
|
246
875
|
const workerRef = evidenceRef("e", "f");
|
|
247
876
|
const canaryBundle = {
|
|
@@ -443,6 +1072,33 @@ describe("agent case placement (D-1, D-1b)", () => {
|
|
|
443
1072
|
]);
|
|
444
1073
|
});
|
|
445
1074
|
|
|
1075
|
+
it.each(["issue", "issues"])(
|
|
1076
|
+
"pins a %s citation to the issue observation",
|
|
1077
|
+
(observation) => {
|
|
1078
|
+
const resolved = resolveInvestigationCase(
|
|
1079
|
+
projection,
|
|
1080
|
+
{
|
|
1081
|
+
evidence: [
|
|
1082
|
+
linked(ref, "symptom", "The issue is the symptom.", {
|
|
1083
|
+
kind: "Deployment",
|
|
1084
|
+
group: "apps",
|
|
1085
|
+
namespace: "shop",
|
|
1086
|
+
name: "api",
|
|
1087
|
+
observation,
|
|
1088
|
+
}),
|
|
1089
|
+
],
|
|
1090
|
+
},
|
|
1091
|
+
0,
|
|
1092
|
+
);
|
|
1093
|
+
expect(resolved.items[0].placement).toBe("card");
|
|
1094
|
+
expect(
|
|
1095
|
+
projection.groups.find(
|
|
1096
|
+
(group) => group.id === resolved.items[0].groupId,
|
|
1097
|
+
)?.kind,
|
|
1098
|
+
).toBe("issue");
|
|
1099
|
+
},
|
|
1100
|
+
);
|
|
1101
|
+
|
|
446
1102
|
it("pins a subject-bearing item to exactly the observation it names", () => {
|
|
447
1103
|
const resolved = resolveInvestigationCase(
|
|
448
1104
|
projection,
|