Reopen v6 release control as pre-RC hold

This commit is contained in:
rcourtman 2026-04-04 22:47:37 +01:00
parent 9ba7915c7c
commit 7e0f84a40f
11 changed files with 143 additions and 31 deletions

View file

@ -6,7 +6,7 @@
"control_plane_doc": "docs/release-control/internal/CONTROL_PLANE.md",
"control_plane_schema": "docs/release-control/control_plane.schema.json",
"active_profile_id": "v6",
"active_target_id": "v6-ga-promotion",
"active_target_id": "v6-rc-cut",
"profiles": [
{
"id": "v6",
@ -30,16 +30,16 @@
"id": "v6-rc-cut",
"profile_id": "v6",
"kind": "release",
"status": "completed",
"summary": "Drive Pulse v6 to a governed RC cut by clearing prerelease blockers, proving migration safety, and exercising the pre-RC high-risk gates.",
"status": "active",
"summary": "Hold Pulse v6 on the governed prerelease-cut target until RC publication is explicitly judged ready, while continuing to clear prerelease blockers, prove migration safety, and exercise the pre-RC high-risk gates.",
"completion_rule": "rc_ready"
},
{
"id": "v6-rc-stabilization",
"profile_id": "v6",
"kind": "stabilization",
"status": "completed",
"summary": "Keep Pulse v6 simple, accurate, and cleanly readable around the proven monitoring-and-alerting RC floor, using resource-change intelligence only where it materially improves investigation or safe AI flows while broader control-plane expansion stays out of the default target until it is proven.",
"status": "planned",
"summary": "Once a real governed RC exists, keep Pulse v6 simple, accurate, and cleanly readable around that proven monitoring-first floor while broader control-plane expansion stays out of the default target until it is proven.",
"completion_rule": "manual",
"proof_scope": "none"
},
@ -47,8 +47,8 @@
"id": "v6-ga-promotion",
"profile_id": "v6",
"kind": "release",
"status": "active",
"summary": "Promote Pulse v6 from a validated RC to governed GA with exercised promotion proof, rollback clarity, and only GA-blocking decisions remaining.",
"status": "planned",
"summary": "Promote Pulse v6 from a validated, actually shipped RC to governed GA with exercised promotion proof, rollback clarity, and only GA-blocking decisions remaining.",
"completion_rule": "release_ready"
},
{

View file

@ -232,18 +232,18 @@ user language should update the control plane.
## Current State
1. v6 is the current active release profile.
2. `v6-ga-promotion` is the current active engineering target.
2. `v6-rc-cut` is the current active engineering target.
It is a release target, so default slice selection should stay centered on
exercised prerelease lineage, release-pipeline proof, rollback metadata,
and the remaining GA-blocking documentation inputs until `release_ready`
is actually met.
prerelease blockers, RC-publication judgment, and the remaining proof needed
before a real governed RC should actually be cut.
3. `v6-product-lane-expansion` remains planned and is still blocked on the
broader surfaced product case being proven.
Its candidate-lane surface remains available in `available_candidate_lane_queue`
plus the linked `candidate_lanes` and `coverage_gaps`.
4. `v6-rc-stabilization` is complete and remains the predecessor
stabilization target for the current v6 line.
5. `v6-rc-cut` is complete and remains the predecessor release-cut target.
4. `v6-ga-promotion` remains planned and must stay dormant until a real
governed RC has actually shipped and been validated.
5. `v6-rc-stabilization` remains planned and should only become active once
the first governed RC exists.
6. Its files remain under `docs/release-control/v6/`.
7. The existing v6 control surfaces are still live, but they now sit underneath
an evergreen Pulse control plane rather than pretending to be the whole

View file

@ -13,10 +13,13 @@ Use this as the final gate before cutting a Pulse v6 pre-release.
## Current Status
- Automated command-driven checks completed on 2026-03-06 are marked `[x]` below.
- `status.json` is no longer overclaiming threshold-unmet gates as passed.
- There are no remaining `rc-ready` high-risk blockers; `rc_ready` now derives
true in `status.json`.
- The active target is back on `v6-rc-stabilization`, so GA rehearsal remains
intentionally out of scope until promotion is resumed explicitly.
- There are no remaining `rc-ready` high-risk release-gate blockers, but
`rc_ready` is intentionally held false in `status.json` by the open decision
`rc-publication-judgment` until the current candidate is explicitly judged
ready for a real governed RC.
- The active target is back on `v6-rc-cut`, so GA rehearsal remains
intentionally out of scope until a real RC has actually shipped and
promotion is resumed explicitly.
- 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:

View file

@ -154,8 +154,8 @@ Pulse v6 is ready when these outcomes land together:
customer exposure.
Pulse v6 is still a bridge release toward that unified operations layer, while
the current active target remains governed GA promotion rather than broad new
platform execution.
the current active target remains governed prerelease-cut readiness with an
explicit RC-publication hold rather than broad new platform execution.
For this profile, "bridge release" means the product should stop reading as a
monitoring tool growing sideways and instead make the mixed-estate operator
surface legible through canonical resources, investigation context, governed

View file

@ -0,0 +1,80 @@
# Prerelease-to-GA Promotion Readiness Blocked Record
- Date: `2026-04-04`
- Gate: `rc-to-ga-promotion-readiness`
- Result: `blocked`
## Blocking Facts
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 prerelease lineage.
3. The selected remote ref `origin/pulse/v6-release` is still behind the current
local governed branch state, so `Release Dry Run` would exercise stale remote
control-plane metadata instead of the intended candidate.
4. The governed release profile in `docs/release-control/control_plane.json`
currently declares both `prerelease_branch` and `stable_branch` as
`pulse/v6-release`.
5. The active control-plane target is still `v6-rc-cut`, not
`v6-ga-promotion`.
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 `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` 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 prerelease has shipped
- the artifact-owned candidate stable tag for that rehearsal
- the artifact-owned promotion channel 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
- an explicit `rollback_version`
- the exact derived rollback command that artifact will publish
## Why The Gate Cannot Be Cleared Yet
The blocker is no longer missing governance text. The remaining problem is that
the control plane still holds v6 on the pre-GA prerelease 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
users would still be the first real cohort for the final promotion path.
## Required Unblock Steps
1. Promote the active target from `v6-rc-cut` to
`v6-ga-promotion` only when that change is actually intended.
2. Push the governed `pulse/v6-release` branch state that is intended to become the
stable `6.0.0` candidate, including the eventual `VERSION=6.0.0`
change and release-control records, to `origin/pulse/v6-release`.
3. 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.
4. 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 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
- an explicit `ga_date` chosen for that rehearsal
- an explicit stable `rollback_version`
- the exact derived rollback command that artifact will publish
- an explicit `v5_eos_date` chosen for that rehearsal
5. Capture the `rc-to-ga-rehearsal-summary` artifact and run URL.
6. Materialize the final rehearsal record from that artifact without
hand-repairing any missing candidate tag, promoted prerelease tag, rollback
target, rollback command, or GA/EOS metadata.
7. Change the gate from `blocked` only if the rehearsal passes and the rollout
inputs remain explicit.

View file

@ -2698,7 +2698,7 @@
"status": "partial",
"completion": {
"state": "bounded-residual",
"summary": "First-session UX reached the RC floor with the runtime wizard reduced to welcome and security, direct handoff into Infrastructure Operations install, and setup completion retained only as a separate preview surface; broader polish and parity work remain intentionally outside the current stabilization target.",
"summary": "First-session UX reached the RC floor with the runtime wizard reduced to welcome and security, direct handoff into Infrastructure Operations install, and setup completion retained only as a separate preview surface; broader polish and parity work remain intentionally outside the current prerelease target.",
"tracking": [
{
"kind": "lane-followup",
@ -3624,6 +3624,12 @@
"kind": "file",
"evidence_tier": "local-rehearsal"
},
{
"repo": "pulse",
"path": "docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-04-04.md",
"kind": "file",
"evidence_tier": "local-rehearsal"
},
{
"repo": "pulse",
"path": "docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-rehearsal-blocked-2026-03-26.md",
@ -4216,7 +4222,23 @@
}
],
"work_claims": [],
"open_decisions": [],
"open_decisions": [
{
"id": "rc-publication-judgment",
"summary": "Pulse v6 prerelease publication remains an explicit product judgment: even if the machine-derived RC floor looks close, `rc_ready` stays blocked until the current candidate is explicitly judged ready for a real governed RC and not inferred from accidental `6.0.0-rc.1` lineage or checklist completion.",
"owner": "project-owner",
"blocking_level": "rc-ready",
"status": "open",
"opened_at": "2026-04-04",
"lane_ids": [
"L1",
"L8",
"L9",
"L12"
],
"subsystem_ids": []
}
],
"source_of_truth_file": "docs/release-control/v6/internal/SOURCE_OF_TRUTH.md",
"resolved_decisions": [
{

View file

@ -510,7 +510,7 @@ func TestReleaseControlPlaneFilesExist(t *testing.T) {
if !ok || activeTargetID == "" {
t.Fatalf("%s missing active_target_id", jsonRel)
}
if activeTargetID != "v6-rc-stabilization" && activeTargetID != "v6-ga-promotion" && activeTargetID != "v6-product-lane-expansion" {
if activeTargetID != "v6-rc-cut" && activeTargetID != "v6-rc-stabilization" && activeTargetID != "v6-ga-promotion" && activeTargetID != "v6-product-lane-expansion" {
t.Fatalf("%s has unexpected active_target_id %q", jsonRel, activeTargetID)
}
@ -557,8 +557,8 @@ func TestReleaseControlPlaneFilesExist(t *testing.T) {
if status, _ := targetsByID[activeTargetID]["status"].(string); status != "active" {
t.Fatalf("%s active_target_id %q is not marked active", jsonRel, activeTargetID)
}
if status, _ := targetsByID["v6-rc-cut"]["status"].(string); status != "completed" {
t.Fatalf("%s v6-rc-cut must remain completed", jsonRel)
if status, _ := targetsByID["v6-rc-cut"]["status"].(string); status != "active" && status != "completed" {
t.Fatalf("%s v6-rc-cut must remain either active or completed", jsonRel)
}
if rule, _ := targetsByID["v6-rc-stabilization"]["completion_rule"].(string); rule != "manual" {
t.Fatalf("%s v6-rc-stabilization must keep completion_rule=manual", jsonRel)

View file

@ -253,7 +253,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 the prerelease-stabilization line, the working",
"the control plane still holds v6 on the pre-GA prerelease 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",
@ -273,7 +273,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 the prerelease-stabilization line, and there is",
"the control plane still holds v6 on the pre-GA prerelease 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.",

View file

@ -239,7 +239,7 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
self.assertIn(promotion_metadata_envelope(), normalize_ws(runbook))
def test_blocked_record_tracks_current_target_and_candidate_version(self) -> None:
blocked = read("docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.md")
blocked = read("docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-04-04.md")
self.assertIn("origin/pulse/v6-release", blocked)
self.assertIn("VERSION=6.0.0-rc.1", blocked)
self.assertIn("artifact-owned candidate stable tag", blocked)
@ -248,10 +248,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase):
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 prerelease tag, rollback", blocked)
self.assertIn("The active control-plane target is `v6-ga-promotion`", blocked)
self.assertIn("The active control-plane target is still `v6-rc-cut`, not", 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-28")
expected = blocked_record.build_blocked_record(record_date="2026-04-04")
self.assertEqual(blocked, expected)

View file

@ -1,5 +1,6 @@
from __future__ import annotations
import copy
from datetime import datetime, timezone
import os
import tempfile
@ -1354,9 +1355,15 @@ class StatusAuditTest(unittest.TestCase):
],
)
control_plane = copy.deepcopy(status_audit.DEFAULT_CONTROL_PLANE)
control_plane["targets_by_id"]["v6-rc-cut"]["status"] = "completed"
with mock.patch.dict(os.environ, {"PULSE_REPO_ROOT_PULSE": str(pulse)}, clear=False), mock.patch(
"status_audit.load_subsystem_rules",
return_value=[],
), mock.patch(
"status_audit.DEFAULT_CONTROL_PLANE",
control_plane,
):
report = audit_status_payload(payload)

View file

@ -76,7 +76,7 @@ class SubsystemLookupTest(unittest.TestCase):
self.assertEqual(result["status_audit_errors"], [])
self.assertIn(
result["control_plane"]["active_target"]["id"],
{"v6-rc-stabilization", "v6-ga-promotion", "v6-product-lane-expansion"},
{"v6-rc-cut", "v6-rc-stabilization", "v6-ga-promotion", "v6-product-lane-expansion"},
)
self.assertEqual(result["scope"]["control_plane_repo"], "pulse")
self.assertEqual(result["status_summary"]["lane_count"], 16)