mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 10:33:29 +00:00
Record v6.1.0 unsigned Windows exception
This commit is contained in:
parent
2c3c188fc8
commit
674364c749
20 changed files with 369 additions and 52 deletions
|
|
@ -365,7 +365,7 @@ jobs:
|
|||
retention-days: 30
|
||||
|
||||
build:
|
||||
name: Build and Validate Signed Candidate
|
||||
name: Build and Validate Release Candidate
|
||||
needs: [sign-macos-agent, sign-windows-agent]
|
||||
if: ${{ always() && (!inputs.require_macos_signing || needs.sign-macos-agent.result == 'success') && (!inputs.require_windows_signing || needs.sign-windows-agent.result == 'success') }}
|
||||
runs-on: ubuntu-24.04
|
||||
|
|
@ -450,7 +450,7 @@ jobs:
|
|||
name: windows-signing-evidence-${{ github.sha }}-${{ inputs.version }}
|
||||
path: release-candidate-manifest
|
||||
|
||||
- name: Build signed release candidate
|
||||
- name: Build release candidate
|
||||
run: ./scripts/build-release.sh "${{ inputs.version }}"
|
||||
env:
|
||||
PULSE_LICENSE_PUBLIC_KEY: ${{ secrets.PULSE_LICENSE_PUBLIC_KEY }}
|
||||
|
|
|
|||
29
.github/workflows/create-release.yml
vendored
29
.github/workflows/create-release.yml
vendored
|
|
@ -37,6 +37,15 @@ on:
|
|||
description: 'Stable only: reason for hotfix soak exception'
|
||||
required: false
|
||||
type: string
|
||||
unsigned_windows_exception:
|
||||
description: 'v6.1.0 only: publish Windows agents without Authenticode under the recorded owner exception'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
unsigned_windows_reason:
|
||||
description: 'v6.1.0 only: owner reason for the unsigned Windows exception'
|
||||
required: false
|
||||
type: string
|
||||
historical_asset_backfill_only:
|
||||
description: 'Repair an already-published release packet in place without rebuilding binaries'
|
||||
required: false
|
||||
|
|
@ -82,6 +91,9 @@ jobs:
|
|||
v5_eos_date: ${{ steps.promotion.outputs.v5_eos_date }}
|
||||
hotfix_exception: ${{ steps.promotion.outputs.hotfix_exception }}
|
||||
hotfix_reason: ${{ steps.promotion.outputs.hotfix_reason }}
|
||||
require_windows_signing: ${{ steps.promotion.outputs.require_windows_signing }}
|
||||
unsigned_windows_exception: ${{ steps.promotion.outputs.unsigned_windows_exception }}
|
||||
unsigned_windows_reason: ${{ steps.promotion.outputs.unsigned_windows_reason }}
|
||||
promotion_mode: ${{ steps.promotion.outputs.promotion_mode }}
|
||||
is_stable_patch: ${{ steps.promotion.outputs.is_stable_patch }}
|
||||
historical_asset_backfill_only: ${{ steps.extract.outputs.historical_asset_backfill_only }}
|
||||
|
|
@ -177,6 +189,8 @@ jobs:
|
|||
V5_EOS_DATE_INPUT: ${{ github.event.inputs.v5_eos_date }}
|
||||
HOTFIX_EXCEPTION_INPUT: ${{ github.event.inputs.hotfix_exception }}
|
||||
HOTFIX_REASON_INPUT: ${{ github.event.inputs.hotfix_reason }}
|
||||
UNSIGNED_WINDOWS_EXCEPTION_INPUT: ${{ github.event.inputs.unsigned_windows_exception }}
|
||||
UNSIGNED_WINDOWS_REASON_INPUT: ${{ github.event.inputs.unsigned_windows_reason }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -201,6 +215,14 @@ jobs:
|
|||
if [ "${HOTFIX_EXCEPTION_INPUT:-false}" = "true" ]; then
|
||||
HELPER_ARGS+=(--hotfix-exception)
|
||||
fi
|
||||
if [ "${UNSIGNED_WINDOWS_EXCEPTION_INPUT:-false}" = "true" ]; then
|
||||
HELPER_ARGS+=(
|
||||
--unsigned-windows-exception
|
||||
--unsigned-windows-reason "${UNSIGNED_WINDOWS_REASON_INPUT:-}"
|
||||
)
|
||||
elif [ -n "${UNSIGNED_WINDOWS_REASON_INPUT:-}" ]; then
|
||||
HELPER_ARGS+=(--unsigned-windows-reason "${UNSIGNED_WINDOWS_REASON_INPUT}")
|
||||
fi
|
||||
|
||||
python3 scripts/release_control/resolve_release_promotion.py "${HELPER_ARGS[@]}" > "$RUNNER_TEMP/promotion-metadata.out"
|
||||
rm -f "$NOTES_FILE"
|
||||
|
|
@ -223,7 +245,7 @@ jobs:
|
|||
with:
|
||||
version: ${{ needs.prepare.outputs.version }}
|
||||
require_macos_signing: true
|
||||
require_windows_signing: ${{ needs.prepare.outputs.is_prerelease != 'true' }}
|
||||
require_windows_signing: ${{ needs.prepare.outputs.require_windows_signing == 'true' }}
|
||||
windows_signing_backend: signpath
|
||||
|
||||
# Frontend checks run in parallel with backend tests
|
||||
|
|
@ -724,7 +746,10 @@ jobs:
|
|||
--planned-ga-date "${{ needs.prepare.outputs.ga_date }}" \
|
||||
--planned-v5-eos-date "${{ needs.prepare.outputs.v5_eos_date }}" \
|
||||
--hotfix-exception "${{ needs.prepare.outputs.hotfix_exception }}" \
|
||||
--hotfix-reason "${{ needs.prepare.outputs.hotfix_reason }}"
|
||||
--hotfix-reason "${{ needs.prepare.outputs.hotfix_reason }}" \
|
||||
--require-windows-signing "${{ needs.prepare.outputs.require_windows_signing }}" \
|
||||
--unsigned-windows-exception "${{ needs.prepare.outputs.unsigned_windows_exception }}" \
|
||||
--unsigned-windows-reason "${{ needs.prepare.outputs.unsigned_windows_reason }}"
|
||||
|
||||
# The in-app "What's New" banner only fires when the release body has
|
||||
# a Highlights section (see frontend-modern/src/components/whatsNewModel.ts).
|
||||
|
|
|
|||
41
.github/workflows/release-dry-run.yml
vendored
41
.github/workflows/release-dry-run.yml
vendored
|
|
@ -42,6 +42,15 @@ on:
|
|||
description: 'Stable rehearsal only: reason for hotfix soak exception'
|
||||
required: false
|
||||
type: string
|
||||
unsigned_windows_exception:
|
||||
description: 'v6.1.0 only: rehearse Windows agents without Authenticode under the recorded owner exception'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
unsigned_windows_reason:
|
||||
description: 'v6.1.0 only: owner reason for the unsigned Windows exception'
|
||||
required: false
|
||||
type: string
|
||||
note:
|
||||
description: 'Optional note/reason for the dry run'
|
||||
required: false
|
||||
|
|
@ -70,7 +79,7 @@ jobs:
|
|||
with:
|
||||
version: ${{ inputs.version }}
|
||||
require_macos_signing: true
|
||||
require_windows_signing: ${{ !contains(inputs.version, '-') }}
|
||||
require_windows_signing: ${{ !contains(inputs.version, '-') && !(inputs.version == '6.1.0' && inputs.unsigned_windows_exception) }}
|
||||
windows_signing_backend: signpath
|
||||
|
||||
dry-run:
|
||||
|
|
@ -92,6 +101,9 @@ jobs:
|
|||
soak_hours: ${{ steps.rehearsal.outputs.soak_hours }}
|
||||
hotfix_exception: ${{ steps.rehearsal.outputs.hotfix_exception }}
|
||||
hotfix_reason: ${{ steps.rehearsal.outputs.hotfix_reason }}
|
||||
require_windows_signing: ${{ steps.rehearsal.outputs.require_windows_signing }}
|
||||
unsigned_windows_exception: ${{ steps.rehearsal.outputs.unsigned_windows_exception }}
|
||||
unsigned_windows_reason: ${{ steps.rehearsal.outputs.unsigned_windows_reason }}
|
||||
|
||||
steps:
|
||||
- name: Validate release ref
|
||||
|
|
@ -131,6 +143,8 @@ jobs:
|
|||
V5_EOS_DATE_INPUT: ${{ inputs.v5_eos_date }}
|
||||
HOTFIX_EXCEPTION_INPUT: ${{ inputs.hotfix_exception }}
|
||||
HOTFIX_REASON_INPUT: ${{ inputs.hotfix_reason }}
|
||||
UNSIGNED_WINDOWS_EXCEPTION_INPUT: ${{ inputs.unsigned_windows_exception }}
|
||||
UNSIGNED_WINDOWS_REASON_INPUT: ${{ inputs.unsigned_windows_reason }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
|
|
@ -171,6 +185,14 @@ jobs:
|
|||
if [ "${HOTFIX_EXCEPTION_INPUT:-false}" = "true" ]; then
|
||||
HELPER_ARGS+=(--hotfix-exception)
|
||||
fi
|
||||
if [ "${UNSIGNED_WINDOWS_EXCEPTION_INPUT:-false}" = "true" ]; then
|
||||
HELPER_ARGS+=(
|
||||
--unsigned-windows-exception
|
||||
--unsigned-windows-reason "${UNSIGNED_WINDOWS_REASON_INPUT:-}"
|
||||
)
|
||||
elif [ -n "${UNSIGNED_WINDOWS_REASON_INPUT:-}" ]; then
|
||||
HELPER_ARGS+=(--unsigned-windows-reason "${UNSIGNED_WINDOWS_REASON_INPUT}")
|
||||
fi
|
||||
if [ "${EVENT_NAME}" = "schedule" ] && [ -z "${ROLLBACK_VERSION_INPUT:-}" ]; then
|
||||
# Scheduled watchdog runs carry no dispatch inputs; derive the
|
||||
# rollback target instead of failing. Manual dispatches never get
|
||||
|
|
@ -373,6 +395,11 @@ jobs:
|
|||
if [ -n "${{ steps.rehearsal.outputs.hotfix_reason }}" ]; then
|
||||
echo "- Hotfix reason: ${{ steps.rehearsal.outputs.hotfix_reason }}"
|
||||
fi
|
||||
echo "- Windows Authenticode required: ${{ steps.rehearsal.outputs.require_windows_signing }}"
|
||||
echo "- Unsigned Windows exception: ${{ steps.rehearsal.outputs.unsigned_windows_exception }}"
|
||||
if [ -n "${{ steps.rehearsal.outputs.unsigned_windows_reason }}" ]; then
|
||||
echo "- Unsigned Windows reason: ${{ steps.rehearsal.outputs.unsigned_windows_reason }}"
|
||||
fi
|
||||
if [ -n "${NOTE}" ]; then
|
||||
echo "- Operator note: ${NOTE}"
|
||||
fi
|
||||
|
|
@ -429,6 +456,9 @@ jobs:
|
|||
PREFLIGHT_RESULT: ${{ needs.dry-run.result }}
|
||||
DEMO_RESULT: ${{ needs.demo_path_preflight.result }}
|
||||
WINDOWS_SIGNING_BACKEND: ${{ needs.build_release_candidate.outputs.windows_signing_backend }}
|
||||
REQUIRE_WINDOWS_SIGNING: ${{ needs.dry-run.outputs.require_windows_signing }}
|
||||
UNSIGNED_WINDOWS_EXCEPTION: ${{ needs.dry-run.outputs.unsigned_windows_exception }}
|
||||
UNSIGNED_WINDOWS_REASON: ${{ needs.dry-run.outputs.unsigned_windows_reason }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
failed=0
|
||||
|
|
@ -445,7 +475,7 @@ jobs:
|
|||
require_result "release preflight" "$PREFLIGHT_RESULT" success
|
||||
require_result "stable demo no-mutation verification" "$DEMO_RESULT" success
|
||||
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "${VERSION_INPUT:-}" ]; then
|
||||
require_result "exact-SHA signed candidate" "$CANDIDATE_RESULT" success
|
||||
require_result "exact-SHA release candidate" "$CANDIDATE_RESULT" success
|
||||
elif [ "$CANDIDATE_RESULT" != "skipped" ]; then
|
||||
echo "::error::Scheduled watchdog candidate result was ${CANDIDATE_RESULT}; expected skipped."
|
||||
failed=1
|
||||
|
|
@ -458,7 +488,12 @@ jobs:
|
|||
echo "- Version: \`${VERSION_INPUT:-scheduled}\`"
|
||||
echo "- Release preflight: \`${PREFLIGHT_RESULT}\`"
|
||||
echo "- Stable demo verification: \`${DEMO_RESULT}\`"
|
||||
echo "- Signed candidate: \`${CANDIDATE_RESULT}\`"
|
||||
echo "- Release candidate: \`${CANDIDATE_RESULT}\`"
|
||||
echo "- Windows Authenticode required: \`${REQUIRE_WINDOWS_SIGNING}\`"
|
||||
echo "- Unsigned Windows exception: \`${UNSIGNED_WINDOWS_EXCEPTION}\`"
|
||||
if [ -n "${UNSIGNED_WINDOWS_REASON:-}" ]; then
|
||||
echo "- Unsigned Windows reason: ${UNSIGNED_WINDOWS_REASON}"
|
||||
fi
|
||||
if [ -n "${WINDOWS_SIGNING_BACKEND:-}" ]; then
|
||||
echo "- Windows signing backend: \`${WINDOWS_SIGNING_BACKEND}\`"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1021,18 +1021,23 @@ Companion drill:
|
|||
6. Confirm `v6.0.5` release timestamps and asset count remain unchanged and
|
||||
the public demo remains healthy on stable `6.0.5`.
|
||||
- Pass when:
|
||||
The external rehearsal proves the canonical candidate builder, 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 avoidable post-release
|
||||
serialization.
|
||||
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
|
||||
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
|
||||
avoidable post-release serialization.
|
||||
- Latest exercised record:
|
||||
`docs/release-control/v6/internal/records/single-build-release-promotion-path-2026-07-09.md`
|
||||
- Block release if:
|
||||
Publication rebuilds release assets, the candidate manifest does not pin the
|
||||
exact SHA and complete asset set, standard validation downloads the full
|
||||
release packet, independent post-release jobs are serialized, or the
|
||||
definitive verdict can pass without all applicable downstream results.
|
||||
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.
|
||||
|
||||
## Gate: `operational-trust-canonical-lifecycle`
|
||||
|
||||
|
|
|
|||
|
|
@ -174,6 +174,13 @@ Cloud, and self-hosted production users.
|
|||
Run` and the normal single-build publication workflow. Only a security,
|
||||
data-loss, upgrade-breaking, startup-blocking, or release-pipeline defect
|
||||
may reopen the cutoff; ordinary findings move to v6.1.1.
|
||||
- After exact-SHA rehearsal `29927692302` exposed unavailable external
|
||||
SignPath configuration, the release owner approved a `v6.1.0`-only
|
||||
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.
|
||||
- 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.
|
||||
|
|
@ -181,11 +188,12 @@ Cloud, and self-hosted production users.
|
|||
## Single-Build Release Path
|
||||
|
||||
1. Every normal RC, stable, and patch release is initiated once through
|
||||
`create-release.yml`. The workflow builds one signed candidate for the exact
|
||||
pushed SHA while frontend, backend, Docker, Helm, and integration checks run
|
||||
in parallel. No tag, draft, or public release mutation occurs until those
|
||||
checks and the candidate build pass.
|
||||
2. The signed candidate is uploaded as a one-day Actions artifact with a
|
||||
`create-release.yml`. The workflow builds one exact-SHA candidate with the
|
||||
native signing lanes required by that version's governed policy while
|
||||
frontend, backend, Docker, Helm, and integration checks run in parallel. No
|
||||
tag, draft, or public release mutation occurs until those checks and the
|
||||
candidate build pass.
|
||||
2. The release candidate is uploaded as a one-day Actions artifact with a
|
||||
machine-readable manifest that pins source SHA, version, filename, size, and
|
||||
SHA-256 for every release asset. Publication downloads and verifies that
|
||||
exact candidate; it must not rebuild release binaries or installers.
|
||||
|
|
|
|||
|
|
@ -20,6 +20,21 @@ metadata on top of the content cutoff base. The exact publish SHA must be bound
|
|||
by the no-public-release `Release Dry Run` and then consumed by the normal
|
||||
single-build release workflow without further product changes.
|
||||
|
||||
## Windows Signing Exception
|
||||
|
||||
The first exact-SHA stable rehearsal, GitHub Actions run `29927692302`, stopped
|
||||
before candidate assembly because the external SignPath configuration was
|
||||
unavailable. It created no tag, draft, or public release. The release owner
|
||||
then explicitly approved publishing `v6.1.0` without Windows Authenticode
|
||||
signing rather than entering another release-candidate loop.
|
||||
|
||||
This is a `v6.1.0`-only exception. Windows Unified Agent binaries remain bound
|
||||
to the exact source SHA and candidate manifest and must retain checksum,
|
||||
detached `.sig`/`.sshsig`, and post-publication digest verification. The public
|
||||
release notes must state that the Windows binaries are not
|
||||
Authenticode-signed and may show an Unknown Publisher warning. Every later
|
||||
stable version restores the Windows Authenticode requirement automatically.
|
||||
|
||||
## Cutoff Reopen Rule
|
||||
|
||||
Only a security defect, data-loss risk, broken upgrade, startup failure, or
|
||||
|
|
|
|||
|
|
@ -8396,7 +8396,7 @@
|
|||
},
|
||||
{
|
||||
"id": "single-build-release-promotion-path",
|
||||
"summary": "Confirm normal RC, stable, and patch releases build one exact-SHA signed candidate in parallel with checks, promote that candidate without rebuilding, validate GitHub asset digests without full packet downloads, and run independent post-publication lanes concurrently.",
|
||||
"summary": "Confirm normal RC, stable, and patch releases build one exact-SHA candidate with policy-required native signing in parallel with checks, promote that candidate without rebuilding, validate GitHub asset digests without full packet downloads, and run independent post-publication lanes concurrently.",
|
||||
"owner": "project-owner",
|
||||
"blocking_level": "release-ready",
|
||||
"minimum_evidence_tier": "real-external-e2e",
|
||||
|
|
|
|||
|
|
@ -357,10 +357,10 @@ TLS floor in the dynamic config.
|
|||
1. Add or change deployment-type detection, update planning, or apply behavior through `internal/updates/`
|
||||
2. Add or change release-build metadata injection, Docker build-context allowlists, release artifact assembly, governed promotion metadata resolution, artifact release-line validation, the canonical version file, operator-facing release packet content, prerelease feedback intake wording, historical published-release integrity backfill, release asset validation status publication, download endpoint checksum/signature header proof, end-to-end install.sh smoke against the published release, or the canonical in-repo v6 upgrade guide through `scripts/build-release.sh`, `scripts/release_asset_common.sh`, `scripts/backfill-release-assets.sh`, `scripts/release_ldflags.sh`, `scripts/check-workflow-dispatch-inputs.py`, `scripts/release_control/mobile_release_gate.py`, `scripts/release_control/render_release_body.py`, `scripts/release_control/resolve_release_promotion.py`, `scripts/release_control/validate_artifact_release_line.py`, `scripts/release_control/record_rc_to_ga_rehearsal.py`, `scripts/release_control/internal/record_rc_to_ga_rehearsal.py`, `scripts/release_control/release_promotion_policy_support.py`, `.dockerignore`, `Dockerfile`, `.github/ISSUE_TEMPLATE/v6_rc_feedback.yml`, `docs/RELEASE_NOTES.md`, `docs/releases/`, `docs/UPGRADE_v6.md`, `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`, `docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md`, `docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md`, `scripts/validate-release.sh`, `scripts/validate-published-release.sh`, the operator dispatch helpers `scripts/trigger-release.sh` and `scripts/trigger-release-dry-run.sh`, and the governed release workflows `.github/workflows/backfill-release-assets.yml`, `.github/workflows/create-release.yml`, `.github/workflows/deploy-demo-server.yml`, `.github/workflows/helm-pages.yml`, `.github/workflows/install-sh-smoke.yml`, `.github/workflows/publish-docker.yml`, `.github/workflows/publish-helm-chart.yml`, `.github/workflows/promote-floating-tags.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/update-demo-server.yml`, and `.github/workflows/validate-release-assets.yml`
|
||||
Normal releases are single-build promotions. The exact pushed SHA must
|
||||
produce one signed candidate through
|
||||
`.github/workflows/build-release-candidate.yml` while independent release
|
||||
checks run in parallel. `create-release.yml` may publish only that candidate
|
||||
after `scripts/release_candidate_manifest.py` verifies its version, source
|
||||
produce one release candidate with the policy-required native signing lanes
|
||||
through `.github/workflows/build-release-candidate.yml` while independent
|
||||
release checks run in parallel. `create-release.yml` may publish only that
|
||||
candidate after `scripts/release_candidate_manifest.py` verifies its version, source
|
||||
SHA, filenames, sizes, and SHA-256 values. Standard post-upload validation
|
||||
must compare that manifest with GitHub's server-side asset digests instead
|
||||
of downloading the complete release packet again. Historical repair and
|
||||
|
|
@ -374,17 +374,19 @@ TLS floor in the dynamic config.
|
|||
it must not decompress a multi-gigabyte release archive again for every
|
||||
required entry, and the release-promotion contract test must reject a return
|
||||
to per-entry archive streaming.
|
||||
A manually dispatched release rehearsal must activate the same signed
|
||||
A manually dispatched release rehearsal must activate the same
|
||||
candidate build whenever its required `version` input is non-empty and must
|
||||
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;
|
||||
prerelease candidates may retain checksum and detached-signature
|
||||
verification without Authenticode while the release packet explicitly
|
||||
discloses the unknown-publisher warning and stable promotion remains
|
||||
blocked. 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
|
||||
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
|
||||
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
|
||||
by id, verify every returned executable, and retain evidence binding the
|
||||
request, source SHA, signer identity, and file digests. A repository-secret
|
||||
|
|
@ -1028,9 +1030,12 @@ Authenticode backend. The reusable builder fails fast on missing configuration,
|
|||
submits the GitHub-hosted unsigned artifact through the pinned official action,
|
||||
verifies all returned executables, and stores request/source/signer/digest
|
||||
evidence beside the candidate manifest. Release Dry Run now has a terminal
|
||||
verdict covering the signed candidate and no-mutation demo lane. The gate stays
|
||||
blocked until the external SignPath project is configured and one stable dry
|
||||
run passes for an exact `main` SHA.
|
||||
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.
|
||||
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
|
||||
|
|
@ -1049,7 +1054,7 @@ This stable minor release uses `promoted_from_tag=v6.1.0-rc.4`,
|
|||
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
|
||||
the signed no-publication dry run before the same SHA is dispatched through the
|
||||
the no-publication dry run before the same SHA is dispatched through the
|
||||
single-build publish workflow. The release publishes versioned GitHub, Docker,
|
||||
and Helm artifacts and advances the stable/latest install pointers and stable
|
||||
semver aliases. It promotes
|
||||
|
|
@ -1080,9 +1085,12 @@ Agent Doctor into a routed, filterable diagnostic workflow with copyable
|
|||
reports and platform-correct host-local cleanup handoff, adds SAS and SCSI SMART
|
||||
coverage, restores actionable agent-update controls, fixes Patrol finding and
|
||||
proposal provenance, improves metrics and audit-store concurrency, and prepares
|
||||
the stable Windows SignPath signing path. Stable publication requires the
|
||||
SignPath-returned Windows binaries to pass Authenticode and signer verification;
|
||||
the unsigned-Windows prerelease exception does not apply to `v6.1.0`.
|
||||
the stable Windows SignPath signing path. For `v6.1.0`, the release owner
|
||||
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
|
||||
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`
|
||||
|
|
@ -2465,7 +2473,9 @@ externally owned bounded residual, but only when the RC packet explicitly
|
|||
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. `scripts/build-release.sh` must replace
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -41,8 +41,9 @@ plus the final compatibility and reliability fixes in the stable cutoff.
|
|||
- `--report-ip` can set the Unified Agent's reported host address and becomes
|
||||
the leading host-identity signal.
|
||||
- Disk monitoring understands SAS transport and SCSI SMART attributes.
|
||||
- Windows Unified Agent artifacts use the governed exact-SHA SignPath path;
|
||||
stable publication requires Authenticode, signer, and digest verification.
|
||||
- Windows Unified Agent artifacts remain bound to the exact release SHA and
|
||||
retain checksum, detached-signature, manifest, and published-digest
|
||||
verification.
|
||||
|
||||
## Improved
|
||||
|
||||
|
|
@ -93,6 +94,11 @@ Use the normal v6 install or update flow for `v6.1.0`. Existing Unified Agent
|
|||
configurations keep one authoritative Pulse server; observer reporting remains
|
||||
opt-in and no observer is created during upgrade.
|
||||
|
||||
Windows Unified Agent binaries in `v6.1.0` are not Authenticode-signed and may
|
||||
show an Unknown Publisher warning. Verify the published checksums and detached
|
||||
`.sig` or `.sshsig` signatures before installation. This is a one-release owner
|
||||
exception; later stable releases restore the Windows Authenticode requirement.
|
||||
|
||||
The rollback target is `v6.0.5`. The exact rollback reinstall command is:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ _This changelog describes stable `v6.1.0` compared with stable `v6.0.5`._
|
|||
bounded native-tool execution.
|
||||
- Routed Agent Doctor workflow, `--report-ip` host identity, SAS transport, and
|
||||
SCSI SMART attribute support.
|
||||
- Exact-SHA SignPath submission, Authenticode verification, signer validation,
|
||||
and evidence recording for stable Windows Unified Agent artifacts.
|
||||
- Exact-SHA, checksum, detached-signature, manifest, and published-digest
|
||||
verification for Windows Unified Agent artifacts.
|
||||
|
||||
## Changed
|
||||
|
||||
|
|
@ -60,7 +60,11 @@ _This changelog describes stable `v6.1.0` compared with stable `v6.0.5`._
|
|||
- Rollback target: `v6.0.5`
|
||||
- Rollback command: `./scripts/install.sh --version v6.0.5`
|
||||
- Promotion path: owner-approved exact-SHA stable cutoff from `main`, using the
|
||||
one-version soak exception and the single-build signed-candidate workflow
|
||||
one-version soak exception and the single-build release-candidate workflow
|
||||
- Windows signing decision: one-release owner exception; the `v6.1.0` Windows
|
||||
Unified Agent binaries are not Authenticode-signed and may show an Unknown
|
||||
Publisher warning, while checksums and detached `.sig`/`.sshsig` signatures
|
||||
remain required. Later stable releases restore the Authenticode requirement.
|
||||
- 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
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ func TestCurrentStableMinorReleasePacketTracksInstallMetadata(t *testing.T) {
|
|||
"Operational Trust",
|
||||
"Actions provides a dedicated inbox",
|
||||
"existing Pulse Mobile candidate",
|
||||
"SignPath",
|
||||
"not Authenticode-signed",
|
||||
"The rollback target is `v"+previous+"`",
|
||||
)
|
||||
assertFileContainsAllNormalized(t, changelogPath,
|
||||
|
|
@ -383,9 +383,9 @@ func TestCurrentStableMinorReleasePacketTracksInstallMetadata(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.",
|
||||
"`rollback_version=v"+previous+"`",
|
||||
"The exact stable `main` SHA must pass the signed no-publication dry run before the same SHA is dispatched through the single-build publish workflow.",
|
||||
"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`.",
|
||||
"Stable publication requires the SignPath-returned Windows binaries to pass Authenticode and signer verification",
|
||||
"This exception cannot apply to a later stable version",
|
||||
"For the active stable `v"+version+"` cut, the repo-root compose default and `scripts/install-docker.sh` fallback must both pin `"+version+"`",
|
||||
)
|
||||
}
|
||||
|
|
@ -842,14 +842,23 @@ func TestReleaseCandidateRequiresPlatformNativeAgentSigning(t *testing.T) {
|
|||
}
|
||||
assertFileContainsAll(t, repoFile(".github", "workflows", "create-release.yml"),
|
||||
`require_macos_signing: true`,
|
||||
`require_windows_signing: ${{ needs.prepare.outputs.is_prerelease != 'true' }}`,
|
||||
`require_windows_signing: ${{ needs.prepare.outputs.require_windows_signing == 'true' }}`,
|
||||
`unsigned_windows_exception:`,
|
||||
`unsigned_windows_reason:`,
|
||||
`windows_signing_backend: signpath`,
|
||||
)
|
||||
assertFileContainsAll(t, repoFile(".github", "workflows", "release-dry-run.yml"),
|
||||
`Definitive Dry-Run Verdict`,
|
||||
`require_result "exact-SHA signed candidate" "$CANDIDATE_RESULT" success`,
|
||||
`require_windows_signing: ${{ !contains(inputs.version, '-') && !(inputs.version == '6.1.0' && 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"`,
|
||||
`unsigned_windows_reason is required`,
|
||||
`not Authenticode-signed`,
|
||||
`require_windows_signing = not is_prerelease and not unsigned_windows_exception`,
|
||||
)
|
||||
assertFileContainsAll(t, repoFile("scripts", "build-release.sh"),
|
||||
`PULSE_AGENT_NATIVE_BINARIES_DIR`,
|
||||
`native_targets=()`,
|
||||
|
|
|
|||
|
|
@ -222,6 +222,9 @@ def render_record(
|
|||
published_eos = v5_eos_date or summary_metadata.get("planned_v5_eos_date", "")
|
||||
hotfix_exception = summary_metadata.get("hotfix_exception", "")
|
||||
hotfix_reason = summary_metadata.get("hotfix_reason", "")
|
||||
require_windows_signing = summary_metadata.get("windows_authenticode_required", "")
|
||||
unsigned_windows_exception = summary_metadata.get("unsigned_windows_exception", "")
|
||||
unsigned_windows_reason = summary_metadata.get("unsigned_windows_reason", "")
|
||||
operator_note = summary_metadata.get("operator_note", "")
|
||||
|
||||
lines = [
|
||||
|
|
@ -248,6 +251,12 @@ def render_record(
|
|||
lines.append(f"- Hotfix exception: {hotfix_exception}")
|
||||
if hotfix_reason:
|
||||
lines.append(f"- Hotfix reason: {hotfix_reason}")
|
||||
if require_windows_signing:
|
||||
lines.append(f"- Windows Authenticode required: {require_windows_signing}")
|
||||
if unsigned_windows_exception:
|
||||
lines.append(f"- Unsigned Windows exception: {unsigned_windows_exception}")
|
||||
if unsigned_windows_reason:
|
||||
lines.append(f"- Unsigned Windows reason: {unsigned_windows_reason}")
|
||||
if operator_note:
|
||||
lines.append(f"- Workflow operator note: {operator_note}")
|
||||
for note in notes:
|
||||
|
|
|
|||
|
|
@ -29,6 +29,9 @@ SUMMARY = """# Prerelease-to-GA Rehearsal Summary
|
|||
- Planned GA date: 2026-03-15
|
||||
- Planned v5 end-of-support date: 2026-06-11
|
||||
- Hotfix exception: false
|
||||
- Windows Authenticode required: false
|
||||
- Unsigned Windows exception: true
|
||||
- Unsigned Windows reason: v6.1.0 owner exception
|
||||
- Operator note: dry-run from test
|
||||
"""
|
||||
|
||||
|
|
@ -43,6 +46,8 @@ class RecordRcToGaRehearsalTest(unittest.TestCase):
|
|||
self.assertEqual(parsed["rollback_command"], "`./scripts/install.sh --version v5.1.23`")
|
||||
self.assertEqual(parsed["planned_ga_date"], "2026-03-15")
|
||||
self.assertEqual(parsed["planned_v5_eos_date"], "2026-06-11")
|
||||
self.assertEqual(parsed["windows_authenticode_required"], "false")
|
||||
self.assertEqual(parsed["unsigned_windows_exception"], "true")
|
||||
|
||||
def test_render_record_contains_required_fields(self) -> None:
|
||||
rendered = mod.render_record(
|
||||
|
|
@ -65,6 +70,9 @@ class RecordRcToGaRehearsalTest(unittest.TestCase):
|
|||
self.assertIn("Exact rollback or reinstall command: `./scripts/install.sh --version v5.1.23`", rendered)
|
||||
self.assertIn("Exact GA date to publish: 2026-03-15", rendered)
|
||||
self.assertIn("Exact v5 end-of-support date to publish: 2026-06-13", rendered)
|
||||
self.assertIn("Windows Authenticode required: false", rendered)
|
||||
self.assertIn("Unsigned Windows exception: true", rendered)
|
||||
self.assertIn("Unsigned Windows reason: v6.1.0 owner exception", rendered)
|
||||
self.assertIn("Publish GA release notes with the EOS date.", rendered)
|
||||
self.assertIn("```md", rendered)
|
||||
|
||||
|
|
|
|||
|
|
@ -487,7 +487,12 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("build_release_candidate:", workflow)
|
||||
self.assertIn("if: ${{ inputs.version != '' }}", workflow)
|
||||
self.assertIn("require_macos_signing: true", workflow)
|
||||
self.assertIn("require_windows_signing: ${{ !contains(inputs.version, '-') }}", workflow)
|
||||
self.assertIn(
|
||||
"require_windows_signing: ${{ !contains(inputs.version, '-') && !(inputs.version == '6.1.0' && inputs.unsigned_windows_exception) }}",
|
||||
workflow,
|
||||
)
|
||||
self.assertIn("unsigned_windows_exception:", workflow)
|
||||
self.assertIn("unsigned_windows_reason:", workflow)
|
||||
self.assertIn("windows_signing_backend: signpath", workflow)
|
||||
self.assertRegex(
|
||||
workflow,
|
||||
|
|
@ -500,7 +505,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
r" actions: read\n contents: read\n uses: \.\/\.github\/workflows\/build-release-candidate\.yml$",
|
||||
)
|
||||
self.assertIn("Definitive Dry-Run Verdict", workflow)
|
||||
self.assertIn('require_result "exact-SHA signed candidate" "$CANDIDATE_RESULT" success', workflow)
|
||||
self.assertIn('require_result "exact-SHA release candidate" "$CANDIDATE_RESULT" success', workflow)
|
||||
self.assertIn('require_result "stable demo no-mutation verification" "$DEMO_RESULT" success', workflow)
|
||||
self.assertNotIn("if: ${{ github.event_name == 'workflow_dispatch' }}", workflow)
|
||||
self.assertIn("record_rc_to_ga_rehearsal.py --run-id ${{ github.run_id }}", workflow)
|
||||
|
|
@ -661,7 +666,11 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("timeout-minutes: 60", candidate_workflow)
|
||||
self.assertIn("Verify Native Signing Configuration", candidate_workflow)
|
||||
self.assertEqual(candidate_workflow.count("needs: signing-configuration"), 2)
|
||||
self.assertIn("require_windows_signing: ${{ needs.prepare.outputs.is_prerelease != 'true' }}", content)
|
||||
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("not Authenticode-signed", resolver)
|
||||
self.assertIn("windows_signing_backend: signpath", content)
|
||||
self.assertIn('if [[ "$REQUIRE_WINDOWS_SIGNING" == "true" ]]', candidate_workflow)
|
||||
self.assertIn("inputs.require_windows_signing", candidate_workflow)
|
||||
|
|
|
|||
|
|
@ -219,6 +219,10 @@ def build_promotion_metadata_section(args: argparse.Namespace) -> str:
|
|||
lines.append(f"- Hotfix exception: {args.hotfix_exception}")
|
||||
if args.hotfix_reason:
|
||||
lines.append(f"- Hotfix reason: {args.hotfix_reason}")
|
||||
lines.append(f"- Windows Authenticode required: {args.require_windows_signing}")
|
||||
lines.append(f"- Unsigned Windows exception: {args.unsigned_windows_exception}")
|
||||
if args.unsigned_windows_reason:
|
||||
lines.append(f"- Unsigned Windows reason: {args.unsigned_windows_reason}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
|
@ -239,6 +243,9 @@ def parse_args() -> argparse.Namespace:
|
|||
parser.add_argument("--planned-v5-eos-date", default="")
|
||||
parser.add_argument("--hotfix-exception")
|
||||
parser.add_argument("--hotfix-reason", default="")
|
||||
parser.add_argument("--require-windows-signing")
|
||||
parser.add_argument("--unsigned-windows-exception")
|
||||
parser.add_argument("--unsigned-windows-reason", default="")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.validate_notes_file or args.validate_body_file:
|
||||
|
|
@ -264,6 +271,8 @@ def parse_args() -> argparse.Namespace:
|
|||
"--rollback-target": args.rollback_target,
|
||||
"--rollback-command": args.rollback_command,
|
||||
"--hotfix-exception": args.hotfix_exception,
|
||||
"--require-windows-signing": args.require_windows_signing,
|
||||
"--unsigned-windows-exception": args.unsigned_windows_exception,
|
||||
}
|
||||
missing = [name for name, value in required_render_args.items() if value is None]
|
||||
if missing:
|
||||
|
|
|
|||
|
|
@ -91,6 +91,9 @@ Old metadata section.
|
|||
"planned_v5_eos_date": "",
|
||||
"hotfix_exception": "false",
|
||||
"hotfix_reason": "",
|
||||
"require_windows_signing": "false",
|
||||
"unsigned_windows_exception": "false",
|
||||
"unsigned_windows_reason": "",
|
||||
},
|
||||
)()
|
||||
|
||||
|
|
@ -113,6 +116,8 @@ Old metadata section.
|
|||
)
|
||||
self.assertIn("https://pulserelay.pro/download.html", body)
|
||||
self.assertIn("- Rollback target: v5.1.28", body)
|
||||
self.assertIn("- Windows Authenticode required: false", body)
|
||||
self.assertIn("- Unsigned Windows exception: false", body)
|
||||
render_release_body.validate_release_body_shape(body, "6.0.0-rc.2")
|
||||
|
||||
def test_flattened_release_notes_fail_closed(self) -> None:
|
||||
|
|
|
|||
|
|
@ -221,6 +221,8 @@ def resolve_metadata(
|
|||
hotfix_exception: bool,
|
||||
hotfix_reason_input: str,
|
||||
release_notes_input: str,
|
||||
unsigned_windows_exception: bool = False,
|
||||
unsigned_windows_reason_input: str = "",
|
||||
derive_rollback_when_missing: bool = False,
|
||||
list_stable_tags_fn: Callable[[], list[str]] = list_stable_tags,
|
||||
list_same_version_rc_tags_fn: Callable[[str], list[str]] = list_same_version_rc_tags,
|
||||
|
|
@ -236,11 +238,33 @@ def resolve_metadata(
|
|||
ga_date = (ga_date_input or "").strip()
|
||||
v5_eos_date = (v5_eos_date_input or "").strip()
|
||||
hotfix_reason = normalize_whitespace(hotfix_reason_input)
|
||||
unsigned_windows_reason = normalize_whitespace(unsigned_windows_reason_input)
|
||||
release_notes = release_notes_input or ""
|
||||
is_prerelease = is_prerelease_version(version)
|
||||
stable_patch = is_stable_patch_version(version)
|
||||
promotion_mode = "prerelease" if is_prerelease else "stable-rc-promotion"
|
||||
|
||||
if unsigned_windows_exception:
|
||||
if version != "6.1.0":
|
||||
raise ValueError(
|
||||
"unsigned_windows_exception is approved only for stable v6.1.0. "
|
||||
"Later stable releases must restore Windows Authenticode signing."
|
||||
)
|
||||
if not unsigned_windows_reason:
|
||||
raise ValueError(
|
||||
"unsigned_windows_reason is required when unsigned_windows_exception is true."
|
||||
)
|
||||
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."
|
||||
)
|
||||
elif unsigned_windows_reason:
|
||||
raise ValueError(
|
||||
"unsigned_windows_reason is allowed only when unsigned_windows_exception is true."
|
||||
)
|
||||
|
||||
require_windows_signing = not is_prerelease and not unsigned_windows_exception
|
||||
|
||||
if not rollback_tag and derive_rollback_when_missing:
|
||||
rollback_tag = derive_latest_stable_rollback_tag(version, list_stable_tags_fn())
|
||||
if not rollback_tag:
|
||||
|
|
@ -374,6 +398,9 @@ def resolve_metadata(
|
|||
"v5_eos_date": v5_eos_date,
|
||||
"hotfix_exception": "true" if hotfix_exception else "false",
|
||||
"hotfix_reason": hotfix_reason,
|
||||
"unsigned_windows_exception": "true" if unsigned_windows_exception else "false",
|
||||
"unsigned_windows_reason": unsigned_windows_reason,
|
||||
"require_windows_signing": "true" if require_windows_signing else "false",
|
||||
"soak_hours": soak_hours,
|
||||
}
|
||||
|
||||
|
|
@ -395,6 +422,8 @@ def parse_args() -> argparse.Namespace:
|
|||
parser.add_argument("--v5-eos-date", default="")
|
||||
parser.add_argument("--hotfix-exception", action="store_true")
|
||||
parser.add_argument("--hotfix-reason", default="")
|
||||
parser.add_argument("--unsigned-windows-exception", action="store_true")
|
||||
parser.add_argument("--unsigned-windows-reason", default="")
|
||||
parser.add_argument("--release-notes-file", default="")
|
||||
return parser.parse_args()
|
||||
|
||||
|
|
@ -415,6 +444,8 @@ def main() -> int:
|
|||
hotfix_exception=args.hotfix_exception,
|
||||
hotfix_reason_input=args.hotfix_reason,
|
||||
release_notes_input=release_notes,
|
||||
unsigned_windows_exception=args.unsigned_windows_exception,
|
||||
unsigned_windows_reason_input=args.unsigned_windows_reason,
|
||||
)
|
||||
|
||||
for key, value in metadata.items():
|
||||
|
|
|
|||
|
|
@ -138,6 +138,83 @@ class ResolveReleasePromotionTest(unittest.TestCase):
|
|||
)
|
||||
self.assertEqual(metadata["promoted_from_tag"], "v6.0.0-rc.2")
|
||||
self.assertEqual(metadata["soak_hours"], "73")
|
||||
self.assertEqual(metadata["require_windows_signing"], "true")
|
||||
self.assertEqual(metadata["unsigned_windows_exception"], "false")
|
||||
|
||||
def test_v610_owner_exception_allows_disclosed_unsigned_windows_candidate(self) -> None:
|
||||
metadata = resolver.resolve_metadata(
|
||||
version="6.1.0",
|
||||
promoted_from_tag_input="v6.1.0-rc.4",
|
||||
rollback_version_input="v6.0.5",
|
||||
ga_date_input="",
|
||||
v5_eos_date_input="",
|
||||
hotfix_exception=True,
|
||||
hotfix_reason_input="Owner waived the remaining prerelease soak.",
|
||||
release_notes_input=(
|
||||
"Windows Unified Agent binaries are not Authenticode-signed for v6.1.0."
|
||||
),
|
||||
unsigned_windows_exception=True,
|
||||
unsigned_windows_reason_input=(
|
||||
"Release owner accepted the Windows unknown-publisher warning for v6.1.0."
|
||||
),
|
||||
tag_exists_fn=lambda tag: tag in {"v6.1.0-rc.4", "v6.0.5"},
|
||||
tag_commit_fn=lambda tag: "rc4-commit",
|
||||
head_descends_from_fn=lambda commit: commit == "rc4-commit",
|
||||
tag_created_unix_fn=lambda tag: 100,
|
||||
now_unix_fn=lambda: 100 + (27 * 3600),
|
||||
)
|
||||
|
||||
self.assertEqual(metadata["require_windows_signing"], "false")
|
||||
self.assertEqual(metadata["unsigned_windows_exception"], "true")
|
||||
self.assertEqual(
|
||||
metadata["unsigned_windows_reason"],
|
||||
"Release owner accepted the Windows unknown-publisher warning for v6.1.0.",
|
||||
)
|
||||
|
||||
def test_unsigned_windows_exception_is_rejected_for_other_stable_versions(self) -> None:
|
||||
with self.assertRaisesRegex(ValueError, "approved only for stable v6.1.0"):
|
||||
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="Emergency patch.",
|
||||
release_notes_input="Windows binaries are not Authenticode-signed.",
|
||||
unsigned_windows_exception=True,
|
||||
unsigned_windows_reason_input="Not approved for this version.",
|
||||
tag_exists_fn=lambda tag: True,
|
||||
)
|
||||
|
||||
def test_unsigned_windows_exception_requires_reason_and_release_note_disclosure(self) -> None:
|
||||
common = {
|
||||
"version": "6.1.0",
|
||||
"promoted_from_tag_input": "v6.1.0-rc.4",
|
||||
"rollback_version_input": "v6.0.5",
|
||||
"ga_date_input": "",
|
||||
"v5_eos_date_input": "",
|
||||
"hotfix_exception": True,
|
||||
"hotfix_reason_input": "Owner waived the remaining prerelease soak.",
|
||||
"unsigned_windows_exception": True,
|
||||
"tag_exists_fn": lambda tag: True,
|
||||
"tag_commit_fn": lambda tag: "rc4-commit",
|
||||
"head_descends_from_fn": lambda commit: True,
|
||||
"tag_created_unix_fn": lambda tag: 100,
|
||||
"now_unix_fn": lambda: 100 + (27 * 3600),
|
||||
}
|
||||
with self.assertRaisesRegex(ValueError, "unsigned_windows_reason is required"):
|
||||
resolver.resolve_metadata(
|
||||
**common,
|
||||
release_notes_input="Windows binaries are not Authenticode-signed.",
|
||||
unsigned_windows_reason_input="",
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "must disclose"):
|
||||
resolver.resolve_metadata(
|
||||
**common,
|
||||
release_notes_input="Windows agent details omitted.",
|
||||
unsigned_windows_reason_input="Owner accepted the warning.",
|
||||
)
|
||||
|
||||
def test_stable_requires_release_notes_notice_when_supplied(self) -> None:
|
||||
with self.assertRaisesRegex(
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ python3 scripts/check-workflow-dispatch-inputs.py \
|
|||
--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
|
||||
|
|
@ -114,6 +116,8 @@ GA_DATE=""
|
|||
V5_EOS_DATE=""
|
||||
HOTFIX_EXCEPTION="false"
|
||||
HOTFIX_REASON=""
|
||||
UNSIGNED_WINDOWS_EXCEPTION="false"
|
||||
UNSIGNED_WINDOWS_REASON=""
|
||||
|
||||
echo ""
|
||||
read -r -p "Rollback stable version (for example 5.1.14 or v5.1.14): " ROLLBACK_VERSION
|
||||
|
|
@ -166,6 +170,19 @@ if [ "$IS_PRERELEASE" != "true" ]; then
|
|||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$VERSION" = "6.1.0" ]; then
|
||||
echo ""
|
||||
read -r -p "Use the recorded v6.1.0 unsigned Windows exception? [y/N] " UNSIGNED_WINDOWS_REPLY
|
||||
if [[ "$UNSIGNED_WINDOWS_REPLY" =~ ^[Yy]$ ]]; then
|
||||
UNSIGNED_WINDOWS_EXCEPTION="true"
|
||||
read -r -p "Unsigned Windows exception reason: " UNSIGNED_WINDOWS_REASON
|
||||
if [ -z "$UNSIGNED_WINDOWS_REASON" ]; then
|
||||
echo "❌ Error: an owner reason is required for the unsigned Windows exception"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
|
@ -186,6 +203,12 @@ fi
|
|||
if [ "$HOTFIX_EXCEPTION" = "true" ]; then
|
||||
RESOLVER_ARGS+=(--hotfix-exception --hotfix-reason "$HOTFIX_REASON")
|
||||
fi
|
||||
if [ "$UNSIGNED_WINDOWS_EXCEPTION" = "true" ]; then
|
||||
RESOLVER_ARGS+=(
|
||||
--unsigned-windows-exception
|
||||
--unsigned-windows-reason "$UNSIGNED_WINDOWS_REASON"
|
||||
)
|
||||
fi
|
||||
python3 scripts/release_control/resolve_release_promotion.py "${RESOLVER_ARGS[@]}" >/tmp/pulse-release-dry-run-metadata.out
|
||||
cat /tmp/pulse-release-dry-run-metadata.out
|
||||
|
||||
|
|
@ -200,6 +223,8 @@ gh workflow run release-dry-run.yml \
|
|||
-f v5_eos_date="$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="Governed release rehearsal for ${VERSION}" \
|
||||
-f mobile_release_decision="$MOBILE_RELEASE_DECISION" \
|
||||
-f mobile_release_evidence="$MOBILE_RELEASE_EVIDENCE"
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ python3 scripts/check-workflow-dispatch-inputs.py \
|
|||
--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 +197,8 @@ GA_DATE=""
|
|||
V5_EOS_DATE=""
|
||||
HOTFIX_EXCEPTION="false"
|
||||
HOTFIX_REASON=""
|
||||
UNSIGNED_WINDOWS_EXCEPTION="false"
|
||||
UNSIGNED_WINDOWS_REASON=""
|
||||
|
||||
echo ""
|
||||
read -r -p "Rollback stable version (for example 5.1.14 or v5.1.14): " ROLLBACK_VERSION
|
||||
|
|
@ -256,6 +260,19 @@ if [ "$IS_PRERELEASE" != "true" ]; then
|
|||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$VERSION" = "6.1.0" ]; then
|
||||
echo ""
|
||||
read -r -p "Use the recorded v6.1.0 unsigned Windows exception? [y/N] " UNSIGNED_WINDOWS_REPLY
|
||||
if [[ "$UNSIGNED_WINDOWS_REPLY" =~ ^[Yy]$ ]]; then
|
||||
UNSIGNED_WINDOWS_EXCEPTION="true"
|
||||
read -r -p "Unsigned Windows exception reason: " UNSIGNED_WINDOWS_REASON
|
||||
if [ -z "$UNSIGNED_WINDOWS_REASON" ]; then
|
||||
echo "❌ Error: an owner reason is required for the unsigned Windows exception"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Trigger the workflow
|
||||
|
|
@ -279,6 +296,12 @@ fi
|
|||
if [ "$HOTFIX_EXCEPTION" = "true" ]; then
|
||||
RESOLVER_ARGS+=(--hotfix-exception)
|
||||
fi
|
||||
if [ "$UNSIGNED_WINDOWS_EXCEPTION" = "true" ]; then
|
||||
RESOLVER_ARGS+=(
|
||||
--unsigned-windows-exception
|
||||
--unsigned-windows-reason "$UNSIGNED_WINDOWS_REASON"
|
||||
)
|
||||
fi
|
||||
python3 scripts/release_control/resolve_release_promotion.py "${RESOLVER_ARGS[@]}" >/tmp/pulse-release-metadata.out
|
||||
cat /tmp/pulse-release-metadata.out
|
||||
|
||||
|
|
@ -294,6 +317,8 @@ if [ -n "$NOTES_FILE" ]; then
|
|||
--arg v5_eos_date "$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" \
|
||||
|
|
@ -306,6 +331,8 @@ if [ -n "$NOTES_FILE" ]; then
|
|||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue