Retire v6 preview demo target after GA

This commit is contained in:
rcourtman 2026-07-04 16:16:57 +01:00
parent 72c8d9677a
commit de0c5ff5c1
11 changed files with 93 additions and 166 deletions

View file

@ -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

View file

@ -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: |

View file

@ -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:-}"

View file

@ -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"

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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.

View file

@ -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) {

View file

@ -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'<script\b[^>]*\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)

View file

@ -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: