$ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot $apiPath = Join-Path $root 'utils/api.js' $a01Path = Join-Path $root 'pages/auth/a01-entry.vue' $runtimePath = Join-Path $PSScriptRoot 'auth-api-runtime-smoke.js' $issues = New-Object System.Collections.Generic.List[string] foreach ($path in @($apiPath, $a01Path, $runtimePath)) { if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { $issues.Add("missing authentication owner: $path") } } if ($issues.Count -eq 0) { $api = Get-Content -Raw -Encoding UTF8 -LiteralPath $apiPath $a01 = Get-Content -Raw -Encoding UTF8 -LiteralPath $a01Path $passwordOwner = [regex]::Match($api, '(?s)async loginWithPassword\(\{ phone, passwordHash, validToken \}.*?(?=\s+async loginWithSms)') if (-not $passwordOwner.Success) { $issues.Add('missing bounded password login owner') } else { foreach ($required in @("url: '/genealogy/app/auth/login'", "tenantId: runtimeConfig.tenantId", "grantType: 'password'", 'password: assertPasswordHash(passwordHash)', 'const normalizedValidToken = normalizeOptionalValidToken(validToken)', '...(normalizedValidToken ? { validToken: normalizedValidToken } : {})')) { if (-not $passwordOwner.Value.Contains($required)) { $issues.Add("password login owner missing: $required") } } if ($passwordOwner.Value.Contains('authPayload(')) { $issues.Add('password login must not put clientId in the body') } } foreach ($required in @('const preparePasswordLogin = async () =>', 'appApi.loginWithPassword', 'TAC')) { if (-not $a01.Contains($required)) { $issues.Add("A01 missing password TAC precondition: $required") } } $smsOwner = [regex]::Match($api, '(?s)async sendSmsCode\(\{ operationCode, phone, validToken \}.*?(?=\s+async loginWithPassword)') if (-not $smsOwner.Success -or -not $smsOwner.Value.Contains('const normalizedValidToken = normalizeOptionalValidToken(validToken)') -or -not $smsOwner.Value.Contains('...(normalizedValidToken ? { validToken: normalizedValidToken } : {})')) { $issues.Add('SMS owner must remain the sole validToken consumer and omit it only when the server policy closes TAC') } } if ($issues.Count -eq 0) { $runtimeOutput = @(& node $runtimePath 2>&1) if ($LASTEXITCODE -ne 0 -or 'AUTH-API-RUNTIME-SMOKE PASS' -notin $runtimeOutput) { $issues.Add("password and SMS wire runtime smoke failed: $($runtimeOutput -join ' | ')") } } if ($issues.Count -gt 0) { Write-Output 'AUTH-TAC-OPENAPI-CONTRACT BLOCKED' foreach ($issue in $issues) { Write-Output "- $issue" } Write-Output '- Password login and SMS operations send a validToken only after the server marks that operation as verification-required.' exit 1 } Write-Output 'AUTH-TAC-OPENAPI-CONTRACT PASS'