mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-07-09 16:00:59 +00:00
Rename promotion RC wording to prerelease
This commit is contained in:
parent
31d9d96f82
commit
cb41473018
19 changed files with 126 additions and 111 deletions
10
.github/workflows/create-release.yml
vendored
10
.github/workflows/create-release.yml
vendored
|
|
@ -1,5 +1,5 @@
|
|||
name: Pulse Release Pipeline
|
||||
# Optimized: parallel jobs, fast RC path
|
||||
# Optimized: parallel jobs, fast prerelease path
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
|
@ -13,7 +13,7 @@ on:
|
|||
required: true
|
||||
type: string
|
||||
promoted_from_tag:
|
||||
description: 'Stable only: RC tag being promoted (for example 6.0.0-rc.2)'
|
||||
description: 'Stable only: prerelease tag being promoted (for example 6.0.0-rc.2)'
|
||||
required: false
|
||||
type: string
|
||||
rollback_version:
|
||||
|
|
@ -29,7 +29,7 @@ on:
|
|||
required: false
|
||||
type: string
|
||||
hotfix_exception:
|
||||
description: 'Stable only: bypass the 72-hour RC soak for urgent customer harm'
|
||||
description: 'Stable only: bypass the 72-hour prerelease soak for urgent customer harm'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
|
@ -503,9 +503,9 @@ jobs:
|
|||
echo "- Promotion channel: ${{ needs.prepare.outputs.is_prerelease == 'true' && 'rc' || 'stable' }}"
|
||||
echo "- Candidate stable tag: ${{ needs.prepare.outputs.tag }}"
|
||||
if [ -n "${{ needs.prepare.outputs.promoted_from_tag }}" ]; then
|
||||
echo "- Promoted RC tag: ${{ needs.prepare.outputs.promoted_from_tag }}"
|
||||
echo "- Promoted prerelease tag: ${{ needs.prepare.outputs.promoted_from_tag }}"
|
||||
else
|
||||
echo "- Promoted RC tag: n/a"
|
||||
echo "- Promoted prerelease tag: n/a"
|
||||
fi
|
||||
echo "- Rollback target: ${{ needs.prepare.outputs.rollback_tag }}"
|
||||
echo "- Rollback command: \`${{ needs.prepare.outputs.rollback_command }}\`"
|
||||
|
|
|
|||
4
.github/workflows/promote-floating-tags.yml
vendored
4
.github/workflows/promote-floating-tags.yml
vendored
|
|
@ -108,7 +108,7 @@ jobs:
|
|||
BASE_VERSION="${BASE_VERSION%%-*}"
|
||||
mapfile -t RC_TAGS < <(git tag -l "v${BASE_VERSION}-rc.*" --sort=-version:refname)
|
||||
if [ "${#RC_TAGS[@]}" -eq 0 ]; then
|
||||
echo "::error::Stable tag ${TAG} has no matching RC tags for base version ${BASE_VERSION}."
|
||||
echo "::error::Stable tag ${TAG} has no matching prerelease tags for base version ${BASE_VERSION}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ jobs:
|
|||
done
|
||||
|
||||
if [ -z "$MATCHED_RC" ]; then
|
||||
echo "::error::Stable tag ${TAG} does not descend from any matching RC tag for base version ${BASE_VERSION}."
|
||||
echo "::error::Stable tag ${TAG} does not descend from any matching prerelease tag for base version ${BASE_VERSION}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
4
.github/workflows/publish-docker.yml
vendored
4
.github/workflows/publish-docker.yml
vendored
|
|
@ -80,7 +80,7 @@ jobs:
|
|||
BASE_VERSION="${BASE_VERSION%%-*}"
|
||||
mapfile -t RC_TAGS < <(git tag -l "v${BASE_VERSION}-rc.*" --sort=-version:refname)
|
||||
if [ "${#RC_TAGS[@]}" -eq 0 ]; then
|
||||
echo "::error::Stable tag ${TAG} has no matching RC tags for base version ${BASE_VERSION}."
|
||||
echo "::error::Stable tag ${TAG} has no matching prerelease tags for base version ${BASE_VERSION}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ jobs:
|
|||
done
|
||||
|
||||
if [ -z "$MATCHED_RC" ]; then
|
||||
echo "::error::Stable tag ${TAG} does not descend from any matching RC tag for base version ${BASE_VERSION}."
|
||||
echo "::error::Stable tag ${TAG} does not descend from any matching prerelease tag for base version ${BASE_VERSION}."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
8
.github/workflows/release-dry-run.yml
vendored
8
.github/workflows/release-dry-run.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
|||
required: false
|
||||
type: string
|
||||
promoted_from_tag:
|
||||
description: 'Stable rehearsal only: RC tag being promoted (for example v6.0.0-rc.2)'
|
||||
description: 'Stable rehearsal only: prerelease tag being promoted (for example v6.0.0-rc.2)'
|
||||
required: false
|
||||
type: string
|
||||
rollback_version:
|
||||
|
|
@ -24,7 +24,7 @@ on:
|
|||
required: false
|
||||
type: string
|
||||
hotfix_exception:
|
||||
description: 'Stable rehearsal only: bypass 72-hour RC soak for urgent customer harm'
|
||||
description: 'Stable rehearsal only: bypass 72-hour prerelease soak for urgent customer harm'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
|
@ -256,7 +256,7 @@ jobs:
|
|||
SUMMARY_FILE="release-dry-run/rc-to-ga-rehearsal-summary.md"
|
||||
RUN_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
{
|
||||
echo "# RC-to-GA Rehearsal Summary"
|
||||
echo "# Prerelease-to-GA Rehearsal Summary"
|
||||
echo ""
|
||||
echo "- Workflow run: ${RUN_URL}"
|
||||
echo "- Branch: ${GITHUB_REF_NAME}"
|
||||
|
|
@ -264,7 +264,7 @@ jobs:
|
|||
echo "- Candidate stable tag: ${{ steps.rehearsal.outputs.tag }}"
|
||||
echo "- Promotion channel: ${{ steps.rehearsal.outputs.is_prerelease == 'true' && 'rc' || 'stable' }}"
|
||||
if [ -n "${{ steps.rehearsal.outputs.promoted_from_tag }}" ]; then
|
||||
echo "- Promoted RC tag: ${{ steps.rehearsal.outputs.promoted_from_tag }}"
|
||||
echo "- Promoted prerelease tag: ${{ steps.rehearsal.outputs.promoted_from_tag }}"
|
||||
fi
|
||||
if [ -n "${{ steps.rehearsal.outputs.rollback_tag }}" ]; then
|
||||
echo "- Rollback target: ${{ steps.rehearsal.outputs.rollback_tag }}"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ The evergreen control-plane files live one level up:
|
|||
1. `../CONTROL_PLANE.md` (evergreen governance, active-profile selection, and active-target rules)
|
||||
2. `../control_plane.json` (machine-readable active profile and active target)
|
||||
3. `internal/SOURCE_OF_TRUTH.md` (stable human governance, readiness-assertion design rules, and locked decisions)
|
||||
4. `internal/RELEASE_PROMOTION_POLICY.md` (canonical stable-versus-RC promotion rules, rollout criteria, and rollback expectations)
|
||||
4. `internal/RELEASE_PROMOTION_POLICY.md` (canonical stable-versus-prerelease promotion rules, rollout criteria, and rollback expectations)
|
||||
5. `internal/status.json` (live lane state, coverage-gap discovery records, readiness derivation rules, the active readiness assertion catalog, lane-to-subsystem ownership, structured evidence references, typed lane/subsystem decision records, and canonical ordered lists)
|
||||
6. `status.schema.json` (machine-readable contract for the `status.json` shape)
|
||||
7. `internal/subsystems/registry.json` (machine-readable subsystem ownership, explicit shared-ownership exceptions, and proof routing)
|
||||
|
|
@ -22,7 +22,7 @@ The evergreen control-plane files live one level up:
|
|||
|
||||
`status.json` reporting every lane as `target-met` means the tracked v6
|
||||
repo-hardening work is at target. It does not, by itself, mean Pulse v6 is
|
||||
ready to cut an RC or approve stable/GA while phase-blocking
|
||||
ready to cut a prerelease or approve stable/GA while phase-blocking
|
||||
`readiness_assertions`, `open_decisions`, or `release_gates` remain unresolved.
|
||||
Use `python3 scripts/release_control/status_audit.py --pretty` for the current
|
||||
derived `repo_ready`, `rc_ready`, and `release_ready` summary.
|
||||
|
|
@ -62,7 +62,7 @@ Supporting governance file:
|
|||
- `CONSOLIDATION_MAP.md` (legacy-doc demotion and archival map)
|
||||
- `RETIREMENT_AUDIT_2026-02-27.md` (file-by-file audited retirement decisions)
|
||||
- `internal/CANONICAL_DEVELOPMENT_PROTOCOL.md` (canonical subsystem development protocol)
|
||||
- `internal/RELEASE_PROMOTION_POLICY.md` (customer-facing release-train contract for `stable` versus `rc`)
|
||||
- `internal/RELEASE_PROMOTION_POLICY.md` (customer-facing release-train contract for `stable` versus the prerelease `rc` channel)
|
||||
- `internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md` (manual verification runbook for trust-critical release gates)
|
||||
- `internal/RC_TO_GA_REHEARSAL_TEMPLATE.md` (human record template for the non-publish GA rehearsal run)
|
||||
- `internal/subsystems/*.md` (per-subsystem contracts: truth, shared boundaries, extension points, forbidden paths, completion obligations)
|
||||
|
|
|
|||
|
|
@ -20,14 +20,14 @@ Use this as the final gate before cutting a Pulse v6 pre-release.
|
|||
- The remaining release-ready blocker is `rc-to-ga-promotion-readiness`, which
|
||||
stays blocked until a later stable `6.0.0` candidate completes a matching
|
||||
`Release Dry Run` rehearsal with the canonical promotion artifact envelope:
|
||||
candidate stable tag, promotion channel, promoted RC tag, rollback target,
|
||||
candidate stable tag, promotion channel, promoted prerelease tag, rollback target,
|
||||
exact rollback command, exact GA date, and exact v5 end-of-support inputs.
|
||||
- Mobile is in scope for the release and now has targeted readiness coverage in `pulse-mobile`.
|
||||
- High-risk release confidence now lives in `docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md` and should be cleared alongside this checklist.
|
||||
|
||||
## Promotion Policy
|
||||
- [ ] Record the previous stable tag and exact rollback pin command before publishing a new RC or stable release.
|
||||
- [ ] For the GA/stable candidate, confirm the release pipeline has already been exercised on a real RC tag, not only linted or YAML-parsed.
|
||||
- [ ] For the GA/stable candidate, confirm the release pipeline has already been exercised on a real prerelease tag, not only linted or YAML-parsed.
|
||||
- [ ] For stable promotion, confirm the candidate commit has already shipped on `rc`.
|
||||
- [ ] For stable promotion, confirm the chosen `promoted_from_tag` is a prerelease that was actually published through the governed RC path, not an accidental git tag.
|
||||
- [ ] For stable promotion, confirm the RC soak window is at least 72 hours or document the hotfix exception explicitly.
|
||||
|
|
@ -35,7 +35,7 @@ Use this as the final gate before cutting a Pulse v6 pre-release.
|
|||
- [ ] For GA/stable promotion, confirm `V5_MAINTENANCE_SUPPORT_POLICY.md` is still the intended policy and replace any placeholder GA notice dates with the exact v6 GA date and exact v5 end-of-support date that will ship with the announcement.
|
||||
- [ ] For GA/stable promotion, confirm the default-branch copy of `.github/workflows/release-dry-run.yml` already accepts the governed stable rehearsal metadata envelope (`promoted_from_tag`, `rollback_version`, `ga_date`, `v5_eos_date`) through `workflow_dispatch`, because GitHub validates dispatch inputs against the default branch even when dispatching `pulse/v6-release`.
|
||||
- [ ] For GA/stable promotion, confirm the release rehearsal and publish workflows both derive the governed release branch from release-control metadata instead of hardcoding a branch name inline.
|
||||
- [ ] For GA/stable promotion, attach a `Release Dry Run` run URL and `rc-to-ga-rehearsal-summary` artifact to the release ticket, and confirm that artifact records the canonical promotion metadata envelope for that candidate: candidate stable tag, promotion channel, promoted RC tag, rollback target, exact rollback command, planned GA date, and planned v5 end-of-support date.
|
||||
- [ ] For GA/stable promotion, attach a `Release Dry Run` run URL and `rc-to-ga-rehearsal-summary` artifact to the release ticket, and confirm that artifact records the canonical promotion metadata envelope for that candidate: candidate stable tag, promotion channel, promoted prerelease tag, rollback target, exact rollback command, planned GA date, and planned v5 end-of-support date.
|
||||
|
||||
## Scope
|
||||
- [x] Confirm whether there is a separate mobile app codebase.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Pulse v6 RC-to-GA Rehearsal Record
|
||||
# Pulse v6 Prerelease-to-GA Rehearsal Record
|
||||
|
||||
Use this template to capture the human-side evidence for
|
||||
`rc-to-ga-promotion-readiness` after running the `Release Dry Run` workflow.
|
||||
|
|
@ -11,13 +11,13 @@ The matching GitHub Actions artifact should be the machine-generated
|
|||
`rc-to-ga-rehearsal-summary`.
|
||||
|
||||
Treat that artifact as the machine-owned source for the candidate stable tag,
|
||||
promotion channel, promoted RC tag, rollback target, exact rollback command,
|
||||
promotion channel, promoted prerelease tag, rollback target, exact rollback command,
|
||||
and planned GA/EOS dates. Only override those values in the human record if
|
||||
the operator is correcting a verified artifact mismatch.
|
||||
|
||||
The generator should fail closed if the artifact omits any of that promotion
|
||||
metadata. Do not repair a malformed artifact by hand-writing the missing
|
||||
candidate tag, promotion channel, promoted RC tag, rollback target, rollback
|
||||
candidate tag, promotion channel, promoted prerelease tag, rollback target, rollback
|
||||
command, or GA/EOS dates into the record.
|
||||
|
||||
## Required Fields
|
||||
|
|
@ -27,10 +27,10 @@ command, or GA/EOS dates into the record.
|
|||
3. Version under rehearsal:
|
||||
4. Candidate stable tag:
|
||||
5. Promotion channel:
|
||||
6. Promoted RC tag:
|
||||
6. Promoted prerelease tag:
|
||||
7. Current rollback target:
|
||||
8. Exact rollback or reinstall command:
|
||||
9. RC soak hours at rehearsal time:
|
||||
9. Prerelease soak hours at rehearsal time:
|
||||
10. Exact GA date to publish with GA:
|
||||
11. v5 end-of-support date to publish with GA:
|
||||
12. Result:
|
||||
|
|
@ -42,7 +42,7 @@ command, or GA/EOS dates into the record.
|
|||
publication.
|
||||
3. Confirm no manual input was surprising or ambiguous during the run.
|
||||
4. Confirm the artifact-owned candidate stable tag, promotion channel,
|
||||
promoted RC tag, rollback target, exact rollback command, planned GA date,
|
||||
promoted prerelease tag, rollback target, exact rollback command, planned GA date,
|
||||
and planned v5 end-of-support date match the intended publish notice.
|
||||
5. Record any follow-up issue that must be fixed before the real promotion.
|
||||
6. Link this record and the matching artifact from the release ticket before
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ This file must not contain:
|
|||
Per-subsystem contracts: truth, extension points, forbidden paths, and
|
||||
completion obligations.
|
||||
11. `docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md`
|
||||
Canonical stable-versus-RC promotion rules, rollout criteria, and rollback
|
||||
Canonical stable-versus-prerelease promotion rules, rollout criteria, and rollback
|
||||
expectations for v6 and later release lines.
|
||||
12. `docs/release-control/v6/internal/V5_MAINTENANCE_SUPPORT_POLICY.md`
|
||||
Canonical v5 maintenance-only support policy, release-line rules, and GA
|
||||
|
|
@ -309,7 +309,7 @@ Assertion design rules:
|
|||
header chrome, or section treatment without explicit justification.
|
||||
13. Do not keep polishing strong lanes while weak lanes remain behind.
|
||||
14. Do not treat `status.json` lane scores reaching target as sufficient release approval by themselves; open operational decisions, machine-derived unresolved readiness assertions, and unresolved release gates still apply.
|
||||
15. Do not promote v6 to stable or GA without an exercised RC, a real
|
||||
15. Do not promote v6 to stable or GA without an exercised prerelease, a real
|
||||
release-pipeline proof run, a recorded rollback target plus exact
|
||||
reinstall command, and a written v5 maintenance-only support policy.
|
||||
16. Do not declare a lane or blocker complete just because the first passing
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# RC-to-GA Promotion Readiness Blocked Record
|
||||
# Prerelease-to-GA Promotion Readiness Blocked Record
|
||||
|
||||
- Date: `2026-03-13`
|
||||
- Gate: `rc-to-ga-promotion-readiness`
|
||||
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
## Blocking Facts
|
||||
|
||||
1. No Pulse v6 RC has shipped yet.
|
||||
1. No Pulse v6 prerelease has shipped yet.
|
||||
2. The repository contains accidental prerelease git tag history (`v6.0.0-rc.1`),
|
||||
but those tags were never published and do not count as shipped RC lineage.
|
||||
but those tags were never published and do not count as shipped prerelease lineage.
|
||||
3. GitHub still validates `Release Dry Run` workflow dispatch inputs against the
|
||||
default branch `main`, whose current workflow contract does not
|
||||
accept the governed stable rehearsal metadata envelope (`promoted_from_tag`,
|
||||
|
|
@ -21,30 +21,31 @@
|
|||
6. The active local `pulse/v6-release` branch currently reports `VERSION=6.0.0-rc.1`, so the
|
||||
working line is still prerelease and there is not yet a governed local stable
|
||||
`6.0.0` candidate.
|
||||
7. There is still no governed `RC-to-GA Rehearsal Record` proving a successful
|
||||
7. There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful
|
||||
non-publish `Release Dry Run` for the eventual stable `6.0.0` candidate.
|
||||
8. `docs/releases/RELEASE_NOTES_v6.md` and
|
||||
`docs/release-control/v6/internal/V5_MAINTENANCE_SUPPORT_POLICY.md` now carry the
|
||||
currently proposed exact dates for the eventual GA notice:
|
||||
- `v6` GA date: `2026-03-24`
|
||||
- `v5` end-of-support date: `2026-06-22`
|
||||
`docs/release-control/v6/internal/V5_MAINTENANCE_SUPPORT_POLICY.md` still leave the
|
||||
GA announcement dates as placeholders because no real prerelease lineage or GA-ready
|
||||
rehearsal has locked them yet:
|
||||
- `v6` GA date placeholder: `[v6-ga-date]`
|
||||
- `v5` end-of-support placeholder: `[v5-eos-date]`
|
||||
9. There is still no governed `Release Dry Run` artifact or rehearsal record
|
||||
exercising stable inputs for:
|
||||
- `version=6.0.0`
|
||||
- no governed `promoted_from_tag` exists yet because no RC has shipped
|
||||
- no governed `promoted_from_tag` exists yet because no prerelease has shipped
|
||||
- the artifact-owned candidate stable tag for that rehearsal
|
||||
- the artifact-owned promotion channel for that rehearsal
|
||||
- the artifact-owned promoted RC tag for that rehearsal
|
||||
- the artifact-owned promoted prerelease tag for that rehearsal
|
||||
- the artifact-owned rollback target for that stable candidate
|
||||
- `ga_date=2026-03-24`
|
||||
- no exact `ga_date` is locked yet because the GA notice is still pending
|
||||
- no exact `v5_eos_date` is locked yet because the GA notice is still pending
|
||||
- an explicit `rollback_version`
|
||||
- the exact derived rollback command that artifact will publish
|
||||
- `v5_eos_date=2026-06-22`
|
||||
|
||||
## Why The Gate Cannot Be Cleared Yet
|
||||
|
||||
The blocker is no longer missing governance text. The remaining problem is that
|
||||
the control plane still treats v6 as an RC-stabilization line, the working
|
||||
the control plane still treats v6 as the prerelease-stabilization line, the working
|
||||
version is still prerelease (`6.0.0-rc.1`), and there is still no exercised
|
||||
`Release Dry Run` record proving the eventual stable `6.0.0`
|
||||
candidate is ready for GA-style promotion. Until that rehearsal exists, stable
|
||||
|
|
@ -60,24 +61,24 @@ users would still be the first real cohort for the final promotion path.
|
|||
3. Land the canonical `.github/workflows/release-dry-run.yml` workflow-dispatch input
|
||||
contract on the default branch `main` so GitHub accepts the
|
||||
governed stable rehearsal metadata envelope when dispatching from `pulse/v6-release`.
|
||||
4. Ship the first real RC through the governed prerelease release path and record
|
||||
4. Ship the first real prerelease through the governed prerelease release path and record
|
||||
its exact published prerelease tag plus rollback target and exact derived
|
||||
rollback command.
|
||||
5. Run `Release Dry Run` from `pulse/v6-release` using that published RC as
|
||||
5. Run `Release Dry Run` from `pulse/v6-release` using that published prerelease as
|
||||
`promoted_from_tag` with:
|
||||
- `version=6.0.0`
|
||||
- an artifact-owned candidate stable tag matching that rehearsal
|
||||
- an artifact-owned promotion channel matching that rehearsal
|
||||
- an artifact-owned promoted RC tag matching that rehearsal
|
||||
- an artifact-owned promoted prerelease tag matching that rehearsal
|
||||
- an artifact-owned rollback target for the stable candidate
|
||||
- the exact planned GA and v5 end-of-support dates for the publish notice
|
||||
- `ga_date=2026-03-24`
|
||||
- an explicit `ga_date` chosen for that rehearsal
|
||||
- an explicit stable `rollback_version`
|
||||
- the exact derived rollback command that artifact will publish
|
||||
- `v5_eos_date=2026-06-22`
|
||||
- an explicit `v5_eos_date` chosen for that rehearsal
|
||||
6. Capture the `rc-to-ga-rehearsal-summary` artifact and run URL.
|
||||
7. Materialize the final rehearsal record from that artifact without
|
||||
hand-repairing any missing candidate tag, promoted RC tag, rollback
|
||||
hand-repairing any missing candidate tag, promoted prerelease tag, rollback
|
||||
target, rollback command, or GA/EOS metadata.
|
||||
8. Change the gate from `blocked` only if the rehearsal passes and the rollout
|
||||
inputs remain explicit.
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ server-side update execution surfaces.
|
|||
18. `scripts/dev-launchd-setup.sh`
|
||||
19. `scripts/dev-launchd-wrapper.sh`
|
||||
20. `scripts/com.pulse.hot-dev.plist.template`
|
||||
21. `.github/workflows/create-release.yml`
|
||||
22. `.github/workflows/release-dry-run.yml`
|
||||
23. `.github/workflows/publish-docker.yml`
|
||||
24. `.github/workflows/promote-floating-tags.yml`
|
||||
|
||||
## Shared Boundaries
|
||||
|
||||
|
|
@ -55,6 +59,7 @@ server-side update execution surfaces.
|
|||
3. Add or change shell installer, Windows installer, container-agent installer, or auto-update script behavior through `scripts/install.sh`, `scripts/install.ps1`, `scripts/install-container-agent.sh`, and `scripts/pulse-auto-update.sh`
|
||||
4. Add or change server update transport through `internal/api/updates.go` and `frontend-modern/src/api/updates.ts`
|
||||
5. Add or change local dev-runtime orchestration, managed ownership, browser-runtime proof wiring, frontend/backend coherence diagnostics, canonical developer entry wrappers, or dev-runtime helper control surfaces through `scripts/hot-dev.sh`, `scripts/hot-dev-bg.sh`, `tests/integration/scripts/managed-dev-runtime.mjs`, `package.json`, `frontend-modern/package.json`, `scripts/dev-check.sh`, `scripts/toggle-mock.sh`, `scripts/clean-mock-alerts.sh`, `scripts/dev-launchd-setup.sh`, `scripts/dev-launchd-wrapper.sh`, and `scripts/com.pulse.hot-dev.plist.template`
|
||||
6. Add or change governed release-promotion workflow inputs, operator-facing promotion metadata, prerelease lineage enforcement, or stable-promotion rehearsal summaries through `.github/workflows/create-release.yml`, `.github/workflows/release-dry-run.yml`, `.github/workflows/publish-docker.yml`, and `.github/workflows/promote-floating-tags.yml`
|
||||
|
||||
## Forbidden Paths
|
||||
|
||||
|
|
@ -97,10 +102,15 @@ contract rather than depending on whichever local ldflags string happened to be
|
|||
updated last.
|
||||
The same governed promotion path must now stay explicit too:
|
||||
`scripts/release_control/resolve_release_promotion.py` is the canonical owner
|
||||
for stable-versus-RC metadata validation shared by `.github/workflows/release-dry-run.yml`
|
||||
for stable-versus-prerelease metadata validation shared by `.github/workflows/release-dry-run.yml`
|
||||
and `.github/workflows/create-release.yml`. Promotion rollback targets, promoted
|
||||
RC lineage, soak checks, and GA/v5 notice metadata may not drift between those
|
||||
two workflows through duplicated inline shell validation.
|
||||
Those same governed release workflows also own the operator-facing wording for
|
||||
that promotion metadata. Human-visible workflow inputs, summaries, and error
|
||||
messages must describe the path as a prerelease or preview flow rather than
|
||||
implying a near-ready release candidate, while machine-owned identifiers such
|
||||
as `rc`, `rc-to-ga-*`, and `v6.0.0-rc.1` remain the canonical internal keys.
|
||||
Those same workflows must also fetch and dispatch the governed release branch
|
||||
derived from release-control metadata instead of hardcoding `pulse/v6`,
|
||||
`pulse/v6-release`, or any later branch literal inline.
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ git pull --ff-only
|
|||
instead of returning an opaque 422 from `gh workflow run`.
|
||||
The governed `Release Dry Run` must still carry:
|
||||
- `version`: `6.0.0`
|
||||
- `promoted_from_tag`: exact RC tag being promoted
|
||||
- `promoted_from_tag`: exact prerelease tag being promoted
|
||||
- `rollback_version`: prior stable
|
||||
- `ga_date`: exact published v6 GA date
|
||||
- `v5_eos_date`: exact published v5 end-of-support date
|
||||
|
|
@ -143,7 +143,7 @@ git pull --ff-only
|
|||
Attach the `rc-to-ga-rehearsal-summary` artifact and run URL to the release
|
||||
ticket, and confirm the artifact carries the canonical promotion metadata
|
||||
envelope for that candidate: candidate stable tag, promotion channel,
|
||||
promoted RC tag, rollback target, exact rollback command, planned GA date,
|
||||
promoted prerelease tag, rollback target, exact rollback command, planned GA date,
|
||||
and planned v5 end-of-support date.
|
||||
|
||||
4. Publish the exact v6 GA date and v5 end-of-support date in the GA release
|
||||
|
|
|
|||
|
|
@ -595,7 +595,7 @@ func TestV6ControlDocsReferenceCanonicalDevelopmentProtocol(t *testing.T) {
|
|||
"registry.schema.json",
|
||||
"subsystems/*.md",
|
||||
"structured evidence references",
|
||||
"ready to cut an RC or approve stable/GA",
|
||||
"ready to cut a prerelease or approve stable/GA",
|
||||
"control_plane_audit.py --check",
|
||||
"status_audit.py --pretty",
|
||||
"status.json.coverage_gaps",
|
||||
|
|
@ -621,7 +621,7 @@ func TestV6ControlDocsReferenceCanonicalDevelopmentProtocol(t *testing.T) {
|
|||
"## Evergreen Readiness Assertions",
|
||||
"## Development Governance",
|
||||
"Do not treat `status.json` lane scores reaching target as sufficient release approval by themselves",
|
||||
"Do not promote v6 to stable or GA without an exercised RC",
|
||||
"Do not promote v6 to stable or GA without an exercised prerelease",
|
||||
"v5 maintenance-only support policy",
|
||||
"control_plane_audit.py --check",
|
||||
"When a user states a durable product truth",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Materialize the blocked RC-to-GA promotion record from current repo state."""
|
||||
"""Materialize the blocked prerelease-to-GA promotion record from current repo state."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -34,7 +34,7 @@ STABLE_REHEARSAL_INPUT_KEYS = (
|
|||
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Generate the blocked RC-to-GA promotion record from current repo facts."
|
||||
description="Generate the blocked prerelease-to-GA promotion record from current repo facts."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
|
|
@ -146,7 +146,7 @@ def parse_release_dates() -> tuple[str, str]:
|
|||
ga_match = GA_DATE_RE.search(content)
|
||||
eos_match = V5_EOS_RE.search(content)
|
||||
if not ga_match or not eos_match:
|
||||
raise ValueError("could not derive GA/EOS dates from docs/releases/RELEASE_NOTES_v6.md")
|
||||
return "[v6-ga-date]", "[v5-eos-date]"
|
||||
return ga_match.group("ga_date"), eos_match.group("v5_eos_date")
|
||||
|
||||
|
||||
|
|
@ -192,7 +192,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
f"`{stable_version}` candidate.",
|
||||
],
|
||||
[
|
||||
"There is still no governed `RC-to-GA Rehearsal Record` proving a successful",
|
||||
"There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful",
|
||||
f"non-publish `Release Dry Run` for the eventual stable `{stable_version}` candidate.",
|
||||
],
|
||||
]
|
||||
|
|
@ -203,7 +203,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
"local GA candidate exists on the governed stable line.",
|
||||
],
|
||||
[
|
||||
"There is still no governed `RC-to-GA Rehearsal Record` proving a successful",
|
||||
"There is still no governed `Prerelease-to-GA Rehearsal Record` proving a successful",
|
||||
f"non-publish `Release Dry Run` for the current `{version}` candidate.",
|
||||
],
|
||||
]
|
||||
|
|
@ -249,7 +249,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
if version_is_prerelease:
|
||||
why_lines = [
|
||||
"The blocker is no longer missing governance text. The remaining problem is that",
|
||||
"the control plane still treats v6 as an RC-stabilization line, the working",
|
||||
"the control plane still treats v6 as the prerelease-stabilization line, the working",
|
||||
f"version is still prerelease (`{version}`), and there is still no exercised",
|
||||
f"`Release Dry Run` record proving the eventual stable `{stable_version}`",
|
||||
"candidate is ready for GA-style promotion. Until that rehearsal exists, stable",
|
||||
|
|
@ -269,7 +269,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
else:
|
||||
why_lines = [
|
||||
"The blocker is no longer missing governance text. The remaining problem is that",
|
||||
"the control plane still treats v6 as an RC-stabilization line, and there is",
|
||||
"the control plane still treats v6 as the prerelease-stabilization line, and there is",
|
||||
f"still no exercised `Release Dry Run` record proving the exact `{version}`",
|
||||
"candidate is ready for GA-style promotion. Until that rehearsal exists, stable",
|
||||
"users would still be the first real cohort for the final promotion path.",
|
||||
|
|
@ -289,18 +289,18 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
if rc_tag:
|
||||
blocking_fact_lines.extend(
|
||||
[
|
||||
[f"The latest shipped Pulse v6 RC tag is `{rc_tag}`."],
|
||||
[f"That shipped RC tag resolves to commit `{rc_commit}`."],
|
||||
[f"The latest shipped Pulse v6 prerelease tag is `{rc_tag}`."],
|
||||
[f"That shipped prerelease tag resolves to commit `{rc_commit}`."],
|
||||
]
|
||||
)
|
||||
else:
|
||||
blocking_fact_lines.append(["No Pulse v6 RC has shipped yet."])
|
||||
blocking_fact_lines.append(["No Pulse v6 prerelease has shipped yet."])
|
||||
if accidental_tags:
|
||||
accidental_list = ", ".join(f"`{tag}`" for tag in sorted(accidental_tags))
|
||||
blocking_fact_lines.append(
|
||||
[
|
||||
f"The repository contains accidental prerelease git tag history ({accidental_list}),",
|
||||
"but those tags were never published and do not count as shipped RC lineage.",
|
||||
"but those tags were never published and do not count as shipped prerelease lineage.",
|
||||
]
|
||||
)
|
||||
|
||||
|
|
@ -327,7 +327,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
f"- `version={stable_version}`",
|
||||
"- the artifact-owned candidate stable tag for that rehearsal",
|
||||
"- the artifact-owned promotion channel for that rehearsal",
|
||||
"- the artifact-owned promoted RC tag for that rehearsal",
|
||||
"- the artifact-owned promoted prerelease tag for that rehearsal",
|
||||
"- the artifact-owned rollback target for that stable candidate",
|
||||
f"- `ga_date={ga_date}`",
|
||||
"- an explicit `rollback_version`",
|
||||
|
|
@ -338,7 +338,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
release_date_fact_lines = [
|
||||
"`docs/releases/RELEASE_NOTES_v6.md` and",
|
||||
"`docs/release-control/v6/internal/V5_MAINTENANCE_SUPPORT_POLICY.md` still leave the",
|
||||
"GA announcement dates as placeholders because no real RC lineage or GA-ready",
|
||||
"GA announcement dates as placeholders because no real prerelease lineage or GA-ready",
|
||||
"rehearsal has locked them yet:",
|
||||
f"- `v6` GA date placeholder: `{ga_date}`",
|
||||
f"- `v5` end-of-support placeholder: `{v5_eos_date}`",
|
||||
|
|
@ -347,7 +347,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
f"- `version={stable_version}`",
|
||||
"- the artifact-owned candidate stable tag for that rehearsal",
|
||||
"- the artifact-owned promotion channel for that rehearsal",
|
||||
"- the artifact-owned promoted RC tag for that rehearsal",
|
||||
"- the artifact-owned promoted prerelease tag for that rehearsal",
|
||||
"- the artifact-owned rollback target for that stable candidate",
|
||||
"- no exact `ga_date` is locked yet because the GA notice is still pending",
|
||||
"- no exact `v5_eos_date` is locked yet because the GA notice is still pending",
|
||||
|
|
@ -359,7 +359,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
else:
|
||||
rehearsal_input_lines.insert(
|
||||
1,
|
||||
"- no governed `promoted_from_tag` exists yet because no RC has shipped",
|
||||
"- no governed `promoted_from_tag` exists yet because no prerelease has shipped",
|
||||
)
|
||||
|
||||
required_blocks: list[list[str] | tuple[str, ...] | str] = list(required_step_lines)
|
||||
|
|
@ -374,19 +374,19 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
if not rc_tag:
|
||||
required_blocks.append(
|
||||
[
|
||||
"Ship the first real RC through the governed prerelease release path and record",
|
||||
"Ship the first real prerelease through the governed prerelease release path and record",
|
||||
"its exact published prerelease tag plus rollback target and exact derived",
|
||||
"rollback command.",
|
||||
]
|
||||
)
|
||||
required_blocks.append(
|
||||
[
|
||||
f"Run `Release Dry Run` from `{stable_branch}` using that published RC as",
|
||||
f"Run `Release Dry Run` from `{stable_branch}` using that published prerelease as",
|
||||
"`promoted_from_tag` with:",
|
||||
f"- `version={stable_version}`",
|
||||
"- an artifact-owned candidate stable tag matching that rehearsal",
|
||||
"- an artifact-owned promotion channel matching that rehearsal",
|
||||
"- an artifact-owned promoted RC tag matching that rehearsal",
|
||||
"- an artifact-owned promoted prerelease tag matching that rehearsal",
|
||||
"- an artifact-owned rollback target for the stable candidate",
|
||||
"- the exact planned GA and v5 end-of-support dates for the publish notice",
|
||||
f"- `ga_date={ga_date}`" if dates_are_locked else "- an explicit `ga_date` chosen for that rehearsal",
|
||||
|
|
@ -403,7 +403,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
f"- `promoted_from_tag={rc_tag}`",
|
||||
"- an artifact-owned candidate stable tag matching that rehearsal",
|
||||
"- an artifact-owned promotion channel matching that rehearsal",
|
||||
"- an artifact-owned promoted RC tag matching that rehearsal",
|
||||
"- an artifact-owned promoted prerelease tag matching that rehearsal",
|
||||
"- an artifact-owned rollback target for the stable candidate",
|
||||
"- the exact planned GA and v5 end-of-support dates for the publish notice",
|
||||
f"- `ga_date={ga_date}`" if dates_are_locked else "- an explicit `ga_date` chosen for that rehearsal",
|
||||
|
|
@ -417,7 +417,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
["Capture the `rc-to-ga-rehearsal-summary` artifact and run URL."],
|
||||
[
|
||||
"Materialize the final rehearsal record from that artifact without",
|
||||
"hand-repairing any missing candidate tag, promoted RC tag, rollback",
|
||||
"hand-repairing any missing candidate tag, promoted prerelease tag, rollback",
|
||||
"target, rollback command, or GA/EOS metadata.",
|
||||
],
|
||||
[
|
||||
|
|
@ -428,7 +428,7 @@ def build_blocked_record(*, record_date: str) -> str:
|
|||
)
|
||||
|
||||
lines = [
|
||||
"# RC-to-GA Promotion Readiness Blocked Record",
|
||||
"# Prerelease-to-GA Promotion Readiness Blocked Record",
|
||||
"",
|
||||
f"- Date: `{record_date}`",
|
||||
"- Gate: `rc-to-ga-promotion-readiness`",
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Shared helpers for RC-to-GA promotion proof governance."""
|
||||
"""Shared helpers for prerelease-to-GA promotion proof governance."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ REQUIRED_STAGED_GOVERNANCE_INPUTS: tuple[str, ...] = (
|
|||
PROMOTION_METADATA_FIELDS: tuple[tuple[str, str], ...] = (
|
||||
("tag", "candidate stable tag"),
|
||||
("channel_under_rehearsal", "promotion channel"),
|
||||
("promoted_from_rc", "promoted RC tag"),
|
||||
("promoted_from_rc", "promoted prerelease tag"),
|
||||
("rollback_target", "rollback target"),
|
||||
("rollback_command", "exact rollback command"),
|
||||
("planned_ga_date", "planned GA date"),
|
||||
|
|
@ -194,7 +194,7 @@ def staged_governance_input_errors(*, use_staged_governance: bool) -> list[str]:
|
|||
if (
|
||||
"Exact rollback or reinstall command" not in template
|
||||
or "rc-to-ga-rehearsal-summary" not in template
|
||||
or "promotion channel, promoted RC tag, rollback target, exact rollback command" not in template
|
||||
or "promotion channel, promoted prerelease tag, rollback target, exact rollback command" not in template
|
||||
):
|
||||
errors.append(
|
||||
"stage the updated docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md "
|
||||
|
|
|
|||
|
|
@ -128,7 +128,7 @@ on:
|
|||
),
|
||||
patch(
|
||||
"release_promotion_policy_support.read_repo_text",
|
||||
return_value="# Pulse v6 RC-to-GA Rehearsal Record\n",
|
||||
return_value="# Pulse v6 Prerelease-to-GA Rehearsal Record\n",
|
||||
) as read_repo_text,
|
||||
):
|
||||
self.assertEqual(
|
||||
|
|
@ -154,7 +154,7 @@ on:
|
|||
"release_promotion_policy_support.read_repo_text",
|
||||
side_effect=[
|
||||
"# Pulse v6 Pre-Release Checklist\n- rc-to-ga-rehearsal-summary\n",
|
||||
"# Pulse v6 RC-to-GA Rehearsal Record\n",
|
||||
"# Pulse v6 Prerelease-to-GA Rehearsal Record\n",
|
||||
],
|
||||
):
|
||||
self.assertEqual(
|
||||
|
|
|
|||
|
|
@ -78,11 +78,11 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
def test_release_promotion_policy_requires_live_rc_and_v5_policy(self) -> None:
|
||||
content = read("docs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.md")
|
||||
self.assertIn("Every candidate intended for broad customer use must ship to `rc`", content)
|
||||
self.assertIn("live run of the release pipeline for the RC tag itself", content)
|
||||
self.assertIn("live run of the release pipeline for the prerelease tag itself", content)
|
||||
self.assertIn("an accidental git tag by itself", content)
|
||||
self.assertIn("does not count as a shipped RC", content)
|
||||
self.assertIn("does not count as a shipped prerelease", content)
|
||||
self.assertIn("do not promote to `stable` until the active control-plane target", content)
|
||||
self.assertIn("A live release-pipeline exercise already completed for the promoted RC", content)
|
||||
self.assertIn("A live release-pipeline exercise already completed for the promoted prerelease tag", content)
|
||||
self.assertIn("maintenance-only window lasts 90 calendar days", content)
|
||||
self.assertIn("V5_MAINTENANCE_SUPPORT_POLICY.md", content)
|
||||
self.assertIn("release notes may keep placeholder", content)
|
||||
|
|
@ -91,7 +91,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
|
||||
def test_pre_release_checklist_tracks_rc_to_ga_gate_inputs(self) -> None:
|
||||
content = read("docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.md")
|
||||
self.assertIn("release pipeline has already been exercised on a real RC tag", content)
|
||||
self.assertIn("release pipeline has already been exercised on a real prerelease tag", content)
|
||||
self.assertIn("not an accidental git tag", content)
|
||||
self.assertIn("V5_MAINTENANCE_SUPPORT_POLICY.md", content)
|
||||
self.assertIn("replace any placeholder GA notice dates", content)
|
||||
|
|
@ -105,16 +105,20 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("maintenance-only support immediately on the v6 GA date", policy)
|
||||
self.assertIn("90 calendar days from the v6 GA", policy)
|
||||
self.assertIn("pulse/v5-maintenance", policy)
|
||||
self.assertIn("Pulse v5 Support Transition", release_notes)
|
||||
self.assertIn("publish an explicit exception", release_notes)
|
||||
self.assertRegex(
|
||||
release_notes,
|
||||
re.compile(r"Pulse v5 entered maintenance-only support on `(?:\[v6-ga-date\]|\d{4}-\d{2}-\d{2})`\.")
|
||||
)
|
||||
self.assertRegex(
|
||||
release_notes,
|
||||
re.compile(r"existing v5 users until `(?:\[v5-eos-date\]|\d{4}-\d{2}-\d{2})`\.")
|
||||
)
|
||||
if "Pulse v5 Support Transition" in release_notes:
|
||||
self.assertIn("publish an explicit exception", release_notes)
|
||||
self.assertRegex(
|
||||
release_notes,
|
||||
re.compile(r"Pulse v5 entered maintenance-only support on `(?:\[v6-ga-date\]|\d{4}-\d{2}-\d{2})`\.")
|
||||
)
|
||||
self.assertRegex(
|
||||
release_notes,
|
||||
re.compile(r"existing v5 users until `(?:\[v5-eos-date\]|\d{4}-\d{2}-\d{2})`\.")
|
||||
)
|
||||
else:
|
||||
self.assertIn("Pre-Release Notes", release_notes)
|
||||
self.assertIn("final GA release", release_notes)
|
||||
self.assertNotIn("Pulse v5 Support Transition", release_notes)
|
||||
|
||||
def test_rehearsal_template_and_workflow_capture_ga_rehearsal_record(self) -> None:
|
||||
template = read("docs/release-control/v6/internal/RC_TO_GA_REHEARSAL_TEMPLATE.md")
|
||||
|
|
@ -132,7 +136,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("- Rollback command:", workflow)
|
||||
self.assertIn("- Candidate stable tag:", workflow)
|
||||
self.assertIn("- Promotion channel:", workflow)
|
||||
self.assertIn("- Promoted RC tag:", workflow)
|
||||
self.assertIn("- Promoted prerelease tag:", workflow)
|
||||
self.assertIn("Planned GA date", workflow)
|
||||
self.assertIn("Planned v5 end-of-support date", workflow)
|
||||
self.assertIn("resolve_release_promotion.py", release_workflow)
|
||||
|
|
@ -162,7 +166,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("minimum is 72 hours unless hotfix_exception is true", resolver)
|
||||
self.assertIn("- Candidate stable tag:", content)
|
||||
self.assertIn("- Promotion channel:", content)
|
||||
self.assertIn("- Promoted RC tag:", content)
|
||||
self.assertIn("- Promoted prerelease tag:", content)
|
||||
self.assertIn("ga_date", content)
|
||||
self.assertIn("v5_eos_date", content)
|
||||
self.assertIn("Derived rollback command:", helper)
|
||||
|
|
@ -201,8 +205,8 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("control_plane.py --branch-for-version", promote)
|
||||
self.assertIn("control_plane.py --branch-for-version", demo)
|
||||
self.assertIn("control_plane.py --branch-for-version", helm)
|
||||
self.assertIn("does not descend from any matching RC tag", publish)
|
||||
self.assertIn("does not descend from any matching RC tag", promote)
|
||||
self.assertIn("does not descend from any matching prerelease tag", publish)
|
||||
self.assertIn("does not descend from any matching prerelease tag", promote)
|
||||
self.assertIn("both stable and prerelease releases dispatch", runbook)
|
||||
self.assertIn("Release `6.0.0` from `pulse/v6-release`", runbook)
|
||||
self.assertIn(promotion_metadata_envelope(), normalize_ws(runbook))
|
||||
|
|
@ -213,10 +217,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
|
|||
self.assertIn("rollback target and exact derived", blocked)
|
||||
self.assertIn("artifact-owned candidate stable tag", blocked)
|
||||
self.assertIn("artifact-owned promotion channel", blocked)
|
||||
self.assertIn("artifact-owned promoted RC tag", blocked)
|
||||
self.assertIn("artifact-owned promoted prerelease tag", blocked)
|
||||
self.assertIn("artifact-owned rollback target", blocked)
|
||||
self.assertIn("Materialize the final rehearsal record from that artifact without", blocked)
|
||||
self.assertIn("hand-repairing any missing candidate tag, promoted RC tag, rollback", blocked)
|
||||
self.assertIn("hand-repairing any missing candidate tag, promoted prerelease tag, rollback", blocked)
|
||||
matrix = read("docs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md")
|
||||
self.assertIn(promotion_metadata_envelope(), normalize_ws(matrix))
|
||||
expected = blocked_record.build_blocked_record(record_date="2026-03-13")
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ def tag_created_unix(tag: str) -> int:
|
|||
)
|
||||
value = result.stdout.strip().splitlines()
|
||||
if not value or not value[0].strip():
|
||||
raise ValueError(f"Could not determine creation time for promoted RC tag {tag}.")
|
||||
raise ValueError(f"Could not determine creation time for promoted prerelease tag {tag}.")
|
||||
return int(value[0].strip())
|
||||
|
||||
|
||||
|
|
@ -125,11 +125,11 @@ def resolve_metadata(
|
|||
promoted_from_tag = normalize_tag(promoted_from_tag_input)
|
||||
if not promoted_from_tag:
|
||||
raise ValueError(
|
||||
"Stable promotion requires promoted_from_tag naming the RC being promoted."
|
||||
"Stable promotion requires promoted_from_tag naming the prerelease being promoted."
|
||||
)
|
||||
if not re.match(rf"^v{re.escape(version)}-rc\.\d+$", promoted_from_tag):
|
||||
raise ValueError(
|
||||
f"promoted_from_tag must reference an RC tag for the same stable version ({version}), got {promoted_from_tag}."
|
||||
f"promoted_from_tag must reference a prerelease tag for the same stable version ({version}), got {promoted_from_tag}."
|
||||
)
|
||||
if not tag_exists_fn(promoted_from_tag):
|
||||
raise ValueError(
|
||||
|
|
@ -139,7 +139,7 @@ def resolve_metadata(
|
|||
promoted_commit = tag_commit_fn(promoted_from_tag)
|
||||
if not head_descends_from_fn(promoted_commit):
|
||||
raise ValueError(
|
||||
f"Stable promotion {tag} must descend from promoted RC tag {promoted_from_tag}."
|
||||
f"Stable promotion {tag} must descend from promoted prerelease tag {promoted_from_tag}."
|
||||
)
|
||||
|
||||
promoted_tag_ts = tag_created_unix_fn(promoted_from_tag)
|
||||
|
|
|
|||
|
|
@ -102,23 +102,23 @@ if [ "$IS_PRERELEASE" != "true" ]; then
|
|||
DEFAULT_PROMOTED_FROM_TAG=$(git tag -l "v${VERSION}-rc.*" --sort=-version:refname | head -1 || true)
|
||||
echo ""
|
||||
if [ -n "$DEFAULT_PROMOTED_FROM_TAG" ]; then
|
||||
read -r -p "Promoted RC tag [${DEFAULT_PROMOTED_FROM_TAG}]: " PROMOTED_FROM_TAG
|
||||
read -r -p "Promoted prerelease tag [${DEFAULT_PROMOTED_FROM_TAG}]: " PROMOTED_FROM_TAG
|
||||
PROMOTED_FROM_TAG="${PROMOTED_FROM_TAG:-$DEFAULT_PROMOTED_FROM_TAG}"
|
||||
else
|
||||
read -r -p "Promoted RC tag (for example v${VERSION}-rc.2): " PROMOTED_FROM_TAG
|
||||
read -r -p "Promoted prerelease tag (for example v${VERSION}-rc.2): " PROMOTED_FROM_TAG
|
||||
fi
|
||||
if [ -z "$PROMOTED_FROM_TAG" ]; then
|
||||
echo "❌ Error: promoted RC tag is required for stable rehearsals"
|
||||
echo "❌ Error: promoted prerelease tag is required for stable rehearsals"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -r -p "Hotfix exception to bypass 72-hour RC soak? [y/N] " HOTFIX_REPLY
|
||||
read -r -p "Hotfix exception to bypass 72-hour prerelease soak? [y/N] " HOTFIX_REPLY
|
||||
if [[ "$HOTFIX_REPLY" =~ ^[Yy]$ ]]; then
|
||||
HOTFIX_EXCEPTION="true"
|
||||
read -r -p "Hotfix reason: " HOTFIX_REASON
|
||||
if [ -z "$HOTFIX_REASON" ]; then
|
||||
echo "❌ Error: hotfix reason is required when bypassing RC soak"
|
||||
echo "❌ Error: hotfix reason is required when bypassing prerelease soak"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -186,23 +186,23 @@ if [ "$IS_PRERELEASE" != "true" ]; then
|
|||
DEFAULT_PROMOTED_FROM_TAG=$(git tag -l "v${VERSION}-rc.*" --sort=-version:refname | head -1 || true)
|
||||
echo ""
|
||||
if [ -n "$DEFAULT_PROMOTED_FROM_TAG" ]; then
|
||||
read -r -p "Promoted RC tag [${DEFAULT_PROMOTED_FROM_TAG}]: " PROMOTED_FROM_TAG
|
||||
read -r -p "Promoted prerelease tag [${DEFAULT_PROMOTED_FROM_TAG}]: " PROMOTED_FROM_TAG
|
||||
PROMOTED_FROM_TAG="${PROMOTED_FROM_TAG:-$DEFAULT_PROMOTED_FROM_TAG}"
|
||||
else
|
||||
read -r -p "Promoted RC tag (for example v${VERSION}-rc.2): " PROMOTED_FROM_TAG
|
||||
read -r -p "Promoted prerelease tag (for example v${VERSION}-rc.2): " PROMOTED_FROM_TAG
|
||||
fi
|
||||
if [ -z "$PROMOTED_FROM_TAG" ]; then
|
||||
echo "❌ Error: promoted RC tag is required for stable releases"
|
||||
echo "❌ Error: promoted prerelease tag is required for stable releases"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
read -r -p "Hotfix exception to bypass 72-hour RC soak? [y/N] " HOTFIX_REPLY
|
||||
read -r -p "Hotfix exception to bypass 72-hour prerelease soak? [y/N] " HOTFIX_REPLY
|
||||
if [[ "$HOTFIX_REPLY" =~ ^[Yy]$ ]]; then
|
||||
HOTFIX_EXCEPTION="true"
|
||||
read -r -p "Hotfix reason: " HOTFIX_REASON
|
||||
if [ -z "$HOTFIX_REASON" ]; then
|
||||
echo "❌ Error: hotfix reason is required when bypassing RC soak"
|
||||
echo "❌ Error: hotfix reason is required when bypassing prerelease soak"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue