Commit Graph

722 Commits

Author SHA1 Message Date
rcourtman eac7dfe9ef Make Patrol reasoning model-owned 2026-05-15 12:22:52 +01:00
rcourtman 376b2f80fc Simplify Patrol Assistant handoffs 2026-05-15 10:17:10 +01:00
rcourtman 7d6b447c59 Simplify recovery presentation and type contracts 2026-05-14 21:08:54 +01:00
rcourtman 3580a5ed6d Clarify storage topology and recovery guards 2026-05-14 20:51:32 +01:00
rcourtman a6c460daa0 Stabilize infrastructure realtime identity 2026-05-14 11:50:29 +01:00
rcourtman 321f563a52 Surface blocked workload inventory sources
Show workload-capable source failures on Workloads and keep matching Proxmox host agents attached to their API source when inventory collection is blocked.
2026-05-14 00:22:18 +01:00
rcourtman cbe595572b Improve action audit refusal presentation
Present stable refusal prefixes through shared frontend helpers and show verification outcomes in resource action history without exposing raw refusal tokens. Extend action audit tests, drawer coverage, and VMware history spec evidence.
2026-05-13 23:03:12 +01:00
rcourtman f024d3b560 Align action audit verification projection 2026-05-13 18:36:00 +01:00
rcourtman 53ebbf97f8 Harden recovery activity timeline ranges 2026-05-13 18:32:25 +01:00
rcourtman 3cbd49cb54 Align Patrol coverage caveats with full runs 2026-05-12 21:33:44 +01:00
rcourtman 5b18438528 Use checked wording for limited Patrol recency 2026-05-12 17:53:53 +01:00
rcourtman 80c061cd45 Separate Patrol runtime issues from findings copy 2026-05-12 17:43:28 +01:00
rcourtman 450e7516f0 Keep Patrol findings from showing all-clear copy 2026-05-12 17:34:30 +01:00
rcourtman edda19aadf Catch up frontend tests with collapsed Patrol details and refactored FindingsPanel 2026-05-11 23:37:21 +01:00
rcourtman 2725a325f2 Consolidate AgentIntegrationsPanel doc links into shipped AGENT_SUBSTRATE.md 2026-05-11 23:25:55 +01:00
rcourtman 68e2100955 Suppress empty metric values on state-alert Assistant handoffs
The Pulse Assistant briefing and prompt for a powered-off alert
rendered "Current value 0.0%; threshold 0.0%" because the backend
sends value=0 and threshold=0 for state alerts (which have no
metric semantics). That line is misleading to the operator and
gives the LLM no useful signal.

Adds isMetricAlertType / isStateAlertType helpers to
frontend-modern/src/utils/alerts.ts naming the state-alert set
(powered-off, unreachable, offline, host-offline, connectivity,
docker-container-state, docker-container-health,
docker-host-offline). State alerts represent binary or enumerated
conditions, not metric threshold crossings.

The alert handoff builder routes through that helper:
  - Briefing detailLines omit the value/threshold line when the
    alert is a state alert.
  - Prompt omits the **Current Value:** and **Threshold:** lines.
  - Prompt now includes **Message:** so the actual signal is
    surfaced (was previously dropped from the prompt).
  - Prompt step 2 swaps "Check related metrics" for "Check what
    changed recently for this resource (state events, recent
    commands, related alerts)" — the right question for a
    binary-state alert.

Two new tests cover the state-alert and metric-alert branches.
2026-05-10 23:01:21 +01:00
rcourtman 77479fb9ca Rename Patrol schedule "Disabled" preset to "Off"
The Configure Patrol drawer paired a master "Patrol Running" toggle
with a "Run every: Disabled" dropdown for the no-schedule case. Two
contradictory wordings on the same surface — easy to misread the
schedule label as "Patrol is disabled" when the master toggle is
actually on and triggered/manual runs are still working.

Rename the zero-interval preset to "Off" and rewrite the helper
text to spell out that manual runs and alert/anomaly triggers still
fire when the schedule is off. The change is naming only; the
underlying interval value (0) is unchanged so persisted settings
continue to round-trip.

Updates the canonical-presets test assertion to match.
2026-05-10 19:40:16 +01:00
rcourtman a17f879a19 Stop redacting resource names in remaining operator-local UI
Continues the policy-redaction scope fix from abdde303a. The same wrong
invariant -- "operator-local UI redacts when policy says redact" -- was
also encoded in:

- alertResourceTableModel.getAlertResourceLabel (the /alerts table that
  feeds AlertResourceTableMobile and AlertResourceTableRow)
- AlertResourceIncidentsPanel (the incident card shown after the operator
  clicks into a resource)
- infrastructureSelectors.matchesSearch (/infrastructure search filter,
  whose haystack already includes raw hostname/ips so redacting the
  displayName was internally inconsistent)
