Differentiate Patrol no-findings from all-clear

Patrol findings empty states now interpret degraded assessment context instead of repeating the summary prediction verbatim.
This commit is contained in:
rcourtman 2026-03-25 11:31:42 +00:00
parent 38fc165922
commit 2dd90da65e
4 changed files with 52 additions and 4 deletions

View file

@ -120,6 +120,12 @@ overall-health summary is degraded or not fully verified. The green healthy
empty state belongs only to an actually healthy Patrol summary, while degraded
coverage or paused-runtime states must surface the governing warning/error copy
through `frontend-modern/src/utils/patrolEmptyStatePresentation.ts`.
That degraded empty-state copy must also interpret the finding state rather
than simply replaying the primary assessment sentence verbatim: when coverage
is incomplete, the findings panel should tell the operator that Patrol has not
surfaced active findings but that this is not a full all-clear, so the page
does not duplicate the summary prediction as if it were a second independent
status surface.
The Patrol summary surface must follow that same hierarchy. The primary summary
headline in `frontend-modern/src/features/patrol/PatrolIntelligenceSummary.tsx`
should state Patrol's current assessment first, such as verified healthy,

View file

@ -818,6 +818,11 @@ describe('AIIntelligence entitlement gating', () => {
expect(screen.queryByText('No issues found')).not.toBeInTheDocument();
expect(screen.queryByText('Partial verification')).not.toBeInTheDocument();
expect(
screen.getAllByText(
'Patrol coverage is incomplete: recent activity was limited to scoped runs and ended with errors, so overall health is not fully verified.',
),
).toHaveLength(1);
});
it('treats a selected zero-finding run as an empty snapshot and uses effective scope ids', async () => {

View file

@ -59,11 +59,38 @@ describe('patrolEmptyStatePresentation', () => {
}),
).toEqual({
title: 'No active findings',
body: 'Patrol coverage is incomplete: recent activity was limited to scoped runs and ended with errors, so overall health is not fully verified.',
body: 'Patrol has not surfaced active findings, but coverage is incomplete, so this is not a full all-clear.',
tone: 'warning',
});
});
it('uses an attention-focused empty state when patrol health is degraded for non-coverage reasons', () => {
expect(
getPatrolFindingsEmptyState({
filter: 'active',
overallHealth: {
score: 45,
grade: 'D',
trend: 'declining',
factors: [
{
name: 'Critical unresolved risk',
impact: -0.55,
description: 'Recent Patrol evidence indicates unresolved infrastructure risk.',
category: 'findings',
},
],
prediction: 'Critical infrastructure risk still requires attention.',
},
runtimeState: 'active',
}),
).toEqual({
title: 'No active findings',
body: 'Patrol has not surfaced active findings, but the overall Patrol assessment still needs attention.',
tone: 'error',
});
});
it('returns the patrol runtime explanation when the runtime is blocked', () => {
expect(
getPatrolFindingsEmptyState({

View file

@ -60,6 +60,10 @@ export interface PatrolFindingsEmptyStateCopy {
}
const HEALTHY_PATROL_EMPTY_STATE_BODY = 'Your infrastructure looks healthy!';
const DEGRADED_COVERAGE_EMPTY_STATE_BODY =
'Patrol has not surfaced active findings, but coverage is incomplete, so this is not a full all-clear.';
const DEGRADED_HEALTH_EMPTY_STATE_BODY =
'Patrol has not surfaced active findings, but the overall Patrol assessment still needs attention.';
function getHealthDegradedTone(overallHealth: IntelligenceHealthScore): SemanticTone {
return overallHealth.grade === 'D' || overallHealth.grade === 'F' ? 'error' : 'warning';
@ -77,6 +81,14 @@ function shouldSuppressHealthyEmptyState(overallHealth: IntelligenceHealthScore
return overallHealth.grade !== 'A';
}
function getDegradedPatrolEmptyStateBody(overallHealth: IntelligenceHealthScore): string {
if (overallHealth.factors.some((factor) => factor.category === 'coverage')) {
return DEGRADED_COVERAGE_EMPTY_STATE_BODY;
}
return DEGRADED_HEALTH_EMPTY_STATE_BODY;
}
export function getPatrolFindingsEmptyState(args: {
filter: FindingsFilter;
overallHealth?: IntelligenceHealthScore;
@ -106,9 +118,7 @@ export function getPatrolFindingsEmptyState(args: {
if (shouldSuppressHealthyEmptyState(args.overallHealth)) {
return {
title: 'No active findings',
body:
args.overallHealth?.prediction?.trim() ||
'Patrol has not surfaced active findings, but overall infrastructure health is not fully verified.',
body: getDegradedPatrolEmptyStateBody(args.overallHealth!),
tone: getHealthDegradedTone(args.overallHealth!),
};
}