Show workload-capable source failures on Workloads and keep matching Proxmox host agents attached to their API source when inventory collection is blocked.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
- 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.
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.
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.
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.
- 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