$ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot $issues = New-Object System.Collections.Generic.List[string] function Add-Issue { param([string]$Message) $script:issues.Add($Message) } function Read-RequiredFile { param([string]$RelativePath) $path = Join-Path $root $RelativePath if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { Add-Issue "missing required client owner: $RelativePath" return '' } return Get-Content -Raw -Encoding UTF8 -LiteralPath $path } function Assert-Contains { param([string]$Content, [string]$Label, [string]$Expected) if (-not $Content.Contains($Expected)) { Add-Issue "$Label missing production ownership marker: $Expected" } } function Assert-DoesNotContain { param([string]$Content, [string]$Label, [string]$Forbidden) if ($Content.Contains($Forbidden)) { Add-Issue "$Label retains retired preview/legacy owner: $Forbidden" } } $page = Read-RequiredFile 'pages/genealogy/g03-create-genealogy.vue' $api = Read-RequiredFile 'utils/api.js' $coordinator = Read-RequiredFile 'utils/genealogy-bootstrap.js' $accessContract = Read-RequiredFile 'utils/genealogy-contracts.js' $mock = Read-RequiredFile 'data/mock.js' $staticContract = Read-RequiredFile 'tests/g03-create-flow-contract.ps1' $legacyRuntimeSmoke = Read-RequiredFile 'tests/g03-create-flow-runtime-smoke.js' $runtimePath = Join-Path $root 'tests/g03-bootstrap-runtime-smoke.js' # Keep only durable public ownership checks here. Internal helper names, cache strategy, # debounce timers, and test titles are intentionally left to executable behavior tests. Assert-Contains $page 'G03 page' '@/utils/genealogy-bootstrap.js' Assert-Contains $page 'G03 page' 'createGenealogyBootstrapCoordinator' Assert-Contains $api 'utils/api.js' '/genealogy/app/genealogies' Assert-Contains $api 'utils/api.js' '/genealogy/app/genealogy-bootstrap-operations/' Assert-Contains $api 'utils/api.js' '/genealogy/app/region/search' Assert-Contains $api 'utils/api.js' 'Idempotency-Key' Assert-Contains $coordinator 'utils/genealogy-bootstrap.js' 'createGenealogyBootstrapCoordinator' foreach ($entry in @( [pscustomobject]@{ Content = $page; Label = 'G03 page' }, [pscustomobject]@{ Content = $mock; Label = 'data/mock.js' }, [pscustomobject]@{ Content = $staticContract; Label = 'G03 static flow contract' }, [pscustomobject]@{ Content = $legacyRuntimeSmoke; Label = 'G03 legacy runtime smoke' } )) { foreach ($retiredOwner in @( 'createLocalGenealogyPreview', 'updateLocalGenealogyPreview', 'updateLocalGenealogyPreviewAncestor', 'removeLocalGenealogyPreview', 'local-created-' )) { Assert-DoesNotContain $entry.Content $entry.Label $retiredOwner } } foreach ($retiredApiOwner in @('async createGenealogy(', 'genealogies.unshift(created)')) { Assert-DoesNotContain $api 'utils/api.js' $retiredApiOwner } foreach ($retiredAccessOwner in @( 'visibility:', 'joinMode:', 'fromApiGenealogyAccess', 'toApiGenealogyAccess' )) { Assert-DoesNotContain $accessContract 'utils/genealogy-contracts.js' $retiredAccessOwner } # The release gate executes the dependency-injected state machine suite. The suite must # deep-compare the exact marker, use request/storage/cache/context/navigation spies, and # publish a machine-readable case ledger only after all assertions pass. $expectedCases = @( 'ACCOUNT_EPOCH_ISOLATION', 'CONTEXT_FAILURE_LOCAL_RETRY', 'FAILED_CLEAR', 'FATAL_EXPLICIT_ABANDON_CLEAR', 'KEY_REUSED_QUARANTINE', 'KNOWN_NO_COMMIT', 'LOCAL_ZERO_DISPATCH', 'MARKER_EXACT', 'NAVIGATION_FAILURE_LOCAL_RETRY', 'NO_MOCK_MUTATION', 'NO_NEW_KEY', 'NO_PII', 'PENDING_RETRY_AFTER', 'POST_408_UNKNOWN', 'POST_CANCEL_UNKNOWN', 'POST_UNEXPECTED_STATUS_UNKNOWN', 'POST_UNKNOWN', 'PRECLAIM_400', 'PRECLAIM_401', 'PRECLAIM_403', 'PROCESS_RECOVERY_STATUS_ONLY', 'REQUEST_BUILD_ZERO_DISPATCH', 'RETRY_429', 'STATUS_400_CLEAR', 'STATUS_401_CLEAR_SESSION', 'STATUS_404_KEEP', 'STATUS_429_KEEP', 'STATUS_500_KEEP', 'STATUS_CANCEL_KEEP', 'STATUS_MALFORMED_KEEP', 'STATUS_NETWORK_KEEP', 'STATUS_UNEXPECTED_STATUS_KEEP', 'SUCCEEDED_ORDER' ) | Sort-Object if (-not (Test-Path -LiteralPath $runtimePath -PathType Leaf)) { Add-Issue 'missing executable state-machine suite: tests/g03-bootstrap-runtime-smoke.js' } else { $runtimeOutput = @(& node $runtimePath 2>&1) $runtimeExit = $LASTEXITCODE if ($runtimeExit -ne 0) { Add-Issue "G03 runtime state-machine suite failed: $($runtimeOutput -join ' | ')" } else { if ('G03-BOOTSTRAP-RUNTIME PASS' -notin $runtimeOutput) { Add-Issue 'G03 runtime state-machine suite did not emit its PASS marker' } $coverageLine = @($runtimeOutput | Where-Object { $_ -like 'G03-BOOTSTRAP-RUNTIME-COVERAGE *' }) if ($coverageLine.Count -ne 1) { Add-Issue 'G03 runtime state-machine suite must emit exactly one coverage ledger' } else { $actualCases = @(($coverageLine[0] -replace '^G03-BOOTSTRAP-RUNTIME-COVERAGE\s+', '').Split(',') | Where-Object { $_ } | Sort-Object) if (($actualCases -join ',') -ne ($expectedCases -join ',')) { Add-Issue "G03 runtime coverage ledger drifted: $($actualCases -join ',')" } } } } if ($issues.Count -gt 0) { $lines = New-Object System.Collections.Generic.List[string] $lines.Add('G03-BOOTSTRAP-CLIENT-RELEASE BLOCKED') foreach ($issue in $issues) { $lines.Add("- $issue") } $lines.Add('- Keep the honest local preview until backend, workspace, client, and MuMu release gates are green.') $lines.Add('- Migrate the coordinator, strict transport, marker/status recovery, context, fixtures, focused tests, and docs as one owner change.') throw ($lines -join [Environment]::NewLine) } Write-Output 'G03-BOOTSTRAP-CLIENT-RELEASE PASS'