Three new branch-coverage tests in internal/api, each taking its named targets
from 0.0% to 100.0%.
report_schedules: htmlEscape single-pass escaping, parseReportScheduleWeekday
over every accepted form plus rejects, occurrenceKey timezone defaulting,
lastReportScheduleOccurrenceAt for monthly and weekly cadences with its four
error paths, resourceHasAnyReportScheduleTag, and the validation error writer.
recovery_handlers: parseRecoveryListPointsOptions accept and reject paths,
protectionPostureStateRank over every state, paginateProtectionPostures edges,
and the series and facet builders including timezone offset bucketing.
maintenance_verification_wiring: alertMatchesResource, both severity mappers
exhaustively over their declared constants, canonicalToSourceID, the org
context, and extractMaintenanceVerificationReportID.
All three files are new; no source or existing test was touched.
useSystemSettingsState.branchcov0722pm covers the fifteen accessors and change
handlers that no test reached: the six env-override lock gates, the backup
interval select value and summary, the four settings toggle handlers, and the
update check and install path. Zero-hit functions on the module go from 15 to 0
and uncovered branches from 23 to 20.
useInfrastructureOperationsState.branchcov0722pm is the first test for that
module, which shipped untested today. It drives the privilege-escalation
rewrite, the insecure and custom-CA flag builders for both shell and
PowerShell, the resolved command token, and the canonical uninstall and
connection identity helpers, reaching 65 of 77 branches and 19 of 24 functions.
A third candidate covering useSettingsNavigation was written and dropped: it
added no branch coverage and its handleSelectAgent cases only round-tripped a
one-line setter.
Both files are new; no source or existing test was touched.
Dependabot alert 137 (low). Fixes a CUSTOM_ELEMENT_HANDLING hook-policy
bypass upstream; Pulse does not enable CUSTOM_ELEMENT_HANDLING so the
bump is hygiene, not an active exposure. Verified vite build, bundle
budget, and the AI chat vitest suite that exercises the sanitizer.
Contract-Neutral: Dependabot alert 137 GHSA-c2j3-45gr-mqc4: patch-level dompurify 3.4.11->3.4.12 security bump in frontend-modern; no dev-runtime orchestration, manifest-floor, or build-chunking behavior changed, contract already governs security lockfile bumps generically; dev-runtime proof artifacts are inapplicable to a frontend dependency version pin
The Check Interval and Preferred Time selectors persisted values that
nothing consumes: the host systemd timer installed by install.sh owns
the schedule (daily overnight window with a randomized delay) and only
reads the autoUpdateEnabled toggle and channel from system.json. The
selectors promised schedule control they did not deliver.
The enabled state now shows an honest static note describing the real
schedule. The toggle keeps working as before. The persisted
autoUpdateCheckInterval/autoUpdateTime fields stay in the API and
config for compatibility; the UI just no longer pretends to edit
anything with them.
Contract-Neutral: presentation-only removal of dead UI controls: the auto-update Check Interval / Preferred Time selectors persisted values nothing consumes (host systemd timer owns the schedule); API payload fields, persisted config, and the toggle behavior are unchanged
v6.1.0-rc.4 shipped the attention workbench gated on monitoring:read
only, so mobile relay tokens (which carry just relay:mobile-access) got
403 on alert sync, finding detail, and acknowledge the moment a server
upgraded to rc.4. The attention routes supersede the legacy patrol
findings routes, which accepted the mobile capability, so they join the
governed relay mobile runtime route inventory: reads accept
relay:mobile-access alongside monitoring:read (legacy ai:execute),
mutations alongside monitoring:write.
The 1-run-per-hour Community cadence gate on manual Patrol runs keyed off
lastFullPatrol, which is stamped on every completed run including errored
ones. Debugging a broken provider therefore cost an hour per attempt,
raised in discussion #1571. The gate now keys off the most recent
successful full run from history, matching the success-aware skip logic
the startup path already uses.
Contract-Neutral: Behavioral fix: Community manual-Patrol cadence gate now ignores failed runs; no API payload or endpoint change (#1571)
The advisor endpoint capped the whole four-turn evaluation at a fixed two
minutes, ignoring the operator's provider request timeout. Local runtimes
like LM Studio that legitimately spend minutes on prompt processing per
turn were cut off mid-run, reported in discussion #1571 on rc.4. The
handler budget now scales to four streaming turns at the configured
timeout plus slack, floored at the old two minutes.
Contract-Neutral: Behavioral fix: Patrol readiness advisor HTTP budget now scales with configured provider timeout; no API payload or endpoint change (#1571)
The updater reports 'restarting'/'completed' and then keeps the old
process alive for a ~2s grace period before exiting for the service
restart. The post-update reload keyed on /api/health alone, so the
first probe hit the still-running old process, reloaded the old bundle
before the restart happened, and nothing re-triggered afterwards - the
page kept showing the old version until a manual refresh.
All four reload sites in UpdateProgressModal now probe /api/version
(public, no-store) and reload only when the reported version differs
from the version that started the update, which also covers rollbacks.
A bounded same-version fallback keeps deployments that intentionally
never restart (mock/CI) from waiting forever.
The curated highlights are multi-bullet reading content, which the old
full-width top banner rendered as a cramped, edge-to-edge wall of text
that pushed the whole app down and even scrolled internally. The shared
Dialog gives the content a readable measure, a proper header and footer,
and leaves the first post-update paint of the dashboard unobstructed.
Show/dismiss gating is unchanged: once per release, only when the
release has a Highlights section, and any close path records the
version as seen.
Contract-Neutral: presentation-only change: post-update What's New highlights move from a top banner strip to the shared Dialog; show/dismiss gating, highlights extraction, and release-note transport are untouched
Removed-agent records kept no platform identity, so the fleet doctor
emitted an empty platform for removed rows and the Agent Doctor
uninstall handoff could never tighten to a single platform command.
Host and Docker removal now capture the agent's last-known reported
platform onto the removed record, and the fleet diagnostic resolves the
retained value through the same strict normalization as live subjects,
so /api/agents/diagnostics reports a platform for removed agents and
the handoff shipped in 4fc86ec18 scopes to one command automatically.
A retained value that does not normalize to a known platform still
yields an empty diagnostic platform rather than a guess, and removed
Kubernetes clusters retain nothing because the cluster report never
carries a platform. The field is additive and optional, so removed
lists serialized before it existed load unchanged. Monitoring contract
updated; proofs staged in agent_fleet_doctor_test.go and
monitor_host_agents_test.go.
Removed targets previously dead-ended with no next step. Their row
expansion now offers the host-local uninstall command, resolved from the
retained diagnostic identity via the strict platform resolver, or both
labeled Linux-family and Windows commands when the platform is unknown
(the current wire reality, since removed records retain no platform).
The handoff carries the diagnostic's agent id and hostname as uninstall
identity flags and states the command runs on the affected host, not
from Pulse. The governed uninstall builders now accept a narrowed
AgentUninstallIdentity view so surfaces without a ledger-backed
inventory row reuse the same transport.
The summary chips now double as status filters over the fleet table
(click to filter, click again or let the status empty out to clear),
so a large fleet collapses to the rows that need attention. The page
also offers plain-text diagnostic reports for pasting into a support
thread or GitHub issue: a fleet-level copy over the currently visible
rows and a per-agent copy inside each row expansion. Reports carry
status, versions, last-seen, diagnosis reasons, identity evidence, and
non-command repair actions, and deliberately never embed host-local
update commands since those can carry install tokens.
Agent Doctor now lives at /settings/infrastructure/agent-doctor as a
full page in the settings shell instead of a max-w-lg dialog stacked
over the Infrastructure workspace, which was cramped on phones. The
per-agent card pile becomes one fleet table (agent, system, status,
reported and target versions, last seen) with per-row expansions
holding the diagnosis reasons, identity evidence, repair actions, and
the host-local update command. A lone scoped target auto-expands so
platform-page deep links land straight on the diagnosis.
Legacy ?agentDoctor=1 / ?agentUpdates=1 workspace links redirect onto
the route with their agents scope preserved, and closing the page is
now ordinary back navigation. The dialog component is renamed to
InfrastructureAgentDoctorPage; shared-template registry, the
agent-lifecycle subsystem inventory, and the frontend-primitives
settings deep-link clause follow the change.
Retention deletes freed pages inside audit.db but the file never
shrank on disk, the same bloat class #1496 fixed for
unified_resources.db. Migrate existing databases to incremental
auto-vacuum at startup and return freed pages to the OS after each
retention pass, capped per cycle so a backlog drains gradually.
In-place updates never rewrite the systemd unit, so installs upgraded
past v6.1.0-rc.1 keep a unit without AmbientCapabilities=CAP_NET_RAW
and every ICMP probe fails with ping's raw stderr. Detect the
capability failure and point at the unit and its fix instead (#1554).
The Unraid disk mergers, the Proxmox setup output classifiers, the ZFS SMART
annotator, the Docker runtime command builder, the approval decision actor
and the update retry classifier were all at zero coverage. Each decides
something a user feels directly, whether an Unraid disk keeps its name after
a merge, whether a setup run reports an already-registered token as a
failure, which pool a SMART entry is attributed to, and whether a failed
update request is retried or surfaced as an error.
Adds branch coverage for mergeUnraidDiskINI, mergeUnraidDisk,
defaultUnraidDiskName, isAlreadyExistsOutput, the client error Error and
Unwrap methods, annotateSMARTWithZFSPools, dockerRuntimeCommand,
approvalDecisionActor and isRetryableUpdateRequestError, including nil and
whitespace arms, field precedence between base and incoming values, in-place
slice mutation asserted on the caller's value, and wrapped errors that only
resolve through errors.Is and errors.As. Every named target moves from zero
to full statement coverage.
Test-only change.
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no contract surface touched
The TrueNAS settings panel, the connections ledger, the workloads guest
metadata subscription and the agent update commands model all changed in the
last drop and each kept a block of branches no test reached. Those branches
decide which error a user sees when a TrueNAS connection fails, which rows
the connections ledger shows, whether guest metadata survives a canonical
event, and whether manual update commands stay available while an update is
applying, so a silent regression there lands straight in front of a user.
Adds branch coverage for getTrueNASErrorMessage and the dialog open, close,
reset and delete transitions, for findById plus the ledger derivation
fallbacks, for the metadata subscribe, refresh and teardown arms, and for the
actively-applying guards on the update commands. Measured uncovered branches
drop from 24 to 17, 35 to 12, 23 to 15 and 14 to zero, with the six and one
zero-hit functions in the first two modules now covered.
Test-only change.
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no contract surface touched
The connections aggregator, alerts and grouping helpers plus the monitoring
group-key, availability-key and read-state mapper helpers were all at zero
coverage. Each of these is a pure value-in to value-out function on a path
that decides how a connection is grouped, whether an alert snapshot is
emitted, and how host thermal state crosses the read-state boundary, so a
silent regression there is invisible until it reaches a user.
Adds branch coverage for buildPMGConnection, connectionTypeForAlerts,
snapshotConnectionsForAlerts, uniqueMatch, connectedInfrastructureGroupKey,
availabilityConnectionKey, ConnectionHealthKey, pingArgs,
hostThermalStateFromReadStateView, latestMetricPoint and MemorySourceTrust,
including nil and whitespace arms, candidate precedence order, and clone
independence in both directions for the mapper. Every named target moves
from zero to full statement coverage apart from the pingArgs arms that are
unreachable on this platform.
Test-only change.
PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no contract surface touched
An explicit --report-ip is the user naming the primary address on a
multi-NIC host, but identityFromHost appended it after the auto-detected
interface addresses while every consumer of ResourceIdentity.IPAddresses
treats the first entry as primary, so the override never changed what
the Machines table displayed. Prepend it instead.
The install script also rejected --report-ip as an unknown argument even
though the agent supports the flag, forcing hand edits to the service
unit that a later --update run would drop. Accept the flag, render it
into the service ExecStart, persist it in connection state, and
recognise it during saved-state and arg-stream recovery so updates
preserve it.
Refs #829
Contract-Neutral: behavioral fix: user-specified report-ip leads host identity addresses and the installer passes --report-ip through; no public contract change (#829)
The infrastructure manage flow froze the row it opened with, so removing
an attached Pulse Agent kept rendering the removed agent as Active even
though the server had already deleted it, which read as the button doing
nothing. Derive the editing row from the live ledger by ID, falling back
to the opening snapshot while a reload is in flight.
TrueNAS connection tests collapsed every failure into the generic
"Failed to connect to TrueNAS" toast while the actionable cause (the
exact x509 verification error) rode unread in details.error. Prefer the
detail, mirroring the existing VMware presenter, so users can tell an
untrusted CA from a hostname mismatch instead of reaching for Skip TLS
Verification.
Refs #1602
Contract-Neutral: behavioral fix: manage flow renders live ledger state after agent removal and TrueNAS test toasts surface the underlying TLS error detail; no public contract change (#1602)
The license-server broker now serves stable and rc manifest slots
(pulse-pro side). The Pro self-updater passes channel=rc for rc-channel
installs so they track the RC slot; stable installs keep the unchanged
default URL, which older brokers also understand. The client-side guard
that refuses a prerelease pin on the stable channel stays as the backstop
for a drifted or single-manifest broker. Contract updated under
deployment-installability; fixture now pins the channel query behavior.
One vCenter connection spans many ESXi hosts, so the Infrastructure source
row now lists them the way Proxmox cluster rows list their nodes: an
expandable member list with per-host state, aliases, and last-seen. Members
are API-side composition only — no primary marker, no agent connection, and
the member subtitle reads 'vSphere host' instead of cluster-node wording.
TrueNAS keeps no member composition because that connection monitors exactly
one machine.
Mock mode now feeds its vCenter and TrueNAS fixtures into the connections
aggregator (only when no real instances are configured), so the mock ledger
shows the same platform source rows a real deployment would instead of
omitting vSphere and TrueNAS entirely.
vSphere ESXi hosts and TrueNAS boxes rendered as standalone rows under
'Pulse Agent hosts' in Infrastructure settings even though no Pulse Agent
runs on them. The rows were unmanageable duplicates of their owning platform
connection: they carry no credentials, pause, or remove semantics, and they
can never attach to their vSphere/TrueNAS system because attachment requires
a shared host while the machine hostname differs from the vCenter address.
They also inflated connected-system counts.
buildConnections now skips hosts with a non-empty IntegrationSource, so the
ledger and grouped systems represent those machines solely through their
owning platform connection. Per-machine visibility is unchanged on the
vSphere/TrueNAS platform pages and Machines, which read the unified fabric
directly. Connection.integrationSource stays declared as defense-in-depth
for agent-only client workflows. No alert impact: agent-type rows were
already dropped from alert snapshots.
The connections ledger derives agent rows from the unified fabric, which
includes machines whose telemetry comes from platform integrations (vSphere
ESXi hosts, TrueNAS). Agent Doctor rendered every one as a permanent
'Unknown / no structured reason' row, while agents the ledger does not carry
(Docker-only, Kubernetes-only) were silently dropped from the fleet view.
- Expose HostView.IntegrationSource() (source-set based: only SourceAgent
ingest counts, since integration providers fabricate an Agent payload) and
plumb it through models.Host to the connections ledger as the optional
integrationSource field.
- Agent Doctor skips integration-backed connections and appends
diagnostics-only agents, honoring scope, so the doctor covers exactly the
real Pulse Agent fleet.
- Update readiness agent checks no longer count integration-backed machines
as registered agents.
- Humanize doctor copy: plain-language stale message with '10m 2s'-style
durations, offline wording without enum leakage, no 'Supported target:
Unknown' cell when no target is published, host-local command banner only
when a command is actually offered, and a compact non-zero summary strip.
Contracts updated for unified-resources, monitoring, api-contracts,
agent-lifecycle, and dependent storage-recovery; verification via
views_test.go, monitor_host_agents_test.go, state_host_test.go,
contract_test.go, and the frontend connections API test.
The metrics store capped its SQLite pool at one connection, so every UI
history read queued behind every buffered-write commit, and behind the
WAL checkpoints those commits pick up at the 4000-page threshold. On
write-heavy installs (many Docker agents with churning containers) that
serialization presented as sustained 120-260ms COMMIT warnings and an
unresponsive UI even with idle CPU and fast disks. Writes were never the
risk: flush, rollup, retention, and maintenance already funnel through
the single background worker goroutine, and the WriteBatchSync poller
path serializes on the WAL write lock via busy_timeout.
Raising the pool exposed a second bug: auto_vacuum(INCREMENTAL) in the
per-connection DSN pragmas replays as a database-header write whenever
the pool opens a new connection, which blocks connection creation behind
the active writer for up to the full 30s busy_timeout. auto_vacuum is a
persistent database property that migrateAutoVacuum already establishes
once at startup, so the per-connection copy is dropped.
Refs #1601
Contract-Neutral: behavioral fix: metrics store read concurrency and per-connection auto_vacuum pragma removal, no public contract delta (#1601)
PVE's disks/list endpoint labels healthy SCSI/SAS drives OK while ATA
drives say PASSED, and failing ATA drives come back as FAILED! with the
bang. Pulse ingested those raw strings, so a healthy SAS drive rendered
as health Unknown even though the Proxmox UI showed S.M.A.R.T. OK. The
host agent and TrueNAS paths already normalize their health text; the
PVE ingestion was the only entry that did not.
Map OK/PASS to PASSED and FAIL-containing values to FAILED at ingestion,
and accept OK as healthy in the disk presentation layer as defense for
state produced by older servers.
Refs #1595
Contract-Neutral: behavioral fix: normalize PVE disk health strings at ingestion, no public contract delta (#1595)
The requirements table named an Enterprise license but the pricing page
only sells Community, Relay, and Pro, leaving no visible path to the
capability. A Pro buyer purchased today expecting multi-org on the
strength of this doc. State plainly that the self-serve tiers do not
include the capability and where to ask for Enterprise licensing.
Contract-Neutral: docs-only: state how the Enterprise multi_tenant license is obtained; no runtime change
smartctl reports SAS drives with device protocol SCSI, so detectDiskType
fell through to its blanket sata default, and that non-empty type also
masked the text-output transport evidence the fallback parser had
already extracted. The wrong sata label then blocked the merge layer
from promoting the smartctl serial over the SAS transport address
Proxmox reports.
Classify SCSI-protocol devices via the scsi_transport_protocol
descriptor, let the text and sysfs refinements upgrade a generic scsi
label, and apply the legacy sata default only after all evidence is
exhausted. Parse the SCSI log-page fields (power-on hours, grown defect
count, endurance used) that SCSI drives report instead of an ATA
attribute table, and let agent-reported sas replace coarse hdd/ssd/sata
types during the disk merge.
Refs #1595
Contract-Neutral: behavioral fix: SAS transport detection and SCSI attribute parsing in host agent SMART collection; no public contract delta (#1595)
Agent Doctor withheld the manual update command whenever an eligible
agent had auto-update on and an update pending, including agents idle
until their next scheduled check or stuck mid-convergence, leaving no
manual escape hatch. Block the command only while the updater reports
state updating with a fresh attempt timestamp, and restore it once an
in-flight attempt is older than ten minutes.
Refs #1564
Contract-Neutral: behavioral fix: Agent Doctor manual command gate now blocks only live in-flight updates; no public contract delta (#1564)
Patrol's verdict contract required the model to echo finding IDs like
update-analysis-docker:<uuid>/<64-hex-digest> exactly. Smaller models
(reported with Gemini 2.5 Flash-Lite) drop the key prefix or digest
tail, every patrol_assess_finding call fails, and the whole run ends
with 'Patrol finding assessment incomplete'. Resolve an ID that
unambiguously identifies one active in-scope finding (case-insensitive,
prefix, or separator-suffix match), record the canonical ID so the
verdict accounting and duplicate guard still hold, and list the valid
active IDs in the lookup error so the model can self-correct instead of
guessing. Hallucinated or ambiguous IDs still fail closed.
Refs support report from Johannes Strasser, 2026-07-20
Contract-Neutral: behavioral fix: tolerant patrol assessment ID resolution for weak models; fail-closed contract unchanged
The workloads metadata state still listened for the legacy
pulse:metadata-changed event, which no code dispatches since URL saves
moved to dispatchResourceMetadataChanged, and its handler rewrote
3-part canonical guest ids into the v5-era instance-vmid shape that no
lookup uses. In-page saves only stayed live through the drawer's direct
callback; any other dispatcher was invisible. Listen for
pulse:resource-metadata-changed and apply updates under the dispatched
metadata id, ignoring agent-kind events that never key workload rows.
Refs #1556
Contract-Neutral: behavioral fix: workloads metadata state listened for a legacy event nothing dispatches; no public contract change (#1556)
Agents report network interfaces sorted by name, which places docker0
and br-* bridges ahead of eth*/en* interfaces, and every consumer of
ResourceIdentity.IPAddresses treats the first entry as the host's
primary address. Docker hosts therefore showed a 172.x bridge IP in
the Machines and Docker tables instead of the machine's LAN address.
Collect physical-looking interfaces first so bridge and overlay
addresses never lead the list.
Refs #1529
Contract-Neutral: behavioral fix: order physical interface IPs before virtual bridges in identity collection; no public contract change (#1529)