gephyr 1.16.7

Gephyr headless AI relay service for Google AI services
Documentation
<#
.SYNOPSIS
Validates static guardrails for Google generation route/caller mapping.

.DESCRIPTION
This script enforces two code-level invariants:
1) Only allowlisted non-test files may call UpstreamClient `call_v1_internal*`.
2) Expected generation ingress route paths and handler symbols are present in
   `src/proxy/routes/mod.rs`.

Use this as a strict regression guard when refactoring routing or handlers.
#>
param(
    [string]$CallerAllowlistPath = "scripts/allowlists/google_generation_upstream_callers.txt",
    [string]$RouteAllowlistPath = "scripts/allowlists/google_generation_ingress_routes.txt",
    [string]$RoutesFilePath = "src/proxy/routes/mod.rs",
    [string]$OutJson = "output/google_generation_mapping_validation.json",
    [string]$OutText = "output/google_generation_mapping_validation.txt",
    [switch]$NoThrow
)

$ErrorActionPreference = "Stop"

function Load-Allowlist {
    param([string]$Path)
    if (-not (Test-Path $Path)) {
        throw "Allowlist file not found: $Path"
    }
    return @(Get-Content $Path |
        ForEach-Object { $_.Trim() } |
        Where-Object { $_ -and -not $_.StartsWith("#") })
}

function Normalize-RepoPath {
    param([string]$Path)
    if (-not $Path) { return $Path }
    return ($Path -replace '\\', '/')
}

if (-not (Get-Command rg -ErrorAction SilentlyContinue)) {
    throw "ripgrep (rg) is required for this validator."
}

if (-not (Test-Path $RoutesFilePath)) {
    throw "Routes file not found: $RoutesFilePath"
}

$callerAllow = Load-Allowlist -Path $CallerAllowlistPath
$routeAllow = Load-Allowlist -Path $RouteAllowlistPath

$callerAllowSet = New-Object System.Collections.Generic.HashSet[string]
foreach ($p in $callerAllow) { [void]$callerAllowSet.Add((Normalize-RepoPath -Path $p)) }

$callerMatches = & rg -n "call_v1_internal_with_headers\(|call_v1_internal\(" src/proxy src/modules -S

$observedCallers = New-Object System.Collections.Generic.HashSet[string]
foreach ($line in $callerMatches) {
    if ($line -notmatch '^(?<path>[^:]+):(?<line>\d+):(?<text>.*)$') {
        continue
    }
    $path = Normalize-RepoPath -Path $Matches.path
    # Exclude upstream client implementation and tests; we only care about production call paths.
    if ($path -eq "src/proxy/upstream/client.rs") { continue }
    if ($path -match '[/\\]tests[/\\]') { continue }
    [void]$observedCallers.Add($path)
}

$unknownCallers = @($observedCallers | Where-Object { -not $callerAllowSet.Contains($_) } | Sort-Object)
$missingAllowedCallers = @($callerAllow | Where-Object { $_ -notin $observedCallers } | Sort-Object)
$observedAllowedCallers = @($observedCallers | Where-Object { $callerAllowSet.Contains($_) } | Sort-Object)

$routesText = Get-Content $RoutesFilePath -Raw
$missingRoutes = @()
foreach ($route in $routeAllow) {
    if ($routesText -notmatch [regex]::Escape($route)) {
        $missingRoutes += $route
    }
}

$requiredSymbols = @(
    "handlers::openai::handle_chat_completions",
    "handlers::openai::handle_completions",
    "handlers::claude::handle_messages",
    "handlers::gemini::handle_generate"
)
$missingSymbols = @()
foreach ($sym in $requiredSymbols) {
    if ($routesText -notmatch [regex]::Escape($sym)) {
        $missingSymbols += $sym
    }
}

$pass = ($unknownCallers.Count -eq 0 -and $missingRoutes.Count -eq 0 -and $missingSymbols.Count -eq 0)

$result = [pscustomobject]@{
    generated_at = (Get-Date).ToString("o")
    caller_allowlist_path = $CallerAllowlistPath
    route_allowlist_path = $RouteAllowlistPath
    routes_file_path = $RoutesFilePath
    caller_allow_count = $callerAllow.Count
    observed_non_test_callers_count = $observedCallers.Count
    observed_allowed_callers = $observedAllowedCallers
    unknown_callers = $unknownCallers
    missing_allowed_callers = $missingAllowedCallers
    required_route_paths_missing = $missingRoutes
    required_handler_symbols_missing = $missingSymbols
    pass = $pass
}

$outDirJson = Split-Path -Parent $OutJson
$outDirText = Split-Path -Parent $OutText
if ($outDirJson) { New-Item -ItemType Directory -Force -Path $outDirJson | Out-Null }
if ($outDirText) { New-Item -ItemType Directory -Force -Path $outDirText | Out-Null }

$result | ConvertTo-Json -Depth 8 | Set-Content -Path $OutJson

$lines = @()
$lines += "Google Generation Mapping Validation"
$lines += "Generated: $($result.generated_at)"
$lines += "Routes file: $RoutesFilePath"
$lines += "Caller allowlist: $CallerAllowlistPath"
$lines += "Route allowlist: $RouteAllowlistPath"
$lines += "Pass: $($result.pass)"
$lines += ""
$lines += "Unknown non-test call_v1_internal* callers:"
if ($unknownCallers.Count -eq 0) { $lines += "  (none)" } else { $unknownCallers | ForEach-Object { $lines += "  $_" } }
$lines += ""
$lines += "Missing allowlisted callers (informational):"
if ($missingAllowedCallers.Count -eq 0) { $lines += "  (none)" } else { $missingAllowedCallers | ForEach-Object { $lines += "  $_" } }
$lines += ""
$lines += "Missing required route paths:"
if ($missingRoutes.Count -eq 0) { $lines += "  (none)" } else { $missingRoutes | ForEach-Object { $lines += "  $_" } }
$lines += ""
$lines += "Missing required route handler symbols:"
if ($missingSymbols.Count -eq 0) { $lines += "  (none)" } else { $missingSymbols | ForEach-Object { $lines += "  $_" } }

$lines | Set-Content -Path $OutText

Write-Host "Validation report written:"
Write-Host "  $OutText"
Write-Host "  $OutJson"
Write-Host "Pass: $($result.pass)"

if (-not $NoThrow -and -not $result.pass) {
    throw "Google generation mapping validation failed. See: $OutText"
}