narrow patrol to verification-first flow

This commit is contained in:
rcourtman
2026-04-14 23:58:04 +01:00
parent 5a95b6ebcb
commit 540e59cb4a
17 changed files with 120 additions and 88 deletions
@@ -180,11 +180,12 @@ when the disabled candidate no longer counts toward monitored-system capacity.
and the learned-correlation payload loaded into the shared AI intelligence store, so the Patrol intelligence page and the AI summary page consume the same governed correlation slice instead of each page fetching its own copy
and the shared dashboard-load bundle inside `frontend-modern/src/stores/aiIntelligence.ts`, so the page orchestration stays on the store-owned bundle instead of enumerating the AI fetches inline
and the shared `frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx` card, so the AI summary page renders the governed policy-posture counts while the resource drawer stays on per-resource policy lines instead of carrying duplicate posture UI loops
and the dedicated `frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts` owner, so recent-change, learned-correlation, and governed-resource summary text stays derived from the canonical AI payload in one place instead of as hook-local count and pluralization logic
and the dedicated `frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts` owner, so recent-change, learned-correlation, and policy-coverage summary text stays derived from the canonical AI payload in one place instead of as hook-local count and pluralization logic
and the dedicated `frontend-modern/src/stores/aiIntelligenceSummaryModel.ts` owner, so recent-change counts and governed policy-posture fallbacks normalize once at the shared store boundary instead of as Patrol-hook-local payload repair
and the shared `frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx` card, so learned correlations and correlation context stay rendered through one governed frontend card instead of separate page-local list loops
and the same shared correlation card's ordering and truncation rule, so callers pass raw correlations instead of encoding their own top-N sort behavior
and the shared `frontend-modern/src/components/Infrastructure/ResourceChangeSummary.tsx` and `frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx` cards' infrastructure resource-link default, so the Patrol page, resource drawer, and problem-resource dashboard panels inherit the canonical resource-filter path construction instead of rebuilding infrastructure URLs inline
and the Patrol runtime-remediation destination shared with `/api/settings/ai`, so summary actions and runtime-finding actions may reuse the governed provider-settings route while still presenting that destination to Patrol operators as Patrol provider configuration instead of generic `AI Settings` copy
8. Route frontend API-client parsed error propagation, API-error-status fallback handling, allowed-status handling, custom status-specific error handling, command-trigger success envelope handling, shared response parsing pipelines, missing-resource lookup handling, metadata CRUD routing, stream event consumption, response status, collection normalization, scalar payload coercion, and structured error normalization through canonical shared helpers under `frontend-modern/src/api/`
9. Add or change API token scope, assignment, and revocation presentation through `frontend-modern/src/components/Settings/APITokenManager.tsx`, `frontend-modern/src/components/Settings/apiTokenManagerModel.ts`, and `frontend-modern/src/components/Settings/useAPITokenManagerState.ts`
10. Add or change infrastructure operations token generation, lookup, assignment, the pure unified-agent inventory/install model, the split infrastructure install/reporting state owners, the split direct-node/discovery infrastructure settings owners, the shared infrastructure-operations state provider/context shell, and reporting/install presentation through `frontend-modern/src/components/Settings/InfrastructureOperationsController.tsx`, `frontend-modern/src/components/Settings/infrastructureOperationsModel.tsx`, `frontend-modern/src/components/Settings/useInfrastructureConfiguredNodesState.ts`, `frontend-modern/src/components/Settings/useInfrastructureDiscoveryRuntimeState.ts`, `frontend-modern/src/components/Settings/useInfrastructureInstallState.tsx`, `frontend-modern/src/components/Settings/useInfrastructureOperationsState.tsx`, and `frontend-modern/src/components/Settings/useInfrastructureReportingState.tsx`
@@ -371,6 +372,11 @@ when the disabled candidate no longer counts toward monitored-system capacity.
and the main Patrol page composition boundary, so once that governed
secondary area exists inside the summary shell the same payloads must not
also drive a second page-level status strip elsewhere on the route
and the Patrol supporting-context disclosure rule, so recent changes,
learned correlations, and policy coverage stay secondary explanatory context
that opens only when degraded verification, active findings, or selected-run
investigation makes that evidence relevant instead of advertising a parallel
Patrol workflow on otherwise healthy fully verified states
9. Keep AI settings setup transport vendor-neutral: `/api/settings/ai/update`
must accept provider credentials or base URLs without a baked vendor model
ID, resolve the effective BYOK `model` through the canonical runtime
@@ -280,6 +280,22 @@ work extends shared components instead of creating new local variants.
explicit disabled/null inputs to shared data owners when the selector is
hidden instead of hydrating background summary data for chrome the page is
not rendering.
18. Keep Patrol shell composition and product-first provider vocabulary on the
shared feature-presentation boundary.
`frontend-modern/src/features/patrol/PatrolIntelligenceSummary.tsx`,
`frontend-modern/src/features/patrol/PatrolIntelligenceWorkspace.tsx`,
`frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx`,
`frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx`,
`frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts`,
`frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts`,
and `frontend-modern/src/components/patrol/PatrolStatusBar.tsx` must keep
Patrol assessment, verification, and findings primary; surface recent
changes, learned correlations, and policy coverage only as explicitly
secondary supporting context when degraded or incomplete verification,
active findings, or selected-run investigation makes that evidence
relevant; and use Patrol/provider wording for the shared provider settings,
provider model, and provider circuit-breaker affordances instead of
generic AI labels inside Patrol-owned shells.
## Forbidden Paths
@@ -1560,6 +1576,12 @@ should present alert-triggered and anomaly-triggered Patrol toggles as distinct
controls, and `frontend-modern/src/components/patrol/PatrolStatusBar.tsx`
should render compact activity breakdown and scoped-trigger-state copy from the
shared transport rather than leaving busy Patrol periods as unexplained noise.
That same Patrol-facing primitive vocabulary must stay product-first. Patrol
summary actions, runtime banners, circuit-breaker copy, and Patrol
configuration controls may point at the shared provider settings route or model
catalog, but they should describe those controls as Patrol/provider surfaces
rather than falling back to generic `AI Settings`, `AI Model`, or `AI circuit
breaker` copy inside the Patrol shell itself.
On the main Patrol page, though, that same governed activity context belongs
inside `frontend-modern/src/features/patrol/PatrolIntelligenceSummary.tsx`
alongside the verification readout rather than as a second full-width band
@@ -1570,6 +1592,12 @@ That same composition rule applies to `frontend-modern/src/features/patrol/Patro
once the summary shell carries the operator-facing verification and activity
story, the workspace should move directly into findings and run history instead
of repeating that same runtime context through a second pre-tab status strip.
Supporting context follows that same composition rule. Recent changes, learned
correlations, and policy coverage belong behind an explicitly secondary
supporting-context disclosure that only appears when Patrol has active
findings, degraded or incomplete verification, or a selected run that needs
explanation; healthy fully verified Patrol states must not advertise that
supporting evidence as a peer workflow.
Shared primitive consumers that split status-dot tone and status-text tone
must now keep both values routed through the same exported presentation helper.
@@ -221,9 +221,9 @@ assessment copy should name the concrete runtime failure, such as
runtime findings.
That same runtime-owned assessment must expose the fix path directly. When the
primary Patrol issue is a Patrol runtime/provider problem rather than an
infrastructure finding, the summary card should offer a direct `Open AI
Settings` action instead of making the operator dig through the findings list
to discover where to correct provider configuration.
infrastructure finding, the summary card should offer a direct `Open Patrol
provider settings` action instead of making the operator dig through the
findings list to discover where to correct provider configuration.
That same runtime-versus-infrastructure distinction should route through the
shared finding-presentation helper instead of being re-inferred separately by
the summary card and the findings list. The active finding row should surface
@@ -243,14 +243,14 @@ rather than repeating the product prefix as `Pulse Patrol: Insufficient API
credits` once the surrounding UI already makes the Patrol context explicit.
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.
offer the same direct `Open Patrol provider settings` action that the top
assessment uses, instead of falling back to only generic acknowledge, snooze,
or dismiss controls.
That same contract must fail closed on manual lifecycle controls too. Patrol
runtime findings are Patrol-owned impairment signals, not ordinary estate
findings, so the findings list must not offer generic acknowledge, snooze,
dismiss, resolve, or suppress controls for them. The correct operator path is
to fix AI/provider configuration and rerun Patrol, optionally adding context
to fix Patrol provider configuration and rerun Patrol, optionally adding context
notes, rather than hiding the runtime issue.
That same runtime-versus-infrastructure split must carry through the summary
metrics strip as well. When Patrol-owned runtime issues are active, the
@@ -306,10 +306,13 @@ recent activity mix explicitly instead of leaving operators to reconcile a
`Recently verified` headline with a busy Patrol strip elsewhere on the page.
Fix-verification checks belong to that same explanation layer as targeted
activity, not as evidence of a fresh full-estate sweep.
The same hierarchy applies to investigation context. Correlations, recent
The same hierarchy applies to supporting context. Correlations, recent
changes, and policy posture are secondary evidence for deeper investigation, so
the `Investigation context` section belongs beneath the primary findings/history
the supporting-context disclosure belongs beneath the primary findings/history
workspace rather than inside the assessment card itself.
When Patrol is healthy and fully verified, that supporting-context disclosure
should stay out of the main page flow instead of advertising a second parallel
Patrol workflow with nothing active to explain.
That same operational context belongs inside the same secondary status area as
verification, not as a separate full-width strip that competes with the
findings workspace. `frontend-modern/src/features/patrol/PatrolIntelligenceSummary.tsx`
@@ -572,10 +575,10 @@ card, so the data-governance posture counts stay rendered from one governed
frontend component on the page instead of being duplicated in the resource
drawer.
That same Patrol summary surface now keeps health and findings primary while
rendering recent changes, learned correlations, and policy posture as
secondary investigation context behind an explicit disclosure, so expansion
lane concepts stay available for deeper investigation without reading as the
headline Patrol product story.
rendering recent changes, learned correlations, and policy posture only as
supporting context behind an explicit disclosure, so expansion lane concepts
stay available for deeper investigation without reading as the headline Patrol
product story.
That secondary investigation-context summary now also routes through the
dedicated `frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts`
owner, so the Patrol hook composes one canonical payload-to-summary derivation
@@ -308,7 +308,7 @@ describe('aiFindingPresentation', () => {
});
describe('findingPrimaryActionPresentation', () => {
it('offers AI settings as the primary action for Patrol runtime findings', () => {
it('offers Patrol provider settings as the primary action for Patrol runtime findings', () => {
expect(
getFindingPrimaryActionPresentation({
resourceId: 'ai-service',
@@ -316,12 +316,12 @@ describe('aiFindingPresentation', () => {
title: 'Pulse Patrol: Insufficient API credits',
}),
).toEqual({
label: 'Open AI Settings',
label: 'Open Patrol provider settings',
href: '/settings/system-ai',
});
});
it('does not expose AI settings as the primary action for infrastructure findings', () => {
it('does not expose Patrol provider settings as the primary action for infrastructure findings', () => {
expect(
getFindingPrimaryActionPresentation({
resourceId: 'vm-101',
@@ -339,7 +339,7 @@ describe('aiFindingPresentation', () => {
title: 'Insufficient API credits',
}),
).toEqual({
label: 'Open AI Settings',
label: 'Open Patrol provider settings',
href: '/settings/system-ai',
});
});
@@ -108,7 +108,7 @@ export const PatrolStatusBar: Component<PatrolStatusBarProps> = (props) => {
<div class="flex items-center gap-1.5 mb-1.5 pb-1.5 border-b border-red-200 dark:border-red-800">
<AlertTriangleIcon class="w-3.5 h-3.5 text-red-500" />
<span class="text-red-600 dark:text-red-400 font-medium text-xs">
AI circuit breaker tripped — Patrol paused after{' '}
Provider circuit breaker tripped — Patrol paused after{' '}
{circuitBreaker()!.consecutive_failures} consecutive failures
</span>
</div>
@@ -117,7 +117,7 @@ export const PatrolStatusBar: Component<PatrolStatusBarProps> = (props) => {
<div class="flex items-center gap-1.5 mb-1.5 pb-1.5 border-b border-amber-200 dark:border-amber-800">
<AlertCircleIcon class="w-3.5 h-3.5 text-amber-500" />
<span class="text-amber-600 dark:text-amber-400 font-medium text-xs">
AI circuit breaker recovering — testing with next patrol run
Provider circuit breaker recovering — testing with next patrol run
</span>
</div>
</Show>
@@ -62,7 +62,7 @@ describe('ActionRequiredPanel', () => {
expect(actionRequiredPanelSource).toContain('{title}');
});
it('uses Open AI Settings instead of rejected generic controls for Patrol runtime findings', () => {
it('uses the Patrol provider settings action instead of rejected generic controls for Patrol runtime findings', () => {
expect(
getFindingTitlePresentation({
resourceId: 'ai-service',
@@ -79,7 +79,7 @@ describe('ActionRequiredPanel', () => {
title: 'Pulse Patrol: Insufficient API credits',
}),
).toEqual({
label: 'Open AI Settings',
label: 'Open Patrol provider settings',
href: '/settings/system-ai',
});
expect(
@@ -87,7 +87,7 @@ export function PatrolIntelligenceBanners(props: { state: PatrolIntelligenceStat
class="inline-flex items-center justify-center gap-2 px-3 py-1.5 text-xs font-semibold text-amber-900 dark:text-amber-100 bg-amber-100 dark:bg-amber-900 border border-amber-200 dark:border-amber-700 rounded-md hover:bg-amber-200 dark:hover:bg-amber-900 transition-colors"
>
<SettingsIcon class="w-3.5 h-3.5" />
Open AI Settings
Open Patrol provider settings
</a>
<Show when={!presentationPolicyHidesUpgradePrompts() && state.licenseRequired()}>
<UpgradeLink
@@ -185,7 +185,7 @@ export function PatrolIntelligenceHeader(props: { state: PatrolIntelligenceState
<div class="grid grid-cols-2 gap-4">
<div class="space-y-1.5">
<label class="text-xs font-semibold uppercase tracking-wider text-muted">
AI Model
Provider model
</label>
<select
ref={state.setPatrolModelSelectRef}
@@ -91,13 +91,13 @@ export function PatrolIntelligenceSummary(props: { state: PatrolIntelligenceStat
if (circuitBreaker.state === 'open') {
return {
message: `AI circuit breaker tripped after ${circuitBreaker.consecutive_failures} consecutive failures.`,
message: `Provider circuit breaker tripped after ${circuitBreaker.consecutive_failures} consecutive failures.`,
toneClass: 'text-red-600 dark:text-red-400',
};
}
return {
message: 'AI circuit breaker recovering; the next Patrol run is a live test.',
message: 'Provider circuit breaker recovering; the next Patrol run is a live test.',
toneClass: 'text-amber-600 dark:text-amber-400',
};
});
@@ -300,7 +300,7 @@ export function PatrolIntelligenceSummary(props: { state: PatrolIntelligenceStat
<div class="p-3">
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted">
Activity
Latest activity
</p>
<Show
when={latestRun()}
@@ -123,15 +123,16 @@ export function PatrolIntelligenceWorkspace(props: { state: PatrolIntelligenceSt
/>
</Show>
<Show when={state.hasInvestigationContext()}>
<section class="rounded-md border border-border-subtle bg-base/90 p-3">
<Show when={state.shouldSurfaceInvestigationContext()}>
<section class="rounded-md border border-border-subtle bg-surface-alt/40 p-3">
<div class="flex flex-wrap items-start justify-between gap-3">
<div>
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted">
Investigation context
Supporting context
</p>
<p class="mt-1 text-sm text-muted">
Secondary change and policy signals for deeper investigation.
Recent changes, learned correlations, and policy coverage that may explain
findings or incomplete verification.
</p>
<Show when={state.investigationContextSummary()}>
<p class="mt-1 text-xs text-base-content">{state.investigationContextSummary()}</p>
@@ -143,7 +144,7 @@ export function PatrolIntelligenceWorkspace(props: { state: PatrolIntelligenceSt
onClick={() => state.setShowInvestigationContext((value) => !value)}
class="inline-flex items-center rounded-md border border-border bg-surface px-3 py-1.5 text-xs font-medium text-base-content transition-colors hover:bg-surface-hover"
>
{state.showInvestigationContext() ? 'Hide context' : 'Show context'}
{state.showInvestigationContext() ? 'Hide supporting context' : 'View supporting context'}
</button>
</div>
@@ -163,13 +164,13 @@ export function PatrolIntelligenceWorkspace(props: { state: PatrolIntelligenceSt
<div class="space-y-4">
<Show when={state.correlations().length > 0}>
<ResourceCorrelationSummary
title="Correlations"
title="Learned correlations"
correlations={state.correlations()}
summaryText={`${state.correlationTotal()} total`}
/>
</Show>
<ResourcePolicySummary posture={state.policyPosture()} title="Policy posture" />
<ResourcePolicySummary posture={state.policyPosture()} title="Policy coverage" />
</div>
</div>
</Show>
@@ -22,7 +22,7 @@ describe('patrolInvestigationContextModel', () => {
correlationCount: 2,
governedResourceCount: 4,
hasContext: true,
summaryText: '1 recent change · 2 correlations · 4 governed resources',
summaryText: '1 recent change · 2 correlations · 4 policy-covered resources',
});
});
@@ -30,7 +30,7 @@ export function buildPatrolInvestigationContextSummary(
}
if (governedResourceCount > 0) {
parts.push(
`${governedResourceCount} governed resource${governedResourceCount === 1 ? '' : 's'}`,
`${governedResourceCount} policy-covered resource${governedResourceCount === 1 ? '' : 's'}`,
);
}
@@ -606,6 +606,26 @@ export function usePatrolIntelligenceState() {
const activePatrolFindings = () =>
allPatrolFindings().filter((finding) => finding.status === 'active');
const shouldSurfaceInvestigationContext = createMemo(() => {
if (!hasInvestigationContext()) {
return false;
}
if (selectedRun()) {
return true;
}
if (activePatrolFindings().length > 0) {
return true;
}
const overallHealth = intelligenceSummary()?.overall_health;
if (!overallHealth) {
return false;
}
return overallHealth.grade !== 'A' || overallHealth.factors.length > 0;
});
const findingsTabBadgeFindings = createMemo(() => {
const snapshotFindings = selectedRunPatrolFindings();
if (snapshotFindings === null) {
@@ -738,6 +758,7 @@ export function usePatrolIntelligenceState() {
showAdvancedSettings,
showBlockedBanner,
showInvestigationContext,
shouldSurfaceInvestigationContext,
startingTrial,
summaryStats,
triggerPatrolDisabledReason,
@@ -403,11 +403,20 @@ describe('AIIntelligence entitlement gating', () => {
intelligenceState.summary = {
timestamp: '2026-03-01T00:00:00Z',
overall_health: {
score: 91,
grade: 'A',
score: 63,
grade: 'C',
trend: 'stable',
factors: [],
prediction: 'Stable',
factors: [
{
name: 'Patrol coverage incomplete',
impact: -0.35,
description:
'Patrol coverage is incomplete: recent activity was limited to scoped runs and ended with errors, so overall health is not fully verified.',
category: 'coverage',
},
],
prediction:
'Patrol coverage is incomplete: recent activity was limited to scoped runs and ended with errors, so overall health is not fully verified.',
},
findings_count: {
critical: 0,
@@ -465,13 +474,13 @@ describe('AIIntelligence entitlement gating', () => {
render(() => <AIIntelligence />);
await waitFor(() => {
expect(screen.getByText('Investigation context')).toBeInTheDocument();
expect(screen.getByText('Supporting context')).toBeInTheDocument();
});
expect(screen.queryByRole('heading', { name: 'Correlations' })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Show context' }));
expect(screen.queryByRole('heading', { name: 'Learned correlations' })).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'View supporting context' }));
await waitFor(() => {
expect(screen.getByRole('heading', { name: 'Correlations' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Learned correlations' })).toBeInTheDocument();
});
expect(screen.getByText('2 total')).toBeInTheDocument();
@@ -635,46 +644,10 @@ describe('AIIntelligence entitlement gating', () => {
expect(screen.getByText('No active issues detected')).toBeInTheDocument();
});
const findingsPanel = screen.getByTestId('findings-panel');
const contextHeading = screen.getByText('Investigation context');
expect(
Boolean(
findingsPanel.compareDocumentPosition(contextHeading) & Node.DOCUMENT_POSITION_FOLLOWING,
),
).toBe(true);
expect(screen.getByText(/Health A · 91\/100/)).toBeInTheDocument();
expect(screen.getByText('Investigation context')).toBeInTheDocument();
expect(screen.getByText('1 recent change · 4 governed resources')).toBeInTheDocument();
expect(screen.queryByText('Supporting context')).not.toBeInTheDocument();
expect(screen.queryByText('1 recent change · 4 policy-covered resources')).not.toBeInTheDocument();
expect(screen.queryByText('Policy posture')).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('button', { name: 'Show context' }));
await waitFor(() => {
expect(screen.getByText('Policy posture')).toBeInTheDocument();
});
expect(screen.getByText('Config update: Updated guest configuration')).toBeInTheDocument();
expect(screen.getByText('Updated guest configuration')).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'Open resource vm-100 in Infrastructure' }),
).toHaveAttribute('href', '/infrastructure?resource=vm-100');
expect(
screen.getByRole('link', { name: 'Open related resource agent-1 in Infrastructure' }),
).toHaveAttribute('href', '/infrastructure?resource=agent-1');
expect(screen.queryByText('Derived signal coverage')).not.toBeInTheDocument();
expect(screen.queryByText(/baselined/)).not.toBeInTheDocument();
expect(screen.getByText('Recent changes')).toBeInTheDocument();
expect(screen.getByText('4 governed resources')).toBeInTheDocument();
expect(screen.getByText('Public')).toBeInTheDocument();
expect(screen.getByText('Internal')).toBeInTheDocument();
expect(screen.getByText('Sensitive')).toBeInTheDocument();
expect(screen.getByText('Restricted')).toBeInTheDocument();
expect(screen.getByText('Cloud Summary')).toBeInTheDocument();
expect(screen.getByText('Local First')).toBeInTheDocument();
expect(screen.getByText('Local Only')).toBeInTheDocument();
expect(screen.getByText('Hostname 2')).toBeInTheDocument();
expect(screen.getByText('IP Address 1')).toBeInTheDocument();
});
it('does not present a healthy patrol summary when patrol is blocked on exhausted quickstart credits', async () => {
@@ -1123,11 +1096,11 @@ describe('AIIntelligence entitlement gating', () => {
),
).toBeInTheDocument();
expect(screen.getByText('Runtime issues')).toBeInTheDocument();
expect(screen.getByText('Activity')).toBeInTheDocument();
expect(screen.getByText('Latest activity')).toBeInTheDocument();
});
expect(screen.getByText(/Assessment C · 60\/100/)).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'Open AI Settings' })).toHaveAttribute(
expect(screen.getByRole('link', { name: 'Open Patrol provider settings' })).toHaveAttribute(
'href',
'/settings/system-ai',
);
@@ -180,7 +180,7 @@ describe('getPatrolSummaryPresentation', () => {
).toBe('Assessment');
});
it('offers a direct AI settings action for Patrol runtime issues', () => {
it('offers a direct Patrol provider settings action for Patrol runtime issues', () => {
expect(
getPatrolAssessmentAction({
activeFindings: [
@@ -194,7 +194,7 @@ describe('getPatrolSummaryPresentation', () => {
] as never,
}),
).toEqual({
label: 'Open AI Settings',
label: 'Open Patrol provider settings',
href: '/settings/system-ai',
});
});
@@ -410,7 +410,7 @@ export const getFindingPrimaryActionPresentation = (
): FindingPrimaryActionPresentation | undefined => {
if (isPatrolRuntimeFinding(finding)) {
return {
label: 'Open AI Settings',
label: 'Open Patrol provider settings',
href: '/settings/system-ai',
};
}
@@ -257,7 +257,7 @@ export function getPatrolAssessmentAction(args: {
const classified = classifyActiveFindings(args.activeFindings);
if (classified.infrastructureTotal === 0 && classified.runtimeTotal > 0) {
return {
label: 'Open AI Settings',
label: 'Open Patrol provider settings',
href: '/settings/system-ai',
};
}