Add release service health telemetry

This commit is contained in:
Pulse Test 2026-08-29 11:24:53 +01:00
parent db7e26deac
commit b75a5aeec2
17 changed files with 1064 additions and 59 deletions

View file

@ -16,7 +16,7 @@ third-party analytics, support diagnostics, or ordinary Settings surfaces.
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.
The telemetry payload does not include hostnames, credentials, infrastructure identifiers, IP addresses, URLs, paths, locale, prompts, chat messages, command text, action output, token values, names, email addresses, or account identifiers. Lifecycle and outcome signals are deliberately limited to closed buckets, booleans, and aggregate counts. Pulse does not send browser events or an event-level clickstream. See the full field list below.
The telemetry payload does not include hostnames, credentials, infrastructure identifiers, IP addresses, URLs, paths, locale, prompts, chat messages, command text, action output, token values, names, email addresses, or account identifiers. Lifecycle and outcome signals are deliberately limited to closed buckets, booleans, and aggregate counts. Local service-health signals are likewise limited to closed buckets, booleans, and normalized release versions. Pulse does not send browser events or an event-level clickstream. The local service-health probe requests only Pulse's own bound listener and never transmits the listener address, request URL, response content, or raw failure text. See the full field list below.
While mock/demo fixture mode is enabled, Pulse suppresses outbound telemetry entirely: a mock-mode instance reports a synthetic fixture fleet rather than a real installation, so it never pings.
@ -37,7 +37,7 @@ Every field is listed below with the reason it exists. Nothing else is included
| Field | Example | Purpose |
|-------|---------|---------|
| Schema version | `3` | Identify the exact payload contract so old and new signals are not mixed silently |
| Schema version | `13` | Identify the exact payload contract so old and new signals are not mixed silently |
| Sent at | `2026-07-23T08:30:00Z` | Date the individual heartbeat without sending a history of client activity |
| Install ID | `a1b2c3d4-...` | Distinguish active installations within one rotation window without tying telemetry to an account or person |
| Version | `6.0.0-rc.1` | Track the canonical release identity currently deployed |
@ -120,6 +120,13 @@ Every field is listed below with the reason it exists. Nothing else is included
| Update successes 30d | `1` | Count successful update attempts in the current 30-day telemetry window |
| Update failures 30d | `1` | Count failed or rolled-back update attempts in the current 30-day telemetry window without sending raw errors, logs, URLs, or command output |
| Update last failure category | `download` | Send only a coarse category for the latest update failure, such as `download`, `signature`, `checksum`, `disk_space`, `extract`, `backup`, `apply`, `restart`, `rolled_back`, or `unknown` |
| Service health observed | `true`/`false` | Distinguish a release that performed the bounded local UI/API self-check from an older release with no signal |
| Service health healthy | `true`/`false` | Report whether Pulse's locally bound listener served a healthy API response, UI document, and every referenced local frontend asset without sending an address, URL, response, or error text |
| Service health failure category | `listener`, `startup`, `runtime`, `api_connectivity`, `api_status`, `ui_status`, `frontend_assets`, or `unknown` | Classify a failed local self-check or startup path into one fixed category without sending the listener address, request URL, HTTP body, asset name, IP address, or raw error |
| Service health cohort | `first_observation`, `same_version`, or `version_change` | Mark whether this direct observation is the first schema-v13 observation, another observation of the same release, or the first observed state after a release-version change |
| Service health previous version | `6.4.0` | Retain only the normalized immediately previous Pulse release identity so aggregate reporting can compare a post-upgrade before/after cohort without treating rolling counters as new-release activity |
| Service health previous observed | `true`/`false` | State whether the immediately previous release recorded the bounded local service-health signal |
| Service health previous healthy | `true`/`false` | State whether that immediately previous release's last bounded local observation was healthy, without retaining its address, URL, response, asset names, or failure text |
| Node test attempts 30d | `3` | Count node connection tests that reached the connection stage in the current 30-day telemetry window, without sending hostnames, addresses, credentials, or error text |
| Node test failures 30d | `2` | Count those connection tests that could not reach or authenticate against the target, so an install that tried to add a node and failed is distinguishable from one that never attempted it, without sending hostnames, addresses, credentials, or error text |
| Pulse Intelligence loop configured | `true`/`false` | See whether Assistant, Patrol, governed actions, or external-agent access is configured so adoption can be measured without sending configuration details |
@ -219,6 +226,15 @@ configuration, destination rejection, and unknown failures. Classification is
performed locally; raw error text and destination/provider identity are never
included in the payload.
Telemetry schema v13 adds a direct local UI/API service observation. Pulse
checks its own bound listener, `/api/health`, the UI document, and the local
frontend assets referenced by that document. It retains only the current and
immediately previous normalized release observation, so a version-change
cohort can compare before and after health without attributing rolling update
or usage counters to the new release. Listener, startup, runtime, API, UI, and
asset failures collapse into fixed categories. Addresses, URLs, IP addresses,
asset names, response bodies, and raw errors are neither sent nor stored.
#### Server-side handling and retention
- Telemetry pings are stored on the Pulse license server only for aggregate install/use analysis.
@ -226,6 +242,7 @@ included in the payload.
- 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.
- Aggregate reports preserve the closed deployment-method buckets, but treat them as best-effort current-runtime evidence. In particular, `container_other` and `binary_other` are unknown fallbacks for many upgraded installs, not precise original installation provenance.
- Aggregate reports describe the known-age bucket as time since Pulse first created the schema-v2 lifecycle record. It is only a lower bound for an upgraded install and must not be presented as original installation age.
- Aggregate release-health reports use the direct schema-v13 current and immediately previous observations. They do not infer new-release health from rolling update, feature, alert, or action counters.
- 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.
- The receiver stores only fields in its versioned telemetry allowlist. A cross-repository parity check prevents client fields from being silently dropped and prevents the storage contract from growing beyond the disclosed payload.
- Telemetry rows older than **90 days** are purged automatically.

