mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-23 19:57:09 +00:00
Surface governed action posture in Patrol Assistant briefings
This commit is contained in:
@@ -458,7 +458,10 @@ the canonical monitored-system blocked payload.
|
||||
finding entries may carry live pending Patrol approval posture only as safe
|
||||
structured handoff actions with approval ID/status/risk/target/request/expiry
|
||||
metadata, action plan identity/policy/expiry, dry-run posture, and command
|
||||
counts instead of pasting page-local UI text or raw command payloads into chat
|
||||
counts instead of pasting page-local UI text or raw command payloads into chat,
|
||||
and may derive visible action labels, safety notes, and approval-aware
|
||||
suggested prompts from that same safe metadata so the drawer does not fall
|
||||
back to generic assessment copy when governed actions are present
|
||||
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,
|
||||
@@ -3037,10 +3040,12 @@ visible handoffs from findings, while the downstream chat
|
||||
service hydrates live resource state, timeline, and action audit context around
|
||||
that same handoff. Frontend Patrol handoff helpers may consume current pending
|
||||
approval list payloads only as safe metadata for that visible briefing and any
|
||||
structured `handoff_actions`: approval ID, status, risk, request/expiry
|
||||
timestamps, target label, action ID, approval policy, plan expiry, and dry-run
|
||||
summary are allowed, while approval command text remains inside the governed
|
||||
approval/remediation surface.
|
||||
structured `handoff_actions`: approval ID, status, risk, request/expiry
|
||||
timestamps, target label, action ID, approval policy, plan expiry, and dry-run
|
||||
summary are allowed. Assessment-level visible briefings may reuse that same
|
||||
safe metadata for action labels, safety notes, and approval-aware suggested
|
||||
prompts, while approval command text remains inside the governed
|
||||
approval/remediation surface.
|
||||
Patrol approval-row Assistant prompts must use the same safe metadata boundary
|
||||
and set `autonomousMode:false` for the request-local chat handoff; they must not
|
||||
paste raw approval or proposed-fix command text into the authored chat prompt.
|
||||
|
||||
@@ -819,7 +819,10 @@ frontend primitive boundary.
|
||||
approval policy, plan expiry, dry-run posture, and command count. Those
|
||||
entries may be passed through shared chat transport as `handoff_actions` for
|
||||
model-only refresh, but the shared drawer stays a generic shell rather than
|
||||
a Patrol summary prompt builder. The
|
||||
a Patrol summary prompt builder. The Patrol helper may turn those same safe
|
||||
references into visible action labels, safety notes, and approval-aware
|
||||
suggested prompts, but it must not expose raw command or execution payloads.
|
||||
The
|
||||
drawer may render a generic
|
||||
context-briefing band from `frontend-modern/src/stores/aiChat.ts`, but
|
||||
feature-owned helpers must provide the source labels, attention reason,
|
||||
|
||||
@@ -223,9 +223,10 @@ Patrol-specific presentation helpers.
|
||||
approval ID, pending status, risk, target, requested/expiry timestamps,
|
||||
action plan identity, approval policy, plan expiry, dry-run posture, and
|
||||
command count. They must force request-local approval-required mode, keep raw
|
||||
command and approval payloads out of prompt and drawer copy, and frame
|
||||
Assistant as explanation, prioritization, and safe next-step review rather
|
||||
than a generic reactive chat
|
||||
command and approval payloads out of prompt and drawer copy, surface visible
|
||||
drawer action posture from the same safe references, and frame Assistant as
|
||||
explanation, prioritization, and safe next-step review rather than a generic
|
||||
reactive chat
|
||||
box.
|
||||
|
||||
## Current State
|
||||
@@ -284,7 +285,10 @@ evidence, bounded active-finding summaries, source-owned suggested prompts, and
|
||||
deduped resource references plus safe structured approval/action references as
|
||||
model-only context while forcing `autonomousMode:false` and summarizing
|
||||
proposed-fix command-bearing records and command-bearing change events without
|
||||
raw command text.
|
||||
raw command text. Its visible Assistant briefing must also use those safe
|
||||
references to distinguish pending governed approvals or attached action
|
||||
references from a generic assessment discussion, including approval-policy and
|
||||
dry-run posture when available, while command payloads stay out of the drawer.
|
||||
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.
|
||||
|
||||
@@ -88,10 +88,13 @@ describe('PatrolIntelligenceSummary', () => {
|
||||
actionDryRunSummary: 'No provider-supported dry run is available for this action.',
|
||||
});
|
||||
expect(context.briefing).toMatchObject({
|
||||
actionLabel: '1 pending governed approval attached',
|
||||
safetyNote:
|
||||
'Review approvals in the governed flow; approval policy is attached; dry-run posture is attached; raw command payloads stay out of Assistant.',
|
||||
suggestedPrompts: [
|
||||
'Prioritize findings and safest next step',
|
||||
'Explain recent changes and correlations',
|
||||
'Summarize governed remediation risks',
|
||||
'Review pending approvals and safest next step',
|
||||
],
|
||||
});
|
||||
expect(JSON.stringify(context)).not.toContain('systemctl restart workload.service');
|
||||
|
||||
+9
-2
@@ -262,7 +262,9 @@ describe('patrolInvestigationContextModel', () => {
|
||||
sourceLabel: 'Pulse Patrol',
|
||||
title: 'Patrol assessment attached',
|
||||
subject: 'Issues detected',
|
||||
safetyNote: 'Diagnostics and remediation require governed approval.',
|
||||
actionLabel: '1 governed action reference attached',
|
||||
safetyNote:
|
||||
'Review action posture in the governed flow; raw command payloads stay out of Assistant.',
|
||||
suggestedPrompts: [
|
||||
'Prioritize findings and safest next step',
|
||||
'Explain recent changes and correlations',
|
||||
@@ -345,8 +347,13 @@ describe('patrolInvestigationContextModel', () => {
|
||||
expect(handoff.context.context).toMatchObject({
|
||||
pendingApprovalCount: 1,
|
||||
});
|
||||
expect(handoff.context.briefing).toMatchObject({
|
||||
actionLabel: '1 pending governed approval attached',
|
||||
safetyNote:
|
||||
'Review approvals in the governed flow; approval policy is attached; dry-run posture is attached; destructive actions remain approval-bound; raw command payloads stay out of Assistant.',
|
||||
});
|
||||
expect(handoff.context.briefing?.suggestedPrompts).toContain(
|
||||
'Summarize governed remediation risks',
|
||||
'Review pending approvals and safest next step',
|
||||
);
|
||||
expect(JSON.stringify(handoff)).not.toContain('systemctl restart workload.service');
|
||||
});
|
||||
|
||||
@@ -410,6 +410,8 @@ function buildPatrolAssessmentAssistantBriefing(
|
||||
const recentChanges = normalizeAssessmentRecentChanges(input.supportingEvidence?.recentChanges);
|
||||
const correlations = normalizeAssessmentCorrelations(input.supportingEvidence?.correlations);
|
||||
const findingEvidence = findings.map(formatAssessmentFindingEvidence).filter(isNonEmptyString);
|
||||
const handoffActions = buildPatrolAssessmentHandoffActions(input);
|
||||
const actionPosture = buildPatrolAssessmentActionPosture(input, handoffActions);
|
||||
const supportingEvidence = [
|
||||
...recentChanges.map(formatAssessmentRecentChangeEvidence),
|
||||
...correlations.map(formatAssessmentCorrelationEvidence),
|
||||
@@ -426,8 +428,8 @@ function buildPatrolAssessmentAssistantBriefing(
|
||||
.filter(isNonEmptyString)
|
||||
.slice(0, 4),
|
||||
evidence: [...findingEvidence.slice(0, 3), ...supportingEvidence].slice(0, 5),
|
||||
actionLabel: 'Discuss Patrol assessment',
|
||||
safetyNote: 'Diagnostics and remediation require governed approval.',
|
||||
actionLabel: actionPosture.actionLabel,
|
||||
safetyNote: actionPosture.safetyNote,
|
||||
suggestedPrompts: buildPatrolAssessmentSuggestedPrompts(input, {
|
||||
findings,
|
||||
recentChanges,
|
||||
@@ -436,6 +438,76 @@ function buildPatrolAssessmentAssistantBriefing(
|
||||
};
|
||||
}
|
||||
|
||||
function buildPatrolAssessmentActionPosture(
|
||||
input: PatrolAssessmentAssistantHandoffInput,
|
||||
handoffActions: AIChatHandoffAction[],
|
||||
): { actionLabel: string; safetyNote: string } {
|
||||
const pendingApprovalCount = normalizeAssessmentPendingApprovalCount(input.activeFindings);
|
||||
const actionCount = handoffActions.length;
|
||||
const hasDryRunPosture = handoffActions.some((action) =>
|
||||
Boolean(normalizeText(action.actionDryRunSummary) || normalizeText(action.actionPreflight)),
|
||||
);
|
||||
const hasDestructiveAction = handoffActions.some((action) => Boolean(action.destructive));
|
||||
const hasApprovalPolicy = handoffActions.some((action) =>
|
||||
Boolean(normalizeText(action.actionApprovalPolicy)),
|
||||
);
|
||||
|
||||
if (pendingApprovalCount > 0) {
|
||||
return {
|
||||
actionLabel: `${formatAssessmentMetricCount(
|
||||
'Pending governed approvals',
|
||||
pendingApprovalCount,
|
||||
)} attached`,
|
||||
safetyNote: formatAssessmentActionSafetyNote({
|
||||
primary: 'Review approvals in the governed flow',
|
||||
hasDryRunPosture,
|
||||
hasDestructiveAction,
|
||||
hasApprovalPolicy,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
if (actionCount > 0) {
|
||||
return {
|
||||
actionLabel: `${formatAssessmentMetricCount(
|
||||
'Governed action references',
|
||||
actionCount,
|
||||
)} attached`,
|
||||
safetyNote: formatAssessmentActionSafetyNote({
|
||||
primary: 'Review action posture in the governed flow',
|
||||
hasDryRunPosture,
|
||||
hasDestructiveAction,
|
||||
hasApprovalPolicy,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
actionLabel: 'Discuss Patrol assessment',
|
||||
safetyNote: 'Diagnostics and remediation require governed approval.',
|
||||
};
|
||||
}
|
||||
|
||||
function formatAssessmentActionSafetyNote(input: {
|
||||
primary: string;
|
||||
hasDryRunPosture: boolean;
|
||||
hasDestructiveAction: boolean;
|
||||
hasApprovalPolicy: boolean;
|
||||
}): string {
|
||||
const parts = [input.primary];
|
||||
if (input.hasApprovalPolicy) {
|
||||
parts.push('approval policy is attached');
|
||||
}
|
||||
if (input.hasDryRunPosture) {
|
||||
parts.push('dry-run posture is attached');
|
||||
}
|
||||
if (input.hasDestructiveAction) {
|
||||
parts.push('destructive actions remain approval-bound');
|
||||
}
|
||||
parts.push('raw command payloads stay out of Assistant');
|
||||
return `${parts.join('; ')}.`;
|
||||
}
|
||||
|
||||
function buildPatrolAssessmentSuggestedPrompts(
|
||||
input: PatrolAssessmentAssistantHandoffInput,
|
||||
normalized: {
|
||||
@@ -461,7 +533,11 @@ function buildPatrolAssessmentSuggestedPrompts(
|
||||
}
|
||||
|
||||
if (hasGovernedAction) {
|
||||
prompts.push('Summarize governed remediation risks');
|
||||
prompts.push(
|
||||
normalizeAssessmentPendingApprovalCount(input.activeFindings) > 0
|
||||
? 'Review pending approvals and safest next step'
|
||||
: 'Summarize governed remediation risks',
|
||||
);
|
||||
} else if (activeFindingCount > 0) {
|
||||
prompts.push('List evidence to verify before action');
|
||||
} else if (hasSupportingEvidence) {
|
||||
|
||||
Reference in New Issue
Block a user