Commit Graph

1186 Commits

Author SHA1 Message Date
rcourtman 8a70a2c23c Auto-acknowledge findings on intentionally-offline resources
Compounding slice on the per-resource operator-state feature: when an
operator has marked a resource as IntentionallyOffline (via the API
surface from slice 30), new findings against that resource get
auto-dismissed as expected_behavior with the lifecycle event tagged
operator_state_cause=intentionally_offline. Same shape as the
maintenance-window suppression from slice 31 but indefinite — no
scheduled end, no maintenance_end_at metadata.

Restructures the ResourceOperatorStateProvider interface to return a
single ResourceOperatorStateProjection per call rather than a narrow
ActiveMaintenanceWindow method. Adding the second signal would
otherwise have meant a second method and another call per finding;
the projection shape carries every signal in one round-trip and gives
future signals (NeverAutoRemediate, criticality) a stable extension
point.

Maintenance windows take priority over intentionally_offline when
both are active because the time-bounded suppression is more honest
to surface to the operator — they'll see it auto-clear when the
window ends rather than wondering when the indefinite suppression
will lift.
2026-05-09 14:54:45 +01:00
rcourtman cf2e61ea22 Auto-acknowledge new findings during operator-set maintenance windows
Third slice on the per-resource operator-state feature delivers the
behavioral payoff: when an operator has set a maintenance window on a
resource (via the API surface from slice 30), new findings against
that resource get auto-dismissed as expected_behavior at creation time
rather than firing notifications. The finding still lands in durable
history with a UserNote naming the window and a "dismissed" lifecycle
event tagged operator_state_cause=maintenance_window so the operator
can audit what tripped during the window.

Adds a narrow ResourceOperatorStateProvider interface to internal/ai
so the findings runtime stays free of an internal/unifiedresources
import. The API layer wires an adapter in router.go that projects
unified.ResourceOperatorState through state.IsInMaintenanceAt into
the ActiveMaintenanceWindow shape the findings runtime consumes.

Suppression is opt-in: stores without a provider wired keep the
original new-finding behavior bit-for-bit, so deployments that
haven't adopted the operator-state feature see no behavioral change.
IntentionallyOffline and NeverAutoRemediate land as compounding
slices on the same provider interface.
2026-05-09 14:43:22 +01:00
rcourtman 46646b4293 Add /api/resources/{id}/operator-state GET / PUT / DELETE handlers
Second slice on the per-resource operator-state feature: the API
surface that the storage foundation from slice 29 was designed to
support. A frontend, pulse-cli, or Assistant tool call can now read
the operator-set state for a resource, replace it with PUT, or clear
it with DELETE.

Contract decisions worth preserving:
- GET 404s with stable error code operator_state_not_set when no
  entry exists, distinct from a 200 with default (all-zero) fields.
- PUT replaces the entire record. URL canonicalId wins over body to
  prevent body-manipulation retargeting; server-side setAt/setBy
  populate from request time and authenticated identity, ignoring
  client values so the audit trail stays honest.
- Validation rejections surface 400 with operator_state_invalid so
  frontend can branch on the code without string-matching messages.
- DELETE is idempotent — 204 whether or not an entry was present.
- GET runs under monitoring:read; PUT and DELETE under
  monitoring:write because the state modulates Patrol's behavior.

Finding-suppression and action-broker integrations land in subsequent
slices that consume the same ResourceOperatorState shape.
2026-05-09 14:34:43 +01:00
rcourtman 2bd4621b76 Attribute operator-driven Mark resolved closures as "Resolved by you"
Slice 22 added the manual Mark resolved button (auto_resolved=false) but the
resolution-reason copy still flattened every closure into "Condition
cleared" or "Issue no longer detected" — the operator couldn't tell from
the timeline whether they had closed the loop themselves or Pulse had
auto-detected the condition clearing.

Threads the existing Finding.AutoResolved flag through unified.UnifiedFinding
(Go), router.go conversions, UnifiedFindingRecord (TS), and the store-level
UnifiedFinding. The frontend resolution-reason helper now reads "Resolved by
you <time>" when autoResolved === false, while keeping Patrol's specific
fix outcomes (fix_verified, fix_executed, resolved) priority because those
describe Pulse's actual remediation rather than mere auto-detection.
2026-05-09 11:10:11 +01:00
rcourtman 5cc2f61be0 Surface will_fix_later remind-at on dismiss confirm and dismissed rows
Slice 18 made will_fix_later a real operational commitment server-side, but
the new RemindAt field stayed invisible to operators until the reminder fired
a week later. This wires it through the API surface and renders it where the
operator decides and where they later revisit.

UnifiedFinding (Go and TS) and the Patrol Finding TS shape now carry
RemindAt / remind_at; router.go and AddFromAI mirror it like the other
user-feedback fields. FindingsPanel previews "Pulse will stay quiet for 7
days, then surface again on <date>" on the dismiss confirmation panel before
the operator confirms, badges dismissed-as-will_fix_later rows with
"Reminding <date>" in amber, and adds explanatory copy for the other two
dismissal reasons so all three paths feel deliberate rather than
undifferentiated.
2026-05-09 10:47:21 +01:00
rcourtman 07d1ab51d1 Surface trust metrics on the Patrol page
Wire FindingsStore.GetTrustSummary through PatrolService and the
patrol-status API into the Patrol page so the operator can scan
"is Pulse useful?" at a glance. Adds a small Trust strip above the
Findings/Runs tab bar that renders compact signals: fixes verified,
auto-resolved, dismissed-as-noise, dismissed-as-expected, currently
active, and regressed-at-least-once. The strip is hidden when every
signal is zero so a fresh install sees no empty pill.

Plumbing:
- PatrolService.GetFindingsTrustSummary accessor (delegates to the
  store-level method shipped in the prior slice)
- PatrolStatusResponse carries Trust *FindingsTrustSummary; populated
  from the active patrol service, omitted when no service is available
  (snapshot semantics, not lifetime totals)
- TS FindingsTrustSummary mirror in api/patrol.ts and a trust field on
  PatrolStatus
- PatrolIntelligenceWorkspace reads state.patrolStatus()?.trust and
  conditionally renders the strip

Verification artifacts:
- internal/ai/patrol_test.go: TestPatrolService_GetFindingsTrustSummary
- internal/api/contract_test.go: TestContract_PatrolStatusTrustJSONSnapshot
  pinning the canonical wire shape
- frontend-modern/src/api/__tests__/patrol.test.ts: round-trip test for
  the trust block on the patrol-status response
- frontend-modern/src/features/patrol/__tests__/PatrolIntelligenceWorkspace.test.ts:
  source-text test pinning state.patrolStatus()?.trust read,
  aria-label, and field names so future strip additions go through
  the FindingsTrustSummary contract first.
- frontend-modern/src/features/patrol/__tests__/patrolInvestigationContextModel.test.ts:
  pins that the per-finding context model does not synthesize impact
  from trust counters; trust is an aggregate operator-page concern,
  not a per-finding text source.

Updates the api-contracts, ai-runtime, patrol-intelligence,
agent-lifecycle, frontend-primitives, and storage-recovery contracts
to pin the trust block's shape, the strip's contract-first rule, and
the scope boundary (trust counters are advisory operator context, not
enrollment/storage/recovery action authority).
2026-05-08 21:11:24 +01:00
rcourtman cff4226531 Pass stored fingerprint into PVE diagnostic test client
The /api/diagnostics handler builds its own test client per PVE node
to run a live connectivity probe. The PBS branch already passed
node.Fingerprint into the test client config, but the PVE branch did
not. With VerifySSL=true and a self-signed Proxmox cert (the standard
configuration), tlsutil.CreateHTTPClientWithTimeout falls into
default-secure mode and validates against the system CA chain, which
fails the handshake even when the actual poller — which DOES pass
the fingerprint — is connecting fine.

The result was that /api/diagnostics reported delly + pi as
"Failed to connect to Proxmox API" while /api/resources was happily
ingesting all 27 workloads from the same hosts. Mirror the PBS
branch by passing node.Fingerprint into the PVE testCfg so the
diagnostic probe uses the same TLS verification path as the runtime
poller.

Add a regression test that spins up an httptest TLS server, captures
its leaf cert SHA-256, configures a PVE instance with VerifySSL=true
and that fingerprint, and asserts computeDiagnostics reports
Connected=true. The pre-fix code fails this with a "tls: bad
certificate" handshake error.
2026-05-08 20:58:32 +01:00
rcourtman b5c8e00859 Preserve previous successful fix across regressions
Capture the prior InvestigationRecord.ProposedFix.Description into a
new Finding.PreviousResolvedFixSummary field at regression time, before
the InvestigationRecord is cleared. Without this capture the next
investigation starts from blank context whenever a finding regresses,
and operational memory of "what worked last time" is lost.

The summary propagates through:
- FindingsStore.Add regression branch (capture before clear)
- Finding.MarshalJSON / UnmarshalJSON (wire shape)
- Both Finding to UnifiedFinding conversion sites in router.go
- UnifiedFinding (struct + JSON shadow + Marshal/Unmarshal)
- UnifiedStore.AddFromAI update branch (non-empty overwrite)
- Assistant chat context as a "Previous Resolved Fix" line so the LLM
  sees what worked previously rather than blank-slate diagnosing each
  regression.

