diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 013aa4ad1..a1ecfcf9f 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -17,18 +17,16 @@ Issue intake is split deliberately: **File**: `update-demo-server.yml` Automatically updates the governed demo target after a release is published. -Stable releases update the stable public demo. Prerelease tags update the -separate v6 preview demo. +Stable releases update the public demo. Prerelease tags no longer update a +separate v6 preview demo after GA. ### Configuration Required -Create two GitHub Environments: +Create one GitHub Environment: 1. `demo-stable` -2. `demo-preview-v6` -Each environment must define the same secret names so the workflow can select -the target by environment instead of hardcoding separate workflows. +The environment must define the secret names used by the governed demo target. Required environment secrets: @@ -53,26 +51,22 @@ Required shared secret: Required environment variables: 1. **DEMO_EXPECTED_HOSTNAME** - - The remote `hostname` value the selected environment is expected to report + - The remote `hostname` value the stable demo environment is expected to report - Stable example: `pulse-relay` - - Preview example: `pulse-v6-preview` - This is a host-identity guard: the workflow fails closed if the SSH secret points at the wrong machine 2. **DEMO_LOCAL_BASE_URL** - Local URL used on the target host for version and mock-mode verification - Example stable value: `http://localhost:7655` - - Example preview value: `http://localhost:8665` 3. **DEMO_PUBLIC_HEALTH_URL** - - Public health endpoint for the selected demo target + - Public health endpoint for the stable demo target - Example stable value: `https://demo.pulserelay.pro/api/health` - - Example preview value: `https://v6-demo.pulserelay.pro/api/health` Optional environment variables: 1. **DEMO_SERVICE_NAME** - Stable default: `pulse` - - Preview example: `pulse-v6-preview` - When set, the server installer derives the instance-specific install dir, config dir, update helper, and update timer from this service identity. @@ -83,10 +77,10 @@ Optional environment variables: ### How It Works 1. **Trigger**: Runs automatically when a GitHub release is published -2. **Target selection**: Stable tags deploy to `demo-stable`; prerelease tags deploy to `demo-preview-v6` -3. **Service identity guard**: Preview runs default to `pulse-v6-preview` and refuse to target the stable `pulse` service identity +2. **Target selection**: Stable tags deploy to `demo-stable`; prerelease tags are skipped because the public v6 preview target is retired after GA +3. **Service identity**: Stable runs default to the `pulse` service identity 4. **Governance check**: Validates the selected tag is reachable from the governed release branch for that version -5. **Latest check**: Refuses to update a target unless the published tag is the latest release for that target channel +5. **Latest check**: Refuses to update the public demo unless the published tag is the latest stable release 6. **Network attach**: Joins Tailscale before any SSH step so governed demo targets can stay on private hostnames or Tailscale IPs 7. **Update**: SSHs to the selected demo host and runs the tag-matched root installer from that exact git tag 8. **Host identity check**: Verifies the SSH target reports the governed expected hostname before running installer or deploy steps @@ -99,39 +93,29 @@ Optional environment variables: To test without publishing a release: 1. Go to `Actions` tab in GitHub 2. Select `Update Demo Server` workflow -3. Provide a tag and choose `stable`, `preview-v6`, or `auto` +3. Provide a stable tag and choose `stable` or `auto` ### Benefits -- ✅ Stable and preview demos stay on separate governed targets -- ✅ Prereleases no longer require a stable demo overwrite or a manual skip +- ✅ The public demo follows the stable v6 release line after GA +- ✅ Prereleases no longer require a second public v6 preview surface - ✅ Validates the real server installer path on the selected target - ✅ Removes release-operator guesswork about which demo should move -### Preview Bootstrap Note - -The preview environment must be bootstrapped once on the host before the update -workflow can keep it current. The supported path is a separate service identity -such as `pulse-v6-preview` plus a separate public route such as -`v6-demo.pulserelay.pro`; do not reuse the stable `pulse.service` instance. - ## Deploy Demo Server **File**: `deploy-demo-server.yml` -Manually deploys the current branch build to either the stable or preview demo -environment without changing the governed release workflow. +Manually deploys the current branch build to the stable demo environment +without changing the governed release workflow. -- Uses the same `demo-stable` / `demo-preview-v6` environment contract as the - release-driven updater +- Uses the same `demo-stable` environment contract as the release-driven updater - Joins Tailscale before SSH so governed demo targets can stay on private addresses instead of requiring public runner reachability - Requires `DEMO_EXPECTED_HOSTNAME`, `DEMO_LOCAL_BASE_URL`, and `DEMO_PUBLIC_HEALTH_URL` - Supports optional `DEMO_SERVICE_NAME`, `DEMO_INSTALL_DIR`, `DEMO_TEST_PORT`, `DEMO_AUTH_USER`, and `DEMO_AUTH_PASS` - Assumes the target service and install directory already exist on the host -- Defaults preview runs to `pulse-v6-preview` and refuses to target the stable - `pulse` service identity - Verifies the SSH target reports the governed expected hostname before deploy - Verifies that the public demo shell serves the same frontend entry asset that was built and deployed diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 2678dc2e4..6fefb0ab8 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -992,12 +992,11 @@ jobs: REQUIRED_BRANCH: ${{ needs.prepare.outputs.required_branch }} run: | if [ "${{ needs.prepare.outputs.is_prerelease }}" = "true" ]; then - TARGET="preview-v6" - else - TARGET="stable" + echo "[OK] Prerelease public demo update skipped; post-GA demo target is stable only." + exit 0 fi - gh workflow run update-demo-server.yml --ref "${REQUIRED_BRANCH}" -f tag="${{ needs.prepare.outputs.tag }}" -f target="${TARGET}" - echo "[OK] Demo server update dispatched for ${TARGET} from ${REQUIRED_BRANCH}" + gh workflow run update-demo-server.yml --ref "${REQUIRED_BRANCH}" -f tag="${{ needs.prepare.outputs.tag }}" -f target="stable" + echo "[OK] Demo server update dispatched for stable from ${REQUIRED_BRANCH}" - name: Summary run: | diff --git a/.github/workflows/deploy-demo-server.yml b/.github/workflows/deploy-demo-server.yml index 6bc5512d0..3f0e0ac59 100644 --- a/.github/workflows/deploy-demo-server.yml +++ b/.github/workflows/deploy-demo-server.yml @@ -10,7 +10,6 @@ on: type: choice options: - stable - - preview-v6 # schedule: # - cron: '0 0 * * *' # Nightly at midnight @@ -36,9 +35,6 @@ jobs: stable) ENVIRONMENT_NAME="demo-stable" ;; - preview-v6) - ENVIRONMENT_NAME="demo-preview-v6" - ;; *) echo "::error::Unsupported demo target: ${TARGET}" exit 1 @@ -180,21 +176,10 @@ jobs: TARGET="${{ needs.resolve.outputs.target }}" SERVICE_NAME="${DEMO_SERVICE_NAME:-}" if [ -z "$SERVICE_NAME" ]; then - case "$TARGET" in - stable) - SERVICE_NAME="pulse" - ;; - preview-v6) - SERVICE_NAME="pulse-v6-preview" - ;; - *) - echo "::error::Unsupported demo target: ${TARGET}" - exit 1 - ;; - esac + SERVICE_NAME="pulse" fi - if [ "$TARGET" = "preview-v6" ] && [ "$SERVICE_NAME" = "pulse" ]; then - echo "::error::Preview demo deployments must not target the stable pulse service." + if [ "$TARGET" != "stable" ]; then + echo "::error::Unsupported demo target: ${TARGET}" exit 1 fi INSTALL_DIR="${DEMO_INSTALL_DIR:-}" diff --git a/.github/workflows/update-demo-server.yml b/.github/workflows/update-demo-server.yml index 408808946..a57cc48fd 100644 --- a/.github/workflows/update-demo-server.yml +++ b/.github/workflows/update-demo-server.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: tag: - description: 'Release tag to deploy (e.g., v6.0.0-rc.N or v6.0.0)' + description: 'Stable release tag to deploy (e.g., v6.0.0)' required: true type: string target: @@ -17,7 +17,6 @@ on: options: - auto - stable - - preview-v6 permissions: contents: read @@ -29,7 +28,7 @@ jobs: tag: ${{ steps.target.outputs.tag }} target: ${{ steps.target.outputs.target }} environment_name: ${{ steps.target.outputs.environment_name }} - skip: ${{ steps.latest.outputs.skip || 'false' }} + skip: ${{ steps.target.outputs.skip || steps.latest.outputs.skip || 'false' }} steps: - name: Resolve target tag and demo environment @@ -59,10 +58,14 @@ jobs: TARGET="${REQUESTED_TARGET:-auto}" if [ -z "$TARGET" ] || [ "$TARGET" = "auto" ]; then if [ "$IS_PRERELEASE" = "true" ]; then - TARGET="preview-v6" - else - TARGET="stable" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "target=stable" >> "$GITHUB_OUTPUT" + echo "environment_name=demo-stable" >> "$GITHUB_OUTPUT" + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "Prerelease demo updates are retired after v6 GA; skipping public demo update for ${TAG}." + exit 0 fi + TARGET="stable" fi case "$TARGET" in @@ -73,13 +76,6 @@ jobs: fi ENVIRONMENT_NAME="demo-stable" ;; - preview-v6) - if [ "$IS_PRERELEASE" != "true" ]; then - echo "::error::Preview demo target only accepts prerelease tags. Refusing stable tag ${TAG}." - exit 1 - fi - ENVIRONMENT_NAME="demo-preview-v6" - ;; *) echo "::error::Unsupported demo target: ${TARGET}" exit 1 @@ -124,31 +120,26 @@ jobs: - name: Skip if not latest published release for target id: latest - if: github.event_name == 'release' + if: github.event_name == 'release' && steps.target.outputs.skip != 'true' env: GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - TARGET="${{ steps.target.outputs.target }}" TAG="${{ steps.target.outputs.tag }}" - if [ "$TARGET" = "stable" ]; then - LATEST=$(gh api "repos/${{ github.repository }}/releases/latest" --jq '.tag_name') - else - LATEST=$(gh api "repos/${{ github.repository }}/releases?per_page=100" --jq '[.[] | select(.draft == false and .prerelease == true)][0].tag_name') - fi + LATEST=$(gh api "repos/${{ github.repository }}/releases/latest" --jq '.tag_name') echo "Target tag: $TAG" - echo "Latest published tag for $TARGET: $LATEST" + echo "Latest published stable release: $LATEST" if [ -z "$LATEST" ] || [ "$LATEST" = "null" ]; then - echo "::error::Could not determine the latest published release for demo target $TARGET." + echo "::error::Could not determine the latest published stable release for demo deployment." exit 1 fi if [ "$TAG" != "$LATEST" ]; then echo "skip=true" >> "$GITHUB_OUTPUT" - echo "Release is not the latest published tag for ${TARGET}; skipping demo update." + echo "Release is not the latest published stable tag; skipping demo update." else echo "skip=false" >> "$GITHUB_OUTPUT" fi @@ -292,21 +283,10 @@ jobs: TARGET="${{ needs.resolve.outputs.target }}" SERVICE_NAME="${DEMO_SERVICE_NAME:-}" if [ -z "$SERVICE_NAME" ]; then - case "$TARGET" in - stable) - SERVICE_NAME="pulse" - ;; - preview-v6) - SERVICE_NAME="pulse-v6-preview" - ;; - *) - echo "::error::Unsupported demo target: ${TARGET}" - exit 1 - ;; - esac + SERVICE_NAME="pulse" fi - if [ "$TARGET" = "preview-v6" ] && [ "$SERVICE_NAME" = "pulse" ]; then - echo "::error::Preview demo updates must not target the stable pulse service." + if [ "$TARGET" != "stable" ]; then + echo "::error::Unsupported demo target: ${TARGET}" exit 1 fi echo "service_name=$SERVICE_NAME" >> "$GITHUB_OUTPUT" diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 71d043696..dc9558a44 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -886,7 +886,7 @@ hands-on Patrol modes, issue investigation, verified fixes, and 90-day history`. may prewarm shared infrastructure summary caches for route-owned surfaces, but public-demo arrival must not front-run a broader infrastructure-summary fetch than the route actually renders. Commercial - posture on `v6-demo` therefore stays governed by the route-owned + posture on the stable public demo therefore stays governed by the route-owned presentation policy and summary scope rather than by app-shell-wide bootstrap heuristics. That same app-shell boundary must also keep the public login entrypoints (`/` and `/login`) quiet before a session exists: @@ -1324,20 +1324,13 @@ The governed browser proof for that posture lives in `tests/integration/tests/53-demo-mode-commercial-boundary.spec.ts` and is expected to stay runnable through `tests/integration/scripts/run-tests.sh demo-contract`. -That same public-demo boundary also owns the governed `pulse-pro` operational -path for the live v6 preview. `pulse-pro/.github/workflows/deploy-v6-preview-demo.yml` -is the canonical operator entrypoint, and it must drive -`pulse-pro/scripts/bootstrap-v6-demo-preview.sh` as the canonical preview -runtime bootstrap/update path. The cost-contained default is the -`pulse-v6-preview.service` instance on `pulse-relay` behind the -`v6-demo.pulserelay.pro` Caddy route; the old dedicated -`pulse-v6-preview` Droplet remains a hibernated snapshot and is only a rebuild -or isolation option. -`pulse-pro/scripts/audit_v6_preview_demo.sh` is the canonical public smoke -proof. The bootstrap must fail closed unless the target preview host is -already the live public target and that public smoke audit passes; any -`--skip-public-audit` escape hatch is only for pre-cutover host staging or -deliberate Caddy/DNS maintenance, never an ordinary live refresh. +That same public-demo boundary now treats the old `v6-demo.pulserelay.pro` +preview route and its `pulse-v6-preview` service/bootstrap tooling as retired +after v6 GA. The stable public demo is the canonical customer-facing v6 demo +surface. Any future prerelease or canary demo needs a newly governed target, +hostname, service identity, and audit path; it must not silently revive the +retired v6-preview route or split the public demo posture back into parallel +stable and v6 surfaces. That same licensing/browser boundary now also owns authenticated commercial posture bootstrap and the prohibition on non-billing entitlement reads. `frontend-modern/src/useAppRuntimeState.ts` is the canonical authenticated diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 6ed89521a..0e47d877c 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -990,21 +990,20 @@ rehearsal skipped that header-composition gate. That same dry-run backend gate must run non-race Go package tests serially with `go test -p 1 ./...` so release SLO proof reflects product behavior rather than cross-package contention on 2-core hosted runners. -That same governed demo-deployment boundary now owns target separation between -the public stable demo and the opt-in v6 preview demo. `.github/workflows/create-release.yml`, +That same governed demo-deployment boundary now owns the post-GA single-demo +contract. `.github/workflows/create-release.yml`, `.github/workflows/update-demo-server.yml`, and `.github/workflows/deploy-demo-server.yml` -must route stable tags to the stable demo environment and prerelease tags to a -separate preview environment instead of skipping prerelease demo updates or -reusing the stable runtime in place. -That same preview deployment boundary also owns service-identity isolation and -public-shell parity proof. Preview demo runs must fail closed onto the -dedicated preview service identity instead of defaulting back to the stable -`pulse` instance, must prove that the SSH target reports the governed expected -hostname before any installer or binary copy runs, and demo deploy/update -verification must prove that the public demo HTML serves the same frontend -entry asset as the target service or freshly built preview artifact rather than -treating a passing `/api/health` response as enough evidence that the public -shell actually updated. That proof +must treat `demo-stable` as the only active public demo target: stable releases +may update it, prerelease tags must not create or update a second public v6 +preview target by default, and any future preview surface requires a new +explicitly governed target instead of reusing the retired v6-preview path. +That same demo deployment boundary also owns service-identity and public-shell +parity proof. Stable demo runs default to the `pulse` service identity, must +prove that the SSH target reports the governed expected hostname before any +installer or binary copy runs, and demo deploy/update verification must prove +that the public demo HTML serves the same frontend entry asset as the target +service or freshly built artifact rather than treating a passing `/api/health` +response as enough evidence that the public shell actually updated. That proof must use a deterministic HTML parser for the actual module entry script rather than brittle escaped shell regex or a first-match asset scrape that can fail differently over SSH or select the wrong preloaded chunk. diff --git a/docs/release-control/v6/internal/subsystems/registry.json b/docs/release-control/v6/internal/subsystems/registry.json index 1a39b9099..49941f7e2 100644 --- a/docs/release-control/v6/internal/subsystems/registry.json +++ b/docs/release-control/v6/internal/subsystems/registry.json @@ -2572,7 +2572,6 @@ "internal/cloudcp/routes.go", "internal/cloudcp/stripe/provisioner.go", "internal/hosted/provisioner.go", - "pulse-pro:.github/workflows/deploy-v6-preview-demo.yml", "pulse-pro:landing-page/thanks.html", "pulse-pro:license-server/grandfathering.go", "pulse-pro:license-server/intelligence_value.go", @@ -2580,9 +2579,6 @@ "pulse-pro:license-server/self_hosted_feature_catalog.generated.go", "pulse-pro:license-server/v6_checkout.go", "pulse-pro:license-server/v6_handlers.go", - "pulse-pro:scripts/audit_v6_preview_demo.sh", - "pulse-pro:scripts/audit_v6_preview_demo_browser.cjs", - "pulse-pro:scripts/bootstrap-v6-demo-preview.sh", "pulse-pro:scripts/grandfathered_recurring_cutover_preview.py" ], "verification": { @@ -3285,23 +3281,6 @@ "pulse-pro:license-server/main_test.go", "pulse-pro:scripts/tests/test_grandfathered_recurring_cutover_preview.py" ] - }, - { - "id": "public-demo-preview-operations", - "label": "public demo preview operations proof", - "match_prefixes": [], - "match_files": [ - "pulse-pro:.github/workflows/deploy-v6-preview-demo.yml", - "pulse-pro:scripts/audit_v6_preview_demo.sh", - "pulse-pro:scripts/audit_v6_preview_demo_browser.cjs", - "pulse-pro:scripts/bootstrap-v6-demo-preview.sh" - ], - "allow_same_subsystem_tests": false, - "test_prefixes": [], - "exact_files": [ - "pulse-pro:scripts/audit_v6_preview_demo.sh", - "tests/integration/tests/53-demo-mode-commercial-boundary.spec.ts" - ] } ], "match_files": null diff --git a/docs/releases/V6_PRERELEASE_RUNBOOK.md b/docs/releases/V6_PRERELEASE_RUNBOOK.md index 96ffd6a20..36b16cb56 100644 --- a/docs/releases/V6_PRERELEASE_RUNBOOK.md +++ b/docs/releases/V6_PRERELEASE_RUNBOOK.md @@ -32,8 +32,9 @@ Release workflows now enforce branch/tag lineage rules: - Validate the release tag commit is reachable from the governed branch for that version instead of assuming `main`. - `update-demo-server.yml` - - Routes stable tags to the stable demo environment and prerelease tags to - the separate v6 preview demo environment. + - Routes stable tags to the stable demo environment. + - Prerelease public demo deployment is retired after v6 GA; prerelease tags + must not create or update a second public v6 demo surface by default. - The selected tag must still be reachable from the governed branch for that version. @@ -132,10 +133,11 @@ git push origin pulse/v6-release - Existing unpublished draft releases for the same tag are updated in place and their tag is retargeted to the current governed `pulse/v6-release` head automatically. Do not delete the tag manually just to retry publish. - - The release workflow dispatches `update-demo-server.yml` against the - `preview-v6` demo target automatically. - - Keep the public stable demo on v5/stable; prereleases must land only on - the separate preview demo runtime. + - Historical prerelease publications used a separate preview demo runtime + while v5 remained the public stable demo. + - Prerelease public demo deployment is retired after v6 GA; future + prerelease tags must not create or update a second public v6 demo surface + unless a new governed preview target is explicitly introduced. 6. Canary rollout: - Upgrade a small user subset first. diff --git a/scripts/installtests/build_release_assets_test.go b/scripts/installtests/build_release_assets_test.go index 0f043059a..52dadcd74 100644 --- a/scripts/installtests/build_release_assets_test.go +++ b/scripts/installtests/build_release_assets_test.go @@ -503,7 +503,6 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { workflowRequired := []string{ `./scripts/release_ldflags.sh server --version "${VERSION}" --build-time "${BUILD_TIME}" --git-commit "${GIT_COMMIT}"`, `-buildvcs=false`, - `demo-preview-v6`, `demo-stable`, `workflow_dispatch:`, `target:`, @@ -513,6 +512,9 @@ func TestDockerAndDemoBuildsUseCanonicalReleaseLdflags(t *testing.T) { t.Fatalf("deploy-demo-server workflow missing canonical release ldflags usage: %s", needle) } } + if strings.Contains(workflow, `preview-v6`) || strings.Contains(workflow, `demo-preview-v6`) { + t.Fatal("deploy-demo-server workflow must not keep a separate v6 preview demo target after GA") + } } func TestAgentRuntimeImagePersistsAgentIdentityByDefault(t *testing.T) { diff --git a/scripts/release_control/release_promotion_policy_test.py b/scripts/release_control/release_promotion_policy_test.py index 72f85eaa1..7b15211c9 100644 --- a/scripts/release_control/release_promotion_policy_test.py +++ b/scripts/release_control/release_promotion_policy_test.py @@ -410,9 +410,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertNotIn("docs/releases/RELEASE_NOTES_v6.md", demo_copy) self.assertNotIn("docs/releases/V6_RC_OPERATOR_SUPPORT_PACK.md", demo_copy) - def test_update_demo_server_workflow_uses_generic_tag_examples(self) -> None: + def test_update_demo_server_workflow_uses_stable_tag_example(self) -> None: workflow = read(".github/workflows/update-demo-server.yml") - self.assertIn("v6.0.0-rc.N or v6.0.0", workflow) + self.assertIn("Stable release tag to deploy (e.g., v6.0.0)", workflow) + self.assertIn("Prerelease demo updates are retired after v6 GA", workflow) self.assertNotIn("v6.0.0-rc.1", workflow) def test_rehearsal_template_and_workflow_capture_ga_rehearsal_record(self) -> None: @@ -701,14 +702,14 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertNotIn("provenance: false", publish) self.assertIn("control_plane.py --branch-for-version", promote) self.assertIn("control_plane.py --branch-for-version", demo) - self.assertIn("preview-v6", demo) - self.assertIn("demo-preview-v6", demo) self.assertIn("demo-stable", demo) self.assertIn("Refusing prerelease tag", demo) - self.assertIn("Refusing stable tag", demo) - self.assertIn("latest published release for target", demo) - self.assertIn('SERVICE_NAME="pulse-v6-preview"', demo) - self.assertIn("Preview demo updates must not target the stable pulse service.", demo) + self.assertIn("Prerelease demo updates are retired after v6 GA", demo) + self.assertIn("Latest published stable release", demo) + self.assertNotIn("preview-v6", demo) + self.assertNotIn("demo-preview-v6", demo) + self.assertNotIn('SERVICE_NAME="pulse-v6-preview"', demo) + self.assertNotIn("Preview demo updates must not target the stable pulse service.", demo) self.assertIn("tailscale/github-action@4e4c49acaa9818630ce0bd7a564372c17e33fb4d # v2", demo) self.assertIn("TS_AUTHKEY", demo) self.assertIn("DEMO_EXPECTED_HOSTNAME", demo) @@ -724,8 +725,10 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("extract_entry_asset()", demo) self.assertIn(r']*\bsrc=\"(/assets/index-[^\"]*\.js)\"', demo) self.assertIn("Public demo is serving $PUBLIC_ASSET but the target service is serving $REMOTE_ASSET.", demo) - self.assertIn('SERVICE_NAME="pulse-v6-preview"', preview_deploy) - self.assertIn("Preview demo deployments must not target the stable pulse service.", preview_deploy) + self.assertNotIn("preview-v6", preview_deploy) + self.assertNotIn("demo-preview-v6", preview_deploy) + self.assertNotIn('SERVICE_NAME="pulse-v6-preview"', preview_deploy) + self.assertNotIn("Preview demo deployments must not target the stable pulse service.", preview_deploy) self.assertIn("DEMO_EXPECTED_HOSTNAME", preview_deploy) self.assertIn("Verify target host identity", preview_deploy) self.assertIn("Demo environment points at host $REMOTE_HOSTNAME but expected $DEMO_EXPECTED_HOSTNAME.", preview_deploy) @@ -762,9 +765,9 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("helm status pulse || true", helm_pages) self.assertIn("kubectl describe pods -A || true", helm_pages) self.assertIn("kubectl get events -A --sort-by=.lastTimestamp || kubectl get events -A || true", helm_pages) - self.assertIn('TARGET="preview-v6"', release_workflow) - self.assertIn('TARGET="stable"', release_workflow) - self.assertIn('gh workflow run update-demo-server.yml --ref "${REQUIRED_BRANCH}" -f tag="${{ needs.prepare.outputs.tag }}" -f target="${TARGET}"', release_workflow) + self.assertIn("Prerelease public demo update skipped; post-GA demo target is stable only.", release_workflow) + self.assertIn('gh workflow run update-demo-server.yml --ref "${REQUIRED_BRANCH}" -f tag="${{ needs.prepare.outputs.tag }}" -f target="stable"', release_workflow) + self.assertNotIn('TARGET="preview-v6"', release_workflow) self.assertIn("sync_chart_release_metadata.py", helm) self.assertIn("sync_chart_release_metadata.py", helm_pages) self.assertIn("--chart deploy/helm/pulse/Chart.yaml", helm) @@ -776,8 +779,9 @@ class ReleasePromotionPolicyTest(unittest.TestCase): self.assertIn("raw.githubusercontent.com/{repo}/{tag}/docs/images/pulse-logo.svg", chart_sync) self.assertIn("both stable and prerelease releases dispatch", runbook) self.assertIn("Release `6.0.0` from `pulse/v6-release`", runbook) - self.assertIn("separate v6 preview demo environment", runbook) - self.assertIn("preview-v6", runbook) + self.assertIn("Prerelease public demo deployment is retired after v6 GA", runbook) + self.assertNotIn("separate v6 preview demo environment", runbook) + self.assertNotIn("preview-v6", runbook) self.assertIn(promotion_metadata_envelope(), normalize_ws(runbook)) self.assertIn("waitUntil: 'domcontentloaded'", demo_smoke) self.assertIn("getByLabel('Username').waitFor({ state: 'visible', timeout: 120000 })", demo_smoke) diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 893636ec7..6f6868fa4 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -152,14 +152,14 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual(match["verification_requirement"]["id"], "mobile-relay-runtime") def test_lookup_paths_normalizes_workspace_relative_cross_repo_runtime_paths(self) -> None: - result = lookup_paths(["repos/pulse-pro/scripts/bootstrap-v6-demo-preview.sh"]) + result = lookup_paths(["repos/pulse-pro/scripts/grandfathered_recurring_cutover_preview.py"]) self.assertEqual(result["unowned_runtime_files"], []) self.assertEqual( {item["subsystem"] for item in result["impacted_subsystems"]}, {"cloud-paid"}, ) file_entry = result["files"][0] - self.assertEqual(file_entry["path"], "pulse-pro:scripts/bootstrap-v6-demo-preview.sh") + self.assertEqual(file_entry["path"], "pulse-pro:scripts/grandfathered_recurring_cutover_preview.py") self.assertEqual(file_entry["classification"], "runtime") self.assertEqual( {match["subsystem"] for match in file_entry["matches"]}, @@ -169,7 +169,7 @@ class SubsystemLookupTest(unittest.TestCase): self.assertEqual(match["lane_context"]["lane_id"], "L3") self.assertEqual( match["verification_requirement"]["id"], - "public-demo-preview-operations", + "legacy-grandfathering-cutover", ) def test_lookup_paths_assigns_shared_tag_badges_to_frontend_primitives(self) -> None: