Commit Graph

7598 Commits

Author SHA1 Message Date
rcourtman ca299ec729 test(workloads): update GuestRow tests for new Availability column
Update column count (21→22), column ordering assertions, and
tablet/compact visible-column expectations to include the new
'availability' column.
2026-06-27 11:27:46 +01:00
rcourtman c0411dd0aa feat(workloads): dedicated Availability column in workload table
Availability probe badges (latency, failed, timed out) previously
rendered inline in the Name column. This moves them to a dedicated
'Avail' column right after Name with center-aligned badge layout
(kind: 'badge'), giving users:

- Vertical scannability: latency values align in a clean column
  instead of floating at variable x-positions after guest names
- Conceptual separation: Name answers 'what is this?', Availability
  answers 'is this up?'

Removes AvailabilityProbeCell from both the Name column and the OS
column fallback. The column is always visible (min layout: mobile)
across all view modes (all, vm, system-container, container,
app-container).

Verified via Playwright: 'Avail' header renders after 'Name', 31 cells
populated, jellyfin shows '6ms' in its own column.
2026-06-27 11:10:33 +01:00
rcourtman 1a1184d27f Auto-recover corrupted unified_resources.db instead of looping 500s
When the SQLite resource database is corrupted (malformed disk image),
NewSQLiteResourceStore now backs up the corrupted file to
*.corrupted.<timestamp> and recreates a fresh database. Without this,
every /api/resources request returned 500 with no recovery path
until the admin manually deleted the file.

Resource data is derived from monitor state and repopulated on the
next poll cycle; user-authored metadata (links, notes) in the corrupted
file is preserved in the backup.
2026-06-27 11:08:21 +01:00
rcourtman 24b16fe6c5 Refresh RC7 release packet after install-default fix 2026-06-27 10:24:01 +01:00
rcourtman 55204cde9b Align RC7 Docker install defaults 2026-06-27 10:18:40 +01:00
rcourtman dd5d6b9ad1 Use product language for Patrol control telemetry disclosure 2026-06-27 09:47:39 +01:00
rcourtman b8d5fa8828 Disclose Patrol control telemetry proof signals 2026-06-27 09:38:45 +01:00
rcourtman 34c60f0c68 Refresh frontend bundle-size baseline 2026-06-27 09:26:03 +01:00
rcourtman 0a6460e32a Fix discovery disabled-state test expectation 2026-06-27 09:16:43 +01:00
rcourtman da14b88d9f Prepare v6.0.0-rc.7 release candidate 2026-06-27 09:06:02 +01:00
rcourtman 5c2e465cde fix(discovery): show availability probe suggestion card for undiscovered guests
hasMeaningfulDiscoveryContext returned false for records without deep
scan data (empty service_type, no facts, no ports), which prevented the
GuestDrawerOverview from rendering the availability probe suggestion
card — even when suggested_availability_probe was populated.

Add hasSuggestedProbe to the meaningful context check so the suggestion
card renders for guests that have a backfilled probe suggestion but
haven't been deep-scanned yet.

Verified via Playwright: bazarr (no existing probe, no deep scan data)
now shows the suggestion card with HTTP :6767 at 192.168.0.78.
2026-06-27 00:08:32 +01:00
rcourtman bd0220ca58 Clear alerts activation config on org switch
The alertsActivation store (config, activation state, active alerts,
error) was not reset on org switch. Stale config from the previous
org (thresholds, activation state) persisted until the next page
navigation triggered refreshConfig.
2026-06-27 00:04:51 +01:00
rcourtman 3fa0a316e2 Immediately refresh patrol findings after org switch
The patrol polling effect did not depend on activeOrgID, so it
continued using the old interval without immediately loading new
org data. With the intelligence state now cleared on org switch,
this caused up to 30s of empty findings until the next poll cycle.
2026-06-27 00:03:48 +01:00
rcourtman eebe0b5f74 Clear AI intelligence state on org switch to prevent data bleed
The aiIntelligence store (findings, patrol findings, remediation plans,
pending approvals, circuit breaker, correlations) was not reset when
switching organizations. Findings from the previous org would remain
visible until the next polling cycle refreshed them. Added org_switched
event listener that resets all signals and clears the pending approval
expiry timer.
2026-06-27 00:00:10 +01:00
rcourtman fa3f57e6ba fix(discovery): backfill availability suggestions for existing discoveries
The refreshSuggestedAvailabilityProbeFromState method existed but was
never called, so existing discovery records never received availability
probe suggestions. This adds backfillAvailabilitySuggestions which:

