diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index 2522c0c52..964b49a7b 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -170,6 +170,11 @@ subject labels, so Patrol-owned synthetic service findings render as `Patrol runtime` rather than leaking backend resource internals like `Pulse Patrol Service (service)` into the primary findings row or assistant handoff prompts. +That same finding presentation contract should own the primary remediation path +for Patrol-owned runtime findings as well. Expanded runtime-finding rows should +offer the same direct `Open AI Settings` action that the top assessment uses, +instead of falling back to only generic acknowledge, snooze, or dismiss +controls. The summary recency chip must follow the same governed scope distinction. When the latest completed activity was only a scoped run, the summary should label that timestamp as `Last activity` instead of `Last patrol`; `Last full patrol` diff --git a/frontend-modern/src/components/AI/FindingsPanel.tsx b/frontend-modern/src/components/AI/FindingsPanel.tsx index 5bc839580..6b0befb61 100644 --- a/frontend-modern/src/components/AI/FindingsPanel.tsx +++ b/frontend-modern/src/components/AI/FindingsPanel.tsx @@ -43,6 +43,7 @@ import { getFindingStatusLabel, getFindingSourceBadgeClasses, getFindingSourceLabel, + getFindingPrimaryActionPresentation, getFindingSubjectPresentation, getPatrolFindingClassification, getFindingRecencyPresentation, @@ -664,6 +665,24 @@ export const FindingsPanel: Component = (props) => { // Render expanded content for a finding const renderExpandedContent = (finding: UnifiedFinding) => (
+ {(() => { + const primaryAction = getFindingPrimaryActionPresentation(finding); + return ( + + {(action) => ( + + )} + + ); + })()}
Triggered by alert{finding.alertType ? ` (${finding.alertType})` : ''} • Identifier{' '} diff --git a/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts b/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts index 9ea2e1048..3ac6b6d3d 100644 --- a/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts +++ b/frontend-modern/src/components/AI/__tests__/FindingsPanel.test.ts @@ -6,6 +6,7 @@ import { formatFindingLifecycleType, formatFindingLoopState, getFindingEmptyStateCopy, + getFindingPrimaryActionPresentation, getFindingSubjectPresentation, getPatrolFindingClassification, getFindingSeverityCompactLabel, @@ -219,6 +220,31 @@ describe('aiFindingPresentation', () => { }); }); + describe('findingPrimaryActionPresentation', () => { + it('offers AI settings as the primary action for Patrol runtime findings', () => { + expect( + getFindingPrimaryActionPresentation({ + resourceId: 'ai-service', + resourceName: 'Pulse Patrol Service', + title: 'Pulse Patrol: Insufficient API credits', + }), + ).toEqual({ + label: 'Open AI Settings', + href: '/settings/system-ai', + }); + }); + + it('does not expose AI settings as the primary action for infrastructure findings', () => { + expect( + getFindingPrimaryActionPresentation({ + resourceId: 'vm-101', + resourceName: 'db-01', + title: 'Disk nearly full', + }), + ).toBeUndefined(); + }); + }); + describe('filterPresentation', () => { it('builds canonical filter options', () => { expect( @@ -268,6 +294,12 @@ describe('aiFindingPresentation', () => { expect(findingsPanelSource).not.toContain('AI-discovered insights'); }); + it('exposes the canonical primary action for Patrol runtime findings inside the expanded row', () => { + expect(findingsPanelSource).toContain('getFindingPrimaryActionPresentation'); + expect(findingsPanelSource).toContain('{action().label}'); + expect(findingsPanelSource).toContain("href={action().href}"); + }); + it('only shows the sort control when there are multiple Patrol findings to sort', () => { expect(findingsPanelSource).toContain(' 1}>'); expect(findingsPanelSource).toContain(''); diff --git a/frontend-modern/src/utils/aiFindingPresentation.ts b/frontend-modern/src/utils/aiFindingPresentation.ts index 1423739b9..3b43204c0 100644 --- a/frontend-modern/src/utils/aiFindingPresentation.ts +++ b/frontend-modern/src/utils/aiFindingPresentation.ts @@ -211,6 +211,11 @@ export interface FindingSubjectPresentation { label: string; } +export interface FindingPrimaryActionPresentation { + label: string; + href: string; +} + export const getFindingSourceLabel = (source: UnifiedFinding['source'] | string): string => FINDING_SOURCE_LABELS[source] || source; @@ -288,6 +293,19 @@ export const getFindingSubjectPresentation = ( }; }; +export const getFindingPrimaryActionPresentation = ( + finding: Pick, +): FindingPrimaryActionPresentation | undefined => { + if (isPatrolRuntimeFinding(finding)) { + return { + label: 'Open AI Settings', + href: '/settings/system-ai', + }; + } + + return undefined; +}; + export const getFindingRecencyPresentation = ( finding: Pick, ): FindingRecencyPresentation => {