45 KiB
Patrol Intelligence Contract
Contract Metadata
{
"subsystem_id": "patrol-intelligence",
"lane": "L6",
"contract_file": "docs/release-control/v6/internal/subsystems/patrol-intelligence.md",
"status_file": "docs/release-control/v6/internal/status.json",
"registry_file": "docs/release-control/v6/internal/subsystems/registry.json",
"dependency_subsystem_ids": ["frontend-primitives"]
}
Purpose
Own the Patrol intelligence route shell, feature surface, local state orchestration, findings and approval presentation, run-history rendering, and Patrol-specific presentation helpers.
Canonical Files
frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsxfrontend-modern/src/features/patrol/usePatrolIntelligenceState.tsfrontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsxfrontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsxfrontend-modern/src/features/patrol/PatrolIntelligenceSummary.tsxfrontend-modern/src/features/patrol/PatrolIntelligenceWorkspace.tsxfrontend-modern/src/features/patrol/patrolInvestigationContextModel.tsfrontend-modern/src/pages/AIIntelligence.tsxfrontend-modern/src/stores/aiIntelligence.tsfrontend-modern/src/stores/aiIntelligenceSummaryModel.tsfrontend-modern/src/types/aiIntelligence.tsfrontend-modern/src/components/AI/FindingsPanel.tsxfrontend-modern/src/components/Brand/PulsePatrolLogo.tsxfrontend-modern/src/components/patrol/frontend-modern/src/utils/aiFindingPresentation.tsfrontend-modern/src/utils/approvalRiskPresentation.tsfrontend-modern/src/utils/aiPatrolSchedulePresentation.tsfrontend-modern/src/utils/aiQuickstartPresentation.tsfrontend-modern/src/utils/findingAlertIdentity.tsfrontend-modern/src/utils/remediationPresentation.tsfrontend-modern/src/utils/patrolEmptyStatePresentation.tsfrontend-modern/src/utils/patrolFormat.tsfrontend-modern/src/utils/patrolRunPresentation.tsfrontend-modern/src/utils/patrolSummaryPresentation.tsfrontend-modern/src/utils/patrolRuntimePresentation.tsfrontend-modern/src/utils/textPresentation.ts
Shared Boundaries
- None.
Extension Points
- Add or change Patrol page orchestration through
frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts, keepfrontend-modern/src/features/patrol/patrolInvestigationContextModel.tsas the canonical investigation-context derivation owner, keepfrontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsxas the feature shell, keep the Patrol-owned section files underfrontend-modern/src/features/patrol/as the heavy render owners, keepfrontend-modern/src/pages/AIIntelligence.tsxas the route shell, keepfrontend-modern/src/stores/aiIntelligenceSummaryModel.tsas the canonical AI summary normalization owner, and updatefrontend-modern/src/stores/aiIntelligence.tstogether - Add or change Patrol findings, approvals, investigation, or run-history presentation through
frontend-modern/src/components/AI/FindingsPanel.tsxandfrontend-modern/src/components/patrol/ - Keep remediation execution badge copy and severity styling aligned through
frontend-modern/src/components/patrol/RemediationStatus.tsxandfrontend-modern/src/utils/remediationPresentation.ts - Add or change Patrol header, summary, or status runtime-state presentation through
frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx,frontend-modern/src/features/patrol/PatrolIntelligenceSummary.tsx,frontend-modern/src/components/patrol/PatrolStatusBar.tsx, andfrontend-modern/src/utils/patrolRuntimePresentation.ts - Add or change Patrol header quickstart-credit or schedule presentation through
frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx,frontend-modern/src/utils/aiQuickstartPresentation.ts, andfrontend-modern/src/utils/aiPatrolSchedulePresentation.tsPatrol must not show the exhausted quickstart-credit warning while runtime state is active on a non-quickstart provider path; that warning belongs only to active quickstart usage or a blocked quickstart-exhausted runtime. - Keep Patrol and chat identifier-label presentation aligned through the shared
frontend-modern/src/utils/textPresentation.ts - Keep Patrol and chat stream-matching / mention dedupe aligned through the shared
frontend-modern/src/utils/chatIdentifiers.ts - Keep Patrol transport and payload changes aligned through the governed AI runtime and API contract transport surfaces
Forbidden Paths
- Reintroducing Patrol finding, investigation, approval, or run-history copy directly inside page components when canonical Patrol presentation helpers already own it
- Duplicating Patrol finding severity, lifecycle, alert-identity, or approval-risk derivation outside the governed Patrol presentation helpers
- Letting the Patrol page, local store, and findings UI drift into separate shadow truths for the same Patrol status or finding lifecycle state
Completion Obligations
- Update Patrol page, state, presentation helpers, and proof files together when Patrol UX semantics change
- Keep Patrol-specific copy and badge logic inside the governed Patrol presentation helpers instead of page-local branches
- Update this contract whenever a new Patrol-specific page, store, helper, or presentation component becomes canonical runtime surface area
- Keep Patrol header quickstart copy Patrol-scoped and runtime-backed: the Patrol header may promise only the server-authoritative quickstart inventory, phrase availability as Patrol runs with no API key on activated or trial-backed installs, and avoid implying broader hosted chat, generic AI credits, or anonymous Community entitlement.
- Keep customer-facing Patrol naming product-first: page titles, route chrome,
summary copy, actions, and empty states should lead with
PatrolorPulse Patrolrather than genericAIbranding. ReserveAIterminology for explicit provider/configuration settings, shared runtime internals, or other technical capability boundaries where the underlying ownership really is AI runtime rather than the Patrol product surface.
Current State
The Patrol page, store, findings UI, and run-history presentation had been outside the governed subsystem map even though they are the top-level runtime surface for Patrol intelligence. This contract now owns that orchestration and presentation boundary while leaving shared transport and payload-shape ownership in the governed AI runtime and API contract surfaces.
The route file frontend-modern/src/pages/AIIntelligence.tsx is now also a
thin shell that delegates to the feature-owned
frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx, so Patrol
runtime state and presentation no longer accumulate directly in the route
component itself. The governed customer-facing route for that shell is now
/patrol, while legacy /ai entry points remain compatibility redirects
instead of the canonical product path.
That route-shell ownership does not make AI the customer-facing Patrol
product name. Internal file, store, and transport names may still carry the
shared AI-runtime boundary where that is the real technical ownership, but the
operator-facing Patrol experience should present Patrol as the product and
avoid drifting back to generic AI Intelligence branding outside explicit
provider/settings affordances.
The feature surface now also keeps the same shell/runtime split internally:
frontend-modern/src/features/patrol/PatrolIntelligenceSurface.tsx owns feature
composition, the Patrol-owned section files under
frontend-modern/src/features/patrol/ own the header, banner, summary, and
workspace render surfaces, and
frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts owns Patrol
state, transport, polling, and effect lifecycle. The shell and section surfaces
must not re-accumulate Patrol API calls, timer orchestration, or store refresh
semantics, and frontend-modern/src/stores/aiIntelligenceSummaryModel.ts now
owns the canonical summary normalization so Patrol consumers inherit one
governed recent-change and policy-posture snapshot instead of reintroducing
hook-local fallback logic.
That same Patrol hook boundary now consumes shared AI settings/model truth
through frontend-modern/src/stores/aiRuntimeState.ts instead of mounting its
own /api/settings/ai or /api/ai/models reads. Patrol-specific state still
owns local toggle optimism, run-status orchestration, and Patrol-only copy,
including quickstart exhaustion and runtime-availability messaging that stays
Patrol-first in operator-facing shells and uses provider/API-key wording only
for the actual configuration boundary, but the underlying AI runtime catalog
must stay shared with chat and AI settings.
The Patrol page now also treats Patrol runtime availability as a first-class
render contract: the header chip, primary summary card, and status bar must
all route through the shared frontend-modern/src/utils/patrolRuntimePresentation.ts
helper plus the backend runtime_state payload instead of inferring operator
state from the last healthy summary snapshot or run history alone.
That active-runtime label must stay operational rather than verdict-like: the
header chip should communicate that Patrol is enabled or available, not imply
that infrastructure health is currently good merely because the runtime is on.
That render rule now also has browser-level proof in
tests/integration/tests/18-patrol-runtime-state.spec.ts: when the backend
reports runtime_state=blocked, the real /patrol route must show Patrol as
paused, keep the blocked reason visible, disable manual Patrol runs, and
suppress stale healthy summary headlines such as Health A · 100/100 even if
the last summary payload still looks healthy. Legacy /ai entry points must
redirect into that same Patrol-owned shell rather than preserving a second
canonical route.
That same Patrol-owned presentation rule also applies to the findings empty
state: frontend-modern/src/components/AI/FindingsPanel.tsx must not treat
0 active findings as equivalent to "your infrastructure looks healthy" when
the Patrol runtime is blocked, disabled, or unavailable, or when the canonical
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.
Patrol paywall actions now follow the same shared commercial navigation split.
frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx,
frontend-modern/src/features/patrol/PatrolIntelligenceBanners.tsx, and
frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts may resolve
commercial destinations from the shared license boundary, but they must leave
internal-versus-external navigation semantics to frontend-primitives once a
Patrol feature can resolve to either product-owned routes or public pricing.
That same Patrol-owned commercial boundary must also fail closed in public
demo runtimes. Patrol header and banner upgrade/trial affordances may render
for real customer workspaces, but the browser-owned trigger for that
suppression is now the shared resolved presentationPolicy from
/api/security/status, seeded by the backend capability fact
sessionCapabilities.demoMode. Patrol surfaces must therefore suppress
upgrade CTAs, trial nudges, and Pro-only helper copy only from that shared
policy instead of reviving local demo heuristics or issuing early commercial
reads before the policy resolves.
That same shared policy now also owns Patrol approval polling posture.
frontend-modern/src/stores/aiIntelligence.ts must fail
loadPendingApprovals() closed in public demo mode and return the canonical
empty queue from the shared store boundary itself, so dashboard and Patrol
shells do not probe /api/ai/approvals after the read-only demo policy has
resolved.
That same store-owned demo boundary also covers remediation artifacts.
frontend-modern/src/stores/aiIntelligence.ts must fail
loadRemediationPlans() closed in public demo mode and
frontend-modern/src/components/AI/FindingsPanel.tsx must consume
aiIntelligenceStore.remediationPlans instead of issuing its own
AIAPI.getRemediationPlans() read, so the public Patrol page does not trigger
/api/ai/remediation/plans paywall probes after demo posture has resolved.
That same posture split now also centralizes Patrol commercial bootstrap.
frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts and
frontend-modern/src/components/patrol/ApprovalSection.tsx may consume the
resolved commercial-posture store when Patrol needs upgrade or trial context,
but they must not trigger their own mount-time loadCommercialPosture()
reads. Authenticated Patrol shells inherit that bootstrap from
frontend-modern/src/useAppRuntimeState.ts, so Patrol-specific hooks do not
quietly retake ownership of commercial fetch timing.
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,
issues detected, or coverage incomplete, with the health grade only as
supporting evidence. Secondary metric strips must not render No issues found
when the same governed overall-health summary says coverage is incomplete or
health still requires attention; that compact summary state now belongs to the
shared frontend-modern/src/utils/patrolSummaryPresentation.ts helper.
That primary assessment shell must also stay inside the shared Pulse card
language. The top summary should use the same neutral bg-surface page-card
base as adjacent Pulse surfaces, carrying severity through compact header
accents, icon treatments, and badges instead of tinting the entire full-width
assessment panel as a one-off warning banner.
That supporting score chip must also avoid overstating infrastructure truth.
When the current state is dominated by incomplete coverage or Patrol-owned
runtime failures, the chip should read as an Assessment grade rather than an
Health grade; Health belongs to verified healthy infrastructure states.
That same helper also owns the primary assessment explanation. The summary card
must not pair an Issues detected headline with a raw coverage-only
overall_health.prediction sentence from a separate source; when active
findings and incomplete verification are both true, the Patrol summary should
describe both in one canonical assessment message.
That same assessment contract must also distinguish Patrol-owned runtime
findings from infrastructure findings. When the only active Patrol findings are
synthetic Patrol service/runtime conditions such as the ai-service
provider-credit failure, the top assessment should read as a Patrol runtime
issue rather than implying infrastructure issues were detected across the
estate. When there is exactly one active Patrol runtime finding, that same
assessment copy should name the concrete runtime failure, such as
Insufficient API credits, instead of reducing the state to a generic count of
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 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
the same Patrol runtime classification with a runtime-qualified severity badge
such as Runtime issue or Runtime critical, rather than pairing a generic
infrastructure severity chip like warning with a second Patrol-runtime
classification badge.
That same shared finding-presentation helper should also own Patrol finding
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 title presentation contract should normalize Patrol-owned finding
titles too. The primary findings row, assistant handoff copy, and inline
approval surfaces should present runtime findings as Insufficient API credits
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 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 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
supporting metrics must stop counting them under generic infrastructure
Warnings or Active findings; the strip should break out Runtime issues
separately and reserve infrastructure finding counts for actual estate issues.
The findings list must respect that same trust priority. When Patrol-owned
runtime issues share a severity tier with ordinary infrastructure findings, the
runtime issue should sort first within that tier so Patrol blindness is not
buried under same-severity estate warnings.
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
belongs only to the most recent completed full Patrol run.
That same distinction is transport-backed. last_patrol_at names the last
completed full Patrol sweep, while last_activity_at may advance on scoped
work or fix-verification checks without claiming a new full verification pass.
That same recency contract also applies to the header metadata row. The top
header must not revert to a generic Last: timestamp when the rest of Patrol
is explicitly distinguishing activity from full verification recency.
That summary surface must also avoid reintroducing a second compact assessment
or verification layer beneath the primary card. Supporting metric strips
belong to counts and outcomes such as active findings, critical findings,
warnings, and fixes; they must not repeat Patrol assessment labels or
verification labels in a second row that competes with the primary governed
assessment and verification copy above.
The same supporting-chip rule applies to timing: the primary summary card may
show health and active-finding support, but it should not add another recency
pill once header metadata, verification, and findings footer already carry the
governed activity/verification timestamps.
That same Patrol-owned run-history surface must keep platform-backed system
counts canonical too. When the backend distinguishes API-backed TrueNAS
systems from unified-agent hosts in Patrol run history, the run-history chips
and selected-run breakdown must render TrueNAS separately instead of
collapsing those systems back into the generic agent count.
That run-history distinction must not reopen a parallel raw truenas
resource-type contract in Patrol findings, scoped-run filters, or alert-backed
resource state. Those payloads stay canonical agent plus platform context,
while the separate TrueNAS count remains a run-history coverage detail.
That summary-card/metrics-strip split also applies to findings counts. The
primary assessment card may keep health as supporting context, but active
findings, warning counts, and critical counts belong to the supporting metric
strip rather than being repeated as duplicate badges inside the primary card.
That same summary surface must also explain what Patrol actually verified.
Recent run history should drive a visible verification summary that tells the
operator whether Patrol recently completed a successful full patrol, only ran
scoped alert-triggered checks, or ended its most recent full patrol with
errors, so the page does not leave trust and coverage as implicit background
knowledge.
When same-day run history shows both a recent full patrol and a burst of
scoped follow-up activity, that same verification surface should expose the
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 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.
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
plus the shared frontend-modern/src/utils/patrolRunPresentation.ts helpers
should carry the latest run kind/result, recent activity mix, scoped-trigger
state, and any circuit-breaker warning as factual support beneath the primary
assessment instead of letting Patrol explain itself through multiple parallel
status bands.
That same secondary area should simplify by consolidation rather than
deletion. Patrol should keep meaningful run and trigger facts visible, but it
should stop repeating the same runtime story as a summary card, a metric row,
and a separate status strip all at once.
If frontend-modern/src/components/patrol/PatrolStatusBar.tsx is reused in a
compact context elsewhere, it must stay factual and operational within that
same hierarchy rather than introducing a second Patrol verdict label.
That same activity explanation should handle noisy Patrol behavior concretely.
When same-day history shows a mix of full sweeps, verification checks, and
scoped alert- or anomaly-triggered patrols, the verification/activity surface
should expose a compact breakdown of those run categories instead of leaving
operators to infer why Patrol looked busy from an undifferentiated run count
alone.
The same operational layer should also surface scoped-trigger state directly.
If Patrol has queued scoped work, is in busy mode, or has one scoped trigger
source disabled, the governed summary should state that as factual runtime
context instead of forcing operators to cross-reference hidden settings or
infer it from missing runs.
That recent-activity copy also has to remain intelligible in compact or
plain-text renders: the latest-run segment must keep an explicit textual
separator between run kind and result, so degraded entries read as
Scoped run · error rather than collapsing into concatenated strings like
Scoped runerror.
Those runtime facts must stay aligned with Patrol configuration copy. The
header settings surface should expose alert-triggered and anomaly-triggered
scoped patrols as separate controls, with the legacy aggregate event-trigger
toggle treated as compatibility-only transport rather than the primary product
model.
The findings empty state must also stay subordinate to the Patrol header and
assessment shell rather than mirroring their timing metadata. In the primary
Patrol page, frontend-modern/src/components/AI/FindingsPanel.tsx should
explain the absence of active findings without repeating Last activity,
Next run, or interval schedule details that already belong to the header and
verification hierarchy above.
That same empty-state contract must become run-snapshot-aware when the user is
filtering findings to an explicit Patrol run. A selected run with an explicit
empty finding_ids snapshot should explain that no findings were recorded for
that run and, when applicable, carry the canonical run coverage summary and
issue caveat instead of falling back to the generic No Patrol findings to display copy.
That same snapshot scoping must apply to the findings control bar too. When the
user is looking at an explicit run snapshot, filter pills and their attention
or approval counts must derive from that snapshot-scoped finding set rather
than borrowing global Patrol finding counts from outside the selected run.
That same snapshot scoping must also apply to the Findings tab badge itself.
When the selected run carries an explicit empty finding_ids snapshot, or when
the run lacks snapshot ids entirely, the tab must fail closed instead of
borrowing global active-finding counts and tones from outside the selected run.
That same scoped count model must drive conditional findings filters too. The
Needs Attention and Approvals buckets, their counts, and the auto-reset
logic that returns the operator to Active when a bucket disappears must all
read from the same snapshot-aware count source rather than mixing
snapshot-scoped pills with global queue truth.
That same fail-closed snapshot rule applies to inline run-history findings as
well. Expanded run cards should route through the same snapshot-aware findings
surface as the primary workspace, so legacy runs without finding_ids still
show an explicit snapshot-unavailable state instead of disappearing or being
coerced into an empty findings snapshot.
That same rule applies to the primary findings workspace when a run is
selected. A selected run without finding_ids must not borrow global Patrol
findings, filter buckets, or queue counts; the findings surface should enter an
explicit snapshot-unavailable state instead.
That same unknown-snapshot state should be visible in the selected-run shell
too. When the operator is filtered to a legacy run without findings snapshot
ids, the selected-run banner should explicitly say that findings snapshot data
is unavailable instead of implying a fully verifiable run-scoped findings view.
That same trust rule applies inside expanded run-history narratives. A legacy
run without findings snapshot ids must not render an All clear conclusion
just because its aggregate counters are zero; the narrative should explicitly
state that run-specific findings could not be fully verified.
That same truthfulness rule applies to the expanded run outcomes strip. Legacy
runs without findings snapshot ids must not render a green All clear outcome
badge from zero aggregate counters; they should show an explicit snapshot
unavailable state instead.
That same caveat belongs in compact latest-run summaries too. When the most
recent Patrol run predates findings snapshots, the status bar's latest-run
segment should say that findings snapshot data is unavailable instead of
flattening the run into a plain healthy-looking summary.
That same caveat belongs in collapsed run-history rows. Legacy runs without
findings snapshot ids must carry an explicit snapshot-unavailable marker in the
top row instead of looking like a clean zero-findings run until expanded.
That same rule applies to run-status badges. A legacy run without findings
snapshot ids must not keep a green healthy badge when the surrounding UI is
saying findings verification is unavailable; the canonical run-status
presentation should downgrade that state to a neutral completed badge.
That same truthfulness rule applies to the run-history shell copy. The Recent patrol runs helper text must not promise that every visible run can filter
findings to a concrete snapshot; when visible runs include legacy entries
without finding_ids, or when the selected run itself predates findings
snapshots, the shell should say so explicitly.
That same findings surface should keep its section chrome functional rather
than promotional. Inside the Patrol findings tab, the selected tab already
names the surface, so the findings card should not add another in-card product
header or marketing subtitle that simply repeats the tab-level context.
That same control bar should also stay proportional to the current findings
set. Sort controls belong only when there are multiple Patrol findings to
order; the empty state or single-finding state should not spend header space
on a no-op sort selector.
The same proportionality rule applies to the findings filter bar itself. When
there are no Patrol findings and no special approval or attention queues to
navigate, the findings surface should not render filter pills that lead only
to the same empty state.
Tab badges and finding metadata in that same surface also need explicit textual
separators rather than CSS-only spacing. Badge counts should preserve readable
plain-text output such as Findings 1 and Runs 30, and metadata trails
should use textual separators like · acknowledged 22d ago so copied or
extracted Patrol output does not collapse words together.
That same findings contract must preserve and use last_seen_at for active
Patrol findings. Recurring active issues should read as last seen ... and
sort by current observation recency rather than presenting only the original
detected_at, which makes still-active Patrol service issues look stale when
they were re-observed on recent runs.
That same finding row should avoid redundant state stacking. When an active
finding is already explicitly marked Acknowledged, the UI should not also add
the baseline detected loop-state badge beside it; loop-state badges only add
value there when they communicate a more specific Patrol state.
When Patrol is currently running, that strip should still stay factual rather
than switching to another verdict label: the runtime may add an explicit
in-progress indicator, but the primary activity label remains recent activity
instead of a competing Patrol-state verdict.
That latest run result must come from the effective run outcome, not the raw
status field alone: shared Patrol run presentation helpers must treat any run
with execution errors as an error result even when the stored status text is
still healthy, so run history and header activity surfaces do not present a
false green outcome for an incomplete Patrol execution.
hook-local fallback logic.
The Patrol header now also has explicit helper ownership for its quickstart and
schedule presentation. frontend-modern/src/utils/aiQuickstartPresentation.ts
and frontend-modern/src/utils/aiPatrolSchedulePresentation.ts are the
canonical owners for quickstart-credit messaging and Patrol interval option
labeling used by frontend-modern/src/features/patrol/PatrolIntelligenceHeader.tsx;
future Patrol header work should extend those helpers instead of rebuilding
credit badges or schedule labels inline in the header shell. That same header
surface must now gate exhausted-credit messaging on actual runtime usage:
Patrol may only show the quickstart exhausted warning when using_quickstart
is true or when the Patrol runtime is explicitly blocked by quickstart
exhaustion, not merely because the stored credit counter reached zero while a
configured provider path keeps Patrol active.
That same header copy must stay Patrol-only in operator-facing language:
available inventory should read as free Patrol quickstart runs with no API
key for Patrol on the current activated or trial-backed install, and
activation-required states must trust the canonical blocked reason instead of
relabeling stale counters as exhausted. Exhaustion should direct the operator
toward BYOK for Patrol rather than implying a broader hosted AI allowance.
frontend-modern/src/utils/remediationPresentation.ts is now also the
canonical owner for remediation result badge copy and success/failure styling
used by frontend-modern/src/components/patrol/RemediationStatus.tsx, so
Patrol execution feedback does not drift into inline message branches inside
the component.
Patrol finding state must now also consume the canonical camelCase
alertIdentifier field and pending-approval expiry metadata end to end.
Frontend Patrol helpers may not keep shadow alert_identifier fallbacks or
drop expiresAt when deciding whether queued investigation fixes still need
operator attention.
Patrol intelligence seed context should also prefer the canonical
unified-resource timeline before falling back to the patrol-local change
detector so recent-change context stays aligned with the resource timeline
that powers the shared resource API.
Patrol-owned intelligence summaries should keep their recent-change counts
backed by the same canonical timeline when available instead of only counting
Patrol-local detector history.
The Patrol-backed /api/ai/intelligence/changes endpoint should also read
through the canonical intelligence facade first and only fall back to the
local detector when the unified timeline is unavailable, so the API payload
stays aligned with the same governed recent-change source.
Patrol-owned resource and global intelligence prompt contexts should also
render the canonical recent changes section before any patrol-local change
detector fallback so the prompt surface stays aligned with the shared
unified-resource timeline.
When that detector fallback is used, the Patrol runtime must render recent
changes through the shared memory presentation helper so the same heading,
resource prefixing, and change labels are reused across the Patrol and AI
fallback paths.
memory.ChangeDetector.GetChangesSummary now also delegates to that shared
helper, so the detector-owned summary API and the Patrol fallback prompt path
stay aligned on the same markdown shape.
Those same Patrol-owned prompt contexts now also surface a canonical
relationship section from unified-resource relationships, so edge labels,
directionality, and provenance stay aligned with the shared relationship model
instead of being reconstructed locally.
That relationship section is now rendered by the shared
internal/unifiedresources.FormatResourceRelationshipContext helper, so the
Patrol runtime only resolves the canonical relationship context rather than
formatting the relationship section itself.
Patrol-owned correlation context now also comes through the shared AI
intelligence facade before reaching the detector, so the learned correlation
surface is routed through the same canonical AI ownership boundary as recent
changes and relationship data instead of being pulled from the detector
directly in each caller.
The Patrol seed context and AI runtime prompt path now also share the same
correlation summary formatter from internal/ai/correlation, so learned-edge
wording and confidence/count annotations stay canonical across the prompt
surface instead of being rebuilt in each caller.
The Patrol page also now renders the canonical intelligence summary card
through the governed AI client and store, so the visible page summary and the
resource/timeline sections stay aligned on the same shared backend slice.
That same summary card now keeps recent changes and learned correlations
primary while leaving the broader learning counters as backend coverage, so
the page does not present telemetry-style counts as a headline intelligence
story.
That Patrol summary card now also includes the canonical data-governance
posture snapshot from the shared AI summary payload, so the visible page can
show the same sensitivity, routing, and redaction distribution that the
runtime derives from unified resources.
The resource drawer now carries canonical dependency and dependent
correlation context plus canonical correlation evidence through the
resource-intelligence payload, so the resource-level AI card can surface
relationship reachability and learned edge patterns directly from the AI
contract instead of inventing a second correlation summary.
The Patrol intelligence page now also consumes the learned correlation list
from the canonical AI correlations endpoint through the shared
frontend-modern/src/stores/aiIntelligence.ts store, so the global summary
and the resource drawer both reflect the same learned edge evidence instead
of each page fetching its own copy. Both surfaces now render that evidence
through the shared frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx
card, so the correlation layout stays governed by one component instead of
two page-local card implementations. That shared card also owns the
correlation ordering and truncation rule, so the page and drawer hand it raw
correlation lists instead of slicing or re-sorting them locally.
The same page and drawer now also share the canonical
frontend-modern/src/components/Infrastructure/ResourceChangeSummary.tsx
card for recent changes, so the timeline layout and relative-time wording
stay governed by one frontend feed instead of separate page-local loops.
Patrol trial-entry surfaces now also share the canonical
frontend-modern/src/utils/trialStartAction.ts owner for hosted handoff and
denial handling. ApprovalSection.tsx and
usePatrolIntelligenceState.ts may still choose Patrol-specific success copy,
but they must not reintroduce local startProTrial() status-code branches
that diverge from the commercial backend contract.
Pending Patrol fix approvals now also require a canonical urgency order across
the store and Patrol approval surfaces. frontend-modern/src/stores/aiIntelligence.ts,
frontend-modern/src/components/patrol/ApprovalBanner.tsx, and dashboard
approval consumers must treat the approval queue as soonest expiry first,
then higher risk, then older request time, rather than inheriting raw API
order. Approval-linked findings must follow that same ordering so multi-approval
Review actions jump to the most urgent finding instead of an arbitrary one.
That same store now owns the Patrol dashboard load bundle as well, so the
page refresh path stays aligned on a single orchestrated AI bundle instead of
repeating the individual summary, findings, approval, and correlation fetches
inline.
The shared
frontend-modern/src/components/Infrastructure/ResourceChangeSummary.tsx and
frontend-modern/src/components/Infrastructure/ResourceCorrelationSummary.tsx
cards now own the canonical Infrastructure resource-link default, so the
Patrol page and resource drawer inherit resource-filter href construction
through the shared summary cards instead of rebuilding local wrappers in each
surface.
The Patrol intelligence page now also renders the canonical
frontend-modern/src/components/Infrastructure/ResourcePolicySummary.tsx
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 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
instead of rebuilding recent-change, correlation, and governed-resource count
copy inline.
The Patrol page's run-history tab label is now also tightened to Runs, while
the underlying run-history panel remains canonical for snapshot filtering and
tool-call inspection. That copy change is intentional: run history is support
context for Patrol findings, not a peer primary workflow beside findings.
Within that same run-history surface, coverage copy must stay canonical across
the header chips, narrative summary, and selected-run snapshot. Scoped runs
must not present scope size and checked-resource count as contradictory
independent facts; the shared run presenter should collapse them into one
coverage statement such as Checked 1 of 2 scoped resources, and zero-coverage
scoped runs should fail closed as Checked 0 of N scoped resources rather
than drifting back to a scope-only headline.
That same normalization applies to supporting effort strips inside the expanded
run card. Once the run presenter is already carrying canonical coverage copy,
secondary chips must not reintroduce a raw Scoped to N resources variant that
re-opens the same ambiguity.
That same zero-coverage rule also applies to the expanded narrative sentence.
When Patrol checked none of a known scoped set, the run summary must still say
Checked 0 of N scoped resources rather than reverting to a generic Patrol completed sentence that hides the failed coverage.
The Patrol page and resource drawer now also share the canonical
frontend-modern/src/utils/resourceChangePresentation.ts formatter so
recent-change kind and headline wording stays aligned wherever the canonical
timeline is surfaced.
The Patrol page and resource drawer now keep canonical relationship semantics
in the backend correlation path, while the visible frontend stays
timeline-first and does not surface a separate relationship presentation
helper.
The backend Patrol and AI runtime summaries now also share
internal/unifiedresources/change_presentation.go for the canonical
change-kind and provenance mapping, so the same resource-model semantics
drive both the backend summaries and the frontend presentation helpers.
That same helper now also owns the one-line recent-change summary text used
by the AI runtime prompt sections and Patrol seed context, so the change
wording itself stays canonical before the surrounding section headers are
applied.
The same helper now also owns the canonical recent-change section wrapper,
so the Patrol page and AI runtime can share the same heading and resource
prefix rules instead of rebuilding that section locally.
The canonical shared AI resource context now also surfaces policy routing and
redaction hints from unified resources, so the Patrol page and resource drawer
see the same governance posture that the runtime uses for export boundaries.
Patrol finding dismissal reasons and Patrol status labels now also route
through the shared frontend identifier-label helper, so the Patrol surfaces
do not keep their own underscore-stripping behavior separate from the rest
of the governed presentation helpers.
That same Patrol findings surface must also stop being a route dead end.
Expanded finding rows should resolve the backing unified resource and surface
canonical Infrastructure, Workloads, Storage, and Recovery handoffs
through frontend-modern/src/routing/resourceLinks.ts where those surfaces
exist, rather than forcing operators to pivot manually through search or
assistant prompts to continue investigating API-backed platforms such as
TrueNAS.
Patrol upgrade and trial posture now follows the same runtime-versus-
commercial split as the rest of the app. Patrol runtime availability must stay
on the non-commercial capability store, while approval/trial CTAs use the
shared commercial-posture store and trial-start helper. Patrol surfaces must not
recombine those two contracts into one entitlement payload.
Patrol approval and trial shells should consume selector helpers such as
canStartCommercialTrial() from
frontend-modern/src/stores/licenseCommercial.ts instead of branching on raw
commercial-posture fields in leaf components.
Patrol findings presentation now also keeps runtime identity and action routing
on shared helpers. Findings shells may link or format from feature-owned
presentation helpers, but Patrol runtime severity, title cleanup, and primary
settings actions must stay keyed to the canonical Patrol service identity
instead of reimplementing those branches in links-only or leaf badge surfaces.