- Triggers from SetReadState (goroutine) and runDiscoveryLoop (ticker)
- Retries with exponential backoff when the state snapshot is empty,
  waiting for the monitor to populate Proxmox data before proceeding
- Matches containers by VMID only (was VMID+Node, which failed in
  clusters where the discovery targetID differs from the container's
  node)

Also extends SuggestAvailabilityProbe with a hostname fallback: when
ServiceType is empty (deep scan not yet run), checks the discovery's
Hostname against webServiceDefaults/tcpServiceDefaults. This covers
containers like jellyfin, grafana, frigate, esphome, zigbee2mqtt,
homeassistant, mqtt, etc. in environments where background AI is
disabled.

Adds zigbee2mqtt and ntfy to webServiceDefaults.
2026-06-26 23:56:15 +01:00
rcourtman 3ea61e117d Add per-route error boundary to keep app shell alive on page crash
Previously a single ErrorBoundary wrapped the entire authenticated
section including sidebar and navigation. If any page component threw,
the full-screen fallback replaced everything — the user was stranded
with no way to navigate.

Added RouteErrorBoundary that wraps only the route content inside
AppLayout. On error it renders an inline error card with a Try Again
button while keeping the sidebar, header, and navigation functional.
2026-06-26 23:50:08 +01:00
rcourtman 83f99f1428 Clear pending ACK safety-valve timeouts on WebSocket store disposal
The 15s safety-valve timeout for pending alert acknowledgments was not
cleared in onCleanup. If the store was disposed while an ack was
pending, the timeout would fire after disposal and show a spurious
error toast. Added disposal cleanup for the timeout map and an
isDisposed guard in the callback.
2026-06-26 23:46:06 +01:00
rcourtman 9cf6c98fca Fix leaked alerts activation event listener on unmount
onCleanup referenced handleAlertsActivation (dead code, never
registered) instead of handleAlertsActivationEvent (the actual
listener). removeEventListener was a no-op, leaking the listener
on every component unmount unless shutdown() was called first.
2026-06-26 23:35:53 +01:00
rcourtman 7b2849d16d Fix NaN propagation in alert grouping window and AI duration display
setGroupingWindow stored Number.parseInt result without checking for
NaN, while the adjacent setEscalationAfter properly guards against it.
If an invalid value reached the parser, NaN would propagate into the
grouping config and could cascade into alert delivery issues.

formatDuration in AIModelSelectionSection displayed raw NaN/Infinity/
negative values without guarding. Now returns '-' for non-finite or
negative inputs.
2026-06-26 23:25:56 +01:00
rcourtman 9d2df76b3f Fix physical disk I/O metrics skipped when SMART data is empty (#1487)
writeHostPhysicalDiskIOMetrics gated ALL per-disk I/O metrics on
host.Sensors.SMART being non-empty. When the agent's SMART collection
fails (smartctl not installed, LXC container can't see /sys/block),
DiskIO data from gopsutil is valid but silently discarded.

Fix: remove the SMART requirement. When no SMART match is found for a
DiskIO entry, try matching against Proxmox API physical disks by device
name (via LinkedNodeID), then fall back to hostID:device as the metric
resource ID. This matches the fallback scheme already used by
HostSMARTDiskSourceID and PhysicalDiskMetaMetricID.

Refs #1487
2026-06-26 23:18:41 +01:00
rcourtman ad3b90e605 fix: ensure availability probes report at least 1ms latency on success
TCP probes to fast local-network services can complete in under 1ms,
causing latency.Milliseconds() to return 0. Combined with omitempty on
the poller status struct, this made some probes show 'Online' with no
latency in the settings panel. Now successful probes always report at
least 1ms.
2026-06-26 22:52:28 +01:00
rcourtman e09fe06201 Fix unreachable critical escalation for high percentage thresholds
The critical (severity-escalation) threshold was hardcoded as
Trigger + 10 for all metric types. For percentage metrics (cpu,
memory, disk, usage) with high triggers, this made critical
escalation unreachable: a CPU trigger of 95% produced a critical
threshold of 105%, which is impossible for a 0-100% metric.

Fix: add computeCriticalThreshold() helper that caps the critical
threshold at 99 for percentage metrics. Non-percentage metrics
(temperature, diskRead, diskWrite, networkIn, networkOut) keep
the Trigger + 10 offset unchanged.

Applied to all three code paths that derive critical from trigger:
- buildCanonicalMetricSpec (canonical evaluation)
- checkMetric new alert creation (legacy path)
- checkMetric existing alert update (legacy path)
2026-06-26 22:42:02 +01:00
rcourtman 06ea25e5ce Fix webhook private CIDR allowlist lost after monitor reload (#1507)
When a monitor reload was triggered by node auto-registration, the
reloadFunc in server.go recreated the monitor (and its notification
manager) but never re-applied system settings. The new notification
manager started with an empty webhook private CIDR allowlist, causing
webhook notifications to private IPs to fail until the allowlist was
manually re-saved in Settings.

Fix: call router.ReloadSystemSettings() at the end of reloadFunc,
after the new monitor references are set. This re-applies all
persisted system settings — including the webhook CIDR allowlist —
to the freshly created notification manager.

Refs #1507
2026-06-26 22:15:48 +01:00
rcourtman f2fe81bf97 fix: route local-network API connections through subprocess to bypass Tailscale NECP
The Tailscale system extension on macOS applies an NECP policy that captures
the Pulse process outbound Go TCP connections, routing them through utun4.
RFC 1918 addresses (192.168.0.x) fail with EHOSTUNREACH; Tailscale CGNAT
addresses (100.x.x.x) work normally. exec.Command subprocesses bypass NECP.

Add a subprocessConn type (subprocess_conn.go) that wraps nc stdin/stdout
as a net.Conn with real deadline enforcement (closes the connection on
timeout). Modify DialContextWithCache (dnscache.go) to route RFC 1918
addresses through the subprocess relay on macOS, while Tailscale IPs and
public IPs continue using the normal dialer.

Result: pbs-docker and pi Proxmox/PBS nodes now connect at the transport
layer. Remaining pi poll timeouts are caused by a pre-existing server-side
reverse DNS lookup delay (3s per HTTPS request); fix is NO_RESOLVE=1 in
/etc/default/pveproxy on each Proxmox node.
2026-06-26 22:13:22 +01:00
rcourtman 3202b4ab56 fix: add retention pruning for unified_resources.db (issue #1496)
The resource_changes and action_audits tables grew without bound
because no retention mechanism existed. In production deployments with
frequent monitoring cycles, the database could reach multiple GB within
weeks.

Add a background goroutine that runs every 6 hours and deletes:
- resource_changes older than 30 days
- action_audits older than 90 days

The goroutine starts in NewSQLiteResourceStore and is stopped via the
retentionStop channel in Close(). The first prune runs on the first
ticker interval (6h after startup), not immediately, so test fixtures
with historical timestamps are not affected.

Fixes #1496.
2026-06-26 21:48:42 +01:00
rcourtman 4a21c733e0 fix: standalone sub-tab title not reflecting active tab
The StandalonePageSurface createEffect that set document.title was being
overridden by AppLayout's competing createEffect, which always set
'Machines' for any standalone path. Consolidated the title resolution
into AppLayout with sub-tab awareness for standalone paths, so
/standalone/availability correctly shows 'Availability checks' and
/standalone/machines shows 'Machines'.

Removed the competing effect from StandalonePageSurface.
2026-06-26 21:26:33 +01:00
rcourtman 4d9972f49a Render finding evidence as 'What Pulse checked' in expanded Patrol finding card
The finding.evidence field (data/commands/metrics that support the finding)
was threaded through the Go backend, API types, and tool schema but was
never rendered in the UI. The expanded finding card now shows it between
Impact and Recommended next step, matching the checklist order from the
strong-version record: problem, affected thing, why it matters, what
Pulse checked, recommended next step.

- Add evidence field to UnifiedFinding interface and both normalization
  functions in aiIntelligence.ts
- Render Show when={finding.evidence} in FindingsPanel renderExpandedContent
- Add source-level guard test and rendering test with evidence data

Refs protection-posture-attention-queue strong-version checklist L35-36, L48-50.
2026-06-26 21:09:19 +01:00
rcourtman db64a11d44 fix: alerts with missing startTime excluded from Triggered (24h) count
Newly created alerts arriving via WebSocket can have missing or invalid
startTime values. new Date(undefined).getTime() returns NaN, which fails
the age >= 0 check, so these alerts were excluded from total24h but
still counted in active — creating a visible discrepancy between the
'Triggered (24h)' stat and the 'Acknowledge all (N)' button count.
Treat NaN timestamps as recent (count them in total24h) since an active
alert with no known start time is obviously recent.
2026-06-26 20:45:34 +01:00
rcourtman 6d36b1029f fix: show latency not protocol in availability probe badge
The badge now shows '22ms' in green instead of 'HTTP' with a colored
dot. The protocol was an implementation detail the user doesn't need
while scanning a table. The tooltip carries the full detail.

Badge: 22ms (green) / failed (red) / timed out (amber)
Tooltip: 31 ms - checked 3s ago
2026-06-26 20:40:01 +01:00
rcourtman 2c6b3e20a7 fix: standalone sub-pages show specific document title
Availability checks tab under Machines showed 'Machines · Pulse'
in the browser tab instead of 'Availability checks · Pulse'.
The tab-level fallback in AppLayout mapped all standalone routes
to 'Machines', and unlike Alerts/Settings pages, StandalonePageSurface
did not use PageHeader (which overrides document.title with the
specific page name). Add a createEffect to set the document title
from the active sub-tab label.
2026-06-26 19:27:46 +01:00
rcourtman 25c93bf77f fix: availability probes fail with exec.Command fallback for local network
All TCP and HTTP availability probes were failing with 'no route to host'
because the Pulse process's Go networking stack cannot reach local subnet
IPs when a Tailscale system extension is active. The ICMP probe already
worked because it uses exec.Command('ping'). Apply the same pattern:

- probeTCP: try Go net.Dialer first, fall back to exec.Command('nc')
- probeHTTP: try Go http.Client first, fall back to exec.Command('curl')
- Both fallbacks use OS-appropriate timeout flags (macOS: -G, Linux: -w)
- HTTP probes now skip TLS verification (availability check, not identity)
- Add AvailabilityProbeHTTPS protocol constant and wire it through
  Validate, HTTPURL, Normalize, and the probe switch
- Replace bulky TooltipPortal tooltip on AvailabilityProbeCell with a
  clean native title attribute ('14 ms - checked 3s ago')

Before: 0/20 probes green (all 'no route to host')
After:  14/20 probes green (6 legitimately down hosts)
2026-06-26 17:57:40 +01:00
rcourtman d14bc41b66 feat(discovery): suggest availability probes from discovered service types
Discovery already identifies services and their default ports. Now it
also suggests an availability probe configuration for each discovered
service with a known web interface (webServiceDefaults) or TCP service
(tcpServiceDefaults). The suggestion appears in the resource drawer as a
card with a 'Monitor availability' button. On approval, it calls the
existing POST /api/availability-targets API — one canonical system, no
second management surface.

Backend:
- New SuggestAvailabilityProbe() generates protocol/port/path from the
  same webServiceDefaults map used for URL suggestions, with a
  tcpServiceDefaults fallback for databases and message brokers
- New AvailabilityProbeSuggestion type on ResourceDiscovery
- Wired into both discovery paths (DiscoverResource + Docker background)
- Cached discoveries get the suggestion via refreshSuggestedAvailabilityProbe
  on read, so existing data picks it up without re-discovery

Frontend:
- AvailabilityProbeSuggestionCard in GuestDrawerOverview with one-click
  creation via AvailabilityTargetsAPI.create()
- Card hidden when the resource already has an availability facet
- Added 'https' to AvailabilityProbeProtocol type (backend already
  supports it)
2026-06-26 17:12:11 +01:00
rcourtman f3d9f426c5 test: fix 60s hang in canonical store refresh test
TestMonitorPollGuestSnapshots_RefreshesStaleCanonicalStoreForClusterGuest
passed context.Background() to pollGuestSnapshots, which creates an
internal 60s snapshot timeout. The backupStorageTimeoutSnapshotClient
mock blocks GetStorage until context cancellation, so the test wasted
60s waiting for the timeout to expire. Pass a 200ms deadline context
so pollGuestSnapshots caps its budget down — test now runs in 0.2s
instead of 60s and no longer times out under -race.
2026-06-26 17:06:02 +01:00
rcourtman 3773749281 Replace as-any cast with proper type guard in guest drawer
isGuestDrawerVM now returns 'guest is VM' instead of boolean, allowing
TypeScript to narrow the union and safely access VM-only properties.
Removes the as-any cast on diskStatusReason access in
GuestDrawerOverview.
2026-06-26 16:33:04 +01:00
rcourtman cc1cbbf133 feat: surface availability probe badge on workload rows
Workload rows now show a compact protocol badge (HTTP/TCP/ICMP) with a
status-colored dot next to the resource name when an availability probe
facet is attached. Badge tone reflects probe result: green for available,
red for failing, amber for degraded. Tooltip shows full probe detail
(method, target, result, check timing, failure count).

Data flows from the unified resource availability facet through
WorkloadGuest.availability into the shared Workloads table, making
probe status visible on any platform page (Proxmox, Docker, etc.).

Per governance: protocol badge in the row identity area, probe detail
in tooltip — no per-row fetch, no duplication across cells.
2026-06-26 16:27:20 +01:00
rcourtman 108dec4d7a Fix dead Investigate button and resolve all frontend TypeScript errors
FindingsPanel: The Investigate button was unreachable dead code.
runtimeState is typed as PatrolRuntimeState (a string union) but the
code accessed .autonomy_level on it. Added a proper autonomyLevel prop
typed as PatrolAutonomyLevel and passed it from
PatrolIntelligenceWorkspace. Tests updated to use the typed prop
instead of as-any casts.

websocket-global: getGlobalWebSocketStore returned T | undefined
because window.__pulseWsStore is optional. Added explicit return type
annotation with non-null assertion. This resolved 15 downstream TS
errors across useResources, useUnifiedResources, and TagBadges.

useWorkloads: Added availability field to local APIResource type to
match existing usage in mapResourceToWorkload.
2026-06-26 16:12:04 +01:00
rcourtman 85f389c1a4 fix: availability source no longer overrides higher-priority status sources
chooseStatus() had a special case for SourceAvailability that always
returned 'incoming', overriding SourceAgent (priority 3) and Proxmox
(priority 2). When an availability probe reported offline, it overwrote
the Proxmox API's online status on every poll cycle, causing node-level
CPU/Mem/Disk to show — because isOnline() gated metric rendering.

Also fix markStaleLocked() to recompute status from remaining fresh
sources via aggregateStatus() instead of blindly downgrading online to
warning. This ensures correct status when high-priority sources go stale
but lower-priority sources are still fresh.
2026-06-26 15:52:21 +01:00
rcourtman 7832014f74 Fix false "unsaved changes" banner on alerts destinations tab
Browser autofill on the email username/password fields in
EmailProviderSelect fired onInput events on page load, which
called onChange -> setHasUnsavedChanges(true) before any user
interaction.

Two-layer fix:
- autocomplete="off" / "new-password" on the username/password
  inputs to discourage browser autofill
- suppressDirtyFlag guard in useAlertsConfigurationState that
  ignores dirty-marking calls during initial config load, re-enabled
  via queueMicrotask after the load cycle completes

Verified in browser: no false banner on load, real edits still
mark unsaved changes correctly.
2026-06-26 15:32:41 +01:00
rcourtman 41fc15edee Reduce console noise: downgrade repetitive alert WebSocket log to debug
The backend broadcasts individual 'alert' WebSocket messages on every
poll cycle for recurring alerts. These are intentionally not processed
by the frontend (handled via state sync), but were logged at warn level,
producing 12+ console warnings per page load. Downgraded to debug.

Also wrapped global WebSocket store creation in createRoot to suppress
SolidJS 'computations created outside createRoot' disposal warning.
2026-06-26 14:49:12 +01:00
rcourtman 1a9dd508f3 fix(dev): clear proxy env vars before launching backend
The opencode shell wrapper sets HTTPS_PROXY for z.ai API egress, and
child processes inherit it. The proxy rejects local IPs with 403,
breaking Proxmox API calls from the backend. Explicitly clear all
proxy env vars so the backend always talks direct to local infra.
2026-06-26 14:40:49 +01:00
rcourtman 7bbc9278a2 Fix ZFS alert flapping caused by composite spec resource ID mismatch
SyncStorageAlertsForInstance matched alert.ResourceID against valid
storage IDs (plain inventory IDs like 'inst-node-local-zfs'). But ZFS
health assessment alerts carry a composite spec resource ID (e.g.
'inst-node-local-zfs/zfs-pool:data') set via params.Spec.ResourceID in
evaluateCanonicalStatefulAlert. Every ZFS alert was immediately cleared
by the sync sweep right after creation, then re-fired on the next poll —
an infinite flapping loop every ~10 seconds.

Fix: when the exact resource ID doesn't match, check whether the prefix
before the first '/' is a valid storage ID. Storage IDs use hyphens
(never slashes), so the prefix cleanly extracts the inventory ID from
the composite spec resource ID.

Added two test cases:
- preserves zfs alerts with composite spec resource ids
- clears zfs alerts when storage is removed (ensures fix doesn't
  prevent legitimate cleanup)
2026-06-26 14:39:01 +01:00
rcourtman f5adf39c99 fix: stop reporting scheduler unhealthy when individual tasks are dead-lettered
The /api/health endpoint gated scheduler health on
DeadLetterCount()==0, causing a 503 whenever any single monitored
node became unreachable. Dead-lettered tasks are the scheduler
working as designed — after repeated failures the task is moved to
the dead-letter queue to prevent infinite retries.

In the live lab this manifested as a persistent 503 on /api/health
because the Pi node (192.168.0.2) times out on every poll cycle,
dead-lettering its task while the other 20+ tasks continue normally.

The scheduler is healthy when the monitor is running. Dead-letter
counts remain available as diagnostic info via the
/api/monitoring/scheduler/health endpoint.
2026-06-26 14:03:47 +01:00
rcourtman 9493a7afe0 fix: update privacy-doc test to match renamed telemetry field labels
The telemetry Ping struct fields were renamed from 'Patrol mode decision'
to 'Patrol control completed operations loop' etc., and PRIVACY.md was
updated to match. Update the frontend test expectations accordingly.
2026-06-26 13:38:48 +01:00
rcourtman ccf90bb26d fix: resolve 4 pre-existing test failures blocking clean CI
1. Regenerate pulse-mcp README from canonical manifest (doc drift)
2. Add 31 missing Pulse Intelligence telemetry fields to both PRIVACY.md
   copies to match current Ping struct JSON tags
3. Rebuild portal frontend bundle to update build_manifest.json hash
4. Update action execution contract test to match current code structure
   (handler wrapped with withExternalAgentCapabilityActivity, error codes
   referenced via agentcapabilities constants instead of literal strings)

Full Go test suite now passes clean: 126 packages, 0 failures.
2026-06-26 13:30:00 +01:00
rcourtman b6f7091952 fix: block SSRF bypass through HTTP proxy in restricted outbound client
NewRestrictedOutboundHTTPClient validates target IPs via a custom
DialContext, but when an HTTP proxy is configured (HTTP_PROXY env var)
DialContext only validates the proxy's address — the actual target host
is never checked. This allows requests to cloud metadata service
addresses (169.254.169.254) and other blocked IPs through the proxy.

Add a restrictedRoundTripper wrapper that validates the request URL
hostname against resolvePermittedOutboundIP before forwarding. This
provides defense-in-depth that works regardless of proxy configuration,
while the existing DialContext guard continues to prevent DNS rebinding
for direct connections.
2026-06-26 13:05:23 +01:00
rcourtman 9bcff2983d Clarify missing license public key activation error 2026-06-26 13:00:42 +01:00
rcourtman fd4cb9c8d5 feat: replace linkedResourceId text input with resource picker dropdown
The 'Link to resource' field in the availability target form was a
plain text input requiring the user to know and type a resource ID.
Replace it with a FormSelect dropdown that lists all known resources
grouped by platform (Agent, Docker, PVE, PBS, etc.), with resource
type labels in parentheses for context.

Default option remains 'Auto-detect by IP (recommended)'. A fallback
option appears for linked resources not currently in the discovery
list.

Also fixes a pre-existing platformType typo ('proxmox' → 'proxmox-pve')
in the availability probe presentation test.
2026-06-26 12:38:54 +01:00
rcourtman 62c2b765d0 feat: attach availability checks as facet on known resource row
Agentless availability checks (ICMP/TCP/HTTP) were always minting
standalone network-endpoint resources, leaving them disconnected from
the known Proxmox/Docker guest they actually monitor. This made
availability evidence invisible on the platform resource row where the
user expects it, per the performance-and-scalability bounded-row
contract.

Backend (unified-resources ingest):
- Add LinkedResourceID field to AvailabilityData and AvailabilityTarget
- resolveAvailabilityLink: explicit link first, then exact-IP unambiguous
  correlation; skip hostname-only (lossy); guard against overwriting a
  different target's facet
- Unlinked/unmatched probes still mint network-endpoint (fallback)

Frontend:
- Relax getAvailabilityProbePresentation for any resource with availability
- Add compact protocol badge to UnifiedResourceHostTableCard name cell
- Add optional 'Link to resource' field to availability target form
- Add linkedResourceId to frontend types

Contracts: api-contracts, unified-resources, monitoring,
performance-and-scalability, storage-recovery.

Governance: coverage_gap + candidate_lane in status.json.
2026-06-26 12:16:11 +01:00
rcourtman adf6c6f1e5 fix: treat absent onboot as no-autostart (Proxmox default) for alert suppression
parseProxmoxOnBoot returned nil when the onboot key was absent from a
valid config, but Proxmox's default is onboot=0 (do not start on boot).
This caused stopped VMs without an explicit onboot line (like
windows-runner and tails-anon) to still generate powered-off alerts
despite not being configured to autostart.

Now returns false for absent keys in non-empty configs. nil is reserved
for empty/failed config fetches and unrecognised values, preserving the
'unknown' fallback for genuine uncertainty.
2026-06-26 11:35:12 +01:00
rcourtman 47d6ad94db test: fix 4 pre-existing frontend test failures blocking clean suite
recoveryDatePresentation: locale-independent date assertion (en-GB
systems produce '9 Mar 2026' not 'Mar 9, 2026')
quickstartCopyContract: update 'tools' to 'capabilities' matching
docs/AI.md copy change
AIIntelligence: update stale empty-state text to full current copy
SharedPrimitives.guardrails: align guardrail + registry with
d79640c41 migration of ProxmoxBackupsTable from FilterButtonGroup to
FilterSegmentedControl

Full suite: 659 files, 6719 tests, 0 failures.
2026-06-26 11:26:16 +01:00