Adds a unit test that walks the full lifecycle (detect, resolve via
UpdateInvestigationRecord + ResolveWithReason, re-detect) and asserts
PreviousResolvedFixSummary is preserved while InvestigationRecord is
cleared, plus two chat-context tests covering the surfaces-when-set and
omits-when-empty cases. Adds a contract test pinning the canonical
"previous_resolved_fix_summary" JSON key. Updates the api-contracts,
ai-runtime, and the dependent agent-lifecycle, performance-and-
scalability, and storage-recovery contracts to pin the operational
memory propagation rule and its scope boundary.
2026-05-08 19:45:44 +01:00
rcourtman 10ff1c4dcf Surface Finding.Impact through UnifiedFinding to operators
Carry the Finding.Impact text added in the previous slice through the
Finding to UnifiedFinding boundary and onto the FindingsPanel surface
so the runtime-failure consequence-if-ignored copy is visible to the
operator. Add Impact to the UnifiedFinding struct, JSON snapshot, and
both Marshal/Unmarshal mirrors; copy f.Impact into both Finding to
UnifiedFinding conversion sites in router.go; mirror impact in the TS
UnifiedFindingRecord and Finding API types and the aiIntelligence
store normalizers; render an Impact line between Description and
Recommendation in FindingsPanel.

Also fix the FindingsStore.Add dedup-merge path so re-detected findings
overwrite existing.Impact alongside Description and Recommendation
rather than preserving the stale empty value left by an older binary.
Without this fix, a freshly-classified runtime failure with new Impact
text would be merged onto the persisted finding but the Impact field
would be silently dropped.

Verified end-to-end against the live runtime: triggered a Patrol run,
watched the runtime-failure finding regenerate, confirmed the
operator-visible card now renders "Impact: While Patrol cannot
analyze..." between Description and Recommendation. Updates the
api-contracts, ai-runtime, patrol-intelligence, and the dependent
agent-lifecycle, performance-and-scalability, and storage-recovery
contracts to pin the propagation rule and the dedup-merge invariant.
2026-05-08 17:31:19 +01:00
rcourtman e7b5650233 Add impact and rollback to investigation records
Promote the seven-field investigation-record shape so Patrol findings
can carry consequence-if-ignored context and a record-level rollback
plan alongside the existing verification array. The shared
aicontracts.InvestigationRecord struct gains top-level Impact and
Rollback fields with matching TS mirrors, normalizes Rollback to an
empty slice, and the Patrol-owned investigation surface renders an
explicit "Impact not assessed" / "Rollback not specified" placeholder
so the operator-visible gap is conspicuous to both the operator and
Assistant when Patrol has not populated them. Backend default leaves
both empty rather than fabricating analysis from severity/category.
Also closes the existing Trigger.cause drift between Go and TS so
frontend handoff context preserves backend-attributed failure cause,
and updates the api-contracts, ai-runtime, frontend-primitives, and
patrol-intelligence subsystem contracts to pin the new shape.
2026-05-08 16:47:55 +01:00
rcourtman f88f0fcf1a Align DeepSeek V4 Patrol readiness 2026-05-08 11:34:07 +01:00
rcourtman ac82a28521 Fix Unraid agent host profile detection 2026-05-08 11:05:14 +01:00
rcourtman d0940db33b Clamp Patrol monitor-only autonomy saves
Refs #1463
2026-05-08 02:21:31 +01:00
rcourtman dd15d23a35 Hydrate Patrol requester in Assistant handoffs
- hydrate approval requester identity into backend handoff actions
- include requester provenance in refreshed finding action context
- document the backend requester authority boundary
2026-05-08 00:26:04 +01:00
rcourtman ea3e1b216a Persist Patrol approval requester identity
- store requester provenance on approval records
- carry requester metadata through approval APIs and Assistant handoffs
- document the safe Patrol approval provenance boundary
2026-05-08 00:12:09 +01:00
rcourtman f563adf136 Mark Patrol queued fixes as Patrol actions
- derive approval requester identity from investigation-fix approvals

- stamp pending action-audit lifecycle events with Patrol as the producer

- document the requester boundary for Patrol handoffs and timelines
2026-05-07 23:48:12 +01:00
rcourtman 4736358acc Drive agent host profiles from platform manifest 2026-05-07 23:42:15 +01:00
rcourtman cf8931031d Record Patrol fix approvals in action audit
- seed queued investigation fixes with governed action plans

- persist planned and pending lifecycle evidence for Patrol approvals

- cover the approval adapter and subsystem contracts
2026-05-07 23:26:39 +01:00
rcourtman bbaa6c0a6c Cover Patrol handoff approval mode
- force non-empty Patrol finding handoffs into approval-required mode

