1762 Commits

Author SHA1 Message Date
Pulse Test 473885e2ce Harden Patrol activation telemetry 2026-08-28 11:35:41 +01:00
pulse-triage[bot] 0c2d3f6d80 Keep agent install token issuance atomic
Restore the complete prior API-token inventory when an agent install credential cannot be persisted, so failed issuance neither admits an undisclosed secret nor evicts an older valid token. Prove the package and API failure boundaries and govern the shared lifecycle contract.

Change-source: pulse-maintainer
2026-08-28 07:33:22 +01:00
pulse-triage[bot] 73be3df3c5 Keep API token creation atomic
Restore the complete pre-creation token inventory when persistence fails so sorting cannot evict an older valid token or leave an unreturned credential active. Govern and test the durable creation boundary across API, security, agent lifecycle, and storage.

Change-source: pulse-maintainer
2026-08-28 07:08:08 +01:00
Pulse Test 5ee1cdd002 Make informational alerts a first-class severity 2026-08-28 00:45:20 +01:00
Pulse Test ff6c6a7fe7 Make mock alert history lifecycle-consistent 2026-08-27 23:36:55 +01:00
rcourtman 8ade613dfd Add complete mock alert timelines 2026-08-27 22:04:26 +01:00
rcourtman 1878245cdb Make alert incident timelines complete 2026-08-27 21:42:25 +01:00
rcourtman a9076de15a fix(patrol): require Docker health recovery 2026-08-27 20:43:54 +01:00
pulse-triage[bot] f9340e3334 Complete Docker action verification proof
Change-source: pulse-maintainer
2026-08-27 20:19:17 +01:00
rcourtman 6163a2a564 feat(patrol): verify Docker actions independently 2026-08-27 20:14:38 +01:00
pulse-triage[bot] 5f6bfefa56 Make notification destination updates durable 2026-08-27 19:46:40 +01:00
rcourtman 1164d37302 feat(patrol): reconcile actionable findings on activation 2026-08-27 19:44:02 +01:00
rcourtman 8df9db7ab1 feat(alerts): add destination severity routing 2026-08-27 19:13:32 +01:00
pulse-triage[bot] e1d05e7b12 Make deploy enrollment credentials atomic 2026-08-27 18:49:41 +01:00
rcourtman 3d4c43162a feat(alerts): add recurring scoped maintenance 2026-08-27 18:20:03 +01:00
rcourtman ff507a6e44 feat(alerts): add per-alert snooze 2026-08-27 17:20:32 +01:00
rcourtman f4e1e47e74 feat(alerts): add external dead-man monitoring 2026-08-27 16:27:46 +01:00
pulse-triage[bot] 30b4fbd95e test(notifications): isolate terminal recovery paths 2026-08-27 16:08:53 +01:00
pulse-triage[bot] d15181e960 Govern API token deletion durability
Record durable persistence as the token-revocation commit boundary across API, security, agent-lifecycle, and storage contracts. Route the exact-removal and rollback proofs through the canonical token lifecycle suite.

Change-source: pulse-maintainer
2026-08-26 22:11:28 +01:00
pulse-triage[bot] 9f971b4727 Keep API token deletion atomic
Roll back the in-memory token inventory and return an error when durable persistence fails. Cover exact deletion from a multi-token inventory and the persistence-failure rollback path.

Change-source: pulse-maintainer
2026-08-26 22:01:56 +01:00
pulse-triage[bot] 18bf3e1a8d Complete alert event log governance proof
Bind monitor-side event-log bootstrap to its canonical boundary and exercise the new event infrastructure through the registry-recognized alerts, API, monitoring, and agent-lifecycle proof files.

Change-source: pulse-maintainer
2026-08-26 21:32:10 +01:00
pulse-triage[bot] e88583866a Repair alert delivery governance proof
Bind the delivery diagnosis, active-card presentation, and additive event log to their canonical contracts and recognized API/frontend tests. Sync the shipped API reference, record current browser evidence, and restore sorted truthful control-plane status evidence.

Change-source: pulse-maintainer
2026-08-26 21:29:03 +01:00
rcourtman 9572a113ae feat(alerts): append-only alert event log with suppression reasons
Adds internal/alerts/eventlog: a SQLite-backed, additive event log that
records lifecycle transitions (resolved, acknowledged, unacknowledged,
escalated, flapping detected) and notification decisions (dispatched,
deferred by quiet hours, suppressed — with the mechanism that held
them). Appends never block alert evaluation: a full buffer drops the
event and counts the drop; a store that fails to open degrades to
recording nothing. 90-day retention, hourly prune.

The manager emits at the existing funnels only — dispatchAlert and the
safe-call resolve/ack/escalate seams — so no lifecycle behavior
changes. Lifecycle "fired" is deliberately not recorded yet: the
active-alert store funnel also runs on persisted restore, so firing
waits for the explicit activation seam in a later phase. The monitoring
bootstrap enables the log per manager; ephemeral managers and tests
record nothing unless they opt in.

GET /api/alerts/events (monitoring:read) reads the log with
alertIdentifier/type/since/limit filters, newest first.

Phase 0 of docs/ALERT_ENGINE_EVOLUTION.md (coverage gap
alert-engine-suppression-observability).
2026-08-26 21:15:37 +01:00
rcourtman 515aa319e8 feat(alerts): bulk delivery diagnosis for all active alerts
GET /api/alerts/delivery-diagnosis without alertIdentifier now returns
the diagnosis array for every active alert in one manager pass, so list
surfaces do not need a request per alert. Extracts the per-alert
diagnosis into a locked helper shared by both paths; single-alert
behavior is unchanged.

First slice of coverage gap alert-engine-suppression-observability
(docs/ALERT_ENGINE_EVOLUTION.md Phase 0).
2026-08-26 21:05:37 +01:00
rcourtman cba1e2abeb Reconcile delivery health and Proxmox Assistant controls 2026-08-26 18:33:41 +01:00
rcourtman 74d0895e6e Fix mock storage history continuity
Contract-Neutral: Storage history behavior is documented in the staged API and storage contracts and covered by staged focused regressions; shared router references do not change agent-lifecycle, performance, or security contracts, and the proof allowlists do not yet name the focused test files.
2026-08-26 13:54:55 +01:00
rcourtman 5d46e7d0e7 Complete mock drawer history coverage
Contract-Neutral: restores existing metricsTarget identity and complete mock history fallback contracts without changing their public shape
2026-08-26 12:11:10 +01:00
rcourtman 1943a1e97f Fix workload drawer history metrics targeting
Contract-Neutral: restores existing metricsTarget identity and mock history fallback contracts without changing their public shape
2026-08-26 11:17:41 +01:00
rcourtman d3bfb3bb7b Tolerate heartbeat pings in websocket initial state test
TestWebSocketSendsInitialState asserted initialState arrives as the
second message, but the hub interleaves 30s heartbeat pings with the
connect sequence, so a slow state build on a loaded runner put a ping
there first and reddened main twice in three runs. Wait for each
expected type the way TestWebsocketPayloadContractShape already does.
The delivery and payload assertions are unchanged.
2026-08-25 07:19:24 +01:00
rcourtman 2ca84cddd7 Prevent connection alerts from bypassing offline policy 2026-08-24 22:45:59 +01:00
rcourtman dec1c94c27 Define recovery list pagination bounds once in the recovery model
Follow-up to 6686cdce2: the 100-default/500-max list page bounds existed
as separate untyped constants in internal/api and internal/recovery/store,
relying on a contract clause to keep them aligned. Export
DefaultListPageLimit / MaxListPageLimit from internal/recovery/model
(re-exported through internal/recovery) and alias both consumers to them,
so drift is impossible by construction. store_test.go and
recovery_handlers_test.go pin both consumers to the shared constants;
the three pagination-meta contract clauses now name the single source.
2026-08-24 10:21:10 +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 6686cdce2c Report recovery pagination meta from the normalized limit
/api/recovery/points and /api/recovery/rollups clamp the requested page
size to [100 default, 500 max] in both the mock paginators and the store
paths, but the meta block was computed from the raw query value. A client
requesting limit=1000 with 1200 rollups was told totalPages=2 while the
server served 3 pages of 500, so iterating totalPages silently dropped
rollups; limit<=0 reported totalPages=1 at an effective limit of 100.

Normalize page and limit once at parse time, compute meta from the
normalized values, and echo the effective limit. Contract clause 34 in
api-contracts.md pins the obligation; storage-recovery and agent-lifecycle
record the boundary alignment and adjacency; recovery_handlers_test.go
pins above-max and non-positive limit meta.
2026-08-24 09:18:36 +01:00
rcourtman 814f700883 Fix main CI fallout from the 50-node demo estate
The larger demo estate overflowed the rollups mock test's single
500-row page and pushed internal/api past the 25m -race budget, and
the alert history tests raced TempDir cleanup by never joining the
periodic save worker.

Walk every rollups page in the integration test, raise the shard
budget to 50m, and stop history managers through Stop() so the save
worker is joined before cleanup.

Contract-Neutral: test-only CI fix: paginate rollups mock test, join history save worker in test teardown, raise shard go test timeout for the 50-node demo estate; no runtime or contract delta
2026-08-24 08:56:07 +01:00
rcourtman b0d6c95a93 Stamp command policy intent on hosted tenant install tokens
The hosted-tenant mint site (GenerateHostedTenantAgentInstallCommand)
issued binding-eligible install tokens without the command_policy_intent
metadata that 87ee3cd44 added to the self-hosted mint sites, so a hosted
tenant reinstalling an agent never got the stale-disabled-policy
reconciliation on first report (#1728). Hosted installs have no
enableCommands toggle, so the intent follows the install type the same
way the self-hosted flow does: pve enabled, pbs disabled.
2026-08-24 08:49:49 +01:00
rcourtman 87ee3cd449 Fix command policy after agent reinstall
Project freshly minted installer command intent onto the stable host before returning remote config, and consume that intent once so later admin policy changes remain authoritative.
2026-08-23 23:39:20 +01:00
rcourtman a7f87471f4 Stop returning the stored Apprise API key from the notifications API
GET /api/notifications/apprise and the PUT echo returned the saved API key
verbatim, so any settings:read token could exfiltrate a write-capable
secret while the sibling email handler blanks the SMTP password under the
same scope. Responses now drop the key and report hasApiKey instead, the
update and test-send paths fall back to the saved key when the request
leaves it blank (matching the email password contract), and the settings
form tells the user a saved key is kept when the field stays empty.
2026-08-23 14:39:07 +01:00
rcourtman 3a9dffa850 Fix Docker command recovery after token rotation 2026-08-23 09:18:44 +01:00
rcourtman bce5564025 fix notification terminal failure recovery 2026-08-23 09:05:54 +01:00
rcourtman ea5c105ff2 Log refused actions and name the missed command-agent lookup
A refused action plan, decision, or execution returned its 409 to the
client and left no trace in the server journal, so every remote report
of "Docker / Podman command agent is not connected" stalled on greps
that could never match anything. Refusals now log one warn line with
the resource, capability, and reason code, and the Docker command-agent
resolver reports which lookup missed (stale enrollment token binding vs
agent-id/hostname session), carried as an optional diagnostic detail on
the readiness contract and in the refusal envelope.

Refs #1728
2026-08-23 06:33:30 +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 08827bb887 Extract chart and resource query services 2026-08-21 18:36:21 +01:00
rcourtman 1327dddad5 Compress release backend test selectors 2026-08-21 17:04:18 +01:00
rcourtman e61715462a Canonicalize install security helpers 2026-08-21 14:56:11 +01:00
rcourtman c68d5dd3d8 Extract configuration API runtime package 2026-08-21 14:56:07 +01:00
rcourtman 58bf77c1ae Extract alert delivery API package 2026-08-21 14:56:04 +01:00
rcourtman 9dac68fd62 Parallelize release qualification on PVE runners 2026-08-21 12:24:47 +01:00
rcourtman 7da385cf28 Prepare v6.3.0-rc.4 release 2026-08-21 00:00:37 +01:00
rcourtman 74873e2b55 Add a user-facing alert delivery log and honest test-send results
The delivery health verdict said when something was wrong; nothing showed
what actually fired and where it went. Expose the queue's retained
per-attempt audit rows as GET /api/notifications/delivery-log (newest
first, retention-labeled, webhook secrets redacted from error text) and
render them as a Recent delivery activity card on the alert destinations
tab, with outcome badges, destination names, and failure classes. Audit
rows now persist the normalized destination identity in a destination_id
column; older rows fall back to their operational links.

Test sends bypass both the queue and the activation gate, which is exactly
how installs came to believe delivery worked while every real alert was
suppressed (483 installs in the 08-18 telemetry read). Successful test
responses now carry deliveryPaused: true whenever the manager is gated
off, and the destinations UI warns instead of celebrating.
2026-08-20 17:35:13 +01:00
rcourtman ffe47fa4b7 Warn when a system settings read fails in router settings paths
A LoadSystemSettings error in configureMonitorDependencies silently
skipped the tenant-monitor inherit block, leaving the monitor on
deny-all-private webhook CIDRs with nothing in the logs to explain why
private webhook targets fail SSRF validation. reloadSystemSettings
fails closed on the same error (embedding off) deliberately, but was
equally silent, so a persistent read failure looked identical to
embedding being switched off on purpose.

Both paths now log a warning with the error, mirroring the pattern from
cf5b86e62 in internal/config. A missing system.json (fresh install)
still stays quiet. Tests cover the warning on read failure, the quiet
fresh-install path, and that the reload still fails closed.
2026-08-20 14:49:36 +01:00