- useResources filter().search (the shared resource hook's search
  predicate, used by Settings panels, /storage, /alerts, FindingsPanel)
- recoveryRecordPresentation.getRecoveryLinkedResourceLabel (recovery
  rollup labels rendered in /recovery)
- Settings/ResourcePicker (the operator's selection UI for the Reporting
  Panel; the picker is local even when the report itself transmits)

All six surfaces are operator-local. Per docs/PRIVACY.md, resource-policy
redaction applies "Before non-local model requests leave the instance" --
it is a transmission-boundary policy, not a local-rendering policy. The
operator must see their own resource names to recognize, search, and
configure them.

Switch each callsite to getPreferredInfrastructureDisplayName. Invert the
five test fixtures (ResourceTable, ResourcePicker, infrastructureSelectors,
UnifiedResourceTable performance contract, WorkloadsSurface performance
contract) that locked in the old redaction-in-local-UI invariant.

Cloud-bound callers (AI Chat, organization sharing prepare-payload, the
resource-detail drawer's governance meta panel) still call
getPreferredResourceDisplayName and continue to redact -- their tests
are unchanged. The only call site of the governed helper that is left
not yet reclassified is problemResourcePresentation, which currently has
no production consumers; leaving it untouched.
2026-05-09 20:16:37 +01:00
rcourtman 3f783a7d98 Distinguish auto-suppressed dismissals from manual operator dismissals on the row
Both serialize as DismissedReason="expected_behavior" but tell
different stories — the auto-dismiss is Pulse staying quiet because
the operator scheduled maintenance or marked the resource
intentionally offline, while the manual dismiss is the operator
deciding the finding is expected. The lifecycle metadata
(operator_state_cause) already distinguishes them; this slice surfaces
the distinction on the FindingsPanel row.

Adds getOperatorStateDismissCause and formatOperatorStateDismissCauseLabel
helpers (the TS mirror of findOperatorStateDismissCause from slice 37).
Renders an "auto: maintenance" / "auto: intentionally offline" badge
next to the existing dismissed-reason badge when the lifecycle scan
finds operator_state_cause on the most-recent dismissed event.

The newest-first scan order is the same as the Go side: a manual
dismissal that supersedes an earlier auto-dismiss reports as manual,
so a stale auto-dismiss cause does not falsely badge the row after
an operator override.
2026-05-09 19:10:27 +01:00
rcourtman 3ddfd224aa Clarify the 24h alert overview stat label
The Acknowledged / Last 24 Hours / Workload Overrides stat row on
/alerts had an ambiguous middle card: "Last 24 Hours" with a bare
number doesn't say what is being counted. Rename the label to
"Triggered (24h)" so the metric (alerts that fired in the window)
is readable at a glance. The underlying total24h calculation is
unchanged.
2026-05-09 18:39:42 +01:00
rcourtman 6e0b951a27 Strip Proxmox pve-manager wrapper from version badges
Proxmox APIs return version strings in the form
'pve-manager/9.1.9/ee7bad0a3d1546c9' (package-name/version/git-hash).
Without stripping the wrapper, badges rendered as
'PVE pve-manager/9.1.9/ee7bad0a3d1546c9', which truncates to the
unreadable 'PVE pve-manage...' in the SYSTEM column on the
infrastructure dashboard.

Extend normalizeVersion to detect and unwrap the package/version/hash
shape (also covers 'proxmox-backup-server/4.0/...' and similar). Now
badges read 'PVE 9.1.9', 'PBS 4.0', etc. as expected.
2026-05-09 18:03:42 +01:00
rcourtman 8504d97c5b Humanize raw backend errors on the connections list
The Infrastructure connections table surfaced lastError.message verbatim
from the backend, which produced user-hostile rows like
'poll_nodes failed on delly: Get "https://192.168.0.134:8006/api2/json/nodes":
context deadline exceeded (Client.Timeout exceeded while awaiting headers)'.

Add a connectionErrorPresentation utility that maps the common Go error
chains we see in practice to a short headline plus a remediation hint:

- timeout / deadline exceeded -> 'Connection timed out'
- DNS lookup failures -> 'Host not found'
- connection refused, unreachable network, TLS issues, 4xx, 5xx etc.

Unknown errors fall back to the raw message stripped of the most common
implementation prefixes (the poll_<x> failed on <y>: + Get "URL": noise)
so they remain readable.

Wire the helper into useConnectionsLedger so every row goes through it;
the rest of the rendering chain stays unchanged.
2026-05-09 13:10:29 +01:00
rcourtman 06ebe55e13 Add Copy summary action to share findings as paste-ready Markdown
Operators frequently need to delegate a finding by pasting it into Slack,
email, or a ticket. Currently they have to manually copy each piece —
title, description, impact, recommendation — out of the expanded card.

Adds formatFindingForClipboard helper that produces Markdown mirroring the
seven-question schema's render order (severity + title + resource, then
description, impact, recommendation, plus trust signals). Wires a Copy
summary button between Explain and Discuss in the expanded card, routing
through the shared copyToClipboard helper with success/error notifications.

Investigation evidence and rollback plans are intentionally omitted —
those are conversation context for the Assistant flow, not "share this
finding" context for chat or tickets.
2026-05-09 13:00:04 +01:00
rcourtman 7593097e04 Surface verified-resource coverage on the Patrol recency line
Third wedge of the Patrol page IA reframe. The recency line "Last full
patrol: 3m ago" tells the operator when Pulse last ran but not what it
covered. Adds a coverage signal so the line reads "Last full patrol: 3m
ago — verified 47 resources" — operators see temporal AND coverage state
without scrolling.

Extends PatrolRecencyPresentation with an optional resourcesChecked
populated from PatrolRunRecord.resources_checked on the latest completed
run. Field is intentionally optional (omitted when zero) so a degenerate
zero-coverage run doesn't render "verified 0 resources" as an alarm.
2026-05-09 12:54:14 +01:00
rcourtman 1088ffe126 Reframe Patrol page copy as proactive intelligence, not runtime control
The Patrol page header used to read "Continuously verify infrastructure
health, review findings, and control Patrol runtime behavior" — passive
monitoring console framing that hides what Patrol actually does. The Pulse
Intelligence vision is proactive: Patrol investigates the infrastructure,
captures evidence, and proposes safe fixes under operator approval.

Updates the canonical PATROL_PAGE_DESCRIPTION to name that loop directly,
adds a PATROL_PAGE_TITLE_TOOLTIP that reads the same string so hover and
inline can't drift apart, and rewires PatrolIntelligenceHeader to consume
both from the canonical helper instead of carrying an inline copy. First
wedge of the Patrol page IA reframe; structural changes (header trust
signals, tab semantics) follow in subsequent slices.
2026-05-09 12:42:41 +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 b8b4fb0439 Tighten add-infrastructure picker copy and ordering
Three small UX gaps surfaced when walking the add-infrastructure flow
as a user:

- Source picker scattered the Proxmox suite (PVE at position 3, PBS
  and PMG at positions 8 and 9). Group PVE, PBS, and PMG adjacent;
  put other API platforms next, then agent-install paths with named
  platforms before the generic Linux/macOS/Windows host card, then
  the network endpoint probe fallback.

- 'First lab ready' badge surfaced internal lane-stage jargon. Rename
  to 'Early support' so the user reads it as 'this works but has had
  limited production exposure' instead of internal QA terminology.

- TrueNAS connection form Name field carried a 'tower' placeholder.
  Tower is iconic Unraid; the placeholder was likely a copy-paste
  leftover. Use 'truenas-1' so the example matches the form's domain.

Update the two tests pinning the picker order and the badge label to
the new canonical state.
2026-05-09 10:48:25 +01:00
rcourtman 2e0a841fd1 Relax 2 grammar-fragile boundary assertions to symbol-presence checks
The boundary test had two stale assertions failing because the
implementation grammar shifted while preserving the contract:

- usePatrolIntelligenceState now imports
  buildPatrolInvestigationContextSummary inside a multi-line grouped
  import. Replace the single-line literal match with two checks
  (symbol present + module present).

- patrolInvestigationContextModel now passes a precomputed
  commandCount through normalizeNonNegativeCount to formatCommandSummary
  instead of '.commands?.length ?? 0'. Assert the helper is invoked
  and the legacy '.commands.join' path is absent without prescribing
  the exact arg shape.

Both contracts are still enforced; the assertions just stop tracking
import formatting and helper-call argument shape.
2026-05-08 22:21:46 +01:00
rcourtman 0204744a88 Route FindingsPanel investigation pointer through shared helper
The proposed-fix briefing path inlined a finding-side pointer check
that touched investigationOutcome, investigationSessionId, and
lastInvestigatedAt directly. The frontend-resource-type-boundaries
guardrail forbids FindingsPanel from reading
finding.investigationSessionId because the documented presentation
boundary keeps those reads inside aiFindingPresentation helpers.

Add hasFindingInvestigationHandoffPointer to aiFindingPresentation
(narrower than hasFindingInvestigationDetails: no investigationStatus
or investigationAttempts; includes lastInvestigatedAt) and use it in
FindingsPanel. The approval-side pointer is still combined inline at
the call site since pendingApprovalBriefing is not finding state.
2026-05-08 22:10:34 +01:00
rcourtman a4346f2e93 Move resolveAlertTargetType boundary to alertAssistantHandoffModel
The boundary test asserted that InvestigateAlertButton owned the
resolveAlertTargetType call. The button has been refactored to
delegate to buildAlertAssistantHandoff, and the handoff model now
owns that boundary. Update the assertion to follow.

Note: the same test block still has unrelated failing assertions
about FindingsPanel.tsx accessing finding.investigationSessionId
and finding.status === 'resolved' directly rather than through the
documented status helpers. That is real implementation drift and
needs a separate FindingsPanel cleanup pass; not bundled here so the
boundary delta stays scoped.
2026-05-08 21:59:40 +01:00
rcourtman e7060bfcd1 Sweep three more stale test assertions to match canonical state
- recoveryCanonicalVocabulary: relax 'platform-first' iterator
  assertion from the expression-body callback shape '(platform) => ('
  to just '(platform) =>'. Both recovery sections now use block-body
  callbacks because they need a local for badge resolution. The
  guardrail's real intent is the 'platform' name over legacy
  'provider', not a specific callback shape.

- SuggestProfileModal: Settings Preview now interpolates
  getSourcePlatformLabel('docker') into the KNOWN_SETTINGS label,
  yielding 'Enable Docker / Podman monitoring' instead of the older
  'Enable Docker Monitoring'.

- UnifiedResourceTable.workloads-link: the Service Infrastructure
  dual-table renders in compact layoutMode in the test environment.
  Update column header expectations to compact equivalents:
  Datastores -> Stores, Action -> Open, Deferred -> Def.
2026-05-08 21:05:46 +01:00
rcourtman c0153f8d41 Surface investigation confidence as a badge in the collapsed finding row
The seven-question schema's confidence answer was previously buried in
the expanded investigation section. Surface it in the collapsed row
next to the investigation outcome badge so operators can scan trust
without expanding every finding.

Adds getInvestigationConfidenceBadgeClasses helper in
aiFindingPresentation.ts with a small palette: high is reassuringly
emphasized (emerald), medium is neutral, low is a soft amber so the
operator notices when the trust signal is weak. The badge renders only
when finding.investigationRecord?.confidence is set; findings without
investigation records continue to show no confidence badge, which is
the correct semantic (we have no recorded confidence to display).

Adds a source-text test that pins the badge wiring against future
refactors. Updates the patrol-intelligence contract to pin the
collapsed-row confidence badge surface and the no-fabrication rule.
2026-05-08 19:52:09 +01:00
rcourtman 3c25674d43 Pin hybrid-source platform version precedence
Add a frontend badge presentation test covering the case where a
hybrid-source resource exposes both a platform-facet version
(proxmox.pveVersion) and an agent OS version (agent.osVersion) for
the underlying Linux distribution. The platform-facet version must
win; otherwise a Pi or Tower style host could badge a Debian or
Raspbian version next to its true platform identity.

Mirrors the backend dual-source platform identity guardrails.
2026-05-08 16:50:39 +01:00
rcourtman 4d5d77cf79 Deduplicate versioned infrastructure system badges 2026-05-08 15:42:18 +01:00
rcourtman aa21e624aa Show platform versions in system badges 2026-05-08 15:07:09 +01:00
rcourtman cb772737e7 Fix platform identity source contracts 2026-05-08 14:54:08 +01:00
rcourtman 2baa05c4e4 Fix platform identity source canonicalization 2026-05-08 12:51:39 +01:00
rcourtman 31d07e74ce Fix Unraid storage health explanations 2026-05-08 12:18:09 +01:00
rcourtman ac82a28521 Fix Unraid agent host profile detection 2026-05-08 11:05:14 +01:00
rcourtman 1450b442b0 Simplify infrastructure source onboarding 2026-05-08 00:57:19 +01:00
rcourtman 91a2904ed2 Make Patrol recommendation actions operational 2026-05-08 00:46:27 +01:00
rcourtman 4b1b7ea0d1 Add Patrol recommended next step 2026-05-08 00:38:32 +01:00
rcourtman f65505ba04 Propagate agent host profiles to resources 2026-05-08 00:01:03 +01:00
rcourtman 4736358acc Drive agent host profiles from platform manifest 2026-05-07 23:42:15 +01:00
rcourtman 671431a862 Make Patrol handoffs context-first
- frame coverage-incomplete assessments as verification gaps
- keep Assistant empty states aligned with attached handoff context
- cover Patrol briefing prompts and chat empty-state presentation
2026-05-07 22:56:06 +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 c9198dd54b Warn paid users on missing Pro runtime 2026-05-07 19:18:31 +01:00
rcourtman 9cbd3dcbc6 Fail closed malformed approval expiry state 2026-05-07 16:56:47 +01:00
rcourtman e7996a5914 Make approval expiry labels fail closed 2026-05-07 16:50:43 +01:00