- add direct helper coverage for finding, scoped, resource, action, and metadata handoffs

- align subsystem contracts with the backend-owned clamp
2026-05-07 23:08:56 +01:00
rcourtman f13a89db62 Clear Patrol runtime issue after scoped success
- mark DeepSeek V4 Flash and Pro tool-ready for Patrol readiness
- resolve synthetic Patrol runtime findings after provider-backed scoped runs
- cover readiness and run-history resolved counts
2026-05-07 22:42:38 +01:00
rcourtman 7da942226a Clarify Pulse Agent host profile support
Separate first-class platform support from Pulse Agent host profiles and classify Unraid as an agent-backed host profile while preserving it as presentation-only platform vocabulary.
2026-05-07 22:28:24 +01:00
rcourtman d9331d39e4 Move Patrol run Assistant handoffs server-side
- rehydrate Patrol run context from Patrol history for chat requests and follow-up sessions
- send only browser-safe run metadata from the frontend
- classify and redact provider runtime failures in handoff prompts and briefings
2026-05-07 21:58:06 +01:00
rcourtman f7992e8e78 Return safe provider preflight diagnostics
Classify Assistant and Patrol provider-test failures through the Patrol runtime failure taxonomy, redact secret-shaped provider evidence, and preserve safe recommendations in the settings shell.

Refs #1463
2026-05-07 20:45:18 +01:00
rcourtman d2625c4dfb Persist Patrol settings with readiness handoff
Refs #1463
2026-05-07 19:26:00 +01:00
rcourtman c9198dd54b Warn paid users on missing Pro runtime 2026-05-07 19:18:31 +01:00
rcourtman 9d1fabef03 Gate Patrol readiness across runtime entrypoints
Refs #1463
2026-05-07 18:24:47 +01:00
rcourtman 371ce99694 Add Patrol runtime readiness contract
Refs #1463
2026-05-07 17:51:40 +01:00
rcourtman ff9cb5c133 Restore Patrol run Assistant session identity
Refs #1463
2026-05-07 15:53:59 +01:00
rcourtman 8e17f16605 Allow monitor Patrol autonomy saves without Pro
Refs #1463
2026-05-07 14:57:53 +01:00
rcourtman 6b4e4d7fe6 Merge Patrol handoff context into Assistant requests 2026-05-07 11:58:02 +01:00
rcourtman a66b80ceb9 Carry structured Patrol actions into Assistant handoffs 2026-05-07 10:48:15 +01:00
rcourtman 7aaf76d03c Carry live approval lifecycle into Assistant handoffs 2026-05-07 10:25:14 +01:00
rcourtman 812c86692d Route Assistant handoffs through model context 2026-05-07 03:00:58 +01:00
rcourtman 2c913fac8e Clamp Patrol finding chat handoffs to approval mode 2026-05-07 01:31:45 +01:00
rcourtman ad61b6c19f Require approval for backend alert investigation commands 2026-05-07 01:24:57 +01:00
rcourtman bf0e4e16ac Align Assistant briefings with handoff actions 2026-05-07 00:38:42 +01:00
rcourtman b8bad16d80 Recover live Patrol approvals for Assistant handoffs 2026-05-07 00:31:44 +01:00
rcourtman 86244d8c13 Track runtime build in license activation 2026-05-06 23:45:37 +01:00
rcourtman 1fc66cf0cb Add attention reasons to Assistant briefings 2026-05-06 23:39:33 +01:00
rcourtman 6b51a15dfe Frame Assistant operator decisions 2026-05-06 23:30:11 +01:00
rcourtman eb88119a29 Add evidence cues to Assistant briefings 2026-05-06 23:17:33 +01:00
rcourtman c50f051e13 Add evidence cues to Assistant briefings 2026-05-06 23:10:32 +01:00
rcourtman 2fd85f62d3 Add related finding recency to Assistant handoffs 2026-05-06 22:50:18 +01:00
rcourtman 1f424523da Surface Assistant finding lifecycle context 2026-05-06 21:21:35 +01:00
rcourtman 6269bc2afa Enrich Assistant related finding handoffs 2026-05-06 21:14:09 +01:00
rcourtman 39a5be31a0 Sanitize Assistant briefing handoffs 2026-05-06 21:01:55 +01:00
rcourtman 67974f2ada Add Assistant operator briefing handoff 2026-05-06 20:48:25 +01:00
rcourtman 8db5bd39f7 Hydrate Assistant resource state context 2026-05-06 20:30:20 +01:00
rcourtman 8d51556d92 Hydrate Assistant action audit context 2026-05-06 20:05:42 +01:00
rcourtman 2e85079263 Hydrate Assistant finding lifecycle context 2026-05-06 19:54:04 +01:00