Harden protected release access guidance

This commit is contained in:
axiomlogicnexus 2026-06-25 09:45:59 +00:00
parent 18f4a32990
commit e8b27022e8
4 changed files with 454 additions and 8 deletions

View file

@ -191,6 +191,9 @@ describe('DashboardOverviewPage', () => {
expect(screen.getByText('Billing price-plan map: missing')).toBeTruthy()
expect(screen.getByText('Paddle webhook secret: missing')).toBeTruthy()
expect(screen.getByText('Public auth runtime posture: local-or-mixed')).toBeTruthy()
expect(screen.getByText('Current release action')).toBeTruthy()
expect(screen.getByText('Desktop access is visible, but no packaged target is published yet.')).toBeTruthy()
expect(screen.getAllByRole('link', { name: 'Review release notes' })[0]?.getAttribute('href')).toBe('/changelog')
expect(screen.getByText('Protected operator quick routes')).toBeTruthy()
expect(screen.getByText('Signed-in help lanes')).toBeTruthy()
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
@ -396,5 +399,8 @@ describe('DashboardOverviewPage', () => {
expect(await screen.findByText('Live authority sync')).toBeTruthy()
expect(screen.getByText(/plan changed from local free to live operator/i)).toBeTruthy()
expect(screen.getByText(/desktop download access changed from local not entitled to live enabled/i)).toBeTruthy()
expect(screen.getByText('Current release action')).toBeTruthy()
expect(screen.getByText('Windows package is ready for this account.')).toBeTruthy()
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
})
})

View file

@ -196,7 +196,7 @@ describe('DownloadCenterPage', () => {
expect(screen.getByText(/preserved your Windows selection/i)).toBeTruthy()
expect(screen.getAllByText('Requested target').length).toBeGreaterThan(0)
expect((await screen.findByRole('link', { name: /download windows/i })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
expect((await screen.findByRole('link', { name: 'Download Windows package' })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
expect(screen.getByText('Release guide')).toBeTruthy()
expect(screen.getByText('First launch follow-through')).toBeTruthy()
expect(screen.getByText('Verify the current training lanes')).toBeTruthy()
@ -291,7 +291,7 @@ describe('DownloadCenterPage', () => {
</QueryClientProvider>,
)
expect((await screen.findByRole('link', { name: /download windows/i })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
expect((await screen.findByRole('link', { name: 'Download Windows package' })).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
await waitFor(() => {
expect(screen.queryByText(/does not yet have desktop download entitlement/i)).toBeNull()
})
@ -316,8 +316,9 @@ describe('DownloadCenterPage', () => {
</QueryClientProvider>,
)
expect(await screen.findByText(/fallback release posture/i)).toBeTruthy()
expect(screen.getByText(/your signed-in account still resolves to desktop access/i)).toBeTruthy()
expect(await screen.findByText('Current release action')).toBeTruthy()
expect(await screen.findAllByText(/your signed-in account still resolves to desktop access/i)).toHaveLength(2)
expect(screen.getAllByText((_content, node) => node?.textContent === 'Live release authority must recover before package delivery resumes.').length).toBeGreaterThan(0)
expect(screen.getAllByText('Live release authority temporarily unavailable').length).toBeGreaterThan(0)
expect(screen.getByText('What stays intentionally withheld')).toBeTruthy()
expect(screen.getAllByRole('link', { name: 'Open dashboard' }).every((link) => link.getAttribute('href') === '/app')).toBe(true)

View file

@ -213,6 +213,9 @@ describe('protected app pages', () => {
expect(await screen.findByText('Release targets')).toBeTruthy()
expect(await screen.findByText('Version: 1.0.0')).toBeTruthy()
expect(screen.getAllByText('Requested target').length).toBeGreaterThan(0)
expect(screen.getByText('Current release action')).toBeTruthy()
expect(screen.getByText('Windows package is ready for this account.')).toBeTruthy()
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
expect(screen.getAllByText('Packaged validation passed').length).toBeGreaterThan(0)
expect(screen.getByText('Protected operator quick routes')).toBeTruthy()
@ -232,14 +235,17 @@ describe('protected app pages', () => {
renderPage(<AccountPage />, '/app/account')
expect(await screen.findByText('Session profile')).toBeTruthy()
expect(await screen.findByRole('link', { name: 'Download Windows package' })).toBeTruthy()
expect(screen.getByText('Current packaged desktop proof')).toBeTruthy()
expect(screen.getByText('Packaged validation passed')).toBeTruthy()
expect(screen.getByText('Protected browser-versus-desktop reality')).toBeTruthy()
expect(screen.getByText('Why the browser is intentionally narrower')).toBeTruthy()
expect(screen.getByText('Current simulator control ownership')).toBeTruthy()
expect(screen.getByText('Current release action')).toBeTruthy()
expect(screen.getByRole('link', { name: 'Download Windows package' }).getAttribute('href')).toBe('https://downloads.hypertwist.app/windows.exe')
expect(screen.getAllByText(/classic-wca-keyboard\/v1/i).length).toBeGreaterThan(0)
expect(screen.getByText(byExactTextContent('Billing source: session', 'LI'))).toBeTruthy()
expect(await screen.findByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toBeTruthy()
expect(await screen.findAllByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toHaveLength(2)
expect(await screen.findByText(byExactTextContent('Windows: Primary shipping lane (Version 1.0.0, Channel candidate)', 'LI'))).toBeTruthy()
expect(screen.getAllByRole('link', { name: 'Open downloads' }).every((link) => link.getAttribute('href') === '/app/downloads')).toBe(true)
expect(screen.getByRole('link', { name: 'Get help' }).getAttribute('href')).toBe('/support?topic=operator-access')
@ -272,8 +278,8 @@ describe('protected app pages', () => {
expect(await screen.findByText('Session profile')).toBeTruthy()
expect(await screen.findByText(byExactTextContent('Manifest viewer authenticated: yes', 'LI'))).toBeTruthy()
expect(await screen.findByText(byExactTextContent('Manifest viewer plan: operator', 'LI'))).toBeTruthy()
expect(await screen.findByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toBeTruthy()
expect(await screen.findAllByText(byExactTextContent('Manifest viewer plan: operator', 'LI'))).toHaveLength(2)
expect(await screen.findAllByText(byExactTextContent('Manifest viewer access: active', 'LI'))).toHaveLength(2)
expect(screen.getByText(/bounded fallback metadata until auth-server release authority returns/i)).toBeTruthy()
})
@ -376,4 +382,99 @@ describe('protected app pages', () => {
expect(screen.getByText(/desktop download access changed from local not entitled to live enabled/i)).toBeTruthy()
expect(screen.getByText(/access status changed from local session-default to live active/i)).toBeTruthy()
})
it('turns protected non-entitled account posture into explicit checkout guidance when live commerce targets exist', async () => {
mockUsePlatformAuth.mockReturnValue({
user: {
id: 'viewer-1',
name: 'Viewer',
email: 'viewer@hypertwist.app',
authMethod: 'email',
plan: 'free',
role: 'viewer',
canDownload: false,
billing: {
source: 'billing-state',
accessStatus: 'payment_required',
canDownload: false,
lastEventType: 'transaction.payment_failed',
},
},
superTokensConfigured: true,
invalidateSession: (...args: unknown[]) => mockInvalidateSession(...args),
reconcileReleaseAuthority: vi.fn(),
releaseAuthoritySyncItems: [],
})
mockGetReleaseManifest.mockResolvedValue({
ok: true,
manifest: {
generated_at: '2026-06-23T12:00:00.000Z',
support_email: 'hello@hypertwist.app',
public_docs_url: 'https://docs.hypertwist.app',
release_notes_url: 'https://notes.hypertwist.app',
corresponding_source_url: 'https://hypertwist.app/open-source/source.zip',
open_source_repo_url: 'https://github.com/hypertwist/hypertwist',
commerce: {
operator_checkout_url: 'https://buy.paddle.com/operator-live',
studio_checkout_url: 'https://buy.paddle.com/studio-live',
plan_price_operator: '$29/mo',
plan_price_studio: '$99/mo',
},
viewer: {
authenticated: true,
canDownload: false,
plan: 'free',
role: 'viewer',
accessStatus: 'payment_required',
},
platforms: [
{
platform_key: 'windows',
platform: 'Windows',
subtitle: 'Primary shipping lane',
details: 'Current packaged validation is strongest on the Windows Unreal lane.',
configured: true,
channel: 'candidate',
version: '1.0.1',
build_id: 'win64-1001',
published_at: '2026-06-23T00:00:00.000Z',
file_name: 'HyperTwist-Windows.zip',
file_size_bytes: 1048576,
checksum_sha256: 'abc124',
download_url: null,
download_available: false,
validation_summary: {
lane: 'Windows Unreal packaged validation',
result: 'passed',
generated_at: '2026-06-23T01:43:08.7625247Z',
configuration: 'Development',
skip_build: true,
smoke_map_count: 2,
smoke_maps: [
{
map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_Magic120CellTraining',
label: 'Magic120Cell dedicated-family training map',
result: 'passed',
},
{
map_url: '/Game/HyperTwistTraining/Maps/L_HyperTwist_MagicCube5DTraining',
label: 'MagicCube5D dedicated-family training map',
result: 'passed',
},
],
},
},
],
},
})
renderPage(<AccountPage />, '/app/account')
expect(await screen.findByText('Current release action')).toBeTruthy()
expect(await screen.findByText('Desktop package delivery still requires billing or operator provisioning.')).toBeTruthy()
expect((await screen.findByRole('link', { name: 'Open operator checkout' })).getAttribute('href')).toBe('https://buy.paddle.com/operator-live')
expect(screen.getByRole('link', { name: 'Open studio checkout' }).getAttribute('href')).toBe('https://buy.paddle.com/studio-live')
expect(screen.getByRole('link', { name: 'Open pricing' }).getAttribute('href')).toBe('/pricing')
})
})

View file

@ -10,7 +10,13 @@ import { ReleaseValidationSummary } from '../components/ui/ReleaseValidationSumm
import { resolvePublicLaunchStatusSummary } from '../public-launch'
import { downloadTargets, launchReadiness, mplSourceUrl, openSourceRepoUrl, publicDocsUrl, releaseNotesUrl } from '../site-config'
import { buildReleaseMetadataItems, resolveReleaseManifestView, type ReleaseManifestView } from '../release-manifest'
import { buildProtectedDownloadPath, buildSupportPath, getDownloadPlatformLabel, normalizeDownloadPlatform } from '../site-routes'
import {
buildProtectedDownloadPath,
buildSupportPath,
getDownloadPlatformLabel,
isExternalHref,
normalizeDownloadPlatform,
} from '../site-routes'
import {
browserDesktopRealityCards,
controlProfileRosterCards,
@ -219,6 +225,21 @@ type ProtectedSupportTopicGuide = (typeof supportTopicDirectory)[number] & {
actions: readonly ProtectedSupportTopicRoute[]
}
type ProtectedReleaseAction = {
label: string
href: string
primary?: boolean
}
type ProtectedReleaseActionSurface = {
badge: string
tone: 'success' | 'info'
title: string
summary: string
items: readonly string[]
actions: readonly ProtectedReleaseAction[]
}
function buildProtectedSupportTopicActions(topicKey: string): readonly ProtectedSupportTopicRoute[] {
switch (topicKey) {
case 'launch-readiness':
@ -249,6 +270,260 @@ const protectedSupportTopicGuides: readonly ProtectedSupportTopicGuide[] = suppo
actions: buildProtectedSupportTopicActions(topic.topicKey),
}))
function ProtectedActionLink({
action,
}: {
action: ProtectedReleaseAction
}) {
const className = action.primary ? 'button button--primary' : 'button button--ghost'
if (isExternalHref(action.href)) {
return (
<a className={className} href={action.href} target="_blank" rel="noreferrer">
{action.label}
</a>
)
}
return (
<Link className={className} to={action.href}>
{action.label}
</Link>
)
}
function describeReleaseTarget(
platform: ReleaseManifestView['platforms'][number] | null | undefined,
) {
if (!platform) {
return 'No desktop target selected yet'
}
const metadata: string[] = []
if (platform.version) {
metadata.push(platform.version)
}
if (platform.channel) {
metadata.push(platform.channel)
}
return metadata.length > 0
? `${platform.platform} (${metadata.join(', ')})`
: platform.platform
}
function buildProtectedCheckoutActions(releaseManifest: ReleaseManifestView) {
const operatorCheckoutUrl = releaseManifest.commerce?.operator_checkout_url || null
const studioCheckoutUrl = releaseManifest.commerce?.studio_checkout_url || null
const preferredCheckoutUrl = releaseManifest.viewer.plan === 'studio'
? (studioCheckoutUrl || operatorCheckoutUrl)
: (operatorCheckoutUrl || studioCheckoutUrl)
const preferredCheckoutLabel = preferredCheckoutUrl === studioCheckoutUrl
? 'Open studio checkout'
: 'Open operator checkout'
const secondaryCheckoutUrl = [operatorCheckoutUrl, studioCheckoutUrl]
.find((candidate) => candidate && candidate !== preferredCheckoutUrl) || null
const secondaryCheckoutLabel = secondaryCheckoutUrl === studioCheckoutUrl
? 'Open studio checkout'
: 'Open operator checkout'
return [
preferredCheckoutUrl ? {
label: preferredCheckoutLabel,
href: preferredCheckoutUrl,
primary: true,
} : null,
secondaryCheckoutUrl ? {
label: secondaryCheckoutLabel,
href: secondaryCheckoutUrl,
} : null,
].filter((action): action is ProtectedReleaseAction => action != null)
}
function buildProtectedReleaseActionSurface({
releaseManifest,
requestedPlatform,
releaseAuthorityUnavailable,
}: {
releaseManifest: ReleaseManifestView
requestedPlatform?: string | null
releaseAuthorityUnavailable: boolean
}): ProtectedReleaseActionSurface {
const configuredTargets = releaseManifest.platforms.filter((platform) => platform.configured)
const requestedTarget = requestedPlatform
? (releaseManifest.platforms.find((platform) => platform.platform_key === requestedPlatform) || null)
: null
const downloadableTargets = releaseManifest.platforms.filter((platform) => Boolean(platform.download_url))
const primaryDownloadTarget = downloadableTargets.find((platform) => (
requestedTarget ? platform.platform_key === requestedTarget.platform_key : platform.platform_key === 'windows'
)) || downloadableTargets[0] || null
const supportEmail = releaseManifest.support_email || 'hello@hypertwist.app'
const manifestPlan = releaseManifest.viewer.plan || 'unresolved'
const manifestAccessStatus = releaseManifest.viewer.accessStatus || 'not resolved yet'
const configuredTargetsLabel = `${configuredTargets.length}/${releaseManifest.platforms.length} configured`
if (releaseAuthorityUnavailable) {
if (releaseManifest.viewer.canDownload) {
return {
badge: 'Fallback release posture',
tone: 'info',
title: 'Live release authority must recover before package delivery resumes.',
summary:
'Your signed-in account still resolves to desktop access, but this protected lane intentionally withholds direct downloads until the auth server returns.',
items: [
`Manifest viewer plan: ${manifestPlan}`,
`Manifest viewer access: ${manifestAccessStatus}`,
`Protected target posture: ${requestedTarget ? describeReleaseTarget(requestedTarget) : 'Windows default'}`,
`Configured release targets: ${configuredTargetsLabel}`,
`Support contact: ${supportEmail}`,
],
actions: [
{ label: 'Open downloads', href: buildProtectedDownloadPath('windows'), primary: true },
{ label: 'Review notices', href: '/app/notices' },
{ label: 'Get help', href: buildSupportPath('operator-access') },
],
}
}
return {
badge: 'Release authority degraded',
tone: 'info',
title: 'Wait for live release authority before trusting upgrade or delivery posture.',
summary:
'This protected lane is still useful for orientation, but checkout, entitlement, and package-delivery decisions should wait until the auth server is back.',
items: [
`Manifest viewer plan: ${manifestPlan}`,
`Manifest viewer access: ${manifestAccessStatus}`,
`Configured release targets: ${configuredTargetsLabel}`,
`Support contact: ${supportEmail}`,
],
actions: [
{ label: 'Open pricing', href: '/pricing', primary: true },
{ label: 'Review notices', href: '/app/notices' },
{ label: 'Get help', href: buildSupportPath('operator-access') },
],
}
}
if (releaseManifest.viewer.canDownload && primaryDownloadTarget?.download_url) {
const primaryActionTarget = requestedTarget && requestedTarget.configured === false
? primaryDownloadTarget
: (requestedTarget || primaryDownloadTarget)
return {
badge: 'Download ready',
tone: 'success',
title: requestedTarget && requestedTarget.configured === false
? `${requestedTarget.platform} is not published yet, but another entitled package is ready.`
: `${primaryActionTarget.platform} package is ready for this account.`,
summary: requestedTarget && requestedTarget.configured === false
? `Your requested ${requestedTarget.platform} lane is not configured yet, but the protected release lane can still deliver ${describeReleaseTarget(primaryDownloadTarget)} right now.`
: 'The protected release lane now has both live entitlement and a current package target, so download and release references can stay attached to the same operator workflow.',
items: [
`Manifest viewer plan: ${manifestPlan}`,
`Manifest viewer access: ${manifestAccessStatus}`,
`Primary release target: ${describeReleaseTarget(primaryActionTarget)}`,
`Configured release targets: ${configuredTargetsLabel}`,
`Support contact: ${supportEmail}`,
],
actions: [
{ label: `Download ${primaryActionTarget.platform} package`, href: primaryDownloadTarget.download_url, primary: true },
{ label: 'Review notices', href: '/app/notices' },
{ label: 'Review release notes', href: '/changelog' },
],
}
}
if (configuredTargets.length === 0) {
return {
badge: 'Awaiting packaged release target',
tone: 'info',
title: 'Desktop access is visible, but no packaged target is published yet.',
summary:
'This protected lane can still show account and release posture, but it cannot deliver a desktop package until at least one target is configured.',
items: [
`Manifest viewer plan: ${manifestPlan}`,
`Manifest viewer access: ${manifestAccessStatus}`,
`Configured release targets: ${configuredTargetsLabel}`,
`Support contact: ${supportEmail}`,
],
actions: [
{ label: 'Review release notes', href: '/changelog', primary: true },
{ label: 'Review notices', href: '/app/notices' },
{ label: 'Get help', href: buildSupportPath('launch-readiness') },
],
}
}
if (!releaseManifest.viewer.canDownload) {
const checkoutActions = buildProtectedCheckoutActions(releaseManifest)
const statusItems = [
`Manifest viewer plan: ${manifestPlan}`,
`Manifest viewer access: ${manifestAccessStatus}`,
`Configured release targets: ${configuredTargetsLabel}`,
`Operator checkout target: ${releaseManifest.commerce?.operator_checkout_url ? 'configured' : 'missing'}`,
`Studio checkout target: ${releaseManifest.commerce?.studio_checkout_url ? 'configured' : 'missing'}`,
]
if (checkoutActions.length > 0) {
return {
badge: 'Upgrade or provisioning required',
tone: 'info',
title: 'Desktop package delivery still requires billing or operator provisioning.',
summary:
'The protected release lane can show live release posture now, but it still keeps actual package delivery behind the current checkout and entitlement rules.',
items: [
...statusItems,
`Support contact: ${supportEmail}`,
],
actions: [
...checkoutActions,
{ label: 'Open pricing', href: '/pricing' },
],
}
}
return {
badge: 'Checkout not live yet',
tone: 'info',
title: 'Protected release posture is ready, but live checkout is not configured.',
summary:
'This deployment can show the signed-in release lane, yet commercial access still depends on manual operator help because live checkout targets are not configured.',
items: [
...statusItems,
`Support contact: ${supportEmail}`,
],
actions: [
{ label: 'Open pricing', href: '/pricing', primary: true },
{ label: 'Review notices', href: '/app/notices' },
{ label: 'Get help', href: buildSupportPath('operator-access') },
],
}
}
return {
badge: 'Release lane ready for review',
tone: 'info',
title: 'Protected release posture is available for operator follow-through.',
summary:
'The protected lane is carrying live release metadata, packaged proof, and sign-in continuity even though a direct package action is not available from this exact state.',
items: [
`Manifest viewer plan: ${manifestPlan}`,
`Manifest viewer access: ${manifestAccessStatus}`,
`Configured release targets: ${configuredTargetsLabel}`,
`Support contact: ${supportEmail}`,
],
actions: [
{ label: 'Open downloads', href: buildProtectedDownloadPath('windows'), primary: true },
{ label: 'Review notices', href: '/app/notices' },
{ label: 'Get help', href: buildSupportPath('operator-access') },
],
}
}
function ProtectedReleaseAuthorityNotice({
badge,
title,
@ -297,6 +572,46 @@ function ProtectedReleaseAuthorityNotice({
)
}
function ProtectedReleaseActionPanel({
title,
kicker,
releaseManifest,
requestedPlatform,
releaseAuthorityUnavailable = false,
}: {
title: string
kicker: string
releaseManifest: ReleaseManifestView
requestedPlatform?: string | null
releaseAuthorityUnavailable?: boolean
}) {
const surface = buildProtectedReleaseActionSurface({
releaseManifest,
requestedPlatform,
releaseAuthorityUnavailable,
})
return (
<Panel title={title} kicker={kicker}>
<p className={`status-pill${surface.tone === 'success' ? ' status-pill--success' : ' status-pill--info'}`}>
{surface.badge}
</p>
<p><strong>{surface.title}</strong></p>
<p>{surface.summary}</p>
<ul className="list top-gap">
{surface.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
<div className="button-row top-gap">
{surface.actions.map((action) => (
<ProtectedActionLink key={`${surface.badge}-${action.label}`} action={action} />
))}
</div>
</Panel>
)
}
function PackagedValidationPanel({
title,
kicker,
@ -724,6 +1039,13 @@ export function DashboardOverviewPage() {
</ul>
</Panel>
<ProtectedReleaseActionPanel
title="Current release action"
kicker="Protected access guidance"
releaseManifest={releaseManifest}
releaseAuthorityUnavailable={releaseManifestQuery.isError}
/>
<Panel title="Launch readiness" kicker="Public release configuration">
<p className={`status-pill${launchStatus.ready ? ' status-pill--success' : ''}`}>
{launchStatus.ready ? 'Launch-ready posture' : 'Preview posture'}
@ -871,6 +1193,14 @@ export function DownloadCenterPage() {
</Panel>
) : null}
<ProtectedReleaseActionPanel
title="Current release action"
kicker="Protected access guidance"
releaseManifest={releaseManifest}
requestedPlatform={requestedPlatform}
releaseAuthorityUnavailable={releaseAuthorityUnavailable}
/>
<Panel title="Release targets" kicker="Desktop distribution">
{!viewerCanDownload ? (
<p className="form-error">
@ -1216,6 +1546,14 @@ export function AccountPage() {
</ul>
) : null}
</Panel>
<ProtectedReleaseActionPanel
title="Current release action"
kicker="Protected access guidance"
releaseManifest={releaseManifest}
releaseAuthorityUnavailable={releaseManifestQuery.isError}
/>
<PackagedValidationPanel
title={protectedPackagedDesktopProofTitle}
kicker="Release evidence"