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.
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.
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.
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.
When a Proxmox node is configured with a bare hostname (e.g. "pi") in its
API endpoint URL, the "Open web interface" link produced an unresolvable
URL (https://pi:8006) because the user's browser cannot resolve local
hostnames via mDNS or /etc/hosts.
Add resolveGuestUrlWithIdentity() to substitute the resource's known LAN
IP (from identity.ips, preferring RFC 1918) when the URL hostname is a
bare label without dots. URLs that already use IPs or FQDNs are left
unchanged.
Verified: pi node link now resolves to https://192.168.0.2:8006 instead
of https://pi:8006. delly and minipc URLs unchanged.
TestAlertLifecycleCanonicalChangesRemainWritable used a hardcoded date
(2026-03-20) that is now >90 days old, exceeding the incident store's
maxAge retention. trimLocked() evicted the incident shell immediately
after creation, causing GetTimelineByAlertIdentifier to return nil.
Switch to time.Now().UTC().Add(-1h) so the test never goes stale.
The alert engine fired powered-off alerts for every stopped VM and
container regardless of whether the guest was configured to autostart.
This meant intentionally-stopped guests (onboot=0) generated alarm
fatigue — in the live lab, 7 of 12 alerts were noise from stopped VMs
that are deliberately off.
Now the alert engine checks the Proxmox onboot setting before firing:
- onboot=true → stopped is unexpected, alert (preserved behavior)
- onboot=false → stopped is expected, suppress
- onboot=nil → unknown, alert (preserved behavior)
For containers, onboot is parsed from the already-fetched container
config in enrichContainerMetadata (no new API call). For VMs, the
config is fetched only for stopped VMs via GetVMConfig (one extra call
per stopped VM, running VMs are unaffected).
Models: add OnBoot *bool to VM and Container.
Alerts: add onboot-aware suppression branch in CheckGuest.
Monitoring: add parseProxmoxOnBoot helper and fetchVMOnBoot.
- Remove AI Context card: always duplicated the parent GuestDrawer's
Discovery readiness banner (same prop, same data, always both rendered)
- Remove confidence percentage from Identified Service card: non-actionable
forensic number that told users nothing useful
- Remove cliAccess text from Identified Service card: developer-facing
jargon (pulse_control/target_host) shown truncated in tiny font;
the Ask Assistant button already provides the action path
Patrol findings hide raw investigation-status and investigation-outcome
badges per the patrol-intelligence contract, but the contract explicitly
allows plain-language actionable state labels (approval required,
investigating, verifying fix, fix failed). This adds a getPatrolFinding-
ActionableState helper and renders the resulting badge on the collapsed
Patrol row between the severity badge and the title, so the operator
sees each finding's state without expanding. The badge is absent when
no actionable state applies, leaving the default new-finding row clean.
Classify active Patrol findings by actionable work type (needs approval,
failed fix, in progress, recurring, new) and weave a compact composition
clause into the workspace description so the operator sees the nature of
their open work at a glance. The clause is empty when all findings are
plain new issues, so the single-finding experience is unchanged.
Advances the protection-posture-attention-queue strong-version checklist
item: Patrol groups operator work across findings, approvals, failed
checks, recurring issues, and unresolved incidents.
The expanded Patrol/AI finding card rendered Description and (conditionally)
Impact, but never the backend-populated `recommendation` field — so the most
actionable content (concrete next steps, plain language) was invisible on the
card, even though it was included in the Copy-summary clipboard output. Render
it as a "Recommended next step:" line in the contract's Description -> Impact
-> Recommendation order.
Contract conformance with patrol-intelligence.md (FindingsPanel canonical
renderer; Impact sits "between Description and Recommendation") and the
2026-06-25 monitor-first strong-version checklist ("each item uses plain
language: ... recommended next step"). Shape fix only — surfaces existing
data, no new payload.
Governed path: FindingsPanel.tsx (patrol-intelligence subsystem, lane L6).
Work claimed against coverage gap protection-posture-attention-queue.
Refs #1234
Force stale negative LXC Docker detections to re-run after the Docker checker is configured so explicit Proxmox guest Docker inventory repopulates after backend restarts.
Treat browser-encoded comma-separated resource type lists the same as literal comma filters so Docker and platform pages keep canonical inventory rows.
Migrate the two consumers of the legacy segmentedButtonClass util onto the
canonical FilterSegmentedControl template so they match every other view
toggle in the app:
- FindingsPanel Active/All/Resolved (+Overdue) filter
- AICostDashboard 1d/7d/30d/90d/1y range toggle
Thread tone and disabled support through FilterSegmentedControl so the
canonical template is a true superset of the legacy util, then delete
segmentedButton.ts and its tests.
Replace the bespoke FilterButtonGroup variant="segmented" Date/Coverage
toggle with the canonical FilterSegmentedControl template used by the
other view toggles (GroupedTableModeSegmentedControl,
MetricDisplayModeSegmentedControl), so the surface matches the shared
compact segmented-control look instead of a one-off style.
The Proxmox Overview led with node/workload telemetry that the Proxmox UI
already shows, while Pulse's unique cross-source value (which workloads are
protected across the estate, via Proxmox guests x PBS) was buried in the Backups
tab. Add a ProxmoxOverviewCoverageStrip at the top of the Overview that reuses
the existing recovery model + coverage strip from Backups, fetching the backup
payloads async (createResource, non-blocking) so the landing paint is unaffected
and the strip fills in once coverage resolves. Gated on guest inventory so it
renders only when there are workloads to cover.
An un-investigated Patrol finding had no forward action on its row: the
Assistant primary action is intentionally suppressed on the Patrol surface
(issue #1078), so the primary area was empty and the row dead-ended at triage.
Add an Investigate primary action that starts the detect -> investigate ->
propose -> approve -> verify loop via the existing reinvestigate endpoint,
surfacing the already-wired InvestigationSection/ApprovalSection once a run
exists.
Investigation is disabled in Watch-only (monitor) mode by policy, so the action
is gated to autonomy modes that permit it (approval/assisted/full) and stays
hidden otherwise rather than erroring in the default mode. Watch-only findings
remain triage-only by design; the workbench value lands once an action mode is
enabled.
The previous commit accidentally overwrote the existing patrol client test
suite (it predated this change). Restore those tests and add the
triggerPatrolRun scope-body cases alongside them instead of replacing the file.
The alert AI button now offers a manual targeted Patrol check alongside the
existing context-only Pulse Assistant handoff, so the take-the-lead intent routes
to Patrol (the investigator) instead of overloading Assistant (the explainer).
POST /api/ai/patrol/run (HandleForcePatrol) accepts an optional scope body and
routes through the same TriggerScopedPatrol engine and scoped run record as
automatic alert-triggered work, not a new trigger route; empty body keeps the
legacy fleet-wide run. Scoped runs honour Patrol readiness but bypass the
full-run cadence gate, consistent with the existing scoped-cadence rule, and
carry resource identity only (no command or remediation payload).
Frontend: triggerPatrolRun(scope?) and a split AI button (patrolOption) on the
alert overview card whose menu runs the scoped check; icon/history rows stay
single-purpose. Contract updates land in the touched/dependent subsystem docs
(ai-runtime, api-contracts, agent-lifecycle, storage-recovery, frontend-primitives,
security-privacy, patrol-intelligence, alerts); subsystem_lookup_test line
expectation refreshed for the api-contracts shift.
The alert-to-Assistant handoff is governed context-only (alerts.md: it must
open the drawer with context rather than seeding a prompt or choosing a
diagnostic route), but the user-visible copy said "investigate", overselling
what the surface actually does. Align button.full, unlockedTitle, and locked
copy (EN/ES/DE) so the promise matches the behaviour. The visible alert-row
label was already honest; briefing internals kept as attached-context labels.
Pages like Patrol use high z-indexes (z-[200]) which leaked into the
root stacking context and painted above the Pulse Assistant overlay
(z-50 panel, z-40 backdrop) when it switched to overlay mode below
1200px, so page text rendered on top of the sidebar. Adding isolate
to app-scroll-shell makes it a stacking-context boundary, trapping all
page content below the overlay regardless of the z-index a page uses.
The Settings > Patrol "Patrol mode" section told every install to choose a mode
on the Patrol page, but plan-locked (free) installs have no mode choice there —
Watch only is their only mode and the paid modes stay off the free surface. The
copy was tier-blind and sent free users on a fool's errand.
Make the section tier-aware via the canonical patrol autonomy availability
resolver: plan-locked installs now read "This install runs Watch only. Patrol
monitors your infrastructure and reports issues here." while Pro/runtime installs
keep the choose-on-the-Patrol-page redirect. The Open Patrol button stays.
The free Patrol working surface stays clean of ambient paid surfacing, but a
plan-locked operator who expands an active critical or warning finding now gets
one honest, non-salesy capability line in the empty primary-action slot: "Pulse
Pro can investigate and fix issues like this." A "Learn about Pulse Pro" action
appears there only when the upgrade-prompt policy allows it.
This is the single allowed contextual at-need prompt — gated to plan-locked
installs, active critical/warning findings, and the upgrade-prompt policy — so
discovery happens at the moment of genuine need without re-tainting the daily-use
surface. Adds getPatrolProInvestigationHandoff in patrolControlPresentation,
threads the handoff through PatrolIntelligenceWorkspace into FindingsPanel,
codifies the carve-out across the owner and dependent contracts, and registers
the patrolControlPresentation test as patrol-page-and-state proof.
Trend cards now average over the selected range (effective_days) so
avg/day x days matches the headline total, instead of averaging only
active days and overstating the run-rate.
Cap the provider/model table to the top 10 by spend with a show-all
toggle, keeping the default view on the cost drivers.
Fold 30-day Budget and Request Timeout into a single two-column row
and drop the negative-margin helper hack.
Reverts the interim opt-in Patrol mode disclosure added in 8fab6e6e0. The
free Patrol surface stays a complete, clean monitoring tool: plan-locked
installs show only the positive Watch-only control with no paid modes,
disabled buttons, Pro badges, or disclosure of any kind in the daily-use
surface. Pro discovery belongs in Settings, the website/docs, and
contextual at-need prompts instead.
Firms up the patrol-intelligence contract (owner plus api-contracts and
frontend-primitives dependents) to drop the permissive carve-out entirely,
and updates the recorded decision to match. An opt-in list of unclickable
modes did neither job well: it was not clean enough to keep the free product
proud, and too passive to drive conversion.
Plan-locked (free) installs showed only a flat "Watch only" with no way to
discover that Patrol has other modes. Add an explicit "See what Patrol can do"
affordance beside the Watch-only control that reveals the four modes (paid
ones disabled and Pro-badged) on demand, so operators can discover the
capability tiers without turning the default header into a Pro-absence matrix.
Resolves a patrol-intelligence subsystem contract conflict (a permissive line
allowing disabled paid buttons vs a prohibition on rendering them) by
codifying opt-in disclosure as the rule across the owner and dependent
contracts: the default (closed) view stays positive Watch-only, and the paid
modes appear only behind the explicit affordance. Records the product decision
in status.json resolved_decisions.
Provider and Service Context accordion toggles carried no aria-expanded,
so the rotated chevron was the only expand/collapse signal. Add
aria-expanded and aria-controls (linked to the panel id) on both
accordion families so assistive tech announces state.
Cost dashboard: cut vanity cards (total tokens, provider/model pairs),
remove the broken duplicate-row "Usage by task" table, label trend
sparklines with the daily average instead of a misleading last-point
value, sort the provider/model table by spend so the cost driver
surfaces first, and collapse the dense unpriced-model list into a
concise note with a hover tooltip.
Provider form: stop auto-expanding every configured provider into a
wall of API-key inputs. Configured providers now start collapsed (the
per-provider badges and health callout still convey status); first-time
setup still expands the default provider, and saves preserve the
operator's own expand/collapse choices.
Z.ai keys on the coding subscription get 429 'Insufficient balance' on the standard /api/paas/v4 endpoint. Add a per-provider BaseURLField to the Z.ai registry entry, a ZaiBaseURL override on AIConfig (returned by GetBaseURLForProvider when set, else the standard default), the handler request/response/apply plumbing, and a 'Custom Base URL' field on the Z.ai provider card so operators can point at /api/coding/paas/v4. Mirrors the existing OpenAI custom-base-URL override; the standard endpoint remains the default for pay-as-you-go users. Adds config/handler/frontend proofs and updates the ai-runtime, api-contracts, frontend-primitives, agent-lifecycle, and storage-recovery contracts.
handleTypeToSearchKeyDown accessed event.key (toLowerCase, length) without a guard, so keydown events with no key (IME composition, unidentified keys) threw a TypeError and broke type-to-search on host surfaces such as the Provider & Models default-model picker. Early-return when event.key is absent; modifier-only keys stay truthy and are unaffected.
Add a registry-backed provider model for runtime metadata and chat-compatible transports.
Support Z.ai, Groq, Mistral, Cerebras, Together, and Fireworks through the shared chat-compatible client path while keeping native providers on their existing adapters. Expose provider metadata through settings and render the expanded provider set in the frontend.
getConfiguredHostIPs() and the new discoveryConfigSnapshot() now deep-copy config under a brief RLock and release it before resolving Proxmox/PBS/PMG hostnames via the injectable lookupConfiguredHostIP seam, so slow or blocked DNS can no longer stall monitor writers. The discovery IPBlocklist is the deduplicated merge of the operator-configured blocklist and the resolved configured-host IPs. Adds lock-freedom and merge coverage tests; updates the monitoring contract obligation.
Manifest-backed MCP tools, prompts, and resources with surface affordance contracts; agent capability manifest and governance projection; API contract tests and capability route projection; operations-loop and intelligence-funnel telemetry; release-control subsystem documentation, registry, and tooling; licensing and configuration.