mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-23 03:33:53 +00:00
Clarify patrol supporting context evidence
This commit is contained in:
@@ -380,7 +380,10 @@ when the disabled candidate no longer counts toward monitored-system capacity.
|
||||
Patrol workflow on otherwise healthy fully verified states, and the
|
||||
disclosure copy must explicitly tell operators that findings and run history
|
||||
are the Patrol verification evidence while those supporting cards only add
|
||||
explanation from the same governed payload family
|
||||
explanation from the same governed payload family, and the Patrol-owned
|
||||
helper `frontend-modern/src/features/patrol/patrolSupportingContextPresentation.ts`
|
||||
must keep that transport-derived trust copy aligned across the workspace
|
||||
disclosure rather than letting page shells invent local wording
|
||||
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
|
||||
|
||||
@@ -288,6 +288,7 @@ work extends shared components instead of creating new local variants.
|
||||
`frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx`,
|
||||
`frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts`,
|
||||
`frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts`,
|
||||
`frontend-modern/src/features/patrol/patrolSupportingContextPresentation.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
|
||||
@@ -299,7 +300,10 @@ work extends shared components instead of creating new local variants.
|
||||
`frontend-modern/src/App.tsx` and `frontend-modern/src/AppLayout.tsx` must
|
||||
likewise expose `/patrol` as the canonical route and navigation target,
|
||||
keeping legacy `/ai` entry points as thin compatibility redirects rather
|
||||
than a second Patrol-branded primary route.
|
||||
than a second Patrol-branded primary route. The Patrol-owned supporting-context
|
||||
presenter must also keep the disclosure toggle plus evidence-boundary copy
|
||||
centralized instead of letting the workspace reintroduce inline shell-local
|
||||
trust wording.
|
||||
19. Keep the shared `system-ai` settings shell product-first.
|
||||
`frontend-modern/src/components/Settings/AISettings.tsx`,
|
||||
`frontend-modern/src/components/Settings/settingsHeaderMeta.ts`,
|
||||
@@ -1630,7 +1634,9 @@ explanation; healthy fully verified Patrol states must not advertise that
|
||||
supporting evidence as a peer workflow. When that disclosure expands, the
|
||||
workspace must explicitly label findings and run history as Patrol verification
|
||||
evidence and frame the supporting cards as explanatory context rather than as a
|
||||
fresh Patrol result.
|
||||
fresh Patrol result. `frontend-modern/src/features/patrol/patrolSupportingContextPresentation.ts`
|
||||
must own that disclosure copy and toggle wording so the Patrol workspace does
|
||||
not regress into inline shell-local trust language.
|
||||
|
||||
Shared primitive consumers that split status-dot tone and status-text tone
|
||||
must now keep both values routed through the same exported presentation helper.
|
||||
|
||||
@@ -317,6 +317,10 @@ The same hierarchy applies to supporting context. Correlations, recent
|
||||
changes, and policy posture are secondary evidence for deeper investigation, so
|
||||
the supporting-context disclosure belongs beneath the primary findings/history
|
||||
workspace rather than inside the assessment card itself.
|
||||
When that disclosure is expanded, the page must explicitly tell operators that
|
||||
findings and run history are Patrol verification evidence, while recent
|
||||
changes, learned correlations, and policy posture are explanatory context and
|
||||
do not count as a fresh Patrol run.
|
||||
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.
|
||||
|
||||
@@ -212,6 +212,12 @@ assembly branch.
|
||||
`Analysis`, `Analysis Reasoning`, and `Safe Summary` rather than reviving
|
||||
generic `AI` or `AI-Safe` branding inside the resource drawer or discovery
|
||||
shell.
|
||||
14. Keep shared policy-posture framing on the unified-resource card owner.
|
||||
`frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx`
|
||||
may accept caller-owned subtitle or resource-count wording when Patrol or
|
||||
another shared surface needs to explain how the same governed policy counts
|
||||
should be read, but those framing lines must extend the shared card API
|
||||
rather than spawning page-local policy summary shells.
|
||||
|
||||
## Forbidden Paths
|
||||
|
||||
@@ -1403,7 +1409,10 @@ sensitivity, routing, and redaction labels and aggregate count summaries.
|
||||
That shared policy card also owns caller-supplied framing lines such as
|
||||
subtitle and resource-count wording, so Patrol or other shared surfaces may
|
||||
clarify whether the same governed counts read as policy-covered-resource
|
||||
context without rebuilding their own policy-posture card shell.
|
||||
context without rebuilding their own policy-posture card shell. New
|
||||
shared-surface framing needs such as Patrol's `policy-covered resources`
|
||||
count label or explanatory subtitle should extend that card API instead of
|
||||
forking a second page-local policy summary.
|
||||
Future correlation or policy-posture wording changes should extend those
|
||||
unified-resource owners instead of drifting into page-local loops in AI,
|
||||
Patrol, or infrastructure surfaces.
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
interface ResourcePolicySummaryProps {
|
||||
posture?: IntelligencePolicyPostureSummary | null;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
resourceCountLabel?: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@@ -28,7 +30,12 @@ export const ResourcePolicySummary: Component<ResourcePolicySummaryProps> = (pro
|
||||
<h3 class="text-sm font-semibold text-base-content">
|
||||
{props.title ?? 'Data Governance'}
|
||||
</h3>
|
||||
<p class="mt-1 text-xs text-muted">{value().total_resources} governed resources</p>
|
||||
<Show when={props.subtitle}>
|
||||
{(subtitle) => <p class="mt-1 text-xs text-muted">{subtitle()}</p>}
|
||||
</Show>
|
||||
<p class="mt-1 text-xs text-muted">
|
||||
{value().total_resources} {props.resourceCountLabel ?? 'governed resources'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
+19
@@ -35,4 +35,23 @@ describe('ResourcePolicySummary', () => {
|
||||
expect(screen.getByText('Hostname 3')).toBeInTheDocument();
|
||||
expect(screen.getByText('IP Address 1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('supports caller-owned subtitle and resource count wording', () => {
|
||||
render(() => (
|
||||
<ResourcePolicySummary
|
||||
title="Policy coverage"
|
||||
subtitle="Coverage posture for policy-covered resources."
|
||||
resourceCountLabel="policy-covered resources"
|
||||
posture={{
|
||||
total_resources: 4,
|
||||
sensitivity_counts: {},
|
||||
routing_counts: {},
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.getByText('Policy coverage')).toBeInTheDocument();
|
||||
expect(screen.getByText('Coverage posture for policy-covered resources.')).toBeInTheDocument();
|
||||
expect(screen.getByText('4 policy-covered resources')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,6 +7,16 @@ import { formatTriggerReason } from '@/utils/patrolFormat';
|
||||
import { ResourcePolicySummary } from '@/components/Infrastructure/ResourcePolicySummary';
|
||||
import { ResourceCorrelationSummary } from '@/components/Infrastructure/ResourceCorrelationSummary';
|
||||
import { ResourceChangeSummary } from '@/components/Infrastructure/ResourceChangeSummary';
|
||||
import {
|
||||
getPatrolSupportingContextCorrelationSummary,
|
||||
getPatrolSupportingContextToggleLabel,
|
||||
PATROL_SUPPORTING_CONTEXT_CHANGE_SUBTITLE,
|
||||
PATROL_SUPPORTING_CONTEXT_DESCRIPTION,
|
||||
PATROL_SUPPORTING_CONTEXT_EVIDENCE_LABEL,
|
||||
PATROL_SUPPORTING_CONTEXT_EVIDENCE_NOTE,
|
||||
PATROL_SUPPORTING_CONTEXT_POLICY_SUBTITLE,
|
||||
PATROL_SUPPORTING_CONTEXT_TITLE,
|
||||
} from './patrolSupportingContextPresentation';
|
||||
import type { PatrolIntelligenceState } from './usePatrolIntelligenceState';
|
||||
|
||||
export function PatrolIntelligenceWorkspace(props: { state: PatrolIntelligenceState }) {
|
||||
@@ -128,11 +138,10 @@ export function PatrolIntelligenceWorkspace(props: { state: PatrolIntelligenceSt
|
||||
<div class="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.16em] text-muted">
|
||||
Supporting context
|
||||
{PATROL_SUPPORTING_CONTEXT_TITLE}
|
||||
</p>
|
||||
<p class="mt-1 text-sm text-muted">
|
||||
Recent changes, learned correlations, and policy coverage that may explain
|
||||
findings or incomplete verification.
|
||||
{PATROL_SUPPORTING_CONTEXT_DESCRIPTION}
|
||||
</p>
|
||||
<Show when={state.investigationContextSummary()}>
|
||||
<p class="mt-1 text-xs text-base-content">{state.investigationContextSummary()}</p>
|
||||
@@ -144,33 +153,49 @@ 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 supporting context' : 'View supporting context'}
|
||||
{getPatrolSupportingContextToggleLabel(state.showInvestigationContext())}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<Show when={state.showInvestigationContext()}>
|
||||
<div class="mt-4 grid gap-4 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
|
||||
<Show when={state.recentChangeCount() > 0}>
|
||||
<ResourceChangeSummary
|
||||
class="space-y-0"
|
||||
title="Recent changes"
|
||||
subtitle="Last 24 hours"
|
||||
changes={state.intelligenceSummary()?.recent_changes}
|
||||
maxChanges={3}
|
||||
compact
|
||||
/>
|
||||
</Show>
|
||||
<div class="mt-4 space-y-4">
|
||||
<div class="rounded-md border border-border-subtle bg-base px-3 py-2">
|
||||
<p class="text-[11px] font-semibold uppercase tracking-[0.16em] text-muted">
|
||||
{PATROL_SUPPORTING_CONTEXT_EVIDENCE_LABEL}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-muted">{PATROL_SUPPORTING_CONTEXT_EVIDENCE_NOTE}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<Show when={state.correlations().length > 0}>
|
||||
<ResourceCorrelationSummary
|
||||
title="Learned correlations"
|
||||
correlations={state.correlations()}
|
||||
summaryText={`${state.correlationTotal()} total`}
|
||||
<div class="grid gap-4 lg:grid-cols-[minmax(0,1.4fr)_minmax(0,1fr)]">
|
||||
<Show when={state.recentChangeCount() > 0}>
|
||||
<ResourceChangeSummary
|
||||
class="space-y-0"
|
||||
title="Recent changes"
|
||||
subtitle={PATROL_SUPPORTING_CONTEXT_CHANGE_SUBTITLE}
|
||||
changes={state.intelligenceSummary()?.recent_changes}
|
||||
maxChanges={3}
|
||||
compact
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<ResourcePolicySummary posture={state.policyPosture()} title="Policy coverage" />
|
||||
<div class="space-y-4">
|
||||
<Show when={state.correlations().length > 0}>
|
||||
<ResourceCorrelationSummary
|
||||
title="Learned correlations"
|
||||
correlations={state.correlations()}
|
||||
summaryText={getPatrolSupportingContextCorrelationSummary(
|
||||
state.correlationTotal(),
|
||||
)}
|
||||
/>
|
||||
</Show>
|
||||
|
||||
<ResourcePolicySummary
|
||||
posture={state.policyPosture()}
|
||||
title="Policy coverage"
|
||||
subtitle={PATROL_SUPPORTING_CONTEXT_POLICY_SUBTITLE}
|
||||
resourceCountLabel="policy-covered resources"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
getPatrolSupportingContextCorrelationSummary,
|
||||
getPatrolSupportingContextToggleLabel,
|
||||
PATROL_SUPPORTING_CONTEXT_CHANGE_SUBTITLE,
|
||||
PATROL_SUPPORTING_CONTEXT_DESCRIPTION,
|
||||
PATROL_SUPPORTING_CONTEXT_EVIDENCE_LABEL,
|
||||
PATROL_SUPPORTING_CONTEXT_EVIDENCE_NOTE,
|
||||
PATROL_SUPPORTING_CONTEXT_POLICY_SUBTITLE,
|
||||
PATROL_SUPPORTING_CONTEXT_TITLE,
|
||||
} from '../patrolSupportingContextPresentation';
|
||||
|
||||
describe('patrolSupportingContextPresentation', () => {
|
||||
it('exports the canonical supporting-context trust copy', () => {
|
||||
expect(PATROL_SUPPORTING_CONTEXT_TITLE).toBe('Supporting context');
|
||||
expect(PATROL_SUPPORTING_CONTEXT_DESCRIPTION).toBe(
|
||||
'Recent changes, learned correlations, and policy coverage that may explain findings or incomplete verification.',
|
||||
);
|
||||
expect(PATROL_SUPPORTING_CONTEXT_EVIDENCE_LABEL).toBe('How to read this');
|
||||
expect(PATROL_SUPPORTING_CONTEXT_EVIDENCE_NOTE).toBe(
|
||||
'Findings and run history are Patrol verification evidence. The cards below add explanatory context and do not count as a fresh full patrol.',
|
||||
);
|
||||
expect(PATROL_SUPPORTING_CONTEXT_CHANGE_SUBTITLE).toBe(
|
||||
'Observed from the canonical timeline in the last 24 hours.',
|
||||
);
|
||||
expect(PATROL_SUPPORTING_CONTEXT_POLICY_SUBTITLE).toBe(
|
||||
'Coverage posture for policy-covered resources.',
|
||||
);
|
||||
});
|
||||
|
||||
it('builds canonical supporting-context action labels and correlation summaries', () => {
|
||||
expect(getPatrolSupportingContextToggleLabel(false)).toBe('View supporting context');
|
||||
expect(getPatrolSupportingContextToggleLabel(true)).toBe('Hide supporting context');
|
||||
expect(getPatrolSupportingContextCorrelationSummary(2)).toBe(
|
||||
'2 learned patterns · explanatory context',
|
||||
);
|
||||
expect(getPatrolSupportingContextCorrelationSummary(1)).toBe(
|
||||
'1 learned pattern · explanatory context',
|
||||
);
|
||||
expect(getPatrolSupportingContextCorrelationSummary(Number.NaN)).toBe(
|
||||
'Learned pattern context',
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
export const PATROL_SUPPORTING_CONTEXT_TITLE = 'Supporting context';
|
||||
export const PATROL_SUPPORTING_CONTEXT_DESCRIPTION =
|
||||
'Recent changes, learned correlations, and policy coverage that may explain findings or incomplete verification.';
|
||||
export const PATROL_SUPPORTING_CONTEXT_EVIDENCE_LABEL = 'How to read this';
|
||||
export const PATROL_SUPPORTING_CONTEXT_EVIDENCE_NOTE =
|
||||
'Findings and run history are Patrol verification evidence. The cards below add explanatory context and do not count as a fresh full patrol.';
|
||||
export const PATROL_SUPPORTING_CONTEXT_CHANGE_SUBTITLE =
|
||||
'Observed from the canonical timeline in the last 24 hours.';
|
||||
export const PATROL_SUPPORTING_CONTEXT_POLICY_SUBTITLE =
|
||||
'Coverage posture for policy-covered resources.';
|
||||
|
||||
export function getPatrolSupportingContextToggleLabel(expanded: boolean) {
|
||||
return expanded ? 'Hide supporting context' : 'View supporting context';
|
||||
}
|
||||
|
||||
export function getPatrolSupportingContextCorrelationSummary(count: number) {
|
||||
if (!Number.isFinite(count) || count <= 0) {
|
||||
return 'Learned pattern context';
|
||||
}
|
||||
return `${count} learned pattern${count === 1 ? '' : 's'} · explanatory context`;
|
||||
}
|
||||
@@ -428,6 +428,11 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
predictions_count: 0,
|
||||
recent_changes_count: 0,
|
||||
recent_changes: [],
|
||||
policy_posture: {
|
||||
total_resources: 3,
|
||||
sensitivity_counts: {},
|
||||
routing_counts: {},
|
||||
},
|
||||
learning: {
|
||||
resources_with_knowledge: 0,
|
||||
total_notes: 0,
|
||||
@@ -483,7 +488,15 @@ describe('AIIntelligence entitlement gating', () => {
|
||||
expect(screen.getByRole('heading', { name: 'Learned correlations' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('2 total')).toBeInTheDocument();
|
||||
expect(screen.getByText('How to read this')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Findings and run history are Patrol verification evidence. The cards below add explanatory context and do not count as a fresh full patrol.',
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('2 learned patterns · explanatory context')).toBeInTheDocument();
|
||||
expect(screen.getByText('Coverage posture for policy-covered resources.')).toBeInTheDocument();
|
||||
expect(screen.getByText('3 policy-covered resources')).toBeInTheDocument();
|
||||
const storage2Link = screen.getByRole('link', {
|
||||
name: 'Open source resource Storage 2 in Infrastructure',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user