View file

@ -9685,6 +9685,21 @@ the operator preview cannot silently omit fields that the sender will export.
The frontend keeps those additions optional only where the Go wire field is
optional; numeric counters remain required and default to zero in fixtures.
### Telemetry payload parity spans three surfaces at schema v13
Schema v13 preserves the same sender, receiver, and Settings-preview parity
rule while adding the direct local release service-health observation. The
preview must expose the observed and healthy booleans, fixed failure category,
fixed first/same/version-change cohort, normalized immediately previous
release, and its observed and healthy booleans exactly as the Go sender would
transmit them. Optional failure, cohort, and previous-version strings follow
the Go JSON omission contract; observation booleans remain required. Neither
the API response nor the TypeScript interface may add a listener address, URL,
IP address, asset path, response content, raw error, or account/customer
identity. The schema parity checker remains the executable cross-repository
proof that the browser preview and private allowlisted receiver cannot drift
from the public runtime payload.
### Per-tenant resource stores are released on offboarding and shutdown
`ResourceHandlers.getStore` opens a SQLite handle per org and caches it for the

View file

@ -7129,6 +7129,7 @@
"internal/crypto/crypto.go",
"internal/logging/logging.go",
"internal/securityutil/secure_storage_dir.go",
"internal/telemetry/service_health.go",
"internal/telemetry/telemetry.go",
"pkg/audit/async_logger.go",
"pkg/audit/audit.go",
@ -7138,6 +7139,7 @@
"pkg/auth/sqlite_manager.go",
"pkg/extensions/audit_admin.go",
"pkg/server/server.go",
"pkg/server/service_health.go",
"pkg/server/telemetry_pulse_intelligence.go",
"pkg/tlsutil/fingerprint.go",
"scripts/telemetry_adoption_report.py",
@ -7161,7 +7163,9 @@
"internal/cloudcp/auth/magiclink_test.go",
"internal/config/config_load_test.go",
"internal/config/watcher_test.go",
"internal/telemetry/service_health_test.go",
"internal/telemetry/telemetry_test.go",
"pkg/server/service_health_test.go",
"pkg/server/telemetry_pulse_intelligence_test.go",
"pkg/tlsutil/tlsutil_test.go",
"scripts/tests/test_telemetry_adoption_report.py"
@ -7176,7 +7180,10 @@
"docs/PRIVACY.md",
"frontend-modern/public/docs/PRIVACY.md",
"frontend-modern/src/components/Settings/useSystemSettingsState.ts",
"internal/telemetry/service_health.go",
"internal/telemetry/telemetry.go",
"pkg/server/server.go",
"pkg/server/service_health.go",
"pkg/server/telemetry_pulse_intelligence.go",
"scripts/telemetry_adoption_report.py",
"SECURITY.md"
@ -7186,7 +7193,9 @@
"exact_files": [
"frontend-modern/src/stores/__tests__/systemSettings.test.ts",
"internal/api/system_settings_telemetry_test.go",
"internal/telemetry/service_health_test.go",
"internal/telemetry/telemetry_test.go",
"pkg/server/service_health_test.go",
"pkg/server/telemetry_pulse_intelligence_test.go",
"scripts/tests/test_telemetry_adoption_report.py"
]

