From 781e168e348a288d6fd288dcab5302382d95cdc9 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Thu, 23 Jul 2026 13:28:17 +0100 Subject: [PATCH] Prepare v6.1.1 emergency patch release --- .github/workflows/create-release.yml | 4 +- .github/workflows/release-dry-run.yml | 6 +- VERSION | 2 +- deploy/helm/pulse/Chart.yaml | 8 +- deploy/helm/pulse/README.md | 2 +- docker-compose.yml | 2 +- docs/RELEASE_NOTES.md | 7 +- docs/UPGRADE_v6.md | 6 +- .../HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md | 10 +- .../v6/internal/RELEASE_PROMOTION_POLICY.md | 18 +++- docs/release-control/v6/internal/status.json | 16 ++- .../subsystems/deployment-installability.md | 72 ++++++++++---- docs/releases/RELEASE_NOTES_NEXT_DRAFT.md | 51 ---------- docs/releases/RELEASE_NOTES_v6.1.1.md | 97 +++++++++++++++++++ docs/releases/V6_CHANGELOG_v6.1.1.md | 45 +++++++++ scripts/install-docker.sh | 2 +- .../installtests/build_release_assets_test.go | 30 +++--- .../installtests/install_docker_sh_test.go | 7 +- .../release_promotion_policy_test.py | 8 +- .../resolve_release_promotion.py | 8 +- .../resolve_release_promotion_test.py | 35 ++++++- scripts/trigger-stable-patch.sh | 25 +++++ 22 files changed, 335 insertions(+), 126 deletions(-) delete mode 100644 docs/releases/RELEASE_NOTES_NEXT_DRAFT.md create mode 100644 docs/releases/RELEASE_NOTES_v6.1.1.md create mode 100644 docs/releases/V6_CHANGELOG_v6.1.1.md diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 68284048f..d5b33f9ff 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -38,12 +38,12 @@ on: required: false type: string unsigned_windows_exception: - description: 'v6.1.0 only: publish Windows agents without Authenticode under the recorded owner exception' + description: 'v6.1.0 or v6.1.1 only: publish Windows agents without Authenticode under a recorded owner exception' required: false type: boolean default: false unsigned_windows_reason: - description: 'v6.1.0 only: owner reason for the unsigned Windows exception' + description: 'v6.1.0 or v6.1.1 only: owner reason for the unsigned Windows exception' required: false type: string historical_asset_backfill_only: diff --git a/.github/workflows/release-dry-run.yml b/.github/workflows/release-dry-run.yml index 6d7cd9f74..e59fe06b1 100644 --- a/.github/workflows/release-dry-run.yml +++ b/.github/workflows/release-dry-run.yml @@ -43,12 +43,12 @@ on: required: false type: string unsigned_windows_exception: - description: 'v6.1.0 only: rehearse Windows agents without Authenticode under the recorded owner exception' + description: 'v6.1.0 or v6.1.1 only: rehearse Windows agents without Authenticode under a recorded owner exception' required: false type: boolean default: false unsigned_windows_reason: - description: 'v6.1.0 only: owner reason for the unsigned Windows exception' + description: 'v6.1.0 or v6.1.1 only: owner reason for the unsigned Windows exception' required: false type: string note: @@ -79,7 +79,7 @@ jobs: with: version: ${{ inputs.version }} require_macos_signing: true - require_windows_signing: ${{ !contains(inputs.version, '-') && !(inputs.version == '6.1.0' && inputs.unsigned_windows_exception) }} + require_windows_signing: ${{ !contains(inputs.version, '-') && !((inputs.version == '6.1.0' || inputs.version == '6.1.1') && inputs.unsigned_windows_exception) }} windows_signing_backend: signpath dry-run: diff --git a/VERSION b/VERSION index dfda3e0b4..f3b5af39e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.0 +6.1.1 diff --git a/deploy/helm/pulse/Chart.yaml b/deploy/helm/pulse/Chart.yaml index f8f11413d..458b5502b 100644 --- a/deploy/helm/pulse/Chart.yaml +++ b/deploy/helm/pulse/Chart.yaml @@ -2,9 +2,9 @@ apiVersion: v2 name: pulse description: Helm chart for deploying the Pulse hub and optional Docker monitoring agent. type: application -version: 6.1.0 -appVersion: "6.1.0" -icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.1.0/docs/images/pulse-logo.svg +version: 6.1.1 +appVersion: "6.1.1" +icon: https://raw.githubusercontent.com/rcourtman/Pulse/v6.1.1/docs/images/pulse-logo.svg keywords: - monitoring - proxmox @@ -32,7 +32,7 @@ annotations: description: Smoke tests with kind cluster deployment artifacthub.io/links: | - name: Documentation - url: https://github.com/rcourtman/Pulse/blob/v6.1.0/docs/KUBERNETES.md + url: https://github.com/rcourtman/Pulse/blob/v6.1.1/docs/KUBERNETES.md - name: Support url: https://github.com/rcourtman/Pulse/discussions artifacthub.io/maintainers: | diff --git a/deploy/helm/pulse/README.md b/deploy/helm/pulse/README.md index 82218e3e8..f7d9623ac 100644 --- a/deploy/helm/pulse/README.md +++ b/deploy/helm/pulse/README.md @@ -1,6 +1,6 @@ # pulse -![Version: 6.1.0](https://img.shields.io/badge/Version-6.1.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 6.1.0](https://img.shields.io/badge/AppVersion-6.1.0-informational?style=flat-square) +![Version: 6.1.1](https://img.shields.io/badge/Version-6.1.1-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 6.1.1](https://img.shields.io/badge/AppVersion-6.1.1-informational?style=flat-square) Helm chart for deploying the Pulse hub and optional Docker monitoring agent. diff --git a/docker-compose.yml b/docker-compose.yml index ebd4eb5cd..33d113b55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.8' services: pulse: - image: ${PULSE_IMAGE:-rcourtman/pulse:6.1.0} + image: ${PULSE_IMAGE:-rcourtman/pulse:6.1.1} container_name: pulse restart: unless-stopped logging: diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 943c7452a..a2fd49e5b 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -3,13 +3,14 @@ Pulse release notes live on GitHub: -For the next release disclosure draft (version assigned at release cut), see: -- `docs/releases/RELEASE_NOTES_NEXT_DRAFT.md` - For historical v4 notes that previously lived in this repo, see: `docs/releases/RELEASE_NOTES_v4.md` For the current stable v6 packet, see: +- `docs/releases/RELEASE_NOTES_v6.1.1.md` +- `docs/releases/V6_CHANGELOG_v6.1.1.md` + +For earlier stable v6.1 packets, see: - `docs/releases/RELEASE_NOTES_v6.1.0.md` - `docs/releases/V6_CHANGELOG_v6.1.0.md` diff --git a/docs/UPGRADE_v6.md b/docs/UPGRADE_v6.md index 192f2a938..0766c2bc4 100644 --- a/docs/UPGRADE_v6.md +++ b/docs/UPGRADE_v6.md @@ -4,11 +4,13 @@ This guide covers practical upgrade steps for existing Pulse installs moving to For the current stable v6 packet, see: -- `docs/releases/RELEASE_NOTES_v6.1.0.md` -- `docs/releases/V6_CHANGELOG_v6.1.0.md` +- `docs/releases/RELEASE_NOTES_v6.1.1.md` +- `docs/releases/V6_CHANGELOG_v6.1.1.md` For earlier stable v6 packets and rollout references, see: +- `docs/releases/RELEASE_NOTES_v6.1.0.md` +- `docs/releases/V6_CHANGELOG_v6.1.0.md` - `docs/releases/RELEASE_NOTES_v6.0.5.md` - `docs/releases/V6_CHANGELOG_v6.0.5.md` - `docs/releases/RELEASE_NOTES_v6.0.4.md` diff --git a/docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md b/docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md index 2a4c9a5fe..fdcd19e48 100644 --- a/docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md +++ b/docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md @@ -1022,9 +1022,10 @@ Companion drill: the public demo remains healthy on stable `6.0.5`. - Pass when: The external rehearsal proves the canonical exact-SHA candidate builder and - its version-specific native-signing policy. If the `v6.1.0` Windows exception - is used, the rehearsal must preserve checksum, detached-signature, manifest, - and digest controls and record the owner reason and public disclosure. Local + its version-specific native-signing policy. If a recorded `v6.1.0` or + `v6.1.1` Windows exception is used, the rehearsal must preserve checksum, + detached-signature, manifest, and digest controls and record the + version-bound owner reason and public disclosure. Local tests pin candidate-only publication and GitHub digest validation, and the static release DAG contains no duplicate release build, backend-to-integration serialization, full-download standard validator, or @@ -1037,7 +1038,8 @@ Companion drill: release packet, independent post-release jobs are serialized, or the definitive verdict can pass without all applicable downstream results. Also block if an unsigned stable Windows exception can apply to any version other - than `v6.1.0` or can omit its owner reason and release-note disclosure. + than the recorded `v6.1.0` and `v6.1.1` decisions or can omit its owner + reason and release-note disclosure. ## Gate: `operational-trust-canonical-lifecycle` diff --git a/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md b/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md index db8fa2acb..68828fcf8 100644 --- a/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md +++ b/docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md @@ -179,8 +179,15 @@ Cloud, and self-hosted production users. Windows Authenticode exception. The Windows artifacts must remain bound by the exact-SHA candidate manifest, checksums, detached `.sig`/`.sshsig` signatures, and published digests, and the public notes must disclose the - Unknown Publisher state. Later stable releases restore the Authenticode - requirement; this flag cannot be reused for another version. + Unknown Publisher state. + - On 2026-07-23, the release owner separately approved a `v6.1.1`-only + Windows Authenticode exception for the emergency patch addressing active + customer update harm. This is a new, version-bound decision rather than a + reuse of the `v6.1.0` exception. The same exact-SHA candidate, checksum, + detached-signature, manifest, published-digest, owner-reason, and public + Unknown Publisher disclosure controls remain mandatory. Stable `v6.1.2` + and later restore the Authenticode requirement unless another explicit + version-bound owner decision is recorded in policy. - Unproved self-service commercial transitions remain unavailable and unadvertised under the exposure-safety gate. This exception does not authorize enabling that feature or running a production billing proof. @@ -237,8 +244,11 @@ Cloud, and self-hosted production users. onto the RC promotion path. The resolver enforces that boundary. Do not use the routine helper to relabel a risky patch as routine. 4. `--emergency-hotfix-reason` is the narrow escape hatch for active customer - harm. It does not remove the exact-SHA dry-run requirement, and the reason is - recorded in the release metadata. + harm. It does not remove the integrated exact-SHA candidate and release + checks, and the reason is recorded in the release metadata. + A version-bound unsigned Windows decision must additionally be supplied + through `--unsigned-windows-exception-reason`; it never follows implicitly + from emergency patch status. 5. The release workflow must await Docker publication, stable demo deployment, public health/browser verification, install smoke, Helm publication, floating-tag promotion, and private Pro promotion where applicable. The diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index b6fb7b97b..c12ab7951 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -9176,7 +9176,21 @@ ] } ], - "work_claims": [], + "work_claims": [ + { + "id": "codex-release-gate-stable-patch-unattended-release-path", + "agent_id": "codex", + "summary": "Prepare and publish v6.1.1 emergency stable patch with owner-approved unsigned Windows exception", + "target_id": "v6-product-lane-expansion", + "claimed_at": "2026-07-23T12:19:02Z", + "heartbeat_at": "2026-07-23T12:19:02Z", + "expires_at": "2026-07-23T16:19:02Z", + "work_item": { + "kind": "release-gate", + "id": "stable-patch-unattended-release-path" + } + } + ], "open_decisions": [], "source_of_truth_file": "docs/release-control/v6/internal/SOURCE_OF_TRUTH.md", "resolved_decisions": [ diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index cec03503c..55a4bd7a9 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -379,12 +379,14 @@ TLS floor in the dynamic config. apply the same channel-specific native-signing policy as a publish run. macOS notarization remains mandatory for both prerelease and stable candidates. Windows Authenticode remains mandatory for stable candidates - except for the explicitly version-bound `v6.1.0` owner exception; - prerelease candidates and that one stable exception may retain checksum and - detached-signature verification without Authenticode while the release - packet explicitly discloses the unknown-publisher warning. Prerelease promotion remains - blocked on the normal stable signing requirement, and every stable version - after `v6.1.0` restores it automatically. A cheap signing-configuration job + except for the explicitly version-bound `v6.1.0` and `v6.1.1` owner + exceptions; prerelease candidates and those two stable exceptions may + retain checksum and detached-signature verification without Authenticode + while the release packet explicitly discloses the unknown-publisher warning. + Prerelease promotion remains blocked on the normal stable signing + requirement, and stable `v6.1.2` and later restore it automatically unless + policy records a new version-bound owner decision. A cheap + signing-configuration job must report every missing secret for the platforms required by that candidate before either platform runner is allocated. Stable Windows signing must use SignPath's GitHub trusted-build-system action by default, submit an immutable GitHub artifact @@ -1055,9 +1057,12 @@ evidence beside the candidate manifest. Release Dry Run now has a terminal verdict covering the exact-SHA candidate and no-mutation demo lane. Stable rehearsal `29927692302` confirmed that the external SignPath project was not configured and stopped without creating a public release. The release owner -subsequently approved a `v6.1.0`-only unsigned-Windows exception; the gate stays -blocked until a new exact-`main` rehearsal proves that exception and the -remaining candidate controls. +subsequently approved and exercised a `v6.1.0`-only unsigned-Windows exception. +On 2026-07-23 the owner separately approved a `v6.1.1`-only exception for the +emergency patch addressing active customer update harm. The new decision must +flow through the normal exact-SHA candidate, checksum, detached-signature, +manifest, published-digest, and definitive-verdict controls with an explicit +owner reason and public Unknown Publisher disclosure. Every caller of the reusable release-candidate builder must delegate `actions: read` alongside `contents: read`; the Windows signing job reads the exact uploaded artifact through the GitHub Actions API, and GitHub validates @@ -1068,11 +1073,35 @@ host-local redirect contract as runtime token minting and exchange. Proof input must reject absolute, scheme-relative, backslash-authority, encoded-separator, and control-character targets before constructing the handoff request. -The active stable `v6.1.0` cut sets the repo-root `VERSION`, -repo-root `docker-compose.yml` image default, `scripts/install-docker.sh` -fallback, and Helm chart release metadata to the same `6.1.0` release version. -This stable minor release uses `promoted_from_tag=v6.1.0-rc.4`, -`rollback_version=v6.0.5`, and the one-version release-owner cutoff exception +The active stable `v6.1.1` cut sets the repo-root `VERSION`, repo-root +`docker-compose.yml` image default, `scripts/install-docker.sh` fallback, and +Helm chart release metadata to the same `6.1.1` release version. This patch +release uses the stable hotfix path with `rollback_version=v6.1.0`, +`hotfix_exception=true`, a release-owner reason, and no fabricated +same-version RC tag. It fixes manual Unified Agent updates that received a +distribution identifier instead of the canonical Linux platform family and +durable Docker update actions that could remain stuck after a terminal +digest-drift preflight refusal. The exact stable `main` SHA must pass the +integrated release checks and immutable-candidate build before the single-build +workflow crosses its public mutation boundary. The same workflow must finish +Docker, Helm, stable demo, install-smoke, public-health, floating-tag, +paid-runtime, and definitive-verdict lanes before the cut is complete. +The `v6.1.1` release is `existing-mobile-build-compatible`: no production +relay or mobile trust contract changed from `v6.1.0`, the matched mobile-facing +path is test-only expiry branch coverage, and Pulse Mobile `1.0.0` iOS build +`11` and Android versionCode `9` remain the compatible candidate builds without +a companion upload or public store rollout. +The release owner separately approved a `v6.1.1`-only unsigned-Windows +exception because SignPath configuration remains unavailable. Windows assets +must remain exact-SHA and manifest-bound with checksums, detached +`.sig`/`.sshsig` signatures, and published-digest verification; the release +notes must disclose the Unknown Publisher state. Stable `v6.1.2` and later +restore Authenticode unless policy records another explicit version-bound +decision. + +The preceding stable `v6.1.0` cut used +`promoted_from_tag=v6.1.0-rc.4`, `rollback_version=v6.0.5`, and the +one-version release-owner cutoff exception recorded on 2026-07-22. The workflow input `hotfix_exception=true` carries that approved soak bypass through the existing promotion resolver; it does not reclassify the release as a patch hotfix. The exact stable `main` SHA must pass @@ -1112,8 +1141,10 @@ explicitly waived Authenticode after the first stable rehearsal exposed unavailable external SignPath configuration. The unsigned Windows binaries remain exact-SHA and manifest-bound with checksum, detached `.sig`/`.sshsig`, and published-digest verification, and the release notes disclose the Unknown -Publisher state. This exception cannot apply to a later stable version. -The stable server cut is classified `existing-mobile-build-compatible`. Pulse +Publisher state. That `v6.1.0` decision does not itself authorize another +version. +The `v6.1.0` stable server cut was classified +`existing-mobile-build-compatible`. Pulse Mobile `1.0.0` iOS build `11` and Android versionCode `9` remain the existing candidate builds; the canonical core/mobile contract proves that `v6.1.0` serves their route, scope, payload, pairing, and push requirements, including @@ -1180,8 +1211,8 @@ compose image default, standalone installer fallback constant, and packaged Helm metadata. A draft release workflow failure caused by stale image or chart pins is a release-packet blocker until the defaults, tests, and evidence record are refreshed from the new branch head. -For the active stable `v6.1.0` cut, the repo-root compose default and -`scripts/install-docker.sh` fallback must both pin `6.1.0` until the next +For the active stable `v6.1.1` cut, the repo-root compose default and +`scripts/install-docker.sh` fallback must both pin `6.1.1` until the next governed stable cut moves them forward. The stable promotion guard remains in force and rejects leftover `-rc.` defaults. The RC7 packet refresh records `fc10de9b5477613316473267b72b05b6b2b7aaff` @@ -2496,8 +2527,9 @@ discloses the unsigned Windows publisher state and the Windows binaries retain the exact-SHA candidate, checksum, detached-signature, and post-publication digest controls. Stable publication and the stable-path dry-run must continue to require both native signing lanes except for the recorded, version-bound -`v6.1.0` Windows exception; every subsequent stable release restores both -requirements. `scripts/build-release.sh` must replace +`v6.1.0` and `v6.1.1` Windows exceptions; stable `v6.1.2` and later restore +both requirements unless policy records a new explicit version-bound owner +decision. `scripts/build-release.sh` must replace only the native targets required by those independent inputs and must fail closed when a required native-binary directory or target is absent. Historical published-release repair must flow through diff --git a/docs/releases/RELEASE_NOTES_NEXT_DRAFT.md b/docs/releases/RELEASE_NOTES_NEXT_DRAFT.md deleted file mode 100644 index a26904379..000000000 --- a/docs/releases/RELEASE_NOTES_NEXT_DRAFT.md +++ /dev/null @@ -1,51 +0,0 @@ -# Next Pulse Release — Disclosure Draft - -This draft records customer-visible disclosures that must be carried into the -next Pulse release packet. The release version is assigned only when that -packet is cut. - -## Outbound usage telemetry schema v2 - -This release updates Pulse's legacy unversioned outbound usage telemetry -payload to schema v2. Telemetry remains enabled by default unless an operator -has disabled it; an existing enabled or disabled choice is preserved on -upgrade. The purpose remains aggregate product and release understanding, and -the payload remains pseudonymous rather than tied to a Pulse account or -person. - -Schema v2 adds these deliberately coarse signal categories: - -- closed deployment-method, known-install-age, activation-stage, - time-to-first-monitored-resource, and estate-size buckets; -- authentication-configured and monitoring-active booleans, plus an aggregate - configured-connection count; -- aggregate alert fired, acknowledged, and resolved counts from the existing - 30-day local window; -- aggregate notification attempt, delivery, and failure counts from the - existing seven-day local window; and -- a boolean indicating whether an operational outcome was observed in the - existing 30-day local window. - -The payload does not include names, email addresses, account IDs, hostnames, -credentials, infrastructure or resource identifiers, IP addresses, URLs, -paths, locale, recipients, notification endpoints, alert or notification -content, prompts, chat messages, command text, action output, token values, -browser events, or an event-level journey or clickstream. The rotating -pseudonymous installation ID continues to rotate every 30 days. Telemetry rows -are retained server-side for up to 90 days; request IP addresses are used only -transiently for rate limiting and are not stored in telemetry rows. - -Existing installations receive a one-time, non-blocking -**Telemetry payload updated** notice after upgrade. It links directly to the -exact payload preview, the disable action, and the full privacy disclosure. -Fresh installations do not receive a duplicate banner because the current -payload and controls are already disclosed during setup. - -## Terminology correction - -Earlier public website copy used an anonymity label that was too strong for a -payload containing a rotating installation identifier. The accurate term is -**pseudonymous**. The website wording was corrected in July 2026, before -schema v2 reached a public Pulse release; the shipped privacy documentation -and in-product control identify the rotating pseudonymous ID and the concrete -data categories excluded from the payload. diff --git a/docs/releases/RELEASE_NOTES_v6.1.1.md b/docs/releases/RELEASE_NOTES_v6.1.1.md new file mode 100644 index 000000000..76aec7fdb --- /dev/null +++ b/docs/releases/RELEASE_NOTES_v6.1.1.md @@ -0,0 +1,97 @@ +# Pulse v6.1.1 Release Notes + +`v6.1.1` is a stable patch release following `v6.1.0`. It takes the governed +emergency stable-patch path to resolve active customer harm in the manual +Unified Agent update flow and durable Docker update recovery. It also carries +the reliability, infrastructure, and privacy-disclosure improvements already +completed on `main` after the `v6.1.0` cutoff. + +## Highlights + +- Manual Unified Agent updates now use the canonical operating-system family, + so Mageia and other supported Linux distributions receive the Linux update + path instead of being rejected by a distribution-specific platform value. +- Docker update actions recover their durable terminal receipt by immutable + action and operation identity. A failed or no-effect digest-drift preflight + no longer leaves an update stuck after the live capability disappears. +- Outbound usage telemetry moves to a documented schema v2 with coarse, + pseudonymous operational signals, an exact payload preview, and a one-time + non-blocking upgrade notice. +- Infrastructure pages retain navigation through stream reconnects, proxy and + SSO bootstrap is more reliable, node edits use the correct update endpoint, + and PBS datastore alert overrides appear on the thresholds page. + +## Changed + +- Agent runtime platform reporting is normalized to the canonical Go operating + system family while preserving the original operating-system identity for + diagnostics. Unsupported platforms still fail closed. +- Outbound usage telemetry remains enabled by default unless an operator has + disabled it, and an existing enabled or disabled preference is preserved on + upgrade. The rotating pseudonymous installation ID continues to rotate every + 30 days. +- Telemetry schema v2 adds deliberately coarse deployment-method, install-age, + activation-stage, time-to-first-monitored-resource, and estate-size buckets; + authentication-configured and monitoring-active booleans; configured + connection count; aggregate alert outcome counts; aggregate notification + attempt, delivery, and failure counts; and an operational-outcome boolean. +- Existing installations receive one non-blocking **Telemetry payload + updated** notice with direct links to the exact payload preview, disable + action, and privacy disclosure. Fresh installations do not receive a + duplicate notice. +- Public privacy terminology now describes this rotating-identifier payload as + **pseudonymous**, not anonymous. + +Telemetry does not include identities, account details, hostnames, credentials, +resource identifiers, IP addresses, URLs, paths, locale, recipients, +notification endpoints, alert or notification content, prompts, chat +messages, command text or output, token values, browser events, or an +event-level journey or clickstream. Telemetry rows are retained server-side for +up to 90 days; request IP addresses are used transiently for rate limiting and +are not stored in telemetry rows. + +## Fixed + +- Manual Unified Agent updates on Mageia and other supported Linux + distributions no longer fail because the update planner receives a distro + identifier instead of the Linux platform family (#1607). +- Docker update actions that reach a terminal digest-drift preflight refusal + now recover as failed or no-effect without redispatch, even when the live + update capability is no longer advertised (#1608). +- Node state aggregation keeps clusters separate when they reuse the same node + names. +- Missing Patrol verdicts are swept with bounded follow-up instead of remaining + indefinitely unresolved. +- Platform navigation survives stream reconnects, and authenticated bootstrap + works correctly through proxy and SSO configurations. +- Infrastructure node edits route to the update endpoint. +- PBS datastore alert overrides are projected onto the thresholds page. +- The Proxmox VE setup script avoids an `awk` variable name that conflicts with + implementations where `exp` is reserved. + +## Upgrade Notes + +Use the normal v6 install or update flow for `v6.1.1`. + +Windows Unified Agent binaries in `v6.1.1` are not Authenticode-signed and may +show an Unknown Publisher warning. Verify published checksums and detached +`.sig` or `.sshsig` signatures before installation. This is a `v6.1.1`-only +release-owner exception; later stable releases restore the Windows +Authenticode requirement. + +The rollback target for this patch release is `v6.1.0`. The exact rollback +reinstall command is: + +```bash +./scripts/install.sh --version v6.1.0 +``` + +The server/mobile decision is `existing-mobile-build-compatible`. Pulse Mobile +`1.0.0` iOS build `11` and Android versionCode `9` remain the compatible +candidate builds. No production relay or mobile trust contract changed after +`v6.1.0`; the matched mobile-facing path is test-only branch coverage. This +release does not upload a companion build or start a public mobile-store +rollout. + +Paid Pulse Pro, Relay, and eligible legacy customers should continue to use the +private download page and private runtime image for paid runtime features. diff --git a/docs/releases/V6_CHANGELOG_v6.1.1.md b/docs/releases/V6_CHANGELOG_v6.1.1.md new file mode 100644 index 000000000..fddba9cd6 --- /dev/null +++ b/docs/releases/V6_CHANGELOG_v6.1.1.md @@ -0,0 +1,45 @@ +# Pulse v6.1.1 + +_This changelog describes stable `v6.1.1` compared with stable `v6.1.0`._ + +## Changed + +- Unified Agent operating-system reporting now provides the canonical runtime + platform family to the update planner while retaining detailed OS identity + for diagnostics. +- Outbound usage telemetry uses schema v2 with rotating pseudonymous identity, + bounded aggregate operational signals, preserved operator preference, exact + payload preview, and a one-time non-blocking upgrade disclosure. +- Privacy copy uses the accurate term **pseudonymous** and explicitly lists the + identity, infrastructure, content, command, and clickstream categories + excluded from telemetry. +- Infrastructure navigation, authenticated proxy/SSO bootstrap, node-edit + routing, and PBS alert-threshold projection are more consistent. + +## Fixed + +- Linux manual agent updates no longer misclassify Mageia or other supported + distributions as an unsupported update platform (#1607). +- Durable Docker update receipt recovery uses immutable action and operation + binding, terminalizes digest-drift preflight refusals after capability loss, + and never redispatches the rejected operation (#1608). +- Cluster node aggregation no longer conflates separate clusters that reuse a + node name. +- Missing Patrol verdicts retain bounded follow-up. +- The Proxmox VE setup script avoids an `awk` identifier collision. + +## Release Metadata + +- Version: `v6.1.1` +- Previous stable: `v6.1.0` +- Rollback target: `v6.1.0` +- Rollback command: `./scripts/install.sh --version v6.1.0` +- Promotion path: stable patch hotfix from `main`, with an owner-recorded reason + for active customer update harm and no fabricated same-version RC tag +- Windows signing decision: `v6.1.1`-only release-owner exception; Windows + Unified Agent binaries are not Authenticode-signed and may show an Unknown + Publisher warning, while exact-SHA candidate binding, checksums, detached + `.sig`/`.sshsig` signatures, manifests, and published digests remain required +- Mobile decision: `existing-mobile-build-compatible`; Pulse Mobile `1.0.0` + iOS build `11` and Android versionCode `9` require no companion upload, and + no public store rollout is part of this server release diff --git a/scripts/install-docker.sh b/scripts/install-docker.sh index fc305a5bc..de64569bc 100755 --- a/scripts/install-docker.sh +++ b/scripts/install-docker.sh @@ -6,7 +6,7 @@ set -euo pipefail SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)" DOCKER_IMAGE_REPO="${DOCKER_IMAGE_REPO:-rcourtman/pulse}" -CANONICAL_DEFAULT_PULSE_VERSION="6.1.0" +CANONICAL_DEFAULT_PULSE_VERSION="6.1.1" resolve_default_pulse_version() { if [ -n "${PULSE_IMAGE_VERSION:-}" ]; then diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 49ed3d06a..8e4039853 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -269,25 +269,23 @@ func TestCurrentStablePatchReleasePacketTracksInstallMetadata(t *testing.T) { assertFileContainsAllNormalized(t, releaseNotesPath, "`v"+version+"` is a stable patch release", "`v"+previous+"`", - "Patrol Gemini model readiness", - "remembered-login submit persistence", - "Proxmox SMART temperature fallback", - "direct SATA/SAT disks", - "paid-runtime activation continuity", - "license-period status fixes", + "Manual Unified Agent updates now use the canonical operating-system family", + "Docker update actions recover their durable terminal receipt", + "Outbound usage telemetry moves to a documented schema v2", + "clusters separate when they reuse the same node names", + "not Authenticode-signed", + "`existing-mobile-build-compatible`", "rollback target for this patch release is `v"+previous+"`", ) assertFileContainsAll(t, changelogPath, "Version: `v"+version+"`", "Rollback target: `v"+previous+"`", "Promotion path: stable patch hotfix from `"+releaseBranch+"`", - "Gemini tool-call capability", - "Remembered-login state now persists", - "Proxmox SMART temperature collection", - "explicit SAT probe", - "Paid runtime activation now writes and reuses a durable installation fingerprint", - "License grants now include the billing `current_period_end`", - "Server installer execution now rejects unsafe piped invocation", + "canonical runtime", + "telemetry uses schema v2", + "Durable Docker update receipt recovery", + "`v6.1.1`-only release-owner exception", + "Mobile decision: `existing-mobile-build-compatible`", ) assertFileContainsAll(t, repoFile("docs", "RELEASE_NOTES.md"), "docs/releases/RELEASE_NOTES_v"+version+".md", @@ -385,7 +383,7 @@ func TestCurrentStableMinorReleasePacketTracksInstallMetadata(t *testing.T) { "`rollback_version=v"+previous+"`", "The exact stable `main` SHA must pass the no-publication dry run before the same SHA is dispatched through the single-build publish workflow.", "The stable server cut is classified `existing-mobile-build-compatible`.", - "This exception cannot apply to a later stable version", + "explicit version-bound decision", "For the active stable `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"`", ) } @@ -849,12 +847,12 @@ func TestReleaseCandidateRequiresPlatformNativeAgentSigning(t *testing.T) { ) assertFileContainsAll(t, repoFile(".github", "workflows", "release-dry-run.yml"), `Definitive Dry-Run Verdict`, - `require_windows_signing: ${{ !contains(inputs.version, '-') && !(inputs.version == '6.1.0' && inputs.unsigned_windows_exception) }}`, + `require_windows_signing: ${{ !contains(inputs.version, '-') && !((inputs.version == '6.1.0' || inputs.version == '6.1.1') && inputs.unsigned_windows_exception) }}`, `require_result "exact-SHA release candidate" "$CANDIDATE_RESULT" success`, `require_result "stable demo no-mutation verification" "$DEMO_RESULT" success`, ) assertFileContainsAll(t, repoFile("scripts", "release_control", "resolve_release_promotion.py"), - `version != "6.1.0"`, + `version not in {"6.1.0", "6.1.1"}`, `unsigned_windows_reason is required`, `not Authenticode-signed`, `require_windows_signing = not is_prerelease and not unsigned_windows_exception`, diff --git a/scripts/installtests/install_docker_sh_test.go b/scripts/installtests/install_docker_sh_test.go index 4a231a49d..b51b682f0 100644 --- a/scripts/installtests/install_docker_sh_test.go +++ b/scripts/installtests/install_docker_sh_test.go @@ -288,9 +288,10 @@ func TestInstallDockerProofTracksStablePatchReleaseContract(t *testing.T) { assertFileContainsAllNormalized(t, repoFile("docs", "release-control", "v6", "internal", "subsystems", "deployment-installability.md"), "The active stable `v"+version+"` cut sets the repo-root `VERSION`, repo-root `docker-compose.yml` image default, `scripts/install-docker.sh` fallback, and Helm chart release metadata to the same `"+version+"` release version.", "This patch release uses the stable hotfix path with `rollback_version=v"+previous+"`, `hotfix_exception=true`, a release-owner reason, and no fabricated same-version RC tag.", - "It advances the v"+previous+" stable line with customer-support fixes", - "paid-runtime activation fingerprint reuse", - "pinned installer execution hardening", + "manual Unified Agent updates", + "durable Docker update actions", + "`existing-mobile-build-compatible`", + "`v"+version+"`-only unsigned-Windows exception", "For the active stable `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"`", ) } diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index c8714e067..17d3ae0db 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -488,7 +488,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("if: ${{ inputs.version != '' }}", workflow) self.assertIn("require_macos_signing: true", workflow) self.assertIn( - "require_windows_signing: ${{ !contains(inputs.version, '-') && !(inputs.version == '6.1.0' && inputs.unsigned_windows_exception) }}", + "require_windows_signing: ${{ !contains(inputs.version, '-') && !((inputs.version == '6.1.0' || inputs.version == '6.1.1') && inputs.unsigned_windows_exception) }}", workflow, ) self.assertIn("unsigned_windows_exception:", workflow) @@ -669,7 +669,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("require_windows_signing: ${{ needs.prepare.outputs.require_windows_signing == 'true' }}", content) self.assertIn("unsigned_windows_exception:", content) self.assertIn("unsigned_windows_reason:", content) - self.assertIn('version != "6.1.0"', resolver) + self.assertIn('version not in {"6.1.0", "6.1.1"}', resolver) self.assertIn("not Authenticode-signed", resolver) self.assertIn("windows_signing_backend: signpath", content) self.assertIn('if [[ "$REQUIRE_WINDOWS_SIGNING" == "true" ]]', candidate_workflow) @@ -1124,6 +1124,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertNotIn(".createdAt >= $cutoff", helper) self.assertIn("gh workflow run create-release.yml", helper) self.assertIn("gh workflow run \"$WORKFLOW\"", helper) + self.assertIn("--unsigned-windows-exception-reason", helper) + self.assertIn("--unsigned-windows-exception", helper) + self.assertIn("unsigned_windows_exception", helper) + self.assertIn("unsigned_windows_reason", helper) self.assertIn("Single-Build Release Path", policy) self.assertIn("Routine Stable Patch Path", policy) self.assertIn("single publish workflow performs the exact-SHA preflight", normalize_ws(policy)) diff --git a/scripts/release_control/resolve_release_promotion.py b/scripts/release_control/resolve_release_promotion.py index 3a36cf2aa..4d5be2776 100644 --- a/scripts/release_control/resolve_release_promotion.py +++ b/scripts/release_control/resolve_release_promotion.py @@ -245,10 +245,10 @@ def resolve_metadata( promotion_mode = "prerelease" if is_prerelease else "stable-rc-promotion" if unsigned_windows_exception: - if version != "6.1.0": + if version not in {"6.1.0", "6.1.1"}: raise ValueError( - "unsigned_windows_exception is approved only for stable v6.1.0. " - "Later stable releases must restore Windows Authenticode signing." + "unsigned_windows_exception is approved only for stable v6.1.0 or v6.1.1. " + "Stable v6.1.2 and later must restore Windows Authenticode signing." ) if not unsigned_windows_reason: raise ValueError( @@ -256,7 +256,7 @@ def resolve_metadata( ) if release_notes and "not authenticode-signed" not in release_notes.lower(): raise ValueError( - "Stable v6.1.0 release_notes must disclose that Windows binaries are not Authenticode-signed." + f"Stable v{version} release_notes must disclose that Windows binaries are not Authenticode-signed." ) elif unsigned_windows_reason: raise ValueError( diff --git a/scripts/release_control/resolve_release_promotion_test.py b/scripts/release_control/resolve_release_promotion_test.py index 25211b37e..c58d7d912 100644 --- a/scripts/release_control/resolve_release_promotion_test.py +++ b/scripts/release_control/resolve_release_promotion_test.py @@ -171,12 +171,41 @@ class ResolveReleasePromotionTest(unittest.TestCase): "Release owner accepted the Windows unknown-publisher warning for v6.1.0.", ) + def test_v611_owner_exception_allows_disclosed_emergency_patch(self) -> None: + metadata = resolver.resolve_metadata( + version="6.1.1", + promoted_from_tag_input="", + rollback_version_input="v6.1.0", + ga_date_input="", + v5_eos_date_input="", + hotfix_exception=True, + hotfix_reason_input="Active customer update harm.", + release_notes_input=( + "Windows Unified Agent binaries are not Authenticode-signed for v6.1.1." + ), + unsigned_windows_exception=True, + unsigned_windows_reason_input=( + "Release owner accepted the Windows unknown-publisher warning for v6.1.1." + ), + list_stable_tags_fn=lambda: ["v6.1.0", "v6.0.5"], + list_same_version_rc_tags_fn=lambda version: [], + changed_paths_fn=lambda tag: ["install.sh"], + tag_exists_fn=lambda tag: tag == "v6.1.0", + tag_commit_fn=lambda tag: "v610-commit", + head_descends_from_fn=lambda commit: commit == "v610-commit", + ) + + self.assertEqual(metadata["promotion_mode"], "emergency-stable-patch") + self.assertEqual(metadata["rollback_tag"], "v6.1.0") + self.assertEqual(metadata["require_windows_signing"], "false") + self.assertEqual(metadata["unsigned_windows_exception"], "true") + def test_unsigned_windows_exception_is_rejected_for_other_stable_versions(self) -> None: - with self.assertRaisesRegex(ValueError, "approved only for stable v6.1.0"): + with self.assertRaisesRegex(ValueError, "approved only for stable v6.1.0 or v6.1.1"): resolver.resolve_metadata( - version="6.1.1", + version="6.1.2", promoted_from_tag_input="", - rollback_version_input="v6.1.0", + rollback_version_input="v6.1.1", ga_date_input="", v5_eos_date_input="", hotfix_exception=True, diff --git a/scripts/trigger-stable-patch.sh b/scripts/trigger-stable-patch.sh index e8206e536..241eec754 100755 --- a/scripts/trigger-stable-patch.sh +++ b/scripts/trigger-stable-patch.sh @@ -6,6 +6,7 @@ VERSION="" MOBILE_RELEASE_DECISION="" MOBILE_RELEASE_EVIDENCE="" HOTFIX_REASON="" +UNSIGNED_WINDOWS_REASON="" usage() { cat <<'EOF' @@ -20,6 +21,8 @@ Options: --mobile-release-decision VALUE Override the inferred mobile decision. --mobile-release-evidence VALUE Evidence for a mobile compatibility decision. --emergency-hotfix-reason VALUE Bypass an RC-required risk with an explicit reason. + --unsigned-windows-exception-reason VALUE + Use an approved version-bound unsigned Windows exception. -h, --help Show this help. EOF } @@ -42,6 +45,10 @@ while [ "$#" -gt 0 ]; do HOTFIX_REASON="${2:?--emergency-hotfix-reason requires a value}" shift 2 ;; + --unsigned-windows-exception-reason) + UNSIGNED_WINDOWS_REASON="${2:?--unsigned-windows-exception-reason requires a value}" + shift 2 + ;; -h|--help) usage exit 0 @@ -111,6 +118,14 @@ if [ -n "$HOTFIX_REASON" ]; then HOTFIX_EXCEPTION="true" RESOLVER_ARGS+=(--hotfix-exception --hotfix-reason "$HOTFIX_REASON") fi +UNSIGNED_WINDOWS_EXCEPTION="false" +if [ -n "$UNSIGNED_WINDOWS_REASON" ]; then + UNSIGNED_WINDOWS_EXCEPTION="true" + RESOLVER_ARGS+=( + --unsigned-windows-exception + --unsigned-windows-reason "$UNSIGNED_WINDOWS_REASON" + ) +fi PROMOTION_METADATA="$(python3 scripts/release_control/resolve_release_promotion.py "${RESOLVER_ARGS[@]}")" ROLLBACK_TAG="$(awk -F= '$1 == "rollback_tag" {print $2}' <<<"$PROMOTION_METADATA")" @@ -150,6 +165,8 @@ if [ "$MODE" = "dry-run" ]; then --require v5_eos_date \ --require hotfix_exception \ --require hotfix_reason \ + --require unsigned_windows_exception \ + --require unsigned_windows_reason \ --require note \ --require mobile_release_decision \ --require mobile_release_evidence @@ -163,6 +180,8 @@ if [ "$MODE" = "dry-run" ]; then -f v5_eos_date="" \ -f hotfix_exception="$HOTFIX_EXCEPTION" \ -f hotfix_reason="$HOTFIX_REASON" \ + -f unsigned_windows_exception="$UNSIGNED_WINDOWS_EXCEPTION" \ + -f unsigned_windows_reason="$UNSIGNED_WINDOWS_REASON" \ -f note="Stable patch preflight for ${VERSION} at ${LOCAL_SHA}" \ -f mobile_release_decision="$MOBILE_RELEASE_DECISION" \ -f mobile_release_evidence="$MOBILE_RELEASE_EVIDENCE" @@ -182,6 +201,8 @@ else --require v5_eos_date \ --require hotfix_exception \ --require hotfix_reason \ + --require unsigned_windows_exception \ + --require unsigned_windows_reason \ --require draft_only \ --require mobile_release_decision \ --require mobile_release_evidence @@ -195,6 +216,8 @@ else --arg v5_eos_date "" \ --argjson hotfix_exception "$HOTFIX_EXCEPTION" \ --arg hotfix_reason "$HOTFIX_REASON" \ + --argjson unsigned_windows_exception "$UNSIGNED_WINDOWS_EXCEPTION" \ + --arg unsigned_windows_reason "$UNSIGNED_WINDOWS_REASON" \ --argjson draft_only false \ --arg mobile_release_decision "$MOBILE_RELEASE_DECISION" \ --arg mobile_release_evidence "$MOBILE_RELEASE_EVIDENCE" \ @@ -207,6 +230,8 @@ else v5_eos_date: $v5_eos_date, hotfix_exception: $hotfix_exception, hotfix_reason: $hotfix_reason, + unsigned_windows_exception: $unsigned_windows_exception, + unsigned_windows_reason: $unsigned_windows_reason, draft_only: $draft_only, mobile_release_decision: $mobile_release_decision, mobile_release_evidence: $mobile_release_evidence