mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
43 KiB
43 KiB
Pulse v6 High-Risk Release Verification Matrix
Use this file for the parts of the release that I should not trust from unit tests alone.
This is the human runbook for status.json.release_gates.
A gate is only passed when its automated proof still passes and the manual
scenario has been exercised in a staging-like environment with the expected
result.
How To Use This Matrix
- Run the automated proof first.
- Run the manual scenario exactly on the runtime surface named below.
- Record the environment, date, and result in the release ticket or inline in this file.
- Update the matching
status.json.release_gates[*].statusentry topassedonly after the full gate is clear. - Treat every failed or unconfirmed gate as a release blocker.
Companion drill:
- For cancellation/reactivation pricing continuity, checkout re-entry, and
Stripe-driven revocation boundaries, run
docs/release-control/v6/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.mdand attach the resulting record to the applicable gate evidence.
Gate: hosted-signup-billing-replay
- Why this is risky: Hosted signup, magic-link access, org provisioning, checkout, and webhook replay are cross-system flows. They can look fine in isolated tests while still failing in the real handoff path.
- Primary runtime surfaces:
frontend-modern/src/pages/HostedSignup.tsxfrontend-modern/src/components/Settings/BillingAdminPanel.tsxinternal/api/public_signup_handlers.gointernal/hosted/...internal/cloudcp/...internal/api/stripe_webhook_handlers*.go - Automated proof:
go test ./internal/api -run 'TestHostedLifecycle|TestHostedSignup' -count=1go test ./internal/api -run 'TestStripeWebhook_'go test ./internal/cloudcp/... -count=1go test ./internal/hosted/... -count=1cd frontend-modern && npx vitest run src/pages/__tests__/HostedSignup.test.tsx src/components/Settings/__tests__/BillingAdminPanel.test.tsxcd tests/integration && PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 npm test -- tests/07-trial-signup-return.spec.ts --project=chromiumcd tests/integration && PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 npm test -- tests/58-self-hosted-trial-rate-limit-ui.spec.ts --project=chromiumThe self-hosted handoff proof must keep the canonical trial-start transport contract:POST /api/license/trial/startreturns409 trial_signup_requiredwhile the hosted-signup retry burst remains open, then transitions to429 trial_rate_limitedplusRetry-Afterbackoff once the limiter actually engages. The Pulse Pro browser CTA proof on/settings/system/billingmust surface that same canonicalRetry-Afterbackoff even whendetails.retry_after_secondsdisagrees. - Live rehearsal helper:
python3 scripts/release_control/hosted_signup_billing_replay_rehearsal.py --base-url <hosted-url> --signup-email <email> --org-name <org> ... - Manual scenario:
- Start a hosted signup from the self-hosted trial/upgrade path.
- Confirm a missing hosted public URL fails closed before any org or RBAC tenant is created.
- Confirm the user is sent to hosted checkout instead of receiving a local entitlement immediately.
- Confirm unresolved org linkage fails closed on webhook handling.
- Replay the same webhook after the linked org exists and confirm it succeeds.
- Confirm billing-admin state reflects the resulting org/subscription state.
- Pass when: Hosted signup fails closed before provisioning when required external URL config is missing, creates the correct org when enabled, webhook replay is fail-closed before linkage and succeeds after linkage, and the UI shows the resulting state coherently.
- Latest exercised record:
docs/release-control/v6/records/hosted-signup-billing-replay-production-fixed-2026-03-13.md - Block release if: Any hosted checkout, org linkage, magic-link, billing-admin, or webhook replay path is unconfirmed or inconsistent.
Gate: cloud-hosted-tier-runtime-readiness
- Why this is risky: Hosted signup alone is not enough. If the real hosted Pulse tier cannot be entered, authenticated, navigated, or administered after provisioning, users will pay for a product tier that exists in pricing and billing but not in dependable runtime behavior.
- Primary runtime surfaces:
internal/cloudcp/...internal/hosted/...internal/api/public_signup_handlers.gointernal/api/hosted_org_admin_handlers.gofrontend-modern/src/pages/HostedSignup.tsxfrontend-modern/src/components/Settings/BillingAdminPanel.tsxfrontend-modern/src/components/Settings/OrganizationBillingPanel.tsx - Automated proof:
go test ./internal/cloudcp/... -count=1go test ./internal/hosted/... -count=1go test ./internal/api -run 'TestHostedLifecycle|TestHostedOrgAdminHandlers|TestHostedSignupSuccess|TestHostedSignupValidationFailures|TestHostedSignupHostedModeGate|TestHostedSignupRateLimit|TestHostedSignupRateLimit_NoProvisioningSideEffects|TestHostedSignupCleanupOnRBACFailure|TestHostedSignupFailsClosedWithoutPublicURL|TestStripeWebhook_' -count=1cd frontend-modern && npx vitest run src/pages/__tests__/HostedSignup.test.tsx src/components/Settings/__tests__/BillingAdminPanel.test.tsx src/components/Settings/__tests__/OrganizationBillingPanel.test.tsxEXPECT_PUBLIC_SIGNUP_ENABLED=false scripts/run_cloud_public_signup_smoke.shwhile public signup is intentionally closed, orEXPECT_PUBLIC_SIGNUP_ENABLED=true scripts/run_cloud_public_signup_smoke.shduring the GA launch window when public signup is expected to be live. - Manual scenario:
- Start from a real hosted Pulse signup or an existing hosted tenant.
- Confirm the user can authenticate into the hosted Pulse app and reach a working hosted runtime instead of a self-hosted setup or dead-end state.
- Confirm hosted billing/admin and organization billing surfaces render coherent plan, seat, and entitlement state for the hosted tenant.
- Confirm hosted-only admin actions and normal post-signup navigation work without self-hosted license prompts or broken hosted assumptions.
- Pass when: A real hosted Pulse customer can sign up or sign in, land in a working hosted runtime, and use the hosted billing/admin surfaces without self-hosted fallbacks or broken post-provisioning behavior.
- Latest exercised record:
docs/release-control/v6/records/cloud-hosted-tier-runtime-readiness-production-followup-2026-03-13.md - Block release if: Hosted Pulse can be sold or provisioned but not entered and used as a coherent hosted product tier afterward.
Gate: commercial-cancellation-reactivation
- Why this is risky: Grandfathered recurring continuity, Stripe cancellation state, entitlement revocation, and public checkout re-entry span multiple repos and billing boundaries. This is exactly the kind of path that can look correct in unit tests while still charging the wrong price or granting the wrong access in a real customer journey.
- Primary runtime surfaces:
internal/api/payments_webhook_handlers.gopkg/licensing/...frontend-modern/src/components/Settings/ProLicensePanel.tsxpulse-pro/license-server/v6_checkout.goStripe customer portal / recurring subscription state - Automated proof:
python3 scripts/release_control/commercial_cancellation_reactivation_proof.pyFor the live external rehearsal path, usepython3 scripts/release_control/commercial_cancellation_reactivation_rehearsal.py. Manual command detail remains documented indocs/release-control/v6/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.md. - Manual scenario:
Execute
CCR-1throughCCR-7fromdocs/release-control/v6/COMMERCIAL_CANCELLATION_REACTIVATION_E2E_TEST_PLAN.mdagainst a staging-like billing environment and write a dated record underdocs/release-control/v6/records/. - Pass when: Active grandfathered subscribers keep their legacy recurring price while the subscription remains continuous, completed cancellation revokes paid access, and any later public re-entry lands on current public v6 pricing rather than reviving the legacy recurring rate.
- Latest exercised record:
docs/release-control/v6/records/commercial-cancellation-reactivation-external-e2e-2026-03-13.md - Block release if: The scenario is unexercised, a returning canceled customer can re-enter on a legacy recurring price, or cancellation/reactivation leaves pricing and entitlement state inconsistent across Stripe, Pulse runtime, and customer UI.
Gate: known-rc-issue-closure-for-ga
- Why this is risky: Pulse v6 GA is now explicitly treated as feature-complete for the admitted v6 scope. If the RC program surfaces user-visible issues and GA still ships with those items open or only half-resolved, stable users become the cohort that pays for unfinished RC fallout anyway.
- Primary runtime surfaces:
docs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.mddocs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.mddocs/release-control/v6/internal/records/known-rc-issue-closure-for-ga-blocked-2026-04-21.mdplus the issue-linked runtime surfaces enumerated in the current dated RC issue-closure record for the candidate. - Automated proof:
python3 scripts/release_control/status_audit.py --prettyThe automation floor only proves that the gate state is recorded. It does not clear the gate by itself. - Manual scenario:
- Open or install the exact GA candidate being considered for promotion.
- Materialize the current dated RC issue-closure record and confirm it enumerates the full current RC issue set in scope for v6 GA.
- Review the active RC feedback intake surfaces for anything newer than the dated record: newly opened issues, new comments on open RC-soak issues, the pinned prerelease feedback hub, and equivalent actionable feedback reports.
- Fold any still-in-scope user-visible item from that intake back into the dated RC issue-closure record before making the gate call.
- For each issue or absorbed feedback item, verify one of these dispositions on the affected surface: fixed in candidate with proof, invalid with evidence, or conservatively superseded with the original user-visible failure resolved or explicitly narrowed.
- Confirm no open RC-era user-visible issue or still-in-scope RC feedback item is being accepted as normal post-GA cleanup for the v6 line.
- Pass when: Every issue or still-in-scope feedback item in the current RC closure set is fixed in the candidate with proof, proven invalid with evidence, or conservatively superseded with the original problem resolved or narrowed, and no user-visible RC-era issue remains open as intended GA carryover.
- Latest exercised record:
docs/release-control/v6/internal/records/known-rc-issue-closure-for-ga-blocked-2026-04-21.md - Block release if: Any RC-era issue or still-in-scope RC feedback item remains open, unverified, or hand-waved into post-GA work, or if the dated RC issue-closure record is missing or stale.
Gate: self-hosted-commercial-ga-coherence
- Why this is risky: A technically promotable build is still a broken GA release if the self-hosted commercial path reads like a bridge product. Public landing copy, in-app upgrade and trial prompts, checkout, Pulse Account/license-management, and GA-facing guidance all need to describe the same Community / Relay / Pro offer without preview-only, legacy-v5, or contradictory framing.
- Primary runtime surfaces:
pulse-pro/landing-page/index.htmlpulse-pro/landing-page/README.mdpulse-pro/V6_LAUNCH_CHECKLIST.mdfrontend-modern/src/components/Settings/BillingAdminPanel.tsxfrontend-modern/src/components/Settings/CommercialBillingSections.tsxfrontend-modern/src/utils/commercialBillingModel.tsdocs/PULSE_PRO.md - Automated proof:
python3 scripts/release_control/documentation_currentness_test.pypython3 scripts/release_control/release_promotion_policy_test.pyThe automation floor only checks governance drift. It does not clear this gate by itself. - Manual scenario:
- Open the externally reachable GA candidate for the self-hosted public site.
- Confirm the public landing surface presents the actual v6 self-hosted Community / Relay / Pro ladder and product promise rather than a live v5 bridge with preview-only v6 packaging.
- Follow a real self-hosted upgrade or trial entry point from the app and confirm the wording, plan identity, and customer expectation match the public site.
- Continue through the real checkout and Pulse Account/license-management path and confirm it stays on the same product story instead of switching back to legacy Pulse Pro framing or ambiguous plan rules.
- Review the GA-facing guidance/release communication intended to ship with promotion and confirm it matches the same package and customer path.
- Pass when: A new self-hosted customer can move from public understanding to upgrade or trial entry, checkout/account management, and GA-facing guidance without encountering contradictory plan copy, preview-only posture, or legacy-v5 bridge messaging.
- Latest exercised record:
docs/release-control/v6/internal/records/self-hosted-commercial-ga-coherence-2026-04-20.md - Block release if: GA can still be promoted while the public self-hosted commercial path remains transitional, contradictory, or dependent on RC-only bridge posture.
Gate: documentation-currentness-and-legacy-cleanup
- Why this is risky: Stale release-control or upgrade guidance creates invisible operational drift. Agents and humans will follow whatever the docs say is current, even when the runtime has already moved on.
- Primary runtime surfaces:
docs/release-control/CONTROL_PLANE.mddocs/release-control/control_plane.jsondocs/release-control/v6/internal/SOURCE_OF_TRUTH.mddocs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.mddocs/release-control/v6/README.mddocs/release-control/v6/internal/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md - Automated proof:
python3 scripts/release_control/documentation_currentness_test.py - Manual scenario:
- Review the active v6 guidance surface used by agents and release work.
- Confirm the docs describe the current active target, release phase, and canonical workflow rather than superseded guidance.
- Confirm any remaining legacy, audit, or historical docs are clearly framed as records or reference material instead of current instructions.
- Confirm any stale active doc is updated, archived, or removed rather than left to drift.
- Pass when: Active v6-facing guidance matches the current governed state of the repo, and historical docs no longer present themselves as current guidance.
- Latest exercised record:
docs/release-control/v6/records/documentation-currentness-and-legacy-cleanup-2026-03-13.md - Block release if: Agents or humans can still follow stale v6 guidance, or legacy/historical docs remain mixed into the active v6 instruction surface.
Gate: settings-surface-layout-consistency
- Why this is risky: Settings surfaces are one of the densest trust surfaces in Pulse. If top-level settings pages drift in shell framing, header treatment, or section rhythm, the product feels unfinished and users stop trusting that comparable admin and paid surfaces behave to the same standard.
- Primary runtime surfaces:
frontend-modern/src/components/Settings/Settings.tsxfrontend-modern/src/components/Settings/SettingsPageShell.tsxfrontend-modern/src/components/Settings/settingsPanelRegistry.tsfrontend-modern/src/components/shared/SettingsPanel.tsxrepresentative top-level settings panels underfrontend-modern/src/components/Settings/ - Automated proof:
cd frontend-modern && npx vitest run src/components/Settings/__tests__/settingsArchitecture.test.ts - Manual scenario:
- Run the release build or a staging-like runtime and click through representative top-level settings surfaces across general, billing, relay, organization, security, AI, updates, and recovery panels.
- Confirm each comparable surface uses the same outer settings shell instead of introducing a bespoke page-level header or outer frame.
- Confirm the title, description, and top-level section framing stay visually consistent across those panels.
- Confirm any intentional deviation is rare, product-justified, and documented rather than an accidental local fork.
- Pass when: Comparable top-level settings surfaces route through the canonical settings shell and present consistent top-level framing and header treatment, with no ad hoc page chrome left behind.
- Latest exercised record:
docs/release-control/v6/records/settings-surface-layout-consistency-2026-03-13.md - Block release if: Settings surfaces still mix multiple top-level shells, inconsistent header framing, or bespoke page chrome across otherwise comparable panels.
Gate: paid-feature-entitlement-gating
- Why this is risky: This is where free-vs-paid drift becomes customer-visible. UI claims, API enforcement, entitlements, and agent-allocation accounting all need to agree.
- Primary runtime surfaces:
GET /api/license/entitlementsinternal/api/monitored_system_limit_enforcement.gointernal/api/subscription_entitlements.gofrontend-modern/src/pages/AIIntelligence.tsxfrontend-modern/src/pages/Alerts.tsxfrontend-modern/src/components/Settings/OrganizationBillingPanel.tsxfrontend-modern/src/components/shared/MonitoredSystemLimitWarningBanner.tsxinternal/cloudcp/entitlements/service.gopkg/licensing/entitlements.go - Automated proof:
go test ./internal/api -run 'TestEntitlementHandler_|TestRequireLicenseFeature_HostedEntitlements|TestLicenseGatedEmptyResponse_HostedEntitlements' -count=1go test ./internal/api -run 'TestMonitoredSystemLedger|TestHandleAddNode_BlocksNewCountedSystemAtLimit|TestHandleAutoRegister_BlocksNewCountedSystemAtLimit|TestTrueNASHandlers_HandleAdd_BlocksNewCountedSystemAtLimit|TestDockerAgentHandlers_HandleReport_BlocksNewMonitoredSystemAtLimit|TestKubernetesAgentHandlers_HandleReport_BlocksNewMonitoredSystemAtLimit|TestContract_EntitlementPayloadMonitoredSystemUsageJSONSnapshot' -count=1go test ./internal/license/... -count=1go test ./internal/cloudcp/... -count=1cd frontend-modern && npx vitest run src/pages/__tests__/AIIntelligence.test.tsx src/components/Alerts/__tests__/InvestigateAlertButton.test.tsx src/components/Settings/__tests__/OrganizationBillingPanel.test.tsx src/components/Settings/__tests__/RBACPaywallPanels.test.tsx src/components/shared/__tests__/MonitoredSystemLimitWarningBanner.test.tsx src/utils/__tests__/licensePresentation.test.ts src/utils/__tests__/rbacPresentation.test.ts src/utils/__tests__/frontendResourceTypeBoundaries.test.ts - Manual scenario:
- Use a free/community entitlement state and confirm paid features are gated.
- Use a Pro/Cloud entitlement state and confirm the same surfaces unlock.
- Confirm the upgrade path shown in the UI matches the runtime capability.
- Confirm alert analysis, AI autonomy, RBAC-only areas, and cloud-only areas do not leak access for free users.
- Confirm the monitored-system count shown in settings and upgrade-warning surfaces matches the deduped top-level monitored-system count and includes API-backed systems under the same cap.
- Confirm adding a new counted monitored system at limit is blocked while
existing monitored systems continue to report under the canonical
max_monitored_systemsentitlement.
- Pass when: Free users are blocked consistently, paid users are admitted consistently, monitored-system counts and caps stay coherent across UI and runtime, and there is no UI/API disagreement.
- Latest exercised record:
docs/release-control/v6/records/paid-feature-entitlement-gating-2026-03-12.md - Block release if: Any feature can be used without entitlement, or any paid user is blocked on a correctly granted capability, or agent counts/caps disagree across enforcement and user-visible surfaces.
Gate: rc-to-ga-promotion-readiness
- Why this is risky: Stable users must not become the first real validation cohort for v6. The prerelease-to-GA handoff is where migration confidence, release automation, rollback clarity, and the v5 support policy have to become explicit.
- Primary runtime surfaces:
.github/workflows/create-release.yml.github/workflows/publish-docker.yml.github/workflows/promote-floating-tags.ymldocs/release-control/v6/internal/PRE_RELEASE_CHECKLIST.mddocs/release-control/v6/internal/RELEASE_PROMOTION_POLICY.mddocs/releases/RELEASE_NOTES_v6.md - Automated proof:
python3 scripts/release_control/release_promotion_policy_test.py - Manual scenario:
- Identify the exact published prerelease tag and commit that are being considered for stable or GA promotion.
- Confirm the candidate commit has already shipped on
rcthrough a real release-pipeline run, not only workflow lint or static YAML validation. Accidental prerelease git tags do not count as shipped prerelease lineage. - Confirm the candidate satisfies the minimum 72-hour prerelease soak or that a hotfix exception and reason are recorded explicitly before promotion.
- Confirm the previous stable rollback target and exact reinstall or pin command are recorded in the release notes or release ticket.
- Confirm
V5_MAINTENANCE_SUPPORT_POLICY.mdis still the governing v5 support policy and record the exact v6 GA date plus the exact v5 end-of-support date that will ship with the stable or GA announcement. - Confirm the default-branch copy of
.github/workflows/release-dry-run.ymlalready accepts the governed stable rehearsal metadata envelope throughworkflow_dispatch, because GitHub validates dispatch inputs against the default branch even when dispatchingpulse/v6. - Confirm the
Release Dry Runworkflow produced anrc-to-ga-rehearsal-summaryartifact carrying 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, and record the run URL in the release ticket or rehearsal record. - Confirm the migration gate and other applicable high-risk gates are cleared for this same candidate before broad rollout.
- Pass when: Stable or GA promotion is a governed handoff from an exercised prerelease with live release-pipeline proof, explicit rollback instructions, and the published v5 maintenance policy plus exact end-of-support date, with a linked rehearsal run URL and dry-run artifact carrying the full canonical promotion metadata envelope.
- Latest exercised record:
docs/release-control/v6/internal/records/rc-to-ga-promotion-readiness-rehearsal-2026-04-20.md - Block release if: Stable users would become the first real validation cohort, the rollback target is unclear, or the v5 maintenance-only policy is still undecided.
Gate: upgrade-state-and-entitlement-preservation
- Why this is risky: Upgrade pain is trust-breaking and easy to miss when clean-room tests start from fresh installs. Paid continuity, onboarding continuity, and local state preservation all have to survive a real upgrade path.
- Primary runtime surfaces:
pkg/licensing/...internal/api/license_handlers*.gointernal/api/public_signup_handlers.gofrontend-modern/src/components/SetupWizard/...frontend-modern/src/components/Settings/... - Automated proof:
go test ./internal/api -run 'TestHostedLifecycle|TestEntitlementHandler_|TestRequireLicenseFeature_HostedEntitlements' -count=1go test ./pkg/licensing/... -count=1go test ./tests/migration -run 'TestV5PaidLicenseUpgrade_CommercialMigrationFailureMatrix|TestV5PaidLicenseUpgrade_RealLicenseServerExchange|TestV5DataDir_CSRFLegacyMapFormat|TestV5DataDir_CSRFTokenFileContinuity|TestV5DataDir_SessionLegacyMapFormat|TestV5DataDir_SessionTokenContinuity|TestV5DowngradeSafety|TestV5FullUpgradeScenario' -count=1cd frontend-modern && npx vitest run src/components/Settings/__tests__/RBACPaywallPanels.test.tsx src/components/Settings/__tests__/BillingAdminPanel.test.tsx src/pages/__tests__/AIIntelligence.test.tsxcd tests/integration && PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 npm test -- tests/11-first-session.spec.ts --project=chromium - Manual scenario:
- Start from the previous supported Pulse build with non-trivial local state and an already-activated paid entitlement.
- Upgrade directly to the candidate v6 build without deleting local state.
- Confirm the app does not ask for the license again during normal startup.
- Confirm first-session and setup surfaces do not reset or regress into misleading upgrade prompts.
- Confirm paid-only surfaces remain correctly gated after upgrade.
- Pass when: Upgrade keeps the user's local state, entitlements, and first-session continuity intact without requiring manual repair or repeated activation.
- Latest exercised record:
docs/release-control/v6/records/upgrade-state-and-entitlement-preservation-2026-03-13.md - Block release if: Upgrade requires manual cleanup, repeated license entry, or leaves paid and non-paid surfaces in an inconsistent state.
Gate: relay-registration-reconnect-drain
- Why this is risky: Relay failures are highly visible and often only appear under reconnect, eviction, or disconnect pressure.
- Primary runtime surfaces:
internal/relay/...internal/api/router_routes_auth_security.gointernal/api/onboarding_handlers.gopulse-pro/relay-server/...frontend-modern/src/components/Settings/RelaySettingsPanel.tsxpulse-mobile/src/relay/... - Automated proof:
go test ./internal/relay -run 'TestClient_E2E_MultiMobileClientRelay|TestClient_AbruptDisconnectCancelsInFlightHandlers|TestClient_AbruptDisconnectMultipleChannelCleanup|TestClient_DrainDuringInFlightData|TestClient_DrainWithMultipleInFlightChannels|TestClientRegister_SessionResumeRejectionClearsCachedSession|TestRunLoop_SessionResumeRejectionFallsBackToFreshRegister' -count=1go test ./internal/api -run 'TestRelayEndpointsRequireLicenseFeature|TestRelayOnboardingEndpointsRequireLicenseFeature|TestRelayLicenseGatingResponseFormat|TestOnboardingQRPayloadStructure|TestOnboardingValidateSuccessAndFailure|TestOnboardingDeepLinkFormat' -count=1cd frontend-modern && npx vitest run src/components/Settings/__tests__/RelaySettingsPanel.runtime.test.tsx src/components/Settings/__tests__/settingsReadOnlyPanels.test.tsxcd /Volumes/Development/pulse/repos/pulse-mobile && npm test -- --runTestsByPath src/relay/__tests__/client.test.ts src/relay/__tests__/client-hardening.test.ts src/relay/__tests__/protocol-contract.test.ts - Manual scenario:
- Register a fresh relay client.
- Force reconnect after a normal disconnect.
- Force stale session resume or server-side eviction and confirm fresh registration recovery.
- Force abrupt disconnect while work is inflight and confirm drain/recovery behavior is sane.
- Pass when: Fresh register, reconnect, stale resume recovery, and disconnect/drain all behave predictably without hanging or spinning.
- Latest exercised record:
docs/release-control/v6/records/relay-registration-reconnect-drain-2026-03-13.md - Block release if: The relay can strand the app/client in resume loops, dead sessions, or lost inflight work.
Gate: unified-agent-v5-upgrade-continuity
- Why this is risky: The v5-to-v6 unified-agent crossover is where release-asset integrity, updater continuity, legacy compatibility routing, and user-visible agent inventory can drift apart. Repo-local tests cover most of the mechanics, but the real prerelease path still needs one exercised upgrade from an actual v5 install.
- Primary runtime surfaces:
GET /install.shGET /install.ps1GET /api/agent/versioninternal/api/unified_agent.gointernal/api/router_routes_registration.gointernal/agentupdate/update.gointernal/hostagent/agent.gofrontend-modern/src/components/Settings/InfrastructureOperationsController.tsxfrontend-modern/src/components/Settings/OrganizationBillingPanel.tsx - Automated proof:
go test ./internal/api -run 'TestDownloadUnifiedInstallScript|TestDownloadUnifiedInstallScriptPS|TestProxyInstallScriptFromGitHub|TestContract_InstallScriptReleaseAssetURL|TestDownloadUnifiedAgent|TestUnifiedAgentHandlers_LegacyV5ReportUpgradesToSingleCanonicalUnifiedAgent|TestUnifiedAgentEndpointsAcceptLegacyUnifiedAgentReportScopeAlias|TestNormalizeRequestedScopesCanonicalizesLegacyUnifiedAgentAliases|TestContract_APITokenScopeAliasNormalization' -count=1go test ./internal/agentupdate -run 'TestCheckAndUpdateToFirstHostReportCarriesPreviousVersionOnce|TestUpdateToFirstHostReportCarriesPreviousVersionOnce|TestPerformUpdatePersistsPreviousVersionForNextStart' -count=1go test ./internal/hostagent -run 'TestNew_CarriesUpdatedFromIntoFirstV6Report|TestAgentSendReport_SetsHeadersAndPostsJSON' -count=1 - Manual scenario:
- Start from a real Pulse v5 install with an already-enrolled unified agent and non-empty agent inventory.
- Point that install at the candidate v6 prerelease build and trigger the real upgrade path through the release-served installer or updater assets, not a repo-local script.
- Confirm the fetched install script or update asset resolves to the
matching v6 prerelease release asset rather than branch-tip
maincontent. - Confirm the upgraded agent reconnects as one canonical v6 unified agent identity and does not create a duplicate host or agent resource during the crossover.
- Confirm the pre-existing installed agent token still reaches the
canonical
/api/agents/agent/*v6 endpoints even if its persisted scopes originated as legacyhost-agent:*aliases. - Confirm the first canonical v6 report carries the prior v5 version in
updated_fromexactly once. - Confirm a subsequent report clears
updated_from, and the active-agent count shown in settings/billing surfaces still matches runtime enforcement after the upgrade.
- Pass when:
A real v5-installed unified agent upgrades through the candidate v6 prerelease asset
path, reconnects as one canonical v6 agent identity, preserves one-shot
updated_fromcontinuity, and leaves user-visible agent counts aligned with runtime enforcement. - Latest exercised record:
docs/release-control/v6/records/unified-agent-v5-upgrade-continuity-2026-03-12.md - Block release if:
The prerelease asset path serves the wrong installer logic, the upgrade creates
duplicate or orphaned agent identity,
updated_fromcontinuity is missing or repeated, or user-visible agent counts drift from runtime enforcement.
Gate: mobile-relay-auth-approvals
- Why this is risky: Mobile is a separate repo with separate state persistence, auth, and approval behavior. It is easy to miss regressions while the desktop/web app looks fine.
- Primary runtime surfaces:
pulse-mobile/src/stores/authStore.tspulse-mobile/src/stores/instanceStore.tspulse-mobile/src/stores/approvalStore.tspulse-mobile/src/hooks/useRelay.tspulse-mobile/src/api/client.ts - Automated proof:
cd /Volumes/Development/pulse/repos/pulse-mobile && npm test -- --runTestsByPath src/__tests__/mobileRelayAuthApprovals.rehearsal.test.ts src/utils/__tests__/secureStorage.test.ts src/hooks/__tests__/useRelayLifecycle.test.ts src/hooks/__tests__/approvalActionPolicy.test.ts src/stores/__tests__/instanceStore.test.ts src/stores/__tests__/authStore.test.ts src/stores/__tests__/approvalStore.test.tscd /Volumes/Development/pulse/repos/pulse-mobile && npm test -- --runTestsByPath src/relay/__tests__/client.test.ts src/relay/__tests__/client-hardening.test.ts src/relay/__tests__/protocol-contract.test.tscd /Volumes/Development/pulse/repos/pulse-mobile && npm test -- --runTestsByPath src/api/__tests__/client.test.tscd /Volumes/Development/pulse/repos/pulse-mobile && npm test -- --runTestsByPath src/hooks/__tests__/useRelay.test.ts src/hooks/__tests__/relayPushRefresh.test.ts src/notifications/__tests__/notificationRouting.test.ts src/stores/__tests__/mobileAccessState.test.tscd /Volumes/Development/pulse/repos/pulse-enterprise && go test ./internal/aiautofix -run 'TestHandleListApprovals|TestHandleApproveAndExecuteInvestigationFix|TestHandleApprove' -count=1 - Manual scenario:
- Pair the mobile app to a real instance through the relay onboarding path.
- Kill and relaunch the app to confirm secure persistence and reconnect.
- Confirm approval requests appear, are scoped correctly, and resolve cleanly.
- Confirm logout, token expiry, or revoked access forces the app back to a safe state.
- Pass when: Pairing, persistence, reconnect, approvals, and sign-out/revocation behavior all work without stale access.
- Latest exercised record:
docs/release-control/v6/records/mobile-relay-auth-approvals-2026-03-13.md - Block release if: Mobile can keep stale access, lose approval state, or fail to recover from reconnect/auth transitions.
Gate: msp-provider-tenant-management
- Why this is risky: MSP mode is a distinct product promise, not just a pricing label. If one provider account cannot safely manage multiple client tenants from one place, Pulse will appear to support MSPs in billing and marketing while failing in the real operator workflow.
- Primary runtime surfaces:
pkg/licensing/features.gointernal/cloudcp/account/...internal/cloudcp/registry/...internal/cloudcp/stripe/provisioner.gointernal/cloudcp/stripe/msp_lifecycle_integration_test.gointernal/cloudcp/public_cloud_signup_handlers_test.gofrontend-modern/src/components/Settings/OrganizationBillingPanel.tsxfrontend-modern/src/pages/CloudPricing.tsx - Automated proof:
go test ./internal/cloudcp/account ./internal/cloudcp/registry -count=1go test ./internal/cloudcp/stripe -run 'TestMSPLifecycle_AccountToPortal' -count=1go test ./internal/cloudcp -run 'TestPublicCloudSignupCheckoutMetadataRejectsMSPPlanForPublicSignup' -count=1go test ./pkg/licensing -run 'TestMSPPlanAliasCanonicalizationContract' -count=1cd frontend-modern && npx vitest run src/components/Settings/__tests__/OrganizationBillingPanel.test.tsx src/pages/__tests__/CloudPricing.test.tsx - Live rehearsal helper:
python3 scripts/release_control/msp_provider_tenant_management_rehearsal.py --base-url <control-plane-url> --account-id <account> ... - Manual scenario:
- Create or enter an MSP account in a staging-like environment.
- Provision at least two client workspaces or tenants under that MSP account.
- Confirm the provider can view and manage the intended client tenants from one control surface without cross-client data leakage.
- Confirm billing and plan presentation stays coherent per client and does not collapse MSP and individual hosted flows together.
- Confirm public individual signup cannot accidentally drop into MSP-only provisioning semantics.
- Pass when: MSP mode behaves as a real operator workflow: one provider account can manage multiple client tenants coherently, with canonical MSP plan handling and no cross-client leakage or scope confusion.
- Latest exercised record:
docs/release-control/v6/records/msp-provider-tenant-management-production-followup-2026-03-13.md - Block release if: MSP support exists only as pricing or partial provisioning, or a provider cannot safely manage multiple client tenants from one place.
Gate: multi-tenant-runtime-isolation-and-coherence
- Why this is risky: Multi-tenant support is not just an org settings feature. If tenant isolation, tenant-scoped runtime state, or cross-org sharing drifts, Pulse will expose the wrong data to the wrong tenant while still looking healthy in narrower UI-only checks.
- Primary runtime surfaces:
internal/api/org_handlers*.gointernal/api/rbac_handlers*.gointernal/api/resources_tenant_security_test.gointernal/api/router_helpers_more_test.gointernal/api/api_token_org_scope_integration_test.gointernal/monitoring/...frontend-modern/src/components/Settings/Organization*.tsxfrontend-modern/src/components/Settings/RolesPanel.tsxfrontend-modern/src/components/Settings/UserAssignmentsPanel.tsxtests/integration/tests/03-multi-tenant.spec.ts - Automated proof:
go test ./internal/api -run 'TestOrgHandlers|TestMultiTenant|TestResourceHandlers_NonDefaultOrg|TestSetMultiTenantMonitor_WiresHandlers|TestMultiTenantStateProvider|TestMultiTenantAPITokenRemainsScopedToIssuingOrg' -count=1go test ./internal/monitoring -run 'TestMultiTenantMonitor' -count=1go test ./tests/migration -run 'TestV5DataDir_MultiTenantMigration' -count=1cd frontend-modern && npx vitest run src/components/Settings/__tests__/OrganizationSharingPanel.test.tsx src/components/Settings/__tests__/RBACPaywallPanels.test.tsx src/utils/__tests__/rbacPermissions.test.ts src/utils/__tests__/rbacPresentation.test.ts src/utils/__tests__/organizationRolePresentation.test.ts src/utils/__tests__/organizationSettingsPresentation.test.ts - Manual scenario:
- Enable multi-tenant mode and create at least two organizations with different users and roles.
- Confirm each user only sees the orgs, resources, and runtime state they are explicitly allowed to see.
- Confirm role changes and tenant membership changes immediately affect UI and API scope.
- Confirm tenant-scoped runtime paths do not fall back to default or single-tenant state when a non-default org is requested.
- Confirm cross-org sharing grants only the intended access and does not widen tenant visibility.
- Pass when: Multi-tenant Pulse behaves as a coherent tenant-isolated product: org scope, RBAC, runtime state, sharing, and migration all stay within the intended tenant boundary.
- Latest exercised record:
docs/release-control/v6/records/multi-tenant-runtime-isolation-and-coherence-2026-03-13.md - Block release if: A tenant can see or mutate data, runtime state, or shared resources outside the intended tenant boundary, or multi-tenant mode still behaves like a partially upgraded single-tenant system.
Gate: organization-user-scope-and-rbac
- Why this is risky: Multi-tenant scope mistakes are trust-critical. Wrong member roles or org boundaries mean real data exposure.
- Primary runtime surfaces:
internal/api/org_handlers*.gointernal/api/rbac_handlers*.gofrontend-modern/src/components/Settings/Organization*.tsxfrontend-modern/src/components/Settings/RolesPanel.tsxfrontend-modern/src/components/Settings/UserAssignmentsPanel.tsx - Automated proof:
go test ./internal/api -run 'TestOrgHandlers|TestMultiTenant|TestResourceHandlers_NonDefaultOrg|TestSetMultiTenantMonitor_WiresHandlers' -count=1go test ./internal/monitoring -run 'TestMultiTenantMonitor'cd frontend-modern && npx vitest run src/components/Settings/__tests__/OrganizationSharingPanel.test.tsx src/components/Settings/__tests__/RBACPaywallPanels.test.tsx src/utils/__tests__/rbacPermissions.test.ts src/utils/__tests__/rbacPresentation.test.ts src/utils/__tests__/organizationRolePresentation.test.ts src/utils/__tests__/organizationSettingsPresentation.test.ts src/utils/__tests__/frontendResourceTypeBoundaries.test.tscd tests/integration && PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 PULSE_MULTI_TENANT_ENABLED=true npm test -- tests/03-multi-tenant.spec.ts --project=chromium - Manual scenario:
- Add a new user.
- Confirm the user only sees the orgs they belong to.
- Change member role and confirm the UI and API scope update accordingly.
- Confirm self-escalation is blocked.
- Confirm cross-org sharing grants only the intended access level.
- Pass when: Org membership, RBAC role assignment, and cross-org access all enforce the least privilege intended by the UI.
- Latest exercised record:
docs/release-control/v6/records/organization-user-scope-and-rbac-2026-03-12.md - Block release if: A user can see or mutate data outside assigned org or role scope.
Gate: api-token-scope-and-assignment
- Why this is risky: API tokens are long-lived authority. If token identity or scope binding is wrong, automated access will bypass user intent.
- Primary runtime surfaces:
internal/api/router.gointernal/api/router_routes_auth_security.gointernal/api/security_tokens.gointernal/api/system_settings_telemetry_test.gofrontend-modern/src/components/Settings/APIAccessPanel.tsxfrontend-modern/src/components/Settings/APITokenManager.tsxfrontend-modern/src/utils/apiTokenPresentation.tsfrontend-modern/src/utils/url.ts - Automated proof:
go test ./internal/api -run 'Test(APIToken|SecurityTokens|SystemSettings|MultiTenant)' -count=1go test ./internal/api -run 'TestNormalizeRequestedScopesCanonicalizesLegacyUnifiedAgentAliases|TestUnifiedAgentEndpointsAcceptLegacyUnifiedAgentReportScopeAlias|TestContract_APITokenScopeAliasNormalization' -count=1cd frontend-modern && npx vitest run src/components/Settings/__tests__/APITokenManager.test.tsx src/utils/__tests__/apiClient.org.test.ts src/utils/__tests__/apiTokenPresentation.test.ts src/utils/__tests__/frontendResourceTypeBoundaries.test.tscd tests/integration && PULSE_E2E_USE_LOCAL_BACKEND=1 PULSE_E2E_SKIP_PLAYWRIGHT_INSTALL=1 PULSE_MULTI_TENANT_ENABLED=true npm test -- tests/13-api-token-scope.spec.ts --project=chromium - Manual scenario:
- Generate a token for a specific user.
- Confirm the token inherits only the intended user and org scope.
- Use the token against read, mutate, and exec paths that should be denied.
- Revoke the token and confirm the old token immediately stops working.
- Confirm legacy persisted
host-agent:*token scopes still canonicalize to the intended v6agent:*scope checks on installed-agent report and config flows. - Confirm scoped agent/API-token flows fail with a clear message when the scope is insufficient.
- Pass when: Token create, use, read/write/exec scope enforcement, and revocation all behave exactly as intended.
- Latest exercised record:
docs/release-control/v6/records/api-token-scope-and-assignment-2026-03-12.md - Block release if: A token can outlive revocation, exceed assigned scope, or detach from the intended user/org identity.
Gate Ownership Rule
Update these machine-visible gate states in docs/release-control/v6/internal/status.json
as verification progresses:
pendingmeans not yet confirmed end to end.blockedmeans the gate is actively failing or cannot yet be exercised.passedmeans both automated proof and the manual scenario are clear.