View file

@ -106,28 +106,30 @@ promise visible inside the product cannot drift from the repository policy.
26. `internal/config/config.go`
27. `internal/config/watcher.go`
28. `internal/telemetry/telemetry.go`
29. `pkg/server/telemetry_pulse_intelligence.go`
30. `internal/api/router_routes_auth_security.go`
31. `internal/crypto/crypto.go`
32. `internal/securityutil/secure_storage_dir.go`
33. `internal/cloudcp/auth/magiclink.go`
34. `internal/cloudcp/auth/magiclink_store.go`
35. `pkg/tlsutil/fingerprint.go`
36. `pkg/audit/audit.go`
37. `pkg/audit/async_logger.go`
38. `pkg/audit/sqlite_logger.go`
39. `pkg/audit/signer.go`
40. `pkg/audit/sqlite_factory.go`
41. `pkg/extensions/audit_admin.go`
42. `scripts/telemetry_adoption_report.py`
43. `frontend-modern/src/components/Settings/DataHandlingPanel.tsx`
44. `frontend-modern/src/components/Settings/dataHandlingPanelModel.ts`
45. `internal/api/agent_exec_token_binding.go`
46. `internal/logging/logging.go`
47. `pkg/auth/rbac.go`
48. `pkg/auth/rbac_manager.go`
49. `pkg/auth/sqlite_manager.go`
50. `pkg/server/server.go`
29. `internal/telemetry/service_health.go`
30. `pkg/server/service_health.go`
31. `pkg/server/telemetry_pulse_intelligence.go`
32. `internal/api/router_routes_auth_security.go`
33. `internal/crypto/crypto.go`
34. `internal/securityutil/secure_storage_dir.go`
35. `internal/cloudcp/auth/magiclink.go`
36. `internal/cloudcp/auth/magiclink_store.go`
37. `pkg/tlsutil/fingerprint.go`
38. `pkg/audit/audit.go`
39. `pkg/audit/async_logger.go`
40. `pkg/audit/sqlite_logger.go`
41. `pkg/audit/signer.go`
42. `pkg/audit/sqlite_factory.go`
43. `pkg/extensions/audit_admin.go`
44. `scripts/telemetry_adoption_report.py`
45. `frontend-modern/src/components/Settings/DataHandlingPanel.tsx`
46. `frontend-modern/src/components/Settings/dataHandlingPanelModel.ts`
47. `internal/api/agent_exec_token_binding.go`
48. `internal/logging/logging.go`
49. `pkg/auth/rbac.go`
50. `pkg/auth/rbac_manager.go`
51. `pkg/auth/sqlite_manager.go`
52. `pkg/server/server.go`
## Shared Boundaries
@ -436,6 +438,14 @@ the `white_label` branding entitlement.
credentials, recipient details, alert evidence, or other tenant-private
notification configuration into the resource or incident timeline.
6. Change operator-facing telemetry/adoption reporting through `scripts/telemetry_adoption_report.py` together with the privacy disclosure whenever release-identity interpretation changes.
Release service-health telemetry must come from a bounded loopback probe of
the listener Pulse actually bound. It may report only whether the API, UI,
and referenced frontend assets were served, a fixed failure category, and
the immediately previous normalized release observation. It must not report
listener addresses, URLs, asset names, response bodies, errors, hostnames,
customer identity, or account identity. Adoption reporting must interpret
those direct current/previous observations as release cohorts rather than
attributing rolling historical counters to the current release.
The adoption report excludes mock-fixture-fleet-signature rows (120×N
Kubernetes pods with 7×N VMware hosts, the `internal/mock` template) from
adoption reads by default and must disclose the excluded row/install
@ -1205,6 +1215,18 @@ 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.
Schema v13 adds a direct local service-health observation so a process-level
telemetry heartbeat is not mistaken for proof that the installed UI and API
are being served. The runtime probes its bound listener through loopback and
reports only observed/healthy booleans, one fixed failure class (`listener`,
`startup`, `runtime`, `api_connectivity`, `api_status`, `ui_status`,
`frontend_assets`, or `unknown`), a fixed observation cohort, and the
immediately previous normalized release's observed/healthy booleans. No probe
target, listener address, URL, IP address, asset path, response content, raw
error, account, customer, or infrastructure identity may enter the payload or
persisted receiver row. The previous-release fields are direct adjacent-release
observations, not 30-day update counters, and are the only valid basis for a
before/after release-health cohort in the adoption report.
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: