diff --git a/README.md b/README.md
index 5355b8c42..bdc010725 100644
--- a/README.md
+++ b/README.md
@@ -68,7 +68,7 @@ Power-user shortcuts:
- **One-Click Updates**: Easy upgrades for supported deployments
- **OIDC/SSO/SAML**: Single sign-on with multi-provider support
- **Mobile Remote Access**: Relay protocol with end-to-end encryption for supported Pulse Mobile clients (Relay and above)
-- **Privacy Focused**: Anonymous outbound telemetry is enabled by default and [fully documented](docs/PRIVACY.md) — no hostnames, credentials, or personal data is ever sent. Disable any time in Settings or via `PULSE_TELEMETRY=false`.
+- **Privacy Focused**: Outbound usage telemetry is enabled by default and [fully documented](docs/PRIVACY.md) — the payload uses a rotating pseudonymous install ID and does not include hostnames, credentials, names, email addresses, IP addresses, or infrastructure identifiers. Disable any time in Settings or via `PULSE_TELEMETRY=false`.
## ⚡ Quick Start
diff --git a/docs/AI.md b/docs/AI.md
index c0d4dfbe0..10f50c056 100644
--- a/docs/AI.md
+++ b/docs/AI.md
@@ -461,7 +461,7 @@ Use this only in trusted environments.
## Privacy
-Patrol runs on your server and only sends the minimal context needed for analysis to the configured provider (when AI is enabled). Anonymous outbound telemetry (counts, feature flags, and coarse Patrol mode and governed Pulse Intelligence operations adoption flags and counters only; no hostnames, credentials, prompts, chat messages, command text, action output, token values, or resource identifiers) is enabled by default and can be disabled any time. See [Privacy](PRIVACY.md) for details.
+Patrol runs on your server and only sends the minimal context needed for analysis to the configured provider (when AI is enabled). Outbound usage telemetry (a rotating pseudonymous install ID, counts, feature flags, and coarse Patrol mode and governed Pulse Intelligence operations adoption flags and counters only; no hostnames, credentials, prompts, chat messages, command text, action output, token values, IP addresses, or resource identifiers in the payload) is enabled by default and can be disabled any time. See [Privacy](PRIVACY.md) for details.
---
diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md
index d0c487d17..edb7b1cf7 100644
--- a/docs/CONFIGURATION.md
+++ b/docs/CONFIGURATION.md
@@ -306,7 +306,7 @@ When `allowEmbedding` is `false`, Pulse sends `X-Frame-Options: DENY` and `frame
| `PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION` | Allow Proxmox-side LXC Docker socket hinting with `pct exec` | `false` |
| `PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY` | Allow Proxmox-side minimal LXC Docker inventory collection with `pct exec`; collects Docker host/container summary, not inspect/env/mount/process data | `false` |
| `PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS` | Optional comma-separated VMID allowlist for Proxmox-side LXC Docker inventory; empty means all running Docker-enabled LXCs are eligible when inventory is enabled | *(unset)* |
-| `PULSE_TELEMETRY` | Anonymous outbound usage telemetry ([details](PRIVACY.md)); set `false` to disable | `true` |
+| `PULSE_TELEMETRY` | Outbound usage telemetry ([details](PRIVACY.md)); set `false` to disable | `true` |
### Logging Overrides
diff --git a/docs/PRIVACY.md b/docs/PRIVACY.md
index 132519e91..1bab22298 100644
--- a/docs/PRIVACY.md
+++ b/docs/PRIVACY.md
@@ -4,7 +4,7 @@ Pulse is designed to run locally. By default, your monitoring data stays on your
## Usage Data
-Pulse has one outbound usage-data scope: **anonymous outbound telemetry** to
+Pulse has one outbound usage-data scope: **outbound usage telemetry** to
help me understand active installations, release uptake, and feature use in
aggregate.
@@ -12,15 +12,15 @@ Commercial activation and license-recovery runtime records stay on the Pulse
instance where they were created. They are not exported to Pulse infrastructure,
third-party analytics, support diagnostics, or ordinary Settings surfaces.
-### Anonymous outbound telemetry
+### Outbound usage telemetry
-Pulse includes anonymous outbound telemetry that is **enabled by default**. It sends a lightweight ping on startup and once every 24 hours to help me understand how many active installations exist, which releases are actually deployed, which features are in use, and whether Patrol control and governed Pulse Intelligence operations are being adopted.
+Pulse includes outbound usage telemetry that is **enabled by default**. It sends a lightweight ping on startup and once every 24 hours with a rotating pseudonymous install ID to help me understand how many active installations exist, which releases are actually deployed, which features are in use, and whether Patrol control and governed Pulse Intelligence operations are being adopted.
-No hostnames, credentials, infrastructure identifiers, IP addresses, prompts, chat messages, command text, action output, token values, or personally identifiable information is ever sent. See the full field list below.
+The telemetry payload does not include hostnames, credentials, infrastructure identifiers, IP addresses, prompts, chat messages, command text, action output, token values, names, email addresses, or account identifiers. See the full field list below.
#### How to disable
-- **Settings → System → General → Anonymous outbound telemetry** (toggle off), or
+- **Settings → System → General → Outbound usage telemetry** (toggle off), or
- Set the environment variable `PULSE_TELEMETRY=false`
#### How to inspect or rotate it
@@ -31,7 +31,7 @@ No hostnames, credentials, infrastructure identifiers, IP addresses, prompts, ch
#### Exactly what is sent
-Every field is listed below with the reason it exists — nothing else leaves your server:
+Every field is listed below with the reason it exists. Nothing else is included in the telemetry payload:
| Field | Example | Purpose |
|-------|---------|---------|
@@ -148,19 +148,19 @@ Every field is listed below with the reason it exists — nothing else leaves yo
- Pulse may derive aggregate Pulse Intelligence adoption reports from those same rows, including whether an install reached Patrol issue activity, Patrol resolution, Assistant, direct external-agent, or MCP collaboration, Patrol mode starter use, paid Patrol mode cohorts, governed-action activity, approved or rejected action decisions, approved action success, completed Patrol control work, recent retention, and observed free-to-paid movement within the source window. Those reports do not add prompts, findings, resource identifiers, tool names, tool inputs, tool outputs, command payloads, action outputs, account links, or exact commercial tiers.
- External-agent/MCP activity is stored only as a coarse adapter-origin flag plus capability-class counters: context, event stream, provisioning, operator state, findings, and action requests.
- Telemetry rows older than **90 days** are purged automatically.
-- The license server uses client IP addresses transiently for abuse/rate limiting, but it does **not** store IP addresses in telemetry rows.
+- The license server uses request IP addresses transiently for abuse/rate limiting, but it does **not** store IP addresses in telemetry rows.
#### What is NOT sent
-- No IP addresses are stored in telemetry rows
+- No IP addresses are included in the telemetry payload or stored in telemetry rows
- No hostnames, node names, VM names, or any infrastructure identifiers
- No Proxmox credentials, API tokens, or passwords
- No alert content, AI prompts, chat messages, tool names, tool inputs, tool outputs, command text, action output, or token values
-- No personally identifiable information of any kind
+- No names, email addresses, account identifiers, or other intentionally identifying personal content
#### Install ID rotation
-The telemetry install ID is pseudonymous and rotates automatically every 30 days.
+The telemetry install ID is pseudonymous, is not tied to a Pulse account, and rotates automatically every 30 days.
Pulse keeps it only to avoid treating every startup ping as a brand-new install
while still limiting long-term linkage from one heartbeat window to the next.
Operators can also rotate it immediately from **Settings → System → General → Reset ID**.
diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md
index e5aa688b6..f31c2cf83 100644
--- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md
+++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md
@@ -138,12 +138,12 @@ command completed. External-agent/MCP readiness is optional collaboration
setup for agents outside the app, not a first-party Patrol control completion
gate and not a lifecycle setup stage.
Server update funnel telemetry is lifecycle-adjacent only. The updater's local
-history may feed anonymous 30-day attempt, success, failure, and coarse failure
+history may feed outbound usage 30-day attempt, success, failure, and coarse failure
category counters, but those counters are adoption analytics, not proof that a
particular agent update, profile rollout, host command, registration, or fleet
operation succeeded. Lifecycle surfaces must keep reading update readiness and
continuity from the updater, installer, connection ledger, and agent runtime
-state instead of inferring it from anonymous telemetry.
+state instead of inferring it from outbound usage telemetry.
1. `frontend-modern/src/api/agentProfiles.ts` shared with `api-contracts`: the agent profiles frontend client is both an agent lifecycle control surface and a canonical API payload contract boundary.
2. `frontend-modern/src/api/nodes.ts` shared with `api-contracts`: the shared Proxmox node client is both an agent lifecycle setup/install control surface and a canonical API payload contract boundary.
diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md
index 83b964c81..030c63671 100644
--- a/docs/release-control/v6/internal/subsystems/ai-runtime.md
+++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md
@@ -500,7 +500,7 @@ request payload.
must update the MCP README's Pulse Intelligence surface contract and the public
`docs/AI.md` overview from the manifest instead of leaving hand-written
Core/Patrol/Assistant/MCP relationship prose beside generated inventories.
-Public AI docs may describe anonymous outbound telemetry only as counts,
+Public AI docs may describe outbound usage telemetry only as counts,
feature flags, and coarse Patrol control and governed Pulse Intelligence
operations adoption flags and counters.
They must keep prompts, chat messages, command text, action output, token
diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md
index 1b2176629..7c726e4e5 100644
--- a/docs/release-control/v6/internal/subsystems/api-contracts.md
+++ b/docs/release-control/v6/internal/subsystems/api-contracts.md
@@ -3765,14 +3765,14 @@ for logs and response headers.
That same shared settings/licensing contract now also owns the split usage-data
payload model. `frontend-modern/src/api/settings.ts`,
`internal/api/router_routes_licensing.go`, and adjacent settings callers must
-keep anonymous outbound telemetry as the only browser-visible usage-data scope;
+keep outbound usage telemetry as the only browser-visible usage-data scope;
local commercial reporting controls stay internal/admin-owned and must not
surface as ordinary Settings controls. The telemetry preview payload must ship
normalized version identity fields (`version`, `version_raw`, `version_channel`,
`version_build`, `version_is_development`, and
`version_is_published_release`) instead of leaving browser callers to infer
published-release truth from raw build strings.
-That same preview contract now includes the complete anonymous telemetry
+That same preview contract now includes the complete outbound usage telemetry
payload shape, including aggregate self-hosted adoption counters for monitored
platforms, workloads, storage, and availability targets plus coarse feature
booleans, content-free update funnel counters, and content-free Patrol,
diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md
index 18f05ea6b..71d043696 100644
--- a/docs/release-control/v6/internal/subsystems/cloud-paid.md
+++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md
@@ -232,7 +232,7 @@ Stripe-free and avoids a cloud-control-plane report data path across clients.
`pkg/licensing/grant_refresh.go`, and cloud-paid transport must send those
values on activate, legacy exchange, and grant refresh instead of inferring
install version or paid-runtime status from browser state, dev build
- metadata, public image tags, or anonymous telemetry.
+ metadata, public image tags, or outbound usage telemetry.
Active self-hosted paid entitlements must also treat runtime identity as a
first-class product state. A paid Pro, Pro Annual, Pro+, lifetime, or
enterprise entitlement on a non-Pro or missing runtime identity must render
@@ -395,7 +395,7 @@ files.
Pulse Intelligence paid-value evidence is a separate private admin aggregate,
not a revival of those retired funnel stores. The `pulse-pro` license server
may expose `/v1/admin/pulse-intelligence/value` and the matching admin-console
-strip only as coarse install cohorts over anonymous telemetry pings: paid
+strip only as coarse install cohorts over outbound usage telemetry pings: paid
install rate, free-to-paid conversion, returning-install retention, and whether
the full Patrol -> contextual collaboration -> governed action loop, the
stricter Patrol -> contextual collaboration -> approved execution loop, and
diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
index 54b9f5e6c..c000bf5f5 100644
--- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md
+++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md
@@ -507,7 +507,7 @@ AGENT_SURFACE_ID_PULSE_MCP)` and `getAgentSurfaceToolPosturePresentation`,
4. `frontend-modern/src/components/Settings/DataHandlingPanel.tsx` shared with `security-privacy`: the data-handling settings surface is both a security/privacy trust surface and a canonical settings-shell presentation boundary.
5. `frontend-modern/src/components/Settings/dataHandlingPanelModel.ts` shared with `security-privacy`: the data-handling settings model is both a security/privacy posture projection and a canonical settings-shell presentation boundary.
6. `frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx` shared with `security-privacy`: the general settings privacy panel is both a security/privacy control surface and a canonical settings-shell presentation boundary.
- The panel owns compact settings-shell framing for anonymous telemetry, but
+ The panel owns compact settings-shell framing for outbound usage telemetry, but
its vocabulary must stay aligned with `security-privacy`: aggregate
self-hosted adoption counts, coarse feature flags, and coarse Patrol,
Assistant, and external-agent usage counters may be named, while
@@ -2057,7 +2057,7 @@ default` instead of fusing provider and badge text such as
`AIRuntimeControlsSection.tsx` must not hardcode GitHub `main` doc URLs for
privacy, security, proxy-auth, scope-reference, or Terms-of-Service links.
29. Keep shared settings-shell telemetry transparency controls on the governed
- general settings panel. Preview/reset affordances for anonymous telemetry
+ general settings panel. Preview/reset affordances for outbound usage telemetry
must stay rendered inside
`frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx`
instead of drifting into route-local modals, hidden dev tools, or shell
@@ -2069,7 +2069,7 @@ default` instead of fusing provider and badge text such as
`GeneralSettingsPanel.tsx` must state those facts plainly instead of
reverting to a stronger but inaccurate shorthand.
31. Keep maintainer commercial-event controls out of customer settings.
- The shared general settings privacy panel may expose anonymous outbound
+ The shared general settings privacy panel may expose outbound usage
telemetry controls, preview, and reset affordances, but it must not render
local commercial handoff event toggles, `PULSE_DISABLE_LOCAL_UPGRADE_METRICS`,
or other commercial-debug controls as normal customer-facing preferences.
@@ -2446,8 +2446,8 @@ That same shared settings and modal boundary now also owns the public usage-data
vocabulary. `frontend-modern/src/components/Settings/GeneralSettingsPanel.tsx`,
`frontend-modern/src/components/Settings/useSystemSettingsState.ts`, and
`frontend-modern/src/utils/systemSettingsPresentation.ts` must present one
-explicit `Usage data and privacy` model centered on `Anonymous outbound
-telemetry`; maintainer commercial-event controls, upgrade-metrics labels, and
+explicit `Usage data and privacy` model centered on `Outbound usage telemetry`;
+maintainer commercial-event controls, upgrade-metrics labels, and
sales/onboarding reporting language must not appear in customer-facing Settings
or support diagnostics, and public configuration docs must not list their
internal compatibility switches as ordinary operator settings. Customer
@@ -4627,7 +4627,7 @@ through `frontend-modern/src/utils/docsLinks.ts` rather than panel-local
external URLs.
That same shared-shell framing also covers the concise telemetry summary in
General settings. The shell may present the privacy contract in compact product
-copy, but the vocabulary for anonymous outbound telemetry must stay aligned
+copy, but the vocabulary for outbound usage telemetry must stay aligned
with `security-privacy`: aggregate self-hosted adoption counts, coarse feature
flags, and coarse Patrol, Assistant, and external-agent usage counters are
allowed, while hostnames, credentials, infrastructure identifiers, prompts,
diff --git a/docs/release-control/v6/internal/subsystems/monitoring.md b/docs/release-control/v6/internal/subsystems/monitoring.md
index 32fe1348c..95134b8a1 100644
--- a/docs/release-control/v6/internal/subsystems/monitoring.md
+++ b/docs/release-control/v6/internal/subsystems/monitoring.md
@@ -950,7 +950,7 @@ reporting surface that claims installation totals must aggregate across the
provisioned tenant set through the reloadable multi-tenant monitor boundary,
not by reading `GetMonitor()`'s default-org compatibility shim.
Those install-wide counts are now the canonical aggregate adoption signal for
-anonymous telemetry: monitoring owns the source counts for agent hosts, Docker
+outbound usage telemetry: monitoring owns the source counts for agent hosts, Docker
and Kubernetes workloads, storage pools and physical disks, Ceph, network
shares, TrueNAS systems/VMs/apps, VMware hosts/VMs/datastores, availability
targets, and active alerts. Telemetry callers may consume those coarse totals,
diff --git a/docs/release-control/v6/internal/subsystems/security-privacy.md b/docs/release-control/v6/internal/subsystems/security-privacy.md
index 64b7fe411..6fbbbc685 100644
--- a/docs/release-control/v6/internal/subsystems/security-privacy.md
+++ b/docs/release-control/v6/internal/subsystems/security-privacy.md
@@ -398,7 +398,7 @@ the API boundary before it reaches persisted AI config: the settings handler
trim, drop blanks, de-duplicate) so untrusted request bodies cannot widen the
alert-driven investigation surface beyond the validated shape.
That same governed home now also owns the single customer-facing "usage data"
-vocabulary for anonymous outbound telemetry. Local commercial activation and
+vocabulary for outbound usage telemetry. Local commercial activation and
license-recovery runtime records must stay out of ordinary Settings, support
diagnostics, outbound telemetry disclosure copy, and public configuration
reference tables.
@@ -607,14 +607,14 @@ That same disclosure boundary now also fixes the telemetry payload floor:
commercial and auth-adjacent telemetry may report only coarse posture signals
such as whether a paid license is active or whether any API tokens exist.
Exact license tiers and exact API-token counts are not part of the canonical
-anonymous telemetry contract and may not be reintroduced without updating this
+outbound usage telemetry contract and may not be reintroduced without updating this
trust boundary and the governed privacy disclosure together.
That same rule also applies at the license-server ingest and storage boundary:
server-side telemetry rows may preserve the canonical normalized version
identity plus those same coarse booleans, but they must not retain legacy
exact commercial tier or exact API-token count fields as first-class analytics
dimensions just because older clients once sent them.
-That same anonymous telemetry floor now also permits only privacy-safe
+That same outbound usage telemetry floor now also permits only privacy-safe
aggregate self-hosted adoption counters: counts of monitored platforms,
workloads, storage resources, physical disks, Ceph clusters, network shares,
TrueNAS and VMware resource categories, availability targets, and active
@@ -622,7 +622,7 @@ alerts. Those counts may describe scale and feature adoption, but they must not
include hostnames, resource IDs, infrastructure identifiers, credentials,
prompts, chat messages, command text, action output, token values, or personal
information.
-That same anonymous telemetry floor now also permits content-free update
+That same outbound usage telemetry floor now also permits content-free update
funnel counters derived from local update history inside the same rotating
30-day telemetry window: update attempts, successful updates, failed or
rolled-back updates, and the latest coarse failure category. The category may
@@ -631,7 +631,7 @@ identify only the governed class (`download`, `signature`, `checksum`,
`cancelled`, or `unknown`). It must not export raw updater error text,
download URLs, command output, log lines, paths, hostnames, release asset URLs,
checksums, signatures, or operator-entered values.
-That same anonymous telemetry floor now also permits only content-free Pulse
+That same outbound usage telemetry floor now also permits only content-free Pulse
Patrol control and governed Pulse Intelligence operations adoption flags and
counters inside the same rotating 30-day telemetry window:
configured/active/completed/resolved governed-operation and approved-execution
@@ -713,7 +713,7 @@ content-free Pulse Intelligence fields only alongside the canonical coarse
entry-to-retention cohorts, paid Patrol-control completed/resolved
cohorts, and observed free-to-paid conversion counts without linking telemetry
to customer accounts or storing exact commercial tiers. The report may also
-derive or persist a completed governed-operation signal from those same anonymous
+derive or persist a completed governed-operation signal from those same content-free
fields, but completion may only mean observed Patrol
issue evidence plus Assistant governed-context or MCP collaboration activity
plus approved/rejected governed-action decision evidence inside the source window;
@@ -733,7 +733,7 @@ collaboration, and at least one approved governed action completed
successfully. It may not encode finding IDs, resource IDs, fix details,
verification detail, command text, action output, approver identity, or a
causal claim that the approved action directly resolved the finding.
-The Patrol control completed-loop status count follows that same anonymous
+The Patrol control completed-loop status count follows that same content-free telemetry
evidence contract: it may only mean the same content-free window also had
Patrol issue evidence, contextual collaboration, and either a rejected governed
decision or an approved governed decision with verified outcome proof. Legacy
@@ -741,7 +741,7 @@ Patrol autonomy and Pro activation completed-loop fields may mirror that value
for compatibility, but must not add checkout/account identity, prompt content,
action identity, resource identity, finding identity, token identity, or a
causality claim.
-The Patrol control resolved-loop status count follows that same anonymous
+The Patrol control resolved-loop status count follows that same content-free telemetry
evidence contract: it may only mean the same content-free window also had
Patrol issue evidence, contextual collaboration, an approved governed decision,
and verified outcome proof. It must not require MCP readiness, treat rejected
@@ -751,7 +751,7 @@ the status projection and outbound telemetry must derive these Patrol control
completed/resolved values through the shared `internal/telemetry` proof
classifier so privacy-sensitive reporting cannot drift into a richer runtime
event join in one caller.
-That same anonymous telemetry contract also treats `install_id` as a rotating
+That same outbound usage telemetry contract also treats `install_id` as a rotating
pseudonymous identifier, not a lifetime install handle. The runtime may keep a
local rotating UUID so startup and heartbeat pings can still represent an
active installation window, but it may not preserve one stable install
diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md
index 993b17a10..82f62248f 100644
--- a/docs/release-control/v6/internal/subsystems/storage-recovery.md
+++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md
@@ -109,6 +109,12 @@ handlers, including Pro activation entry-point telemetry for the same
operations-loop prompt, is likewise API/privacy/commercial activation evidence
only; storage and recovery surfaces must not treat it as backup readiness,
restore capability, recovered-state proof, or storage-health verification.
+Outbound usage telemetry projection in `internal/api/telemetry_pulse_intelligence.go`
+is likewise API/privacy evidence only. Update funnel and governed-action
+adoption counters may summarize content-free activity for product analytics,
+but storage and recovery surfaces must not treat those counters as backup
+coverage, restore capability, recovery-job proof, storage-health verification,
+or evidence that a specific resource was protected or recovered.
Proxmox page stale-agent notices are adjacent frontend and agent-lifecycle
plumbing even though `ProxmoxPageSurface` is a storage/recovery canonical file.
Those notices may link an operator to scoped agent update commands for
diff --git a/frontend-modern/public/docs/CONFIGURATION.md b/frontend-modern/public/docs/CONFIGURATION.md
index b15aed62d..c3d58f319 100644
--- a/frontend-modern/public/docs/CONFIGURATION.md
+++ b/frontend-modern/public/docs/CONFIGURATION.md
@@ -306,7 +306,7 @@ When `allowEmbedding` is `false`, Pulse sends `X-Frame-Options: DENY` and `frame
| `PULSE_ENABLE_PROXMOX_GUEST_DOCKER_DETECTION` | Allow Proxmox-side LXC Docker socket hinting with `pct exec` | `false` |
| `PULSE_ENABLE_PROXMOX_GUEST_DOCKER_INVENTORY` | Allow Proxmox-side minimal LXC Docker inventory collection with `pct exec`; collects Docker host/container summary, not inspect/env/mount/process data | `false` |
| `PULSE_PROXMOX_GUEST_DOCKER_INVENTORY_VMIDS` | Optional comma-separated VMID allowlist for Proxmox-side LXC Docker inventory; empty means all running Docker-enabled LXCs are eligible when inventory is enabled | *(unset)* |
-| `PULSE_TELEMETRY` | Anonymous outbound usage telemetry ([details](PRIVACY.md)); set `false` to disable | `true` |
+| `PULSE_TELEMETRY` | Outbound usage telemetry ([details](PRIVACY.md)); set `false` to disable | `true` |
### Logging Overrides
diff --git a/frontend-modern/public/docs/PRIVACY.md b/frontend-modern/public/docs/PRIVACY.md
index 132519e91..1bab22298 100644
--- a/frontend-modern/public/docs/PRIVACY.md
+++ b/frontend-modern/public/docs/PRIVACY.md
@@ -4,7 +4,7 @@ Pulse is designed to run locally. By default, your monitoring data stays on your
## Usage Data
-Pulse has one outbound usage-data scope: **anonymous outbound telemetry** to
+Pulse has one outbound usage-data scope: **outbound usage telemetry** to
help me understand active installations, release uptake, and feature use in
aggregate.
@@ -12,15 +12,15 @@ Commercial activation and license-recovery runtime records stay on the Pulse
instance where they were created. They are not exported to Pulse infrastructure,
third-party analytics, support diagnostics, or ordinary Settings surfaces.
-### Anonymous outbound telemetry
+### Outbound usage telemetry
-Pulse includes anonymous outbound telemetry that is **enabled by default**. It sends a lightweight ping on startup and once every 24 hours to help me understand how many active installations exist, which releases are actually deployed, which features are in use, and whether Patrol control and governed Pulse Intelligence operations are being adopted.
+Pulse includes outbound usage telemetry that is **enabled by default**. It sends a lightweight ping on startup and once every 24 hours with a rotating pseudonymous install ID to help me understand how many active installations exist, which releases are actually deployed, which features are in use, and whether Patrol control and governed Pulse Intelligence operations are being adopted.
-No hostnames, credentials, infrastructure identifiers, IP addresses, prompts, chat messages, command text, action output, token values, or personally identifiable information is ever sent. See the full field list below.
+The telemetry payload does not include hostnames, credentials, infrastructure identifiers, IP addresses, prompts, chat messages, command text, action output, token values, names, email addresses, or account identifiers. See the full field list below.
#### How to disable
-- **Settings → System → General → Anonymous outbound telemetry** (toggle off), or
+- **Settings → System → General → Outbound usage telemetry** (toggle off), or
- Set the environment variable `PULSE_TELEMETRY=false`
#### How to inspect or rotate it
@@ -31,7 +31,7 @@ No hostnames, credentials, infrastructure identifiers, IP addresses, prompts, ch
#### Exactly what is sent
-Every field is listed below with the reason it exists — nothing else leaves your server:
+Every field is listed below with the reason it exists. Nothing else is included in the telemetry payload:
| Field | Example | Purpose |
|-------|---------|---------|
@@ -148,19 +148,19 @@ Every field is listed below with the reason it exists — nothing else leaves yo
- Pulse may derive aggregate Pulse Intelligence adoption reports from those same rows, including whether an install reached Patrol issue activity, Patrol resolution, Assistant, direct external-agent, or MCP collaboration, Patrol mode starter use, paid Patrol mode cohorts, governed-action activity, approved or rejected action decisions, approved action success, completed Patrol control work, recent retention, and observed free-to-paid movement within the source window. Those reports do not add prompts, findings, resource identifiers, tool names, tool inputs, tool outputs, command payloads, action outputs, account links, or exact commercial tiers.
- External-agent/MCP activity is stored only as a coarse adapter-origin flag plus capability-class counters: context, event stream, provisioning, operator state, findings, and action requests.
- Telemetry rows older than **90 days** are purged automatically.
-- The license server uses client IP addresses transiently for abuse/rate limiting, but it does **not** store IP addresses in telemetry rows.
+- The license server uses request IP addresses transiently for abuse/rate limiting, but it does **not** store IP addresses in telemetry rows.
#### What is NOT sent
-- No IP addresses are stored in telemetry rows
+- No IP addresses are included in the telemetry payload or stored in telemetry rows
- No hostnames, node names, VM names, or any infrastructure identifiers
- No Proxmox credentials, API tokens, or passwords
- No alert content, AI prompts, chat messages, tool names, tool inputs, tool outputs, command text, action output, or token values
-- No personally identifiable information of any kind
+- No names, email addresses, account identifiers, or other intentionally identifying personal content
#### Install ID rotation
-The telemetry install ID is pseudonymous and rotates automatically every 30 days.
+The telemetry install ID is pseudonymous, is not tied to a Pulse account, and rotates automatically every 30 days.
Pulse keeps it only to avoid treating every startup ping as a brand-new install
while still limiting long-term linkage from one heartbeat window to the next.
Operators can also rotate it immediately from **Settings → System → General → Reset ID**.
diff --git a/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.guardrails.test.ts b/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.guardrails.test.ts
index cc9e5cffe..1ffa9efd3 100644
--- a/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.guardrails.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.guardrails.test.ts
@@ -5,7 +5,7 @@ import { EN_MESSAGES } from '@/i18n/messages';
const migratedGeneralSettingsCopy = [
'Usage data and privacy',
- 'Anonymous outbound telemetry',
+ 'Outbound usage telemetry',
'Preview payload',
'Refresh payload',
'Reset ID',
@@ -54,7 +54,10 @@ describe('GeneralSettingsPanel guardrails', () => {
'retained for up to 90 days',
);
expect(EN_MESSAGES['settings.general.telemetry.description']).toContain(
- 'IP addresses are not stored in telemetry rows',
+ 'request IP addresses are used only transiently for rate limiting',
+ );
+ expect(EN_MESSAGES['settings.general.telemetry.description']).toContain(
+ 'not stored in telemetry rows',
);
});
diff --git a/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx b/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx
index b639427dd..8f2126447 100644
--- a/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/GeneralSettingsPanel.localization.test.tsx
@@ -70,7 +70,7 @@ describe('GeneralSettingsPanel localization', () => {
screen.getByText(/Commands, resource names, and API fields stay unchanged/),
).toBeInTheDocument();
expect(screen.getByText('Usage data and privacy')).toBeInTheDocument();
- expect(screen.getByText('Anonymous outbound telemetry')).toBeInTheDocument();
+ expect(screen.getByText('Outbound usage telemetry')).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Preview payload' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: 'Reset ID' })).toBeInTheDocument();
expect(screen.getByText('Monitoring cadence')).toBeInTheDocument();
diff --git a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
index 869062d4a..7c05cd06f 100644
--- a/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
+++ b/frontend-modern/src/components/Settings/__tests__/settingsArchitecture.test.ts
@@ -626,13 +626,13 @@ describe('settings architecture guardrails', () => {
'aggregate self-hosted adoption',
);
expect(EN_MESSAGES['settings.general.telemetry.description']).toContain(
- 'counts, coarse feature flags, and coarse Patrol, Assistant, and external-agent usage counters',
+ 'aggregate self-hosted adoption counts, coarse feature flags, and coarse Patrol, Assistant, and external-agent usage counters',
);
expect(EN_MESSAGES['settings.general.telemetry.description']).not.toContain(
'Pulse Intelligence loop adoption',
);
expect(EN_MESSAGES['settings.general.telemetry.description']).toContain(
- 'identifiers, prompts, chat messages, command text, action output, token values, or personal information are sent.',
+ 'identifiers, prompts, chat messages, command text, action output, token values, names, email addresses, or IP addresses',
);
expect(generalSettingsPanelSource).toContain('settings.general.telemetry.payloadAriaLabel');
expect(generalSettingsPanelSource).toContain('settings.general.telemetry.resetId');
diff --git a/frontend-modern/src/components/Settings/useSystemSettingsState.ts b/frontend-modern/src/components/Settings/useSystemSettingsState.ts
index 2630c24b5..7c63d5421 100644
--- a/frontend-modern/src/components/Settings/useSystemSettingsState.ts
+++ b/frontend-modern/src/components/Settings/useSystemSettingsState.ts
@@ -322,7 +322,7 @@ export function useSystemSettingsState({
try {
await SettingsAPI.updateSystemSettings({ telemetryEnabled: enabled });
notificationStore.success(
- enabled ? 'Anonymous outbound telemetry enabled' : 'Anonymous outbound telemetry disabled',
+ enabled ? 'Outbound usage telemetry enabled' : 'Outbound usage telemetry disabled',
3000,
);
} catch (error) {
@@ -374,7 +374,7 @@ export function useSystemSettingsState({
if (
!confirm(
- 'Reset the rotating telemetry install ID now? This immediately replaces the local identifier used for anonymous telemetry.',
+ 'Reset the rotating telemetry install ID now? This immediately replaces the local pseudonymous identifier used for outbound usage telemetry.',
)
) {
return;
diff --git a/frontend-modern/src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx b/frontend-modern/src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx
index 3ebf3093d..f5003e183 100644
--- a/frontend-modern/src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx
+++ b/frontend-modern/src/components/SetupWizard/__tests__/SetupWizard.localization.test.tsx
@@ -76,6 +76,7 @@ describe('localized setup wizard journey', () => {
expect(screen.getByText('Bienvenido a Pulse')).toBeInTheDocument();
expect(screen.getByText('Desbloquear configuración')).toBeInTheDocument();
expect(screen.getByText(/Conecta una API de plataforma/)).toBeInTheDocument();
+ expect(screen.getByText('La telemetría de uso está activada por defecto')).toBeInTheDocument();
expect(screen.getByText('sudo pulse bootstrap-token')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Verificar token de bootstrap →' }),
diff --git a/frontend-modern/src/components/SetupWizard/__tests__/WelcomeStep.test.tsx b/frontend-modern/src/components/SetupWizard/__tests__/WelcomeStep.test.tsx
index c7f69a794..43a1b60f1 100644
--- a/frontend-modern/src/components/SetupWizard/__tests__/WelcomeStep.test.tsx
+++ b/frontend-modern/src/components/SetupWizard/__tests__/WelcomeStep.test.tsx
@@ -71,6 +71,14 @@ describe('WelcomeStep', () => {
'Connect a platform API, install Pulse Agent, or use both for full coverage.',
),
).toBeInTheDocument();
+ expect(screen.getByText('Usage telemetry is enabled by default')).toBeInTheDocument();
+ expect(
+ screen.getByText(/To disable it before any ping, set PULSE_TELEMETRY=false/),
+ ).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Full details' })).toHaveAttribute(
+ 'href',
+ '/docs/PRIVACY.md',
+ );
expect(screen.getByText('What this token does')).toBeInTheDocument();
expect(
screen.getByText(
diff --git a/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx b/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx
index 2087092ad..e25d89b36 100644
--- a/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx
+++ b/frontend-modern/src/components/SetupWizard/steps/WelcomeStep.tsx
@@ -4,7 +4,9 @@ import { showError, showSuccess } from '@/utils/toast';
import { apiFetch, apiFetchJSON } from '@/utils/apiClient';
import { logger } from '@/utils/logger';
import { copyToClipboard } from '@/utils/clipboard';
-import { Copy, Check, Terminal } from 'lucide-solid';
+import { ExternalTextLink } from '@/components/shared/ExternalTextLink';
+import { PRIVACY_DOC_URL } from '@/utils/docsLinks';
+import { Copy, Check, Terminal, ShieldCheck } from 'lucide-solid';
interface WelcomeStepProps {
onNext: () => void;
@@ -194,6 +196,23 @@ export const WelcomeStep: Component
+ {t('setup.welcome.telemetryNotice.title')} +
+
+ {t('setup.welcome.telemetryNotice.description')}{' '}
+