From a6ea15bfa974f30979f7be596c4e3d73f1dbbe7f Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 8 May 2026 12:25:32 +0100 Subject: [PATCH] Improve Patrol Assistant handoff approval boundary --- .../v6/internal/subsystems/api-contracts.md | 7 ++-- .../subsystems/frontend-primitives.md | 10 +++--- .../subsystems/patrol-intelligence.md | 8 ++--- .../patrolInvestigationContextModel.test.ts | 34 +++++++++++++++++++ .../patrol/patrolInvestigationContextModel.ts | 8 +---- 5 files changed, 48 insertions(+), 19 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index 568a5cc14..87be557e2 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -369,10 +369,11 @@ the canonical monitored-system blocked payload. remediation-plan handoffs must use the same boundary for frontend-authored prompts: plan status, risk, step labels, and command counts are allowed, while raw command and rollback command payloads remain in governed action - surfaces. Frontend finding-discussion handoffs that carry any live approval, - proposed-fix, fix outcome, or remediation-plan reference must force a + surfaces. Frontend Patrol finding-discussion handoffs must force a request-local approval-required Assistant mode instead of inheriting the - user's persistent autonomous control setting. Frontend-visible Patrol + user's persistent autonomous control setting; live approval, proposed-fix, + fix-outcome, and remediation-plan references only add structured action + posture, they are not the trigger for the boundary. Frontend-visible Patrol briefing payloads may include short suggested prompts, but those prompts must be derived from the same safe action posture, evidence, recurrence, and remediation-plan metadata as the briefing and must never carry raw approval, diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index 264465004..7501d31d6 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -999,11 +999,11 @@ prompt explain the same operator-facing priority. control level. Patrol remediation-plan drawer handoffs must use the same primitive boundary: plan title/status/risk, step labels, and command counts may enter Assistant context; raw command and rollback command payloads must - stay in the governed remediation/action panel. Finding discussion handoffs - that reference a live approval, proposed fix, fix outcome, or remediation - plan must also pass `autonomousMode:false` as a request-local override so - the drawer shows approval-required posture without mutating the persistent - Assistant control setting. + stay in the governed remediation/action panel. All Patrol finding + discussion handoffs, including context-only findings without a live approval + or proposed fix, must pass `autonomousMode:false` as a request-local + override so the drawer shows approval-required posture without mutating the + persistent Assistant control setting. 11. Keep shared filter primitives coherent with route-owned option hydration. Feature shells such as `frontend-modern/src/features/infrastructure/` must keep a route-owned canonical option visible in shared selects like diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index 470af63fc..1978db6aa 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -199,10 +199,10 @@ Patrol-specific presentation helpers. prerequisites, rollback, and verification, while command and rollback command text stays in the governed remediation or approval surface. Generic finding discussion handoffs must also force request-local approval-required mode for - any non-empty Patrol `finding_id`, including findings that reference a live - approval, proposed fix, fix outcome, or remediation plan, so default - autonomous Assistant settings cannot bypass the Patrol action-governance - boundary. The assembled handoff must still pass + any non-empty Patrol `finding_id`, including context-only findings and + findings that reference a live approval, proposed fix, fix outcome, or + remediation plan, so default autonomous Assistant settings cannot bypass the + Patrol action-governance boundary. The assembled handoff must still pass through the Assistant runtime's resource-policy sanitizer before prompt injection, so Patrol-owned prose cannot leak governed resource names, IDs, aliases, nodes, paths, or diff --git a/frontend-modern/src/features/patrol/__tests__/patrolInvestigationContextModel.test.ts b/frontend-modern/src/features/patrol/__tests__/patrolInvestigationContextModel.test.ts index 73543ea57..21b6abd9f 100644 --- a/frontend-modern/src/features/patrol/__tests__/patrolInvestigationContextModel.test.ts +++ b/frontend-modern/src/features/patrol/__tests__/patrolInvestigationContextModel.test.ts @@ -1250,6 +1250,40 @@ describe('patrolInvestigationContextModel', () => { ).toBe(false); }); + it('keeps context-only Patrol finding handoffs approval scoped', () => { + const handoff = buildPatrolAssistantFindingHandoff({ + id: 'finding-context-only', + title: 'Provider connection issue', + subject: 'Patrol runtime', + description: 'Pulse Patrol could not maintain a healthy provider connection.', + severity: 'warning', + findingStatus: 'active', + loopState: 'detected', + resourceId: 'pulse-patrol-runtime', + resourceName: 'Patrol runtime', + resourceType: 'service', + }); + + expect(handoff.context).toMatchObject({ + targetType: 'service', + targetId: 'pulse-patrol-runtime', + findingId: 'finding-context-only', + autonomousMode: false, + context: { + source: 'pulse-patrol-finding', + findingId: 'finding-context-only', + resourceId: 'pulse-patrol-runtime', + resourceName: 'Patrol runtime', + resourceType: 'service', + actionReferenceCount: 0, + }, + }); + expect(handoff.context.handoffActions).toBeUndefined(); + expect(handoff.context.handoffContext).toContain( + 'Operator Boundary: This Patrol finding handoff is model-only context', + ); + }); + it('builds an operator briefing from current finding facts before a Patrol record exists', () => { expect( buildPatrolAssistantFindingBriefing({ diff --git a/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts b/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts index ca7be57e1..f6ac225fb 100644 --- a/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts +++ b/frontend-modern/src/features/patrol/patrolInvestigationContextModel.ts @@ -608,12 +608,6 @@ export function buildPatrolAssistantFindingHandoff( const resource = buildPatrolFindingHandoffResource(input); const handoffResources = resource ? [resource] : []; const handoffActions = buildPatrolAssistantFindingHandoffActions(input); - const requiresApprovalMode = patrolAssistantFindingHandoffRequiresApprovalMode({ - investigationOutcome: input.investigationOutcome, - remediationId: input.remediationId, - pendingApproval: input.pendingApproval, - investigationRecord: input.investigationRecord, - }); return { prompt: buildPatrolAssistantFindingPrompt({ @@ -630,7 +624,7 @@ export function buildPatrolAssistantFindingHandoff( targetType: resource?.type, targetId: resource?.id, findingId: findingId || undefined, - autonomousMode: requiresApprovalMode ? false : undefined, + autonomousMode: false, handoffContext: buildPatrolAssistantFindingModelContext(input), handoffResources: handoffResources.length > 0 ? handoffResources : undefined, handoffActions: handoffActions.length > 0 ? handoffActions : undefined,