Drop Patrol state members no surface reads (#2695)

`usePatrolIntelligenceState()` returned 18 members that no file outside
the hook reads. A TypeScript checker scan of src and tests found no
readers and no spread, computed or `Object.*` escapes of the state
object. Several were also dead inside the hook.

## Change

- **Return object.** Drops activePatrolFindings, activityRefreshTrigger,
acceptedManualRunId, assistantHandoffFindingId, circuitBreakerStatus,
fullModeUnlocked, initialSurfaceReady, isRefreshing, loadAllData,
openAssistantOperationsLoopForFinding, the four decision/outcome counts,
both work-evidence counts, recordPatrolWorkflowStarterActivity and
setFullModeUnlocked, plus handleAssistantFindingHandoff (see below).
- **Dead locals.** Removes the write-only signals (initialSurfaceReady,
isRefreshing, fullModeUnlocked, assistantHandoffFindingId), the 15 s
background refresh timeout that only reset isRefreshing, the unread
memos, and the never-called `openAssistantOperationsLoopForFinding`.
That last one was a second copy of the handoff `FindingsPanel` already
performs. Locals still used inside the hook (activePatrolFindings,
loadAllData, activityRefreshTrigger, acceptedManualRunId) stay.
- **Test-only exports.** `openPatrolAssistantWorkflowHandoff` and
`recordPatrolWorkflowStarterActivity` were used only by that dead path
and by their own tests, so they go.
- **Workspace.** `handleAssistantFindingHandoff` only wrote the unread
handoff signal, so `PatrolIntelligenceWorkspace` no longer passes
`FindingsPanel` an `onAssistantHandoff` callback. The prop is optional.
- **Tests.** Raw-source pins on removed code are dropped. The
manual-sync timeout test now pins the Retry path's own generation guard;
a mutation of its timeout fails it. The non-blocking starter test now
exercises `recordPatrolControlStarterActivity`.
- **Contracts.**
- patrol-intelligence said a finding-to-Assistant handoff *must* record
a `pulse_patrol` workflow starter. Only the dead path did that; the live
`FindingsPanel` handoff never has, so `pulse_patrol` has no browser
producer. The clause now says that and keeps the content-free rules as a
condition.
- The "local Patrol state must expose work evidence" sentence no longer
requires counts nothing reads.
- Three refresh clauses that described a header refresh action now name
the stale-data Retry, the only operator refresh left.
- api-contracts and frontend-primitives record that `FindingsPanel` owns
the handoff.

## Not in this PR

The store still fetches `GET /api/ai/circuit/status` for nothing. That
removal is a follow-up after #2677, because it edits the same lines.

## Evidence

- Production builds before and after compared with chunk hashes, SRI
strings and short identifiers normalised. Only the removed hook code
differs, the unused `pulse_patrol` constant drops out of `aiChat.js`,
and `index.js` reorders the same preload list. `check-bundle-size.mjs`
passes.
- Private demo-mode stacks for both builds, driven by the same
Playwright script at 1440x900 and 390x844: Patrol load, Activity >
Finding options and history, Open in Assistant (drawer opens with the
finding), tab hide/show, and the forced stale-data banner plus Retry.
Each phase hit the same `/api` endpoints on both builds, and neither
build had console errors.
- `tsc --noEmit`, eslint, and vitest for `src/features/patrol`,
`AIIntelligence.test.tsx`, `frontendResourceTypeBoundaries.test.ts` and
`FindingsPanel.test.ts` pass, as does the full pre-commit battery.

Stacked on #2687 (which carries #2682 and #2686). Until those merge,
this PR shows their commits too. `git merge-tree` is clean against main,
#2690, #2677 and #2691.
This commit is contained in:
rcourtman
2026-10-08 03:01:08 +01:00
committed by GitHub
10 changed files with 153 additions and 349 deletions
@@ -3326,6 +3326,7 @@ a new API state machine, queue contract, or verification-accounting field.
and any frontend presentation of the AI summary's `policy_posture` counts, which must render that canonical snapshot through one shared component instead of page-local posture loops; no frontend surface renders those counts today, and the resource drawer stays on per-resource policy lines
and the dedicated `frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts` owner, so if Patrol recent-change, learned-correlation, or policy-coverage summary text returns, it is derived from the canonical AI payload in that one place instead of as hook-local count and pluralization logic; Patrol renders no such summary today and the model carries no such derivation
and the Watch-only forward-path handoffs owned by `frontend-modern/src/features/patrol/patrolControlPresentation.ts` together with the Actions inbox empty-state read of `GET /api/ai/patrol/autonomy`, so finding-level and inbox-level mode guidance consumes the canonical Patrol autonomy read/save contract through the Patrol state hook (`handleAutonomyChange`) instead of introducing a second mode mutation path or a page-local autonomy dialect
and the Patrol finding-to-Assistant handoff owned by `frontend-modern/src/components/AI/FindingsPanel.tsx`, so the pending-approval, investigation, and proposed-fix reads behind that handoff happen once in the panel instead of in a second, unwired copy inside the Patrol state hook
and that same Patrol investigation-context owner, so visible Assistant
drawer handoffs may include live pending-approval metadata only as safe
operator context: approval ID, status, risk, requested/expiry timestamps,
@@ -3767,11 +3767,11 @@ production table, router and styles; it does not qualify full-app scrolling.
assessment readout, but shared drawer/chrome primitives stay free of
the `FindingsTrustSummary` shape so adding new trust signals goes
through the contract first rather than per-shell branching.
Patrol header refresh controls stay on that same feature-owned shell
Patrol refresh controls stay on that same feature-owned shell
boundary: `frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts`
must make the refresh affordance generation-aware and timeout-bounded, so
a slow supporting intelligence read cannot permanently disable the shared
Patrol header control while Patrol findings and status remain visible.
a slow supporting intelligence read cannot permanently disable the
stale-data Retry control while Patrol findings and status remain visible.
That feature-owned
presentation helper is the single emitter for investigation-record
`impact` and `rollback` fields: when an investigation record exists but those fields
@@ -3915,6 +3915,10 @@ production table, router and styles; it does not qualify full-app scrolling.
finding-presentation destinations such as the Patrol provider-settings
route, while `patrol_autonomy` and legacy Pro activation URLs remain parser
aliases only and verified review links use the plain Patrol history anchor.
The Patrol finding-to-Assistant handoff has one owner,
`frontend-modern/src/components/AI/FindingsPanel.tsx`; the Patrol state
hook keeps no second copy of it, and the Patrol workspace passes no
handoff callback because nothing on the page reacts to a handoff.
UI surfaces must not duplicate the `patrolControlStarter` query string or
write Patrol control or legacy entry-point starter telemetry from local
click handlers.
@@ -6856,7 +6860,7 @@ That same Patrol shell ownership includes refresh affordance state:
`frontend-modern/src/features/patrol/usePatrolIntelligenceState.ts` must keep
operator refresh controls generation-aware, timeout-bounded, and separate from
background polling state, so a slow supporting intelligence read cannot make the
shared Patrol header Refresh Patrol action spin indefinitely or stay disabled
Patrol stale-data Retry action spin indefinitely or stay disabled
while Patrol findings and status remain visible.
Neither the Patrol load and poll path (the store's `loadDashboardData` bundle)
nor the Retry path's background supporting reads fetch the global
@@ -329,13 +329,14 @@ or historical proof/counting for resolved-only work.
shared parent node, or explicit correlated finding IDs. Grouping must not
infer causality from title or description prose, and the queue's affected
resource count must continue to count canonical resources rather than
visual groups. Because a
contextual Assistant handoff from that workflow is still a first-party Patrol
starter for the same governed journey, it must record content-free workflow
prompt activity through the shared marker route with the `pulse_patrol`
surface before the drawer opens; it must not include finding IDs, prompt
text, resource context, model output, or direct-action payloads in that
marker.
visual groups. A contextual Assistant handoff from that workflow opens
through `frontend-modern/src/components/AI/FindingsPanel.tsx` and records no
workflow starter today, so the first-party `pulse_patrol` marker has no
browser producer. If that handoff starts recording one, it must write
content-free workflow prompt activity through the shared marker route with
the `pulse_patrol` surface before the drawer opens, with no finding IDs,
prompt text, resource context, model output, or direct-action payloads in
that marker.
A compact Patrol work-group row may appear above the list only when it adds
cross-source current-work grouping that the row title alone cannot express:
pending approvals, failed governed actions, a failed/latest Patrol check,
@@ -471,8 +472,11 @@ or historical proof/counting for resolved-only work.
compact progress label must describe current work state (`ready`, `needs
attention`, `approval needed`, `outcome verified`, `no active work`) instead
of repeating the selected mode; the Patrol mode selector/header
owns current-mode copy, and local Patrol state must expose work evidence as
owns current-mode copy, and any local Patrol work evidence must be
Patrol-owned issue/work counts rather than legacy proof counts.
No Patrol surface reads issue-evidence, governed-action decision, or
verified-outcome counts today, so `usePatrolIntelligenceState.ts` computes
none of them.
When no active finding or pending approval remains, terminal verified or
rejected outcomes must read as history behind a `no active work` current
state; current-state copy must not ask operators to reconcile old approved
@@ -771,12 +775,12 @@ attention`, `approval needed`, `outcome verified`, `no active work`) instead
only block the selected run view while the run references finding ids that
still need the direct Patrol findings payload.
Patrol page refresh state must also separate background data loads from the
operator-clicked Update status action: slow or stalled supporting
intelligence reads may continue in the background, but they must not make the
shared header action spin or stay disabled once Patrol findings and status
remain visible. The header action is a status/history sync affordance; it
must not read like another Patrol run or imply that it changes
infrastructure.
operator-clicked Retry on the stale-data banner: background polls hold no
busy state, and slow or stalled supporting intelligence reads may continue
in the background, but they must not make Retry spin or stay disabled once
Patrol findings and status remain visible. Retry is a status/history sync
affordance; it must not read like another Patrol run or imply that it
changes infrastructure.
Patrol trigger-status copy in the default header and activity strip must stay
actionable. Runtime policy pauses for alert/anomaly-triggered background
checks, such as the local development safety guard, may explain a blocked