diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 8fcad0d52..565200afe 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -369,13 +369,13 @@ profile and assignment columns, but embedded table framing must route through lifecycle transition. Unified finding lifecycle facts, latest lifecycle event briefing lines, and detailed lifecycle context included in Assistant handoff context remain Patrol/AI review metadata and must not become agent - lifecycle state or fleet-control authority. Primary finding recency, - evidence, verification, and operator-decision facts in the operator briefing - and related root-cause or correlated finding summaries resolved for the same - Assistant handoff follow that same isolation rule, including any recency or - latest lifecycle facts attached to those related summaries: they may explain - Patrol context to the model and seed model-only handoff resources, but they - are not enrollment evidence, + lifecycle state or fleet-control authority. Primary finding attention, + recency, evidence, verification, and operator-decision facts in the operator + briefing and related root-cause or correlated finding summaries resolved for + the same Assistant handoff follow that same isolation rule, including any + recency or latest lifecycle facts attached to those related summaries: they + may explain Patrol context to the model and seed model-only handoff + resources, but they are not enrollment evidence, command-websocket identity, agent command grants, or auto-approval policy. Structured Assistant handoff action references from the same Patrol finding remain AI/runtime review metadata only; lifecycle code must not treat diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 183c0fe98..ad8b95eb9 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -207,10 +207,11 @@ runtime cost control, and shared AI transport surfaces. resolved timestamps, recurrence, regression, and recent lifecycle events so Assistant explains the current Patrol record rather than only the original investigation narrative. The operator briefing must surface the primary - finding's current recency facts, bounded evidence snapshot, verification - summary, and explicit operator decision framing before investigation - guidance, may surface the latest lifecycle event as the current handoff - state, and detailed lifecycle events must stay in a bounded + finding's current attention reason, recency facts, bounded evidence + snapshot, verification summary, and explicit operator decision framing + before investigation guidance, may surface the latest lifecycle event as + the current handoff state, and detailed lifecycle events must stay in a + bounded `[Finding Lifecycle Context]` block with an explicit model-only boundary. Assistant runtime may also hydrate canonical resource-policy context for those handoff resources, using the same @@ -963,12 +964,13 @@ top-N slices. Assistant finding handoffs now also receive a model-only operator briefing derived from the current unified finding and structured Patrol investigation record before the lower-level finding context. That briefing must summarize the -finding, resource, priority, current recency facts, bounded evidence and -verification summaries, investigation confidence, recommended next step, -operator decision framing, latest lifecycle event, and governed action posture -as operator guidance, while leaving detailed lifecycle history, current -resource-state, timeline, related-finding, and action-audit hydration in the -existing canonical AI runtime handoff builders. Related root-cause and +finding, resource, priority, current attention reason, current recency facts, +bounded evidence and verification summaries, investigation confidence, +recommended next step, operator decision framing, latest lifecycle event, and +governed action posture as operator guidance, while leaving detailed lifecycle +history, current resource-state, timeline, related-finding, and action-audit +hydration in the existing canonical AI runtime handoff builders. Related +root-cause and correlated finding records may be summarized from current unified finding state, including their recency and latest lifecycle facts, and may seed their own handoff resources for canonical policy, state, topology, and timeline diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index 62231d342..92e62ecb1 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -784,14 +784,14 @@ the canonical monitored-system blocked payload. snoozed/dismissed/suppressed status, detection/last-seen/resolution timestamps, recurrence/regression facts, and recent lifecycle events, so API consumers do not reduce Assistant context to an outdated investigation - summary. The briefing must carry the primary finding's current recency facts, - bounded evidence snapshot, verification summary, and explicit operator - decision framing before investigation guidance and may carry the latest - lifecycle event as the current handoff state, while the detailed lifecycle - list must stay bounded and model-only. Chat execution may also resolve - root-cause and correlated finding IDs from that current unified finding into - compact related-finding summaries and structured handoff resources. Those - related summaries may include the + summary. The briefing must carry the primary finding's current attention + reason, recency facts, bounded evidence snapshot, verification summary, and + explicit operator decision framing before investigation guidance and may + carry the latest lifecycle event as the current handoff state, while the + detailed lifecycle list must stay bounded and model-only. Chat execution may + also resolve root-cause and correlated finding IDs from that current unified + finding into compact related-finding summaries and structured handoff + resources. Those related summaries may include the related record's current recency and latest lifecycle facts, but those related records remain model-only explanation context and must not become saved user text, disclosure authority, lifecycle authority, approval @@ -2935,10 +2935,10 @@ The `/api/ai/chat` finding handoff contract must also include a model-only `[Operator Briefing]` generated from the unified finding and structured Patrol investigation record before detailed `[Finding Context]`. The briefing is the canonical operator-facing frame for Assistant: it carries the finding summary, -resource, priority, current recency facts, bounded evidence and verification -summaries, investigation confidence, recommended next step, and -operator-decision framing plus approval/proposed-fix posture without raw -command text, and the downstream chat service then hydrates live resource +resource, priority, current attention reason, current recency facts, bounded +evidence and verification summaries, investigation confidence, recommended next +step, and operator-decision framing plus approval/proposed-fix posture without +raw command text, and the downstream chat service then hydrates live resource state, timeline, and action audit context around that same handoff. Patrol run-history serialization and persistence must also preserve full field parity across API responses and restart boundaries, including diff --git a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md index db33fd56c..230313c09 100644 --- a/docs/release-control/v6/internal/subsystems/patrol-intelligence.md +++ b/docs/release-control/v6/internal/subsystems/patrol-intelligence.md @@ -131,10 +131,10 @@ Patrol-specific presentation helpers. Assistant handoffs from Patrol findings must also include a concise operator briefing derived from the unified finding and structured investigation record before the detailed finding context, so Assistant leads with the current risk, - recency, evidence snapshot, verification summary, conclusion, latest - lifecycle event, recommended next step, explicit operator decision framing, - and governed approval/proposed-fix posture instead of behaving like a generic - chat over a pasted incident dump. The assembled handoff must still pass + attention reason, recency, evidence snapshot, verification summary, + conclusion, latest lifecycle event, recommended next step, explicit operator + decision framing, and governed approval/proposed-fix posture instead of + behaving like a generic chat over a pasted incident dump. 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/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index fff5cd2a1..ea7516d07 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -427,13 +427,13 @@ bypass the API fail-closed execution gate. review metadata only. Unified finding lifecycle facts, latest lifecycle event briefing lines, and detailed lifecycle context carried by the same handoff remain Patrol/AI review metadata and must not become backup recency, - restore support, or storage-local lifecycle state. Primary finding recency, - evidence, verification, and operator-decision facts in the operator - briefing and related root-cause or correlated finding summaries resolved - for that Assistant handoff are also adjacent AI/runtime explanation context - only, including any recency or latest lifecycle facts attached to those - related summaries; storage and recovery surfaces must not reinterpret those - related records or their seeded handoff resources as backup freshness, + restore support, or storage-local lifecycle state. Primary finding + attention, recency, evidence, verification, and operator-decision facts in + the operator briefing and related root-cause or correlated finding summaries + resolved for that Assistant handoff are also adjacent AI/runtime explanation + context only, including any recency or latest lifecycle facts attached to + those related summaries; storage and recovery surfaces must not reinterpret + those related records or their seeded handoff resources as backup freshness, restore eligibility, recovery execution authority, or storage-local capability truth. Current resource-state, source-health, incident, metric, and capability summaries carried by that handoff are also diff --git a/internal/api/ai_handler.go b/internal/api/ai_handler.go index 1141500b1..6b6274def 100644 --- a/internal/api/ai_handler.go +++ b/internal/api/ai_handler.go @@ -977,6 +977,9 @@ func appendUnifiedFindingOperatorBriefingContext(b *strings.Builder, f *unified. appendChatContextLine(b, "Finding", formatUnifiedFindingBriefingFinding(f)) appendChatContextLine(b, "Resource", formatUnifiedFindingBriefingResource(f)) appendChatContextLine(b, "Priority", formatUnifiedFindingBriefingPriority(f)) + if attention := unifiedFindingBriefingAttention(f); attention != "" { + appendChatContextLine(b, "Attention Reason", attention) + } appendChatContextLine(b, "Recency", formatUnifiedFindingBriefingRecencyFacts(f)) appendChatContextLine(b, "Investigation", formatUnifiedFindingBriefingInvestigation(f)) if evidence := unifiedFindingBriefingEvidence(f); evidence != "" { @@ -1066,6 +1069,79 @@ func formatUnifiedFindingBriefingPriority(f *unified.UnifiedFinding) string { return strings.Join(parts, "; ") } +func unifiedFindingBriefingAttention(f *unified.UnifiedFinding) string { + if f == nil { + return "" + } + + now := time.Now() + parts := make([]string, 0, 8) + status := unifiedFindingChatStatus(f, now) + switch status { + case "active": + if severity := strings.TrimSpace(string(f.Severity)); severity != "" { + parts = append(parts, "active "+severity+" finding") + } else { + parts = append(parts, "active finding") + } + case "resolved": + if f.RegressionCount > 0 { + parts = append(parts, "resolved after prior regression") + } else { + parts = append(parts, "resolved finding") + } + case "snoozed": + parts = append(parts, "snoozed finding") + case "dismissed": + parts = append(parts, "dismissed finding") + case "suppressed": + parts = append(parts, "suppressed finding") + } + if f.RegressionCount > 0 { + parts = append(parts, fmt.Sprintf("regressed %d times", f.RegressionCount)) + } else if f.TimesRaised > 1 { + parts = append(parts, fmt.Sprintf("raised %d times", f.TimesRaised)) + } + if f.LastRegressionAt != nil { + parts = append(parts, "last regression "+f.LastRegressionAt.Format(time.RFC3339)) + } + if loopState := strings.TrimSpace(f.LoopState); loopState != "" { + parts = append(parts, "loop "+loopState) + } + + rec := f.InvestigationRecord + outcome := strings.TrimSpace(f.InvestigationOutcome) + if rec != nil { + outcome = strings.TrimSpace(string(rec.Outcome)) + if approvalID := strings.TrimSpace(rec.ApprovalID); approvalID != "" { + parts = append(parts, "approval "+approvalID) + } + if rec.ProposedFix != nil && rec.ProposedFix.Destructive { + parts = append(parts, "destructive proposed fix") + } + } + switch aicontracts.InvestigationOutcome(outcome) { + case aicontracts.OutcomeFixQueued: + parts = append(parts, "fix queued for governed review") + case aicontracts.OutcomeFixExecuted: + parts = append(parts, "fix executed awaiting verification") + case aicontracts.OutcomeFixFailed: + parts = append(parts, "fix failed") + case aicontracts.OutcomeFixVerificationFailed: + parts = append(parts, "verification failed") + case aicontracts.OutcomeFixVerificationUnknown: + parts = append(parts, "verification inconclusive") + case aicontracts.OutcomeNeedsAttention: + parts = append(parts, "needs operator attention") + case aicontracts.OutcomeCannotFix: + parts = append(parts, "Patrol cannot safely fix") + case aicontracts.OutcomeTimedOut: + parts = append(parts, "Patrol timed out") + } + + return formatBriefingStringList(parts, 8, "attention facts") +} + func formatUnifiedFindingBriefingRecency(f *unified.UnifiedFinding) string { if f == nil { return "" diff --git a/internal/api/ai_handler_test.go b/internal/api/ai_handler_test.go index 592859382..3cc036395 100644 --- a/internal/api/ai_handler_test.go +++ b/internal/api/ai_handler_test.go @@ -732,6 +732,7 @@ func TestHandleChat_IncludesInvestigationRecordContext(t *testing.T) { assert.Contains(t, reqArg.HandoffContext, "Finding: High CPU usage (critical, performance, active)") assert.Contains(t, reqArg.HandoffContext, "Resource: web-server (vm) [vm-100] on pve-1") assert.Contains(t, reqArg.HandoffContext, "Priority: critical performance; status active; loop awaiting_approval; raised 3 times; regressed 2 times") + assert.Contains(t, reqArg.HandoffContext, "Attention Reason: active critical finding; regressed 2 times; last regression 2026-05-06T12:06:00Z; loop awaiting_approval; approval approval-123; destructive proposed fix; fix queued for governed review") assert.Contains(t, reqArg.HandoffContext, "Recency: detected 2026-05-06T12:00:00Z; last seen 2026-05-06T12:02:00Z; raised 3 times; regressed 2 times; last regression 2026-05-06T12:06:00Z") assert.Contains(t, reqArg.HandoffContext, "Investigation: completed; outcome fix_queued; confidence high; attempts 1") assert.Contains(t, reqArg.HandoffContext, "Evidence Snapshot: metrics: CPU stayed above 95% for 10 minutes") diff --git a/internal/api/contract_test.go b/internal/api/contract_test.go index 3f93ff761..098ca1360 100644 --- a/internal/api/contract_test.go +++ b/internal/api/contract_test.go @@ -175,6 +175,8 @@ func TestContract_AssistantFindingContextUsesModelOnlyHandoff(t *testing.T) { "[Operator Briefing]", `appendChatContextLine(b, "Recency", formatUnifiedFindingBriefingRecencyFacts(f))`, "formatUnifiedFindingBriefingRecencyFacts(f)", + `appendChatContextLine(b, "Attention Reason", attention)`, + "unifiedFindingBriefingAttention(f)", `appendChatContextLine(b, "Evidence Snapshot", evidence)`, `appendChatContextLine(b, "Verification", verification)`, "formatInvestigationRecordEvidenceBriefing",