Commit Graph

590 Commits

Author SHA1 Message Date
pulse-triage[bot] abd80f8a6f Bound discovery probe response bodies
Change-source: pulse-maintainer
2026-09-01 10:37:26 +01:00
pulse-triage[bot] 327682ad34 Bound Proxmox API success responses
Change-source: pulse-maintainer
2026-08-31 21:20:30 +01:00
pulse-triage[bot] f69ce1daae Use Kubernetes node identity for host linking
Change-source: pulse-maintainer
2026-08-31 21:20:29 +01:00
rcourtman cf021bc9ce Surface typed helper container degradation 2026-08-31 15:53:50 +01:00
rcourtman d19398d82a Surface typed helper degradation in Agent Doctor 2026-08-31 13:06:28 +01:00
pulse-triage[bot] 8c63984696 Clear stale PBS backup-running state 2026-08-31 07:03:43 +01:00
pulse-triage[bot] e094a55b45 Integrate typed helper container summaries 2026-08-30 21:29:11 +01:00
Pulse Test c0b190bfa1 Add application response contracts to availability checks 2026-08-30 17:38:59 +01:00
Richard Courtman 34849268e5 Enforce collector credential scope allowlists 2026-08-30 17:18:38 +01:00
Pulse Test 712f1a3bfe Add availability history and fleet view 2026-08-30 15:38:34 +01:00
Pulse Test 161e322ee0 Add workload history discovery lens 2026-08-30 13:49:58 +01:00
pulse-triage[bot] ee6bb64d72 Honor cgroup v1 service memory limits
Resolve the process memory-controller hierarchy before selecting the tightest v1 hard limit, so systemd and nested appliance limits actually inform the existing Go runtime headroom.

Contract-Neutral: runtime memory-limit detection only; no API or persistence contract change
2026-08-30 02:15:36 +01:00
Pulse Test 5b2e377843 Expose secure agent runtime posture 2026-08-30 00:38:44 +01:00
pulse-triage[bot] 1a48d3cbd6 Honor namespaced cgroup memory limits 2026-08-29 22:23:54 +01:00
Pulse Test a966264bb1 Contain agent command authority 2026-08-29 22:12:41 +01:00
pulse-triage[bot] 8c65d716f4 Expose honest Proxmox update evidence 2026-08-29 20:51:19 +01:00
pulse-triage[bot] a8b53ca205 Bound idle gzip compression memory 2026-08-29 18:55:06 +01:00
Pulse Test d671f05408 fix(alerts): stabilize noisy lifecycle signals 2026-08-29 17:32:17 +01:00
Pulse Test a506cdde8b Separate notification server errors from rejections 2026-08-29 16:57:44 +01:00
Pulse Test 0df9747bd7 Add privacy-safe alert quality telemetry 2026-08-29 14:00:28 +01:00
pulse-triage[bot] 75a240ad1c Classify offline Proxmox node failures accurately
Treat node-scoped HTTP 595 responses as debug-level resource unavailability instead of repeated authentication warnings. Preserve warnings and returned errors for real credential failures.

Refs #1794.

Contract-Neutral: corrects internal log severity without changing API, resource, or extension contracts
Change-source: pulse-maintainer
2026-08-29 13:16:21 +01:00
Pulse Test b75a5aeec2 Add release service health telemetry 2026-08-29 11:24:53 +01:00
Pulse Test 674ca18968 fix(unifiedresources): merge PVE and agent rows for the same RAID array volume
The PVE disks/list poll and the agent SMART report both observe a RAID
controller's exported volume, but PVE surfaces its NAA identifier as a
bare-hex serial while smartctl reports the same value as a naa.-prefixed
WWN with no serial. The linked-disk join compared serial to serial and
WWN to WWN verbatim, so the volume rendered twice (/dev/sda and sda)
with independent metric histories.

Hardware identity comparison now normalizes reporter framing (naa., eui.,
wwn-, 0x prefixes, case) and folds serial and WWN together before
comparing. Values are never truncated: sibling volumes on one controller
share their leading WWN bytes, and a truncated udev ID_WWN must stay
unequal to a full identifier. Placeholder serials no longer count as an
identity match.

Refs #1720
2026-08-28 06:20:10 +01:00
pulse-triage[bot] 6aab9c532b Wait for completed license status synchronization
Prevent the idempotent poll test from cancelling the first request after the handler starts but before the service records a successful synchronization.

Change-source: pulse-maintainer
2026-08-28 00:44:11 +01:00
rcourtman 1164d37302 feat(patrol): reconcile actionable findings on activation 2026-08-27 19:44:02 +01:00
pulse-triage[bot] 4f91232cc6 fix(proxmox): stabilize storage inventory refreshes
Sort backup-server and datastore rows deterministically so provider refresh order cannot reset expanded detail state. Exclude optical devices from PVE, legacy agent, and canonical physical-disk projections.

Fixes #1785
Fixes #1786

Contract-Neutral: Corrects refresh ordering and non-disk filtering without changing public APIs or subsystem contracts.
Change-source: pulse-maintainer
2026-08-27 11:05:14 +01:00
pulse-triage[bot] 75b6655d6e Restore Proxmox node network details
Refs #1716

Change-source: pulse-maintainer
2026-08-26 15:17:12 +01:00
rcourtman 9e37d629ac Measure node connection test outcomes
Telemetry could see only saved connections, so an install that tried to
reach a node and could not was indistinguishable from one that never
opened the add-node dialog. Both report zero configured connections and
stall at the same activation stage. Fleet data shows that population is
real and concentrated three to one in container deployments, and nothing
recorded whether those installs attempted a connection at all.

Record node connection test attempts and failures in a bounded,
day-bucketed tally in the config directory, pruned to a 31-day retention
window, and report both over the install-ID rotation window as
node_test_attempts_30d and node_test_failures_30d.

Recording starts only once a request carries a target and credentials, so
an incomplete form is never counted as a node that could not be reached.
A host string that turns out to be unusable does count, because the
attempt was made and it failed. Only the add-node dialog endpoint is
instrumented: instrumenting the unused test-config endpoint as well would
double-count a single operator action.

The tally holds counts alone. Hosts, credentials, and error text never
enter it, which is why it is plain JSON rather than encrypted history.
2026-08-24 10:17:17 +01:00
rcourtman f38e95eb72 feat(telemetry): export the Patrol blocked cause at schema v10
An enabled Patrol that can never run and one that runs and finds nothing
were indistinguishable in the fleet: both presented as high run counts
with zero AI calls and zero findings. The install that motivated this
sat blocked for over a month because provider initialisation failed once
at boot and was never retried; the self-heal landed separately, but
telemetry still cannot see which blocked cause dominates in the field.

Schema v10 exports the fixed machine cause code (for example
provider_not_configured) only while Patrol is in the blocked runtime
state. The cause rides the router-owned Pulse Intelligence snapshot into
the outbound ping. Blocked-reason text, provider endpoints, model names,
and configuration stay on the install; an untyped blocked reason exports
nothing rather than free text, and a disabled, active, or mid-run Patrol
exports an empty value even when a stale cause is still recorded.
2026-08-21 21:43:54 +01:00
rcourtman 4686efd8c8 Add a supported least-privilege agent install profile
The unified agent's Linux installer only offered the root profile, and the
docs called non-root unsupported. That default is the most-cited reason
security-conscious evaluators reject Pulse without trying it. install.sh
gains --least-privilege: the service runs as a dedicated nologin
pulse-agent system user with every existing hardening directive, no
LXC-attach ambient capabilities, docker-group membership for socket reads,
and refusal (not silent root fallback) on appliance platforms, non-systemd
init systems, and --enable-commands. Optional --grant-smart and --grant-pct
restore the only two collectors that need elevation through
visudo-validated exact-command sudoers rules and root-owned wrappers the
agent reaches via new absolute-path-only PULSE_SMARTCTL_PATH /
PULSE_PCT_PATH overrides; the pct grant covers pct list and pct df only
and can never widen into pct exec. --update preserves the profile and its
grants by reading the installed unit.

The agent now authors a privilege block in its report (effective root,
service user, active helpers), carried through models into the fleet
doctor as a descriptive field: Agent Doctor shows the profile and its
helpers instead of presenting intentionally absent collectors as a fault,
and a least-privilege agent can never be marked unhealthy on that evidence
alone.
2026-08-20 20:49:44 +01:00
rcourtman 63c40ebe5e fix(telemetry): make patrol run volume and refusal causes readable
Two counters could not answer the question they exist for.

pulse_intelligence_patrol_runs_30d was counted from the operator-facing
patrol run history, which is capped at MaxPatrolRunHistory (100). On any
install patrolling on a normal schedule that cap is reached within hours
of a thirty-day window opening, so the field reported the cap rather than
the run count, and the calls-per-run ratio derived from it was inflated by
a censored denominator. Patrol run history now carries an uncapped daily
tally beside the capped list, advanced from a persisted high-water mark so
repeated full-list saves cannot double count, pruned to 31 days, and read
in preference to the list. An install with no tally yet falls back to the
list, so the counter never regresses on upgrade.

Schema v8 split agent-side pre-mutation refusals into target-change,
prerequisite and contract categories so they would stop hiding in "other".
In production all three are zero fleet-wide and "other" still absorbs every
refusal, because the dominant reason code is the legacy preflight_refused
aggregate recorded when an agent sends no machine reason code at all.
Agents older than the typed refusal contract report every refusal that way,
so the split is starved rather than broken, and folding the two together
made those two states indistinguishable. Schema v9 counts uncoded refusals
separately.
2026-08-19 17:00:02 +01:00
rcourtman 37d6e29e84 Preserve partial-response byte ranges 2026-08-17 20:02:47 +01:00
rcourtman 90081dacb4 Handle informational and bodyless gzip responses 2026-08-16 17:24:01 +01:00
rcourtman a682af0b7b Harden large-estate response and polling limits 2026-08-16 17:07:59 +01:00
rcourtman d08ca46194 Expose Docker health-check dependencies to Patrol 2026-08-16 16:58:44 +01:00
rcourtman 71347eac4c Decide gzip eligibility before a pre-write flush commits headers
A handler that flushes before its first write commits the response
headers with an implicit 200. The gzip wrapper deferred its
compress-or-not decision to the first write, so such a handler would
send headers without Content-Encoding and then a compressed body,
which clients cannot decode. Every current flusher is an SSE handler
whose content type never compresses, so this was latent, but any
future streaming JSON endpoint would have corrupted silently. Flush
now runs the decision first, matching net/http header-commit
semantics.
2026-08-16 14:08:55 +01:00
rcourtman 205df735c7 Compress API responses for clients that accept gzip
Large estates that exceed the WebSocket frame ceiling recover over an
uncompressed /api/state poll loop, which made the biggest deployments
pay the heaviest transfer cost. Wrap the main HTTP handler in a gzip
middleware that compresses JSON and other text responses roughly an
order of magnitude, while leaving WebSocket upgrades, SSE streams,
Range requests, small declared bodies, and binary content untouched.


Contract-Neutral: transport-only gzip response middleware, no RBAC or persistence semantics change
2026-08-16 10:28:08 +01:00
rcourtman 80027e98d6 Project Patrol tools from resource scope 2026-08-15 12:38:41 +01:00
rcourtman 0460797aab Bind runtime version to packaged binary 2026-08-15 03:30:20 +01:00
rcourtman 48fac73ff0 Rework Patrol around outcome-driven autonomous operations 2026-08-14 12:52:51 +01:00
rcourtman 00e570b188 Preserve Patrol objective intent through remediation 2026-08-14 01:41:46 +01:00
rcourtman a7646e5f86 Fix Patrol autonomy and refusal reporting 2026-08-13 23:26:08 +01:00
rcourtman 3981ce552b Honor explicit cluster member address overrides and surface recovery failures
An explicit connection address override on a cluster member was silently
discarded when VerifySSL was enabled and the member had no per-endpoint
fingerprint: the hostname-for-TLS preference displaced the operator's
address, so overriding an undialable discovered hostname changed nothing.
The override now wins in every TLS mode, and the hostname preference
applies only to auto-discovered addresses.

Failed endpoint recovery attempts also logged their cause at debug level
only, leaving the recurring 'No endpoints recovered' warning without a
reason. The warning now carries per-endpoint failure reasons and the
sanitized error is stored so the UI health status shows it too.

Refs #1665

Contract-Neutral: behavioral bug fix refs #1665: cluster member IPOverride honored in all TLS modes, recovery failure reasons surfaced; no public contract delta
2026-08-13 22:06:29 +01:00
Pulse Autonomous Maintainer 1dcb414167 Harden audit signatures against boundary forgery 2026-08-12 05:05:11 +01:00
courtmanr@gmail.com 4dac4dd163 Allow agents to include filtered disk mounts 2026-08-11 16:37:37 +01:00
courtmanr@gmail.com df606ee81a Describe External Probes server-side alerting accurately 2026-08-11 16:37:37 +01:00
Pulse Autonomous Maintainer f445b7fa29 Protect shared metrics database directories 2026-08-11 15:57:17 +01:00
rcourtman 2bc4ed7254 Split bounded pipeline metrics writes from synchronous batch writes
6b79aa997 bounded WriteBatchSync itself, which broke its read-your-writes
contract on slow disks: CI's metrics write-amplification and 500-node
load tests count committed rows after writing, and mock seeding reads
store coverage straight back, so the 2-second early return failed both
(runs 31475700902, 31494553977). Fast local disks masked it.

WriteBatchSync returns to a full commit wait. The monitoring pipeline's
four sync sites move to WriteBatchBounded, which carries the bounded
enqueue-plus-wait semantics, so the #1437 slow-disk stall fix stays
exactly where the hazard is. Both paths share prepareWriteBatch
validation, and a new regression test pins WriteBatchSync waiting past
the bounded budget.

Refs #1437

Contract-Neutral: behavioral fix: split bounded pipeline writes from synchronous batch writes, restores read-your-writes (#1437 follow-up), no public contract delta
2026-08-11 14:42:42 +01:00
rcourtman 6b79aa9972 Bound synchronous metrics writes so a slow disk cannot stall monitoring
WriteBatchSync waited unboundedly for the ingestion worker's commit. The
monitoring pipeline calls it inline from state broadcast, agent ingest,
and poll publish, so a metrics disk slow enough to back up the write
queue froze the monitor after its first cycle: polls stopped being
scheduled, PollStatus.LastSuccess never advanced, and healthy API
sources degraded to stale/agent-only while SQLite ground through
retention maintenance (107s cleanup, multi-second commits on the
reporter's instance).

enqueueAndWait now shares a single 2s budget across enqueue and commit.
A queue that cannot accept the batch within the budget drops it with a
warning, matching enqueueWrite's saturation behavior. A batch that
enqueues but has not committed stays queued and is not lost; the caller
moves on and a rate-limited warning records the backlog. Healthy disks
keep read-your-writes semantics.

Refs #1437

Contract-Neutral: behavioral fix: bound metrics store sync write wait (#1437), no public contract delta
2026-08-11 09:58:29 +01:00
rcourtman be18f99d24 fix(rbac): make SSO user access manageable 2026-08-09 13:36:07 +01:00