@skyhook-io/radar-app 1.14.2 → 1.14.3

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 (74) hide show
  1. package/package.json +2 -2
  2. package/src/api/client.ts +30 -0
  3. package/src/components/CloudConnectFlow.tsx +21 -7
  4. package/src/components/CloudFunnelButton.tsx +85 -8
  5. package/src/components/cloudConnectHandoff.test.ts +10 -1
  6. package/src/components/cloudConnectHandoff.ts +15 -1
  7. package/src/components/diagnose/AIMarkdown.tsx +87 -0
  8. package/src/components/diagnose/ActivityTurn.tsx +1244 -0
  9. package/src/components/diagnose/AgentControls.tsx +603 -0
  10. package/src/components/diagnose/AnalysisStory.test.tsx +31 -25
  11. package/src/components/diagnose/AnalysisStory.tsx +13 -18
  12. package/src/components/diagnose/ApplyDialog.tsx +289 -0
  13. package/src/components/diagnose/AssessmentCard.tsx +1560 -0
  14. package/src/components/diagnose/DiagnoseSurface.test.tsx +9 -9
  15. package/src/components/diagnose/DiagnoseSurface.tsx +5 -1
  16. package/src/components/diagnose/EvidenceCard.tsx +611 -0
  17. package/src/components/diagnose/Home.test.tsx +1 -3
  18. package/src/components/diagnose/Home.tsx +2 -1
  19. package/src/components/diagnose/InvestigationEvidencePane.story.test.tsx +6 -8
  20. package/src/components/diagnose/InvestigationEvidencePane.test.tsx +5 -3
  21. package/src/components/diagnose/InvestigationEvidencePane.tsx +31 -3445
  22. package/src/components/diagnose/InvestigationEvidenceSections.tsx +523 -0
  23. package/src/components/diagnose/InvestigationView.tsx +11 -28
  24. package/src/components/diagnose/StoryExcerpt.tsx +118 -0
  25. package/src/components/diagnose/assessmentCopy.ts +169 -0
  26. package/src/components/diagnose/investigationCase.test.tsx +631 -2
  27. package/src/components/diagnose/investigationCase.ts +132 -24
  28. package/src/components/diagnose/investigationEvidence/adapters/coverage.test.tsx +660 -0
  29. package/src/components/diagnose/investigationEvidence/adapters/{helm.test.ts → helm.test.tsx} +63 -0
  30. package/src/components/diagnose/investigationEvidence/adapters/helm.ts +70 -1
  31. package/src/components/diagnose/investigationEvidence/adapters/listings.ts +134 -0
  32. package/src/components/diagnose/investigationEvidence/adapters/{permissions.test.ts → permissions.test.tsx} +50 -0
  33. package/src/components/diagnose/investigationEvidence/adapters/permissions.ts +18 -0
  34. package/src/components/diagnose/investigationEvidence/adapters/posture.ts +272 -0
  35. package/src/components/diagnose/investigationEvidence/adapters/prometheus.ts +49 -0
  36. package/src/components/diagnose/investigationEvidence/adapters/rankings.ts +147 -0
  37. package/src/components/diagnose/investigationEvidence/adapters/resource.ts +20 -10
  38. package/src/components/diagnose/investigationEvidence/bodies/index.tsx +127 -0
  39. package/src/components/diagnose/investigationEvidence/bodies/inventory.tsx +158 -0
  40. package/src/components/diagnose/investigationEvidence/bodies/metrics.tsx +373 -0
  41. package/src/components/diagnose/investigationEvidence/bodies/network.tsx +274 -0
  42. package/src/components/diagnose/investigationEvidence/bodies/platform.tsx +475 -0
  43. package/src/components/diagnose/investigationEvidence/bodies/posture.tsx +60 -0
  44. package/src/components/diagnose/investigationEvidence/bodies/ranking.tsx +76 -0
  45. package/src/components/diagnose/investigationEvidence/bodies/streams.tsx +212 -0
  46. package/src/components/diagnose/investigationEvidence/bodies/workload.tsx +335 -0
  47. package/src/components/diagnose/investigationEvidence/builder.ts +15 -0
  48. package/src/components/diagnose/investigationEvidence/cardParts.tsx +312 -0
  49. package/src/components/diagnose/investigationEvidence/excerpt.test.ts +105 -0
  50. package/src/components/diagnose/investigationEvidence/excerpt.ts +92 -0
  51. package/src/components/diagnose/investigationEvidence/identity.test.ts +5 -7
  52. package/src/components/diagnose/investigationEvidence/index.ts +1 -1
  53. package/src/components/diagnose/investigationEvidence/navigation.tsx +26 -0
  54. package/src/components/diagnose/investigationEvidence/observations.ts +11 -0
  55. package/src/components/diagnose/investigationEvidence/projection.test.ts +1 -1
  56. package/src/components/diagnose/investigationEvidence/types.ts +97 -2
  57. package/src/components/diagnose/investigationEvidenceKinds.ts +22 -0
  58. package/src/components/diagnose/investigationEvidencePartition.ts +296 -0
  59. package/src/components/diagnose/parts.test.tsx +3 -1
  60. package/src/components/diagnose/parts.tsx +26 -3904
  61. package/src/components/diagnose/target.ts +9 -3
  62. package/src/components/diagnose/toolCallLabel.test.ts +47 -1
  63. package/src/components/diagnose/toolCallLabel.ts +41 -2
  64. package/src/components/helm/HelmReleaseDrawer.tsx +1 -1
  65. package/src/components/home/MCPSetupDialog.tsx +1 -1
  66. package/src/components/resource/PrometheusChartsGrid.tsx +1 -1
  67. package/src/components/resource/WorkloadMetricsHelpDialog.tsx +1 -1
  68. package/src/components/resource/WorkloadMetricsSection.tsx +1 -1
  69. package/src/components/resources/PodFilePreview.tsx +1 -1
  70. package/src/components/ui/CommandPalette.tsx +12 -8
  71. package/src/components/ui/DiagnosticsOverlay.tsx +7 -5
  72. package/src/components/ui/Omnibar.tsx +33 -10
  73. package/src/components/ui/ShortcutHelpOverlay.tsx +8 -5
  74. 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 "./InvestigationEvidencePane";
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 = {