Commit Graph

4828 Commits

Author SHA1 Message Date
pulse-triage[bot] 3334cccfd9 fix(unraid): suppress parity warning for explicit pool-only arrays
Issue #1895 reports parity alerts when mdNumDisks=0 on a pool-only Unraid system. Array service state alone does not establish that a parity array exists.

Preserve the optional disk count from collection through canonical runtime conversion and suppress only the no-parity warning for an explicit zero. Missing or malformed counts retain legacy behaviour, and disk failure reasons remain active.

Validated focused Unraid tests in hostagent, storagehealth, monitoring, unifiedresources and alerts, including JSON zero preservation and canonical round trip. The new pool-only regression fails against the previous warning condition. Both agent and server need this change; no release or reporter retest is claimed.

Change-source: pulse-maintainer
2026-09-04 23:41:22 +01:00
pulse-triage[bot] e7077fb06a Merge delivery trust installer ownership fix
Change-source: pulse-maintainer
2026-09-04 22:35:35 +01:00
pulse-triage[bot] 19c2b6a925 Fix portable root ownership for installer lifecycle state
Issue #1890 reports macOS agent updates stopping because the root group does not exist. Use numeric superuser ownership in the two shared lifecycle writes without relaxing failure handling or the least-privilege group boundary. Add a regression fixture that rejects named root ownership and checks that chown failures still prevent replacement.

Change-source: pulse-maintainer
2026-09-04 22:33:08 +01:00
pulse-triage[bot] 0f972f42f2 Isolate host and Docker CPU sampling baselines (#1894)
Near-synchronous host and Docker reports consumed a shared CPU baseline, measuring collection bursts rather than each module's reporting interval. Retain a collector per host collector and a separate Docker module collector while preserving the package-level convenience API and disk filters.

Add an interleaved-counter regression covering both collection entry points. It fails when routed through the shared baseline and passes with isolated state. All hostmetrics, hostagent and dockeragent tests pass, as do the focused CPU regression tests under the race detector.

Change-source: pulse-maintainer
2026-09-04 22:29:32 +01:00
pulse-triage[bot] 8fe9f3a808 Keep mounted infrastructure state current
Credential slots froze the configured-node snapshot when the editor mounted, so a later server refresh could leave an untouched form stale even after the poll-clobber remount was removed. Keep node and security inputs reactive while the existing dirty guard protects operator edits, and cover both component refresh and browser polling paths.

Contract-Neutral: frontend polling-state bugfix; no API or persisted-data change

Change-source: pulse-maintainer
2026-09-04 15:45:38 +01:00
pulse-triage[bot] e46e897702 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	.github/workflows/build-and-test.yml
#	docs/release-control/v6/internal/subsystems/deployment-installability.md
#	scripts/installtests/build_release_assets_test.go
#	scripts/npm-audit-retry.sh

Change-source: pulse-maintainer
2026-09-04 14:29:31 +01:00
rcourtman b3bbf23f08 Merge pull request #1879 from rcourtman/claude/notification-retry-class-gate
Stop retrying notification failures that cannot succeed
2026-09-04 14:14:01 +01:00
pulse-triage[bot] 6a083ca693 Merge branch 'main' into claude/notification-retry-class-gate 2026-09-04 13:37:18 +01:00
pulse-triage[bot] 79e0f9a673 Merge current Pulse upstream for publication
Change-source: pulse-maintainer
2026-09-04 13:19:05 +01:00
rcourtman 06477a59f5 Merge remote-tracking branch 'origin/main' into claude/npm-audit-budget
# Conflicts:
#	docs/release-control/v6/internal/status.json
2026-09-04 13:10:27 +01:00
rcourtman 5434868bd0 Bound the npm audit retry budget by wall clock
The retry hardening added in #1885 bounded attempts but not time. npm's own
fetch-timeout defaults to five minutes and it retries internally, so three
"attempts" against a hanging advisory endpoint ran for 10m56s on job
100986651307, and a second audit step added 3m36s. The Frontend job was
cancelled 31s into type-check with all 1183 test files already passing, and a
cancelled job reports as a failed required check, so a green run blocked every
pull request. #1888 raised the job timeout to 40 minutes to unblock delivery;
this decides the policy instead.

Each attempt now runs under a hard wall-clock bound and the sequence stops at
a total deadline (60s and 240s by default). npm's internal retry loop is
disabled in favour of this one, since it was the hidden multiplier. The bound
is enforced by a watchdog subshell rather than timeout(1), which is not
present on every developer machine.

What happens when the endpoint stays unreachable is unchanged, because that
split was already right: the run fails when the change touches the dependency
graph and the answer is genuinely unknown, and warns without failing when it
does not, because the graph is then identical to a base commit that already
produced a passing answer. Any advisory at any severity still fails.

Also drops the production-only audit from the per-pull-request path. It audits
a subset of the same packages, so it reports a subset of the same advisories,
and because the complete audit fails the job on any finding, the production
step could only ever execute in the cases where it was already guaranteed
clean. The dev-versus-production split still runs for every npm workspace in
the scheduled security-scan job, where it informs rather than blocks delivery,
and Dependabot security updates remain the route for advisories published
against unchanged dependencies.

With the audit bounded to 4 minutes against an ~11 minute baseline, the job
timeout returns to 30: a stalled endpoint should surface as a warning, not be
absorbed by a budget large enough to hide it.
2026-09-04 12:13:31 +01:00
rcourtman 6a2efd8170 Sort status.json coverage gaps by recorded_at then id
The status audit requires coverage_gaps to be sorted by recorded_at then id,
and main currently fails it: patrol-investigation-rate-metric-invalid (#1883)
was appended after telemetry-test-binary-production-pings (#1878), which share
a recorded_at of 2026-09-03 and are the wrong way round on id.

The pre-commit hook runs that audit, so every commit from a local checkout is
blocked until this is corrected, whatever it touches.

This swaps the two adjacent entries and nothing else; no gap content changes.
2026-09-04 12:12:04 +01:00
pulse-triage[bot] 6d8546b756 Keep Actions workflows on Node 24
Replace the remaining Node 20 action pins before GitHub removes that runtime, and make the reviewed Node 24 pins a workflow trust invariant.

Change-source: pulse-maintainer
2026-09-04 11:08:34 +01:00
pulse-triage[bot] b7e6a1e4e7 Merge privileged runner trust candidate for coordination
Change-source: pulse-maintainer
2026-09-04 09:39:27 +01:00
pulse-triage[bot] 0b72eca737 Keep privileged jobs on ephemeral hosted runners
Retire the unused self-hosted live qualification workflow and reject future secret- or write-capable jobs on persistent or dynamically selected runners. Keep live Patrol qualification as a disposable lab operation.

Change-source: pulse-maintainer
2026-09-04 09:30:11 +01:00
Richard Courtman 197d289415 Correct the development-flag note on the telemetry pollution gap
The gap recorded that the receiver could not filter these installs on
version_is_development, which was true when it was written and is no
longer. The emitter now classifies every 0.0.0 sentinel build as
development, so pings sent after 2026-09-04 carry the flag.

Stored rows keep the values they were sent with and are not backfilled,
so the note now states the boundary rather than the old blanket warning,
and still points reads whose window reaches earlier at
version_is_published_release. Merging main also interleaved this gap with
a peer's, so the list is re-sorted by recorded_at then id.
2026-09-04 09:22:57 +01:00
Richard Courtman 7feba70e45 Merge remote-tracking branch 'origin/main' into claude/telemetry-dev-channel-classifier 2026-09-04 09:22:22 +01:00
pulse-triage[bot] af0fb57ae8 Merge commit 'a91b6da37d6308f3c6f8fddda83978b8fa183e0b'
Change-source: pulse-maintainer
2026-09-04 09:20:37 +01:00
pulse-triage[bot] a91b6da37d Deduplicate rebuilt PBS storage samples
Canonical registry rebuilds were assigning fresh timestamps to unchanged PBS datastore values, so unrelated poll completions wrote duplicate history rows. Preserve the metric source observation time so repeated rebuilds collapse onto the one upstream sample.

Change-source: pulse-maintainer
2026-09-04 09:05:39 +01:00
pulse-triage[bot] 9bd43732c2 Merge frozen Pulse upstream for coordination batch
# Conflicts:
#	.github/workflows/build-and-test.yml
#	scripts/installtests/build_release_assets_test.go
#	scripts/npm-audit-retry.sh

Change-source: pulse-maintainer
2026-09-04 09:01:28 +01:00
rcourtman c281ea9f8c Merge pull request #1885 from rcourtman/claude/npm-audit-outage-hardening
Separate a real advisory from an unreachable npm audit endpoint
2026-09-04 08:53:06 +01:00
pulse-triage[bot] 3507deebf0 Merge remote-tracking branch 'origin/main'
Change-source: pulse-maintainer
2026-09-04 08:43:19 +01:00
pulse-triage[bot] 38575bb5e6 Merge release workflow trust candidate for coordination
Retain the reviewed release-boundary cache and pull-request secret hardening alongside the current Go toolchain work.

Change-source: pulse-maintainer
2026-09-04 08:19:26 +01:00
rcourtman c01d152a35 Separate a real advisory from an unreachable npm audit endpoint
On 2026-09-03 registry.npmjs.org returned 503s and then timeouts from its
bulk advisory endpoint for over an hour. `npm audit` exits non-zero both
for a real advisory and for an endpoint it cannot reach, so the Frontend
job failed four times running and, because it is a required check, no
pull request could land at all - including Go-only ones that touch no
JavaScript. Every one of those failures was the outage. In two of the
runs the other audit call in the same job succeeded and reported zero
vulnerabilities.

The audits now run through scripts/npm-audit-retry.sh, which separates
the two cases and does nothing else. A conclusive result is acted on
immediately, and the gate stays exactly as strict as before: any
vulnerability at any severity still fails, and no severity threshold is
introduced. Only an unreachable endpoint is retried, with backoff.

When retries are exhausted the outcome depends on whether the answer is
actually unknown. A change that touches frontend-modern/package.json,
frontend-modern/package-lock.json, or the runner itself fails, because
the dependency graph moved and no result means no answer. A change that
touches none of them warns and continues, because the graph is then
identical to the base commit that already produced a passing answer.
Advisories published later against unchanged dependencies are what
Dependabot security updates are for, not a per-pull-request audit.

Deliberately not done: relaxing the severity threshold. That was my first
instinct, but the evidence does not support it. None of the four failures
was an advisory, the lockfile reports zero vulnerabilities at every
severity, and a threshold would have weakened the gate without fixing
anything. The contract's rule that audit suppression is not a valid
closure stands.

The pinning test now requires the runner's invocation and the dependency
detection wiring, and additionally asserts the runner carries no
--audit-level flag, so the strictness cannot be quietly traded away
later.
2026-09-04 08:06:35 +01:00
Richard Courtman b581de89d2 Classify every 0.0.0 sentinel build as a development build
normalizeVersionString assigns 0.0.0-<sanitized> to any build string it
cannot parse as a release version — a branch name, an ad-hoc test or
qualification label, an empty VERSION file — but usageDataVersionChannel
only called a build "dev" when it carried git build metadata or its
prerelease was exactly "dev" or "dev.*". Everything else fell through to
"prerelease" with version_is_development clear, so the flag did not mean
what its name says: on 2026-09-03 the receiver held 317 installs of
0.0.0-test-version and 18 of 0.0.0-dev-pro, all flagged as ordinary
prereleases, while only 0.0.0-dev was marked development. A receiver-side
read that filtered on that flag would have excluded none of them.

Pulse never publishes a 0.0.0 release, so the sentinel itself is the
signal and it now decides the channel ahead of the prerelease-text rules.
The existing test named "source branch falls back to prerelease dev
identity" asserted wantDevelopment false, encoding the defect; it is
corrected and joined by the test-version, dev-pro, qual, and empty-string
cases that reached the receiver.

A development build is also no longer reported as a published release at
the same time. The sentinel 0.0.0-rc.1 satisfies
IsPublishedReleaseAssetVersion on prerelease spelling alone, which would
have set both flags for a build that no install can be running. The guard
lives in DescribeUsageDataVersion rather than in
IsPublishedReleaseAssetVersion, which agent update logic in
internal/api/unified_agent.go also relies on and which this change should
not disturb.

Historical rows keep the values they were sent with; only builds
reporting after this lands carry the corrected identity.
2026-09-04 08:00:52 +01:00
rcourtman 5500365291 Record the Patrol investigation rate as an invalid metric
A three-week decline in investigations/new_findings (7.2% to 5.0%) looked
like a Patrol regression. It is not one. The ratio is not a rate at all:
the two counters come from different stores, cover different spans, and
are drawn from populations that barely overlap.

new_findings_30d sums run.NewFindings over history.Runs, which
SavePatrolRunHistory caps at MaxPatrolRunHistory, so it covers at most the
last hundred runs rather than thirty days. investigations_30d instead
scans the current findings store and counts surviving finding records
investigated in-window, including findings created before it, which is how
the paid cohort read 128.57% in the week to 2026-08-25. Finding
ShouldInvestigate returns false at monitor autonomy and effective autonomy
is licence-gated, so free installs produced 4384 findings and 1
investigation while 67 paid installs produced 242.

The decline was composition: flat in version-stable installs, and fleet
investigations rose once the single install that swung the total by 38 was
excluded. Finding-detection code is identical between v6.3.2 and v6.4.1.

The new test pins the asymmetry behind the bad denominator. Its twin
already asserts that runs_30d ignores the history cap after 63c40ebe5e;
nothing asserted that the findings loop immediately below it does not, so
the truncation could regress or be mistaken for a thirty-day total
unnoticed. Fixing it needs a per-day findings tally alongside DailyRuns,
which the coverage gap tracks as its own slice.
2026-09-04 07:57:24 +01:00
pulse-triage[bot] 457aa90458 Keep release workflows free of implicit trust inputs
Remove the pull-request secret exception, drop inert E2E secret references, and disable setup-node caches at release trust boundaries. Document the exact metadata-only privileged trigger exception.

Change-source: pulse-maintainer
2026-09-04 07:30:35 +01:00
pulse-triage[bot] 35d4cb0e97 Advance release builds to Go 1.26.8
Go 1.26.8 supersedes the prior patch release, so every release builder and local toolchain guard must move together to prevent candidate artifacts from retaining an older compiler and runtime.

Contract-Neutral: toolchain-only patch update; no product or runtime contract changed
Change-source: pulse-maintainer
2026-09-04 07:20:28 +01:00
pulse-triage[bot] 627e29bb39 Keep advisory findings ahead of audit retries
Change-source: pulse-maintainer
2026-09-04 04:27:31 +01:00
pulse-triage[bot] 44ca02d3d4 Keep npm audit outages from erasing CI evidence
Retry only explicit registry failures with bounded one-minute attempts while preserving immediate advisory failures. Defer the aggregate audit verdict so frontend tests and builds still report during npm service incidents.
2026-09-04 03:53:58 +01:00
pulse-triage[bot] 74c553aae3 Merge remote-tracking branch 'origin/main' 2026-09-04 02:34:06 +01:00
pulse-triage[bot] 2c123a902f Merge core runtime candidate for coordination
Change-source: pulse-maintainer
2026-09-04 02:12:40 +01:00
pulse-triage[bot] 724a8a4960 Keep same-name Proxmox sites separate in realtime
The browser compatibility coalescer could undo the server's provider-scoped split after websocket reconciliation. Honour machine and provider identity before merging same-hostname rows.
2026-09-04 01:32:20 +01:00
Richard Courtman ddd99ee57a Merge remote-tracking branch 'origin/main' into claude/telemetry-test-version-pings
# Conflicts:
#	docs/release-control/v6/internal/status.json
2026-09-04 01:28:12 +01:00
pulse-triage[bot] 5e30bbb346 Recover Helm Pages from attested OCI charts
Keep convergence recoverable after short-lived Actions artifacts expire by preserving the exact digest-bound package already verified from GHCR.

Change-source: pulse-maintainer
2026-09-04 01:04:30 +01:00
pulse-triage[bot] 52e0b0d72e Merge frozen Pulse upstream for coordination batch
Change-source: pulse-maintainer
2026-09-04 00:38:03 +01:00
pulse-triage[bot] a7406876c0 Merge commit 'da9800ff2f6d1064556c25394e976c9cfa40dc64'
Change-source: pulse-maintainer
2026-09-04 00:30:59 +01:00
pulse-triage[bot] a8e0639db6 Merge commit '6f3c43612178c4f647b3a6443ee120a2ee567f4f'
Change-source: pulse-maintainer
2026-09-04 00:30:59 +01:00
pulse-triage[bot] da9800ff2f Keep infrastructure dialog dismissal in place
The stable-row focus fallback ran after the shared dialog cleanup and could scroll a lower Manage trigger into view. Preserve the existing focus return without changing the operator's viewport, and cover the real desktop and narrow browser path.

Contract-Neutral: frontend focus-restoration bugfix; no API or persisted-data change.

Change-source: pulse-maintainer
2026-09-04 00:26:02 +01:00
pulse-triage[bot] 6f3c436121 Stop retrying unchanged credential blocks
A committed release with an unchanged operator-owned containment failure cannot converge through unattended retries. Classify that evidence without weakening the block, and rearm only when the relevant private inputs or public controls change.

Change-source: pulse-maintainer
2026-09-04 00:14:40 +01:00
pulse-triage[bot] 0329437fc6 Merge commit '384dc53608eebca505b77119fdfc1af891edd6be'
Change-source: pulse-maintainer
2026-09-04 00:04:17 +01:00
Richard Courtman 139ee65b25 Stop test binaries reporting to the production telemetry endpoint
pkg/server tests boot the real server through Run() with the version
literal "test-version", which internal/updates normalizes to
0.0.0-test-version. Each test runs against its own t.TempDir(), so every
run minted a fresh install ID. The startup ping waits two minutes and so
never fired inside a short test, but the service-health failure reporter
added on 2026-08-29 sends synchronously from a deferred handler as soon
as Run() returns an error, so every CI shard containing pkg/server posted
one ping.

The licence server recorded 317 single-ping installs between 2026-08-29
and 2026-09-03 - 311 from linux/amd64 CI runners, 3 from a maintainer
workstation - still arriving at roughly 60 a day. The canonical clean
denominator excludes single-ping installs and was unaffected, but raw
install counts and the operator-evidence blocked-cause read counted them
as real installations.

A test binary is not an installation, which is the same reason mock mode
already suppresses pings, so the guard belongs beside it in the telemetry
package rather than at the four call sites: send() now refuses the
production endpoint whenever testing.Testing() reports true. The check
compares against productionPingEndpoint, so telemetry's own tests keep
asserting on real ping content through a redirected endpoint, and the
server tests additionally opt out at the config layer to say so locally.
2026-09-03 23:54:37 +01:00
Richard Courtman 12e833d3d0 Stop retrying notification failures that cannot succeed
72 installs delivered no notification at all in the week to 2026-09-03 and
between them burned 196,562 attempts for 62,618 dead letters. Their
categorised failures are 79% authentication (11,693), configuration
(9,554) and rejected (5,954) against 930 connectivity: deterministic
verdicts about the request, not conditions that clear. The queue retried
each of them the full ladder anyway, because the retry decision never
consulted the failure class.

Those three classes now dead-letter on the attempt that produced them.
The same payload, sent again to the same destination with the same
credentials, gets the same answer; spending two more attempts on it only
delays the dead letter the operator needs to act on. Connectivity, rate
limiting, server errors and unclassified failures keep the full ladder.

TLS deliberately stays on the retrying side. A handshake can fail
transiently during a rotation, and one wasted ladder is cheaper than
dropping a recoverable notification.

This is the same call webhook delivery already made for HTTP 4xx in
isRetryableWebhookError, now generalised to every destination type and
owned in one place. It was not safely expressible before the class became
sender-declared rather than guessed from error prose.

Nothing is lost by giving up sooner: RetryTerminalFailures still returns
retained terminal failures to the queue with a fresh budget once the
operator fixes the credentials or the configuration, and dead-letter rows
now record the failure class and whether the cause was an exhausted
ladder or a non-retryable class.

Note for telemetry reads: notification_attempts_7d will fall sharply in
the blackout cohort while notification_failures_7d is unchanged, because
the same terminal failures now cost one attempt instead of three. That is
the intended effect and not a drop in notification volume.
2026-09-03 23:53:42 +01:00
Richard Courtman 47b8664f0b Classify notification failures from senders, not error prose
Terminal notification failures are 36% of resolved delivery outcomes
fleet-wide (126,337 dead-lettered against 224,692 delivered in the week
to 2026-09-03, over 6,668 clean installs), and the category breakdown
could not say why: unknown was the modal bucket at 31,218.

The class was being derived by substring-matching the Go error message.
That fails in two ways. Any failure whose text carries none of the ~50
recognised tokens falls through to unknown, which is most of what SMTP
produces: net/smtp reports the server's verdict as a reply code, and only
535 was ever matched, so a 550 relay refusal and a 451 temporary failure
both recorded as unknown. Worse, the text being matched includes the
destination's own response body, so a third party can choose the reason
code Pulse records and shows the operator - a 500 whose body contains
"rate limit" was recorded as rate_limited rather than server_error.

Senders now declare the class where they already know it, and the
classifier reads Go's own error types before it reads any prose:
*textproto.Error for SMTP reply codes, x509 and tls for certificate
failures, net.DNSError and timeouts for connectivity. HTTP status codes
set the class at the five sites that build a status error, so the
response body is preserved for the operator's audit row but can no longer
influence the classification. Prose matching remains only as the last
resort for paths that declare nothing.

SMTP 5xx is deliberately not mapped the way HTTP 5xx is: a 550 is the
destination refusing the message, not the destination breaking, so only
the transient 4xx replies count as server_error.

Registers the wider finding as a coverage gap. The 36% is concentration,
not breadth - 72 installs that delivered nothing at all in seven days
account for half of all terminal failures, and Pulse neither backs off
nor tells those operators the destination has never once succeeded.
2026-09-03 23:42:50 +01:00
pulse-triage[bot] 384dc53608 Keep repeated Proxmox cluster labels provider-scoped
A cluster name is an operator-selected display label, not global machine identity. Two independently configured estates using the same label could share one linked agent and then collapse into one presentation row after delayed cluster detection. Require node identity, exact endpoint, or host corroboration across provider instances instead.

Refs #1753

Change-source: pulse-maintainer
2026-09-03 23:41:31 +01:00
pulse-triage[bot] fa006785c5 Merge commit '59e9f5ea2bfafa13c54bc374ed16cb997748c806' Change-source: pulse-maintainer
Change-source: pulse-maintainer
2026-09-03 22:40:19 +01:00
pulse-triage[bot] 59e9f5ea2b Keep inline drawers from moving the viewport
Shared subtabs used scrollIntoView for horizontal visibility, allowing remounted or programmatically changed drawers to scroll the whole application shell. Reuse the horizontal rail controller instead, and restore disclosure focus without scrolling when a detail row closes.

Record the shared primitive contract and its registry-approved guardrails, with desktop and narrow Backups browser coverage for visible Overview and Manage states, off-screen tab changes, and non-scrolling focus return.

Change-source: pulse-maintainer
2026-09-03 22:34:11 +01:00
pulse-triage[bot] 9d1deb2a0a Allow TrueNAS CORE 12 legacy REST negotiation
CORE 12 reports versions such as TrueNAS-12.0-U5, which the legacy transport gate did not recognise. Admit that bounded version family and retain redirect negotiation coverage for both supported CORE generations.

Change-source: pulse-maintainer
2026-09-03 21:57:07 +01:00
Richard Courtman a70c96c8a3 Make telemetry consent a setup choice and retire the payload banner
The setup wizard's telemetry card only ever offered a way out: it led
with "enabled by default", gave no reason the data exists, and told the
reader to set PULSE_TELEMETRY=false before starting a process that had
already sent its first ping two minutes after boot. The payload-update
banner paired "we now collect more" with a one-click Disable button, was
keyed to schema v2 from July and never re-triggered across fifteen later
bumps, and its text was rewritten in August so anyone who had dismissed
it never saw the new wording. Nothing on either surface said what the
data is for or what it is never used for. No GitHub issue or discussion
has ever complained about the default-on posture, so the defensive
framing was answering a question nobody asked while quietly nudging
people to opt out.

Setup now leads with what the daily summary is for (development effort
follows real use; the features and platforms the operator relies on get
priority), names concrete exclusions (hostnames, credentials, IP
addresses), states what it is never used for (not sold or shared, not
used for advertising, not linked to a Pulse account or license), and
puts a real Usage statistics toggle on the admin-account step. The
toggle defaults to on and, when switched off, is applied through the
canonical system-settings endpoint once the admin token exists, so there
is no setup-only side channel and the account is created either way.
Neither setup screen tells the reader how to turn it off; the switch is
the control. The env-var instruction moves to PRIVACY.md where a reader
can still act on it, alongside a note that the first ping fires about
two minutes after start.

The payload-update banner is retired along with its telemetryAction deep
link that changed the preference on arrival. Payload changes are now
disclosed in a dated changelog in PRIVACY.md (back-filled from schema v2
to v17 from the telemetry package's own version notes) and in release
notes; an in-app notice is reserved for a change in kind. PRIVACY.md
gains a "What it is not used for" section whose statements are facts
about the license-server path, which never joins telemetry rows to
license or customer records; the contract treats any change to that path
as a change in kind. Settings leads with what the data is for and makes
Preview payload the primary action, because the exact runtime payload is
the disclosure an operator can verify. The security-privacy,
deployment-installability, and frontend-primitives contracts record the
new rules. Telemetry and i18n proof tests pin the setup choice, the
purpose-first and never-sold wording in every locale, and the changelog
row for the current schema so a future bump cannot land undisclosed.

Demand ledger: repos/pulse-pro FEATURE_REQUESTS.md "Telemetry consent as
a real setup choice" (named bet, pulse-pro PR #40). Supersedes the
three-commit branch behind Pulse PR #1873, rebuilt on current main.
2026-09-02 19:04:54 +01:00
pulse-triage[bot] 0a08c01132 Merge reviewed release convergence reconciliation
Retain the exact delivery-trust candidate commit and integrate its current-control retry and scheduled reconciliation behavior on canonical main.

Change-source: pulse-maintainer
2026-09-02 13:28:10 +01:00