From 95cba22a9632df7f193e3fa0fa7765d4f82895dd Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sat, 28 Mar 2026 22:13:22 +0000 Subject: [PATCH] Promote v6 control plane to GA readiness --- docs/release-control/control_plane.json | 6 +- .../release-control/internal/CONTROL_PLANE.md | 16 ++-- ...-promotion-readiness-blocked-2026-03-28.md | 77 +++++++++++++++++++ docs/release-control/v6/internal/status.json | 11 +++ .../release_promotion_policy_test.py | 5 +- scripts/release_control/status_audit_test.py | 37 ++++++++- 6 files changed, 136 insertions(+), 16 deletions(-) create mode 100644 docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.md diff --git a/docs/release-control/control_plane.json b/docs/release-control/control_plane.json index 528cced16..8fc7c3fcc 100644 --- a/docs/release-control/control_plane.json +++ b/docs/release-control/control_plane.json @@ -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-rc-stabilization", + "active_target_id": "v6-ga-promotion", "profiles": [ { "id": "v6", @@ -38,7 +38,7 @@ "id": "v6-rc-stabilization", "profile_id": "v6", "kind": "stabilization", - "status": "active", + "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.", "completion_rule": "manual", "proof_scope": "none" @@ -47,7 +47,7 @@ "id": "v6-ga-promotion", "profile_id": "v6", "kind": "release", - "status": "planned", + "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.", "completion_rule": "release_ready" }, diff --git a/docs/release-control/internal/CONTROL_PLANE.md b/docs/release-control/internal/CONTROL_PLANE.md index f2055ebc1..fc6cfe67d 100644 --- a/docs/release-control/internal/CONTROL_PLANE.md +++ b/docs/release-control/internal/CONTROL_PLANE.md @@ -222,24 +222,24 @@ user language should update the control plane. ## Current State 1. v6 is the current active release profile. -2. `v6-rc-stabilization` is the current active engineering target. - It is an intentionally human-held stabilization target with `proof_scope: - none`, and default slice selection should stay centered on the monitoring - floor unless the user explicitly overrides it or the governed target - changes again. +2. `v6-ga-promotion` 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. 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-ga-promotion` remains planned and is still blocked on exercised - promotion proof when that target is resumed. +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. 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 long-term system. 8. Until the explicit post-GA branch cutover happens, both prerelease and - stable v6 promotions resolve to `pulse/v6` via `control_plane.json`. + stable v6 promotions resolve to `pulse/v6-release` via `control_plane.json`. 9. `AGENT_VALUES.md` is the evergreen values-only entry point; prompts should stay close to that layer and delegate detailed behavior to the governed control-plane and profile-specific files. diff --git a/docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.md b/docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.md new file mode 100644 index 000000000..efa405f8f --- /dev/null +++ b/docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.md @@ -0,0 +1,77 @@ +# Prerelease-to-GA Promotion Readiness Blocked Record + +- Date: `2026-03-28` +- 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 `v6-ga-promotion`, so stable or GA + promotion is now the governed objective for Pulse v6. +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 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. 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`. +2. 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. +3. 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 +4. Capture the `rc-to-ga-rehearsal-summary` artifact and run URL. +5. 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. +6. Change the gate from `blocked` only if the rehearsal passes and the rollout + inputs remain explicit. diff --git a/docs/release-control/v6/internal/status.json b/docs/release-control/v6/internal/status.json index ba6958a40..5805b5c61 100644 --- a/docs/release-control/v6/internal/status.json +++ b/docs/release-control/v6/internal/status.json @@ -616,6 +616,11 @@ "path": "docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-26.md", "kind": "file" }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.md", + "kind": "file" + }, { "repo": "pulse", "path": "docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-rehearsal-blocked-2026-03-26.md", @@ -2945,6 +2950,12 @@ "kind": "file", "evidence_tier": "local-rehearsal" }, + { + "repo": "pulse", + "path": "docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.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", diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index cad3d204c..9c9834a4f 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -238,7 +238,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-26.md") + blocked = read("docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-blocked-2026-03-28.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) @@ -247,9 +247,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) 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-26") + expected = blocked_record.build_blocked_record(record_date="2026-03-28") self.assertEqual(blocked, expected) diff --git a/scripts/release_control/status_audit_test.py b/scripts/release_control/status_audit_test.py index ed019c3af..c69b896f7 100644 --- a/scripts/release_control/status_audit_test.py +++ b/scripts/release_control/status_audit_test.py @@ -153,6 +153,21 @@ def work_claim( } +def non_completed_target_id(*preferred_ids: str) -> str: + candidate_ids = preferred_ids or ("v6-ga-promotion", "v6-product-lane-expansion") + for target_id in candidate_ids: + target = status_audit.DEFAULT_CONTROL_PLANE["targets_by_id"].get(target_id) + if target and str(target.get("status", "")).strip() != "completed": + return target_id + for target_id, target in status_audit.DEFAULT_CONTROL_PLANE["targets_by_id"].items(): + if ( + str(target.get("profile_id", "")).strip() == status_audit.ACTIVE_PROFILE_ID + and str(target.get("status", "")).strip() != "completed" + ): + return target_id + raise AssertionError("expected at least one non-completed control-plane target for test fixtures") + + def base_payload( *, lane_status: str = "target-met", @@ -1174,6 +1189,21 @@ class StatusAuditTest(unittest.TestCase): 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.active_target_blocking_levels", + return_value=["repo-ready", "rc-ready", "release-ready"], + ), mock.patch.object( + status_audit, + "ACTIVE_TARGET", + { + **status_audit.ACTIVE_TARGET, + "id": non_completed_target_id("v6-product-lane-expansion", "v6-ga-promotion"), + "kind": "feature", + "summary": "Exercise work-claim expiry without coupling the unit test to the live release target.", + "completion_rule": "manual", + "proof_scope": "none", + "blocking_levels": ["repo-ready", "rc-ready", "release-ready"], + }, ): report = audit_status_payload( payload, @@ -1194,13 +1224,14 @@ class StatusAuditTest(unittest.TestCase): write_file(pulse, "docs/lane-proof.md") write_file(pulse, "docs/proof_test.go") write_file(pulse, "docs/hybrid_test.go") + target_id = non_completed_target_id("v6-ga-promotion", "v6-product-lane-expansion") payload = base_payload( work_claims=[ work_claim( claim_id="claim-lane-broad", agent_id="agent-1", - target_id="v6-rc-stabilization", + target_id=target_id, work_kind="lane", work_id="L1", claimed_at="2026-03-13T09:00:00Z", @@ -1210,7 +1241,7 @@ class StatusAuditTest(unittest.TestCase): work_claim( claim_id="claim-gate-narrow", agent_id="agent-2", - target_id="v6-rc-stabilization", + target_id=target_id, work_kind="release-gate", work_id="g1", claimed_at="2026-03-13T09:20:00Z", @@ -1220,7 +1251,7 @@ class StatusAuditTest(unittest.TestCase): work_claim( claim_id="claim-gate-duplicate", agent_id="agent-3", - target_id="v6-rc-stabilization", + target_id=target_id, work_kind="release-gate", work_id="g1", claimed_at="2026-03-13T09:40:00Z",