Clarify Patrol mode boundaries

This commit is contained in:
rcourtman 2026-06-30 16:14:55 +01:00
parent 5dbf750136
commit 477d549efd
10 changed files with 67 additions and 49 deletions

View file

@ -2746,9 +2746,10 @@ query...`, and `Reading storage...` before streamed tool arguments are
contextual and external-agent access paths over the same governed
capabilities.
Public overview, safety, and onboarding copy must use the same visible
Patrol mode labels as the product (`Watch only`, `Ask before changes`,
`Auto-fix safe issues`, `Policy autopilot`) and must not reintroduce the
retired `Only watch`, `Fix safe issues`, or `Full control` labels as
Patrol mode labels as the product (`Watch only`, `Ask first`,
`Safe auto-fix`, `Autopilot`) and must not reintroduce the retired
`Only watch`, `Ask before changes`, `Fix safe issues`,
`Auto-fix safe issues`, `Full control`, or `Policy autopilot` labels as
customer-facing names.
The public overview must also preserve the model-owned AI boundary: Pulse
Assistant and Patrol provide governed context, tools, safety gates,
@ -3427,9 +3428,11 @@ separate findings. Verification is
Public Pulse Intelligence overview, safety, and onboarding copy now use the
same visible Patrol mode labels as the product: `Watch only`,
`Ask before changes`, `Auto-fix safe issues`, and `Policy autopilot`. The
legacy `Only watch`, `Fix safe issues`, and `Full control` names may remain
only as historical context or compatibility implementation details, not as customer-facing Patrol mode labels.
`Ask first`, `Safe auto-fix`, and `Autopilot`. The legacy `Only watch`,
`Ask before changes`, `Fix safe issues`, `Auto-fix safe issues`,
`Full control`, and `Policy autopilot` names may remain only as historical
context or compatibility implementation details, not as customer-facing Patrol
mode labels.
Assistant provider-readiness repair actions now use the canonical Pulse
Intelligence > Provider & Models route

View file

@ -2036,20 +2036,20 @@ a new API state machine, queue contract, or verification-accounting field.
and the Patrol mode selector boundary, so the default header and the
Patrol mode dialog compose the shared
`frontend-modern/src/components/shared/FilterButtonGroup.tsx` primitive for
the visible `Watch only` / `Ask before changes` / `Auto-fix safe issues` /
`Policy autopilot` presentation while the API contract remains the sole owner of
the visible `Watch only` / `Ask first` / `Safe auto-fix` /
`Autopilot` presentation while the API contract remains the sole owner of
accepted autonomy values, `full_mode_unlocked` persistence,
license-required rejection shape, and the monitor-only clamp; when the
API/license state clamps Patrol to monitor for plan reasons, frontend
consumers must present watch-only as the effective capability and badge paid
levels rather than rendering a default Pro-absence explainer or a full-loop
header description, while runtime-locked Pro installs may explain the missing
runtime; provider model, schedule, trigger tuning, and readiness validation
must remain advanced settings plumbing alongside that inline Patrol mode
boundary. API-adjacent frontend and docs projections may keep legacy wire
values such as `monitor`, `assisted`, and `full`, but customer-facing copy
must name the visible choices as `Watch only`, `Auto-fix safe issues`, and
`Policy autopilot` rather than leaking compatibility terminology.
consumers must present watch-only as the effective capability without paid
level badges, disabled paid choices, default Pro-absence explainers, or a
full-loop header description, while runtime-locked Pro installs may explain
the missing runtime; provider model, schedule, trigger tuning, and readiness
validation must remain advanced settings plumbing alongside that inline
Patrol mode boundary. API-adjacent frontend and docs projections may keep
legacy wire values such as `monitor`, `assisted`, and `full`, but
customer-facing copy must name the visible choices as `Watch only`, `Ask first`,
`Safe auto-fix`, and `Autopilot` rather than leaking compatibility terminology.
policy rather than changing the compatibility API boundary; setup-only
readiness may hide those run/configuration affordances, but the visible
Patrol mode selector remains the primary policy boundary during setup

View file

@ -90,7 +90,7 @@ work extends shared components instead of creating new local variants.
59b. `frontend-modern/src/components/shared/FilterBar/useSavedViews.ts`
59c. `frontend-modern/src/components/shared/FilterBar/filterOptionPresentation.tsx`
60. `frontend-modern/src/components/shared/TypeColumn.guardrails.test.ts`
61. `frontend-modern/src/features/` (including Patrol presentation, where Patrol control starter counts are context only even when mirrored through `patrolAutonomy*` compatibility fields, successful direct Patrol control saves may record the content-free `patrol_control` starter only when paid control is available and the effective control posture changes and must then refresh Patrol status, findings, approvals, and run history before the operator waits for polling, legacy `proActivation*` starter aliases must not render a separate proof strip by themselves, Patrol control completed/resolved counts may only project backend-owned terminal proof, current active findings and pending approvals outrank historical completion proof in the primary operator state, selected run history must read as a Patrol run record rather than a findings filter or snapshot workflow, terminal verified/rejected outcomes with no active finding or pending approval must stay history detail without rendering a no-op proof strip, resolved-only issue history must not be promoted into current-work copy or actions, compact recurrence/trust counters must read as historical evidence rather than current issue state, Patrol-owned status/history evidence must keep the assessment visible when the broader intelligence summary is missing, Patrol work-group chips may group current approvals, failed actions, failed checks, recurring active issues, and stale scheduled protection but must not become a separate status/trust/proof strip, the Patrol route and page title must lead with Patrol while the default workspace underneath may use Open work and run history stays a deliberate secondary review surface, setup-only Patrol runtime failures must instead use `Fix Patrol setup` framing with a dedicated setup task and direct provider-settings action while suppressing generic issue-row chips and filter chrome, Patrol must not expose a generic Details/supporting-context panel for nearby activity, related patterns, or policy limits, locked-control copy must state the watch-only boundary in positive capability language by saying Patrol checks infrastructure and shows current issues, avoid repeating the same sentence across the header and control, and avoid repeatedly restating infrastructure-unchanged caveats or relying on disabled controls, compact Pro badges, Limits controls, or manual-review framing, `patrolControlValueState` decides whether a terminal decision is partial review context or verified value proof while `patrolAutonomyValueState` remains a compatibility mirror, legacy `proActivation*` fields are compatibility fallback only, native Patrol state must not load the operations-loop status projection to decide current work, local Patrol state must expose issue-backed `patrolWork*` evidence rather than legacy proof naming, Patrol mode labels remain domain copy that must describe backend-owned risk policy without creating page-local safety thresholds, the Patrol schedule and model drawer must stay separate from the always-visible Patrol mode selector instead of duplicating the four control choices or reintroducing save/apply configuration framing, and Patrol header refresh controls must call an explicit operator-refresh handler whose spinning/disabled state is separate from background polling and initial data loads)
61. `frontend-modern/src/features/` (including Patrol presentation, where Patrol control starter counts are context only even when mirrored through `patrolAutonomy*` compatibility fields, successful direct Patrol control saves may record the content-free `patrol_control` starter only when paid control is available and the effective control posture changes and must then refresh Patrol status, findings, approvals, and run history before the operator waits for polling, legacy `proActivation*` starter aliases must not render a separate proof strip by themselves, Patrol control completed/resolved counts may only project backend-owned terminal proof, current active findings and pending approvals outrank historical completion proof in the primary operator state, selected run history must read as a Patrol run record rather than a findings filter or snapshot workflow, terminal verified/rejected outcomes with no active finding or pending approval must stay history detail without rendering a no-op proof strip, resolved-only issue history must not be promoted into current-work copy or actions, compact recurrence/trust counters must read as historical evidence rather than current issue state, Patrol-owned status/history evidence must keep the assessment visible when the broader intelligence summary is missing, Patrol work-group chips may group current approvals, failed actions, failed checks, recurring active issues, and stale scheduled protection but must not become a separate status/trust/proof strip, the Patrol route and page title must lead with Patrol while the default workspace underneath may use Open work and run history stays a deliberate secondary review surface, setup-only Patrol runtime failures must instead use `Fix Patrol setup` framing with a dedicated setup task and direct provider-settings action while suppressing generic issue-row chips and filter chrome, Patrol must not expose a generic Details/supporting-context panel for nearby activity, related patterns, or policy limits, locked-control copy must state the watch-only boundary in positive capability language by saying Patrol checks infrastructure and shows current issues, avoid repeating the same sentence across the header and control, and avoid repeatedly restating infrastructure-unchanged caveats or relying on disabled controls, compact Pro badges, Limits controls, or manual-review framing, `patrolControlValueState` decides whether a terminal decision is partial review context or verified value proof while `patrolAutonomyValueState` remains a compatibility mirror, legacy `proActivation*` fields are compatibility fallback only, native Patrol state must not load the operations-loop status projection to decide current work, local Patrol state must expose issue-backed `patrolWork*` evidence rather than legacy proof naming, Patrol mode labels remain domain copy that must describe backend-owned risk policy without creating page-local safety thresholds, the selected Patrol mode sentence must state the approval and policy boundary without adding a second limits panel or proof strip, the Patrol schedule and model drawer must stay separate from the always-visible Patrol mode selector instead of duplicating the four control choices or reintroducing save/apply configuration framing, and Patrol header refresh controls must call an explicit operator-refresh handler whose spinning/disabled state is separate from background polling and initial data loads)
62. `frontend-modern/src/components/SetupWizard/SetupWizard.tsx`
63. `frontend-modern/src/components/Settings/useSettingsInfrastructurePanelProps.ts`
64. `frontend-modern/src/components/SetupWizard/SetupCompletionPreview.tsx`

View file

@ -950,9 +950,9 @@ rather than forking a patrol-local form.
The Patrol page now keeps the Patrol mode policy inline on the main surface,
not inside the secondary Schedule & model drawer: the first configurable decision remains
what Patrol may handle automatically (`Watch only`, `Ask before changes`,
`Auto-fix safe issues`, `Policy autopilot`), and there must be only one visible
chooser for that decision.
what Patrol may handle automatically (`Watch only`, `Ask first`,
`Safe auto-fix`, `Autopilot`), and there must be only one visible chooser for
that decision.
Commercial, runtime, and documentation copy that describes this same decision
must also use those visible labels and the umbrella name `Patrol mode`, not
the retired `Only watch`, `Fix safe issues`, `Full control`, or generic
@ -1095,9 +1095,9 @@ instead of rebuilding a local active-button group. The wide default Patrol
header uses the segmented layout; the constrained configuration dialog may use
the shared prominent layout so all four mode labels remain readable without
inventing a Patrol-local selector. Patrol owns the default visible four-level
policy presentation (`Watch only`, `Ask before changes`, `Auto-fix safe issues`,
`Policy autopilot`), entitlement locks, and the rule that choosing the highest
policy-autopilot level sends `full_mode_unlocked:true` while choosing any lower level clears that
policy presentation (`Watch only`, `Ask first`, `Safe auto-fix`,
`Autopilot`), entitlement locks, and the rule that choosing the highest
Autopilot level sends `full_mode_unlocked:true` while choosing any lower level clears that
acknowledgement. The shared primitive owns pressed-state semantics,
disabled-option behavior, and active/inactive selector styling.
That same Patrol-owned presentation rule also applies to the findings empty

View file

@ -95,23 +95,25 @@ describe('PatrolIntelligenceHeader', () => {
expect(PATROL_AUTONOMY_POLICY_PRESENTATION).toEqual({
monitor: {
label: 'Watch only',
detail: 'Patrol checks and reports issues without making changes.',
detail: 'Patrol checks infrastructure and reports issues only; it does not start fixes.',
compactLabel: 'Watch only',
},
approval: {
label: 'Ask first',
detail: 'Patrol investigates issues and prepares fixes. You approve every change.',
detail:
'Patrol investigates and prepares fixes, but every change waits for your approval.',
compactLabel: 'Ask first',
},
assisted: {
label: 'Safe auto-fix',
detail: 'Patrol fixes safe policy-allowed issues. It asks before anything riskier.',
detail:
'Patrol can run low- or medium-risk fixes allowed by policy; higher-risk work still asks first.',
compactLabel: 'Safe auto-fix',
},
full: {
label: 'Autopilot',
detail:
'Patrol handles policy-approved issues automatically. It asks only when policy requires approval.',
'Patrol can act automatically within policy and still asks when approval is required.',
compactLabel: 'Autopilot',
},
});
@ -144,6 +146,9 @@ describe('PatrolIntelligenceHeader', () => {
expect(headerSource).not.toContain('How much can Patrol do?');
expect(headerSource).not.toContain("level === 'full' ? 'assisted'");
expect(headerSource).not.toContain('Autonomous critical remediation');
expect(headerSource).not.toContain('Ask before changes');
expect(headerSource).not.toContain('Auto-fix safe issues');
expect(headerSource).not.toContain('Policy autopilot');
});
it('keeps Patrol mode inline and routes configuration to settings', () => {

View file

@ -19,23 +19,25 @@ describe('patrolControlPresentation', () => {
expect(PATROL_AUTONOMY_POLICY_PRESENTATION).toEqual({
monitor: {
label: 'Watch only',
detail: 'Patrol checks and reports issues without making changes.',
detail: 'Patrol checks infrastructure and reports issues only; it does not start fixes.',
compactLabel: 'Watch only',
},
approval: {
label: 'Ask first',
detail: 'Patrol investigates issues and prepares fixes. You approve every change.',
detail:
'Patrol investigates and prepares fixes, but every change waits for your approval.',
compactLabel: 'Ask first',
},
assisted: {
label: 'Safe auto-fix',
detail: 'Patrol fixes safe policy-allowed issues. It asks before anything riskier.',
detail:
'Patrol can run low- or medium-risk fixes allowed by policy; higher-risk work still asks first.',
compactLabel: 'Safe auto-fix',
},
full: {
label: 'Autopilot',
detail:
'Patrol handles policy-approved issues automatically. It asks only when policy requires approval.',
'Patrol can act automatically within policy and still asks when approval is required.',
compactLabel: 'Autopilot',
},
});

View file

@ -14,23 +14,23 @@ export const PATROL_AUTONOMY_POLICY_PRESENTATION: Record<
> = {
monitor: {
label: 'Watch only',
detail: 'Patrol checks and reports issues without making changes.',
detail: 'Patrol checks infrastructure and reports issues only; it does not start fixes.',
compactLabel: 'Watch only',
},
approval: {
label: 'Ask first',
detail: 'Patrol investigates issues and prepares fixes. You approve every change.',
detail: 'Patrol investigates and prepares fixes, but every change waits for your approval.',
compactLabel: 'Ask first',
},
assisted: {
label: 'Safe auto-fix',
detail: 'Patrol fixes safe policy-allowed issues. It asks before anything riskier.',
detail:
'Patrol can run low- or medium-risk fixes allowed by policy; higher-risk work still asks first.',
compactLabel: 'Safe auto-fix',
},
full: {
label: 'Autopilot',
detail:
'Patrol handles policy-approved issues automatically. It asks only when policy requires approval.',
detail: 'Patrol can act automatically within policy and still asks when approval is required.',
compactLabel: 'Autopilot',
},
};

View file

@ -650,7 +650,9 @@ describe('AIIntelligence entitlement gating', () => {
expect(patrolControl.getByText('Patrol mode')).toBeInTheDocument();
expect(patrolControl.getAllByText('Watch only').length).toBeGreaterThan(0);
expect(
patrolControl.getByText('Patrol checks and reports issues without making changes.'),
patrolControl.getByText(
'Patrol checks infrastructure and reports issues only; it does not start fixes.',
),
).toBeInTheDocument();
expect(patrolControl.queryByRole('button', { name: 'Limits' })).not.toBeInTheDocument();
expect(patrolControl.queryByRole('button', { name: 'Ask first' })).toBeNull();
@ -1198,7 +1200,9 @@ describe('AIIntelligence entitlement gating', () => {
expect(screen.getByRole('button', { name: 'Safe auto-fix' })).not.toBeDisabled();
expect(screen.getByRole('button', { name: 'Autopilot' })).not.toBeDisabled();
expect(
screen.getByText('Patrol checks and reports issues without making changes.'),
screen.getByText(
'Patrol checks infrastructure and reports issues only; it does not start fixes.',
),
).toBeInTheDocument();
expect(screen.queryByText('Patrol handles')).not.toBeInTheDocument();
expect(screen.queryByRole('button', { name: 'Limits' })).not.toBeInTheDocument();
@ -1209,7 +1213,7 @@ describe('AIIntelligence entitlement gating', () => {
await waitFor(() => {
expect(
screen.getByText(
'Patrol handles policy-approved issues automatically. It asks only when policy requires approval.',
'Patrol can act automatically within policy and still asks when approval is required.',
),
).toBeInTheDocument();
});
@ -1880,7 +1884,9 @@ describe('AIIntelligence entitlement gating', () => {
expect(patrolControl.getByText('Patrol mode')).toBeInTheDocument();
expect(patrolControl.getAllByText('Watch only').length).toBeGreaterThan(0);
expect(
patrolControl.getByText('Patrol checks and reports issues without making changes.'),
patrolControl.getByText(
'Patrol checks infrastructure and reports issues only; it does not start fixes.',
),
).toBeInTheDocument();
expect(patrolControl.getByRole('button', { name: 'Ask first' })).not.toBeDisabled();
expect(patrolControl.getByRole('button', { name: 'Safe auto-fix' })).not.toBeDisabled();

View file

@ -32,7 +32,7 @@ describe('patrolPagePresentation', () => {
'Patrol checks your infrastructure and shows current issues.',
);
expect(PATROL_PAGE_MONITOR_DESCRIPTION).toBe(
'Watch only: Patrol checks and reports issues without making changes.',
'Watch only: Patrol checks infrastructure and reports issues only.',
);
expect(getPatrolPageHeaderMeta({ autonomyLocked: true })).toEqual({
title: 'Patrol',
@ -48,14 +48,16 @@ describe('patrolPagePresentation', () => {
it('describes each governed Patrol mode level by what it can actually do', () => {
expect(getPatrolPageHeaderMeta({ autonomyLevel: 'approval' })).toMatchObject({
description: 'Patrol investigates issues and asks before every change.',
description:
'Ask first: Patrol investigates and prepares fixes, but every change waits for approval.',
});
expect(getPatrolPageHeaderMeta({ autonomyLevel: 'assisted' })).toMatchObject({
description: 'Patrol handles safe policy-allowed fixes and asks before anything riskier.',
description:
'Safe auto-fix: Patrol may run low- or medium-risk fixes allowed by policy.',
});
expect(getPatrolPageHeaderMeta({ autonomyLevel: 'full' })).toMatchObject({
description:
'Patrol handles policy-approved work automatically and asks only when approval is required.',
'Autopilot: Patrol may act automatically within policy and still asks when approval is required.',
});
});
});

View file

@ -6,7 +6,7 @@ export const PATROL_PAGE_WATCH_ONLY_DESCRIPTION =
'Patrol checks your infrastructure and shows current issues.';
export const PATROL_PAGE_MONITOR_DESCRIPTION =
'Watch only: Patrol checks and reports issues without making changes.';
'Watch only: Patrol checks infrastructure and reports issues only.';
export const PATROL_PAGE_DESCRIPTION =
'Patrol checks your infrastructure, explains what it found, follows your mode before acting, and records the result.';
@ -25,11 +25,11 @@ function getPatrolPageDescription(input: PatrolPageHeaderMetaInput): string {
switch (input.autonomyLevel) {
case 'approval':
return 'Patrol investigates issues and asks before every change.';
return 'Ask first: Patrol investigates and prepares fixes, but every change waits for approval.';
case 'assisted':
return 'Patrol handles safe policy-allowed fixes and asks before anything riskier.';
return 'Safe auto-fix: Patrol may run low- or medium-risk fixes allowed by policy.';
case 'full':
return 'Patrol handles policy-approved work automatically and asks only when approval is required.';
return 'Autopilot: Patrol may act automatically within policy and still asks when approval is required.';
default:
return PATROL_PAGE_DESCRIPTION;
}