<#
.SYNOPSIS
    DBAOps Sovereign - Enterprise Shippability Gate Verifier
.DESCRIPTION
    READ-ONLY by default. Verifies the 9 enterprise-readiness items and returns a
    single go/no-go. Automated checks cover what can be proven mechanically;
    items that require human judgment (IP ownership, monitored support inbox,
    enforcement-is-real) are certified via a signed attestation file, so the
    final "100% SHIPPABLE" verdict requires BOTH automated PASS and attestations.

    The one write-capable check (deploy-from-source cold build) is OFF by default
    and gated behind -RunColdBuildProof. When enabled it creates a uniquely-named
    throwaway database, runs migrate.js against it from empty, counts objects, and
    DROPS it in a finally block. Nothing else writes.

    Nine items:
      1 Packaging & release integrity
      2 Deploy-from-source proof
      3 Zero hardcoded environment
      4 Credential & connection security
      5 Least-privilege install / uninstall
      6 Licensing & edition enforcement
      7 Multi-tenant isolation (or declared single-tenant)
      8 Documentation set
      9 Legal & commercial (attestation)
.NOTES
    Requires SqlServer module for DB-side checks (items 2,6,7 partial, 9 partial).
    Attestation template is auto-generated on first run if absent.
#>
[CmdletBinding()]
param(
    [string]$Root      = 'K:\RHOYLLE BUSINESS EMPIRE LLC\Business Plans\PRODUCTS\DBAOps-Sovereign',
    [string]$SqlServer = '.',
    [string]$Database  = 'SovereignRepository',
    [string]$ReportDir = $null,
    [switch]$RunColdBuildProof,                # opt-in; creates+drops a throwaway DB
    [string[]]$BuildTrees = @('releases','pilot-before-website','delivery','dist',
                              'node_modules','.git','.github','_backups',
                              '_ship_readiness','_readiness','_discovery',
                              'coverage','.nyc_output','bin','obj','.vs','build')
)

$ErrorActionPreference = 'Stop'
if (-not (Test-Path -LiteralPath $Root)) { throw "Root not found: $Root" }
if (-not $ReportDir) { $ReportDir = Join-Path $Root '_ship_readiness' }
if (-not (Test-Path -LiteralPath $ReportDir)) { New-Item -Path $ReportDir -ItemType Directory | Out-Null }

$stamp      = Get-Date -Format 'yyyyMMdd_HHmmss'
$reportPath = Join-Path $ReportDir "ShipGateVerify_$stamp.txt"
$attestPath = Join-Path $ReportDir 'SHIP-ATTESTATION.md'
$log        = New-Object System.Collections.Generic.List[string]
$results    = New-Object System.Collections.Generic.List[object]

function W([string]$s, [string]$c = 'Gray') { Write-Host $s -ForegroundColor $c; $log.Add($s) }
function Add-Result($Item, $Check, $Status, $Evidence) {
    $results.Add([pscustomobject]@{ Item=$Item; Check=$Check; Status=$Status; Evidence=$Evidence })
}
# Status: PASS / FAIL / WARN / MANUAL
function Have-Sql { [bool](Get-Command Invoke-Sqlcmd -ErrorAction SilentlyContinue) }
function Q([string]$query, [string]$db = $Database) {
    Invoke-Sqlcmd -ServerInstance $SqlServer -Database $db -Query $query -TrustServerCertificate -QueryTimeout 120 -ErrorAction Stop
}

$escaped   = $BuildTrees | ForEach-Object { [regex]::Escape($_) }
$excludeRx = '(^|\\)(' + ($escaped -join '|') + ')(\\|$)'
$srcFiles  = Get-ChildItem -LiteralPath $Root -Recurse -File -ErrorAction SilentlyContinue |
             Where-Object { $_.FullName.Substring($Root.Length) -notmatch $excludeRx }
$textExt   = '.ps1','.psm1','.psd1','.sql','.md','.txt','.json','.js','.jsx','.ts','.tsx','.mjs','.cjs','.yml','.yaml','.config','.env','.example'
$srcText   = $srcFiles | Where-Object { $textExt -contains $_.Extension.ToLower() }

function Test-InSource([string]$pattern) {
    $skipNames = @('verify-portability.js', 'Verify-ShipReady.ps1', 'Find-ShipGaps.ps1', 'Confirm-ShipReady.ps1')
    $srcText | Where-Object { $skipNames -notcontains $_.Name } |
        Select-String -Pattern $pattern -AllMatches -ErrorAction SilentlyContinue
}
function Find-File([string]$rx) {
    $srcFiles | Where-Object { $_.Name -match $rx } | Select-Object -First 5
}

W "=============================================================" Cyan
W "DBAOps Sovereign - Enterprise Shippability Gate Verifier" Cyan
W "Root     : $Root"
W "DB       : $SqlServer / $Database"
W "Source   : $($srcFiles.Count) files (build trees excluded)"
W "Cold-build proof: $(if ($RunColdBuildProof){'ENABLED (will create+drop a temp DB)'}else{'disabled (use -RunColdBuildProof)'})"
W "Time     : $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
W "=============================================================" Cyan

# ============================================================================
# ITEM 1 - Packaging and release integrity
# ============================================================================
W ""
W '===== ITEM 1: PACKAGING AND RELEASE INTEGRITY =====' Cyan
$gitignore = Join-Path $Root '.gitignore'
$npmignore = Join-Path $Root '.npmignore'
$pkgJson   = Join-Path $Root 'package.json'

$needIgnored = @('releases','pilot-before-website','delivery','node_modules','.env','__tests__','_backups')
if (Test-Path $gitignore) {
    $gi = Get-Content $gitignore -Raw
    $missing = $needIgnored | Where-Object { $gi -notmatch [regex]::Escape($_) }
    if ($missing) { Add-Result 1 '.gitignore excludes build output' 'FAIL' "not ignored: $($missing -join ', ')" }
    else          { Add-Result 1 '.gitignore excludes build output' 'PASS' 'all build/output/env patterns present' }
} else { Add-Result 1 '.gitignore present' 'FAIL' 'no .gitignore at root' }

# packaging allowlist: either .npmignore or package.json "files"
$hasAllowlist = $false; $allowEvidence = ''
if (Test-Path $pkgJson) {
    try { $pj = Get-Content $pkgJson -Raw | ConvertFrom-Json } catch { $pj = $null }
    if ($pj -and $pj.files) { $hasAllowlist = $true; $allowEvidence = 'package.json "files" allowlist present' }
}
if (-not $hasAllowlist -and (Test-Path $npmignore)) { $hasAllowlist = $true; $allowEvidence = '.npmignore present' }
if ($hasAllowlist) { Add-Result 1 'Packaging manifest / allowlist' 'PASS' $allowEvidence }
else               { Add-Result 1 'Packaging manifest / allowlist' 'FAIL' 'no package.json "files" and no .npmignore - bundle would include everything' }

# version consistency: VERSION file vs package.json vs on-disk bundle names
$verFile = Join-Path $Root 'VERSION'
$verText = if (Test-Path $verFile) { (Get-Content $verFile -Raw).Trim() } else { $null }
$verPkg  = if ($pj) { $pj.version } else { $null }
$bundleVers = @()
Get-ChildItem -LiteralPath $Root -Directory -ErrorAction SilentlyContinue |
    Where-Object { $_.Name -match '(\d+\.\d+\.\d+)' } | ForEach-Object {
        if ($_.Name -match '(\d+\.\d+\.\d+)') { $bundleVers += $matches[1] }
    }
Get-ChildItem -LiteralPath (Join-Path $Root 'pilot-before-website') -Directory -ErrorAction SilentlyContinue |
    ForEach-Object { if ($_.Name -match '(\d+\.\d+\.\d+)') { $bundleVers += $matches[1] } }
$distinct = @($verText, $verPkg) + $bundleVers | Where-Object { $_ } | Select-Object -Unique
if ($distinct.Count -le 1) { Add-Result 1 'Version consistency' 'PASS' "single version: $($distinct -join '')" }
else { Add-Result 1 'Version consistency' 'FAIL' "conflicting versions -> VERSION='$verText' package.json='$verPkg' bundles='$($bundleVers -join ',')'" }

# ============================================================================
# ITEM 2 - Deploy-from-source proof
# ============================================================================
W ""
W "===== ITEM 2: DEPLOY-FROM-SOURCE PROOF =====" Cyan
$migrate = $srcFiles | Where-Object { $_.Name -eq 'migrate.js' } | Select-Object -First 1
if ($migrate) { Add-Result 2 'migrate.js present' 'PASS' $migrate.FullName.Replace($Root,'.') }
else          { Add-Result 2 'migrate.js present' 'FAIL' 'no migrate.js found in source' }

if ($RunColdBuildProof) {
    if (-not (Have-Sql)) {
        Add-Result 2 'Cold-build proof' 'FAIL' 'SqlServer module not available'
    } elseif (-not $migrate) {
        Add-Result 2 'Cold-build proof' 'FAIL' 'migrate.js not found'
    } else {
        $proofDb = "SovereignShipProof_$stamp"
        W "  Creating throwaway database [$proofDb] for cold-build proof..." Yellow
        try {
            Q "CREATE DATABASE [$proofDb];" 'master' | Out-Null
            $connBak = $env:SOVEREIGN_DB_CONNECTION
            $env:SOVEREIGN_DB_CONNECTION = "Server=$SqlServer;Database=master;Trusted_Connection=Yes;TrustServerCertificate=True;"
            $migrateJs = Join-Path $Root 'src\api\scripts\migrate.js'
            W "  Running migrate.js --database $proofDb from repo root..." Yellow
            Push-Location $Root
            $migOut = & node $migrateJs --database $proofDb 2>&1 | Out-String
            $migExit = $LASTEXITCODE
            Pop-Location
            $env:SOVEREIGN_DB_CONNECTION = $connBak
            if ($migExit -ne 0) { throw "migrate.js exit $migExit : $migOut" }
            $objCount = (Q "SELECT COUNT(*) c FROM sys.objects WHERE is_ms_shipped=0;" $proofDb).c
            $refCount = (Q "SELECT COUNT(*) c FROM sys.objects WHERE is_ms_shipped=0;" $Database).c
            $pct = if ($refCount -gt 0) { [math]::Round(100.0*$objCount/$refCount,1) } else { 0 }
            if ($objCount -gt 0 -and $pct -ge 95) {
                Add-Result 2 'Cold-build proof' 'PASS' "empty->$objCount objects ($pct% of dev repo $refCount) via migrate.js"
            } else {
                Add-Result 2 'Cold-build proof' 'FAIL' "cold build produced $objCount objects ($pct% of $refCount) - schema incomplete from source"
            }
        } catch {
            Add-Result 2 'Cold-build proof' 'FAIL' "cold build errored: $($_.Exception.Message)"
        } finally {
            try { Q "IF DB_ID('$proofDb') IS NOT NULL BEGIN ALTER DATABASE [$proofDb] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE [$proofDb]; END" 'master' | Out-Null; W "  Throwaway DB dropped." Green }
            catch { W "  WARNING: could not drop [$proofDb] - remove it manually." Red }
        }
    }
} else {
    Add-Result 2 'Cold-build proof' 'MANUAL' 'not run - re-run with -RunColdBuildProof. Incremental migrate against the dev DB does NOT prove this.'
}

# ============================================================================
# ITEM 3 - Zero hardcoded environment
# ============================================================================
W ""
W "===== ITEM 3: ZERO HARDCODED ENVIRONMENT =====" Cyan
$envPatterns = @{
    'Dev machine (JBGWALLY)' = '(?i)\bJBGWALLY\b'
    'AZDPS identifiers'      = '(?i)(AZDBAOPS|AZAPPPROD|AZODSSQL|AZTRACS|AZDWVAULT|AZDPS|INTERNAL\\|AGDOMC|10\.98\.|ZPSTW|E:\\DBAOps|DBAOpsRepository)'
}
foreach ($k in $envPatterns.Keys) {
    $hits = Test-InSource $envPatterns[$k]
    $n = @($hits).Count
    if ($n -eq 0) { Add-Result 3 "No $k in source" 'PASS' 'zero occurrences' }
    else {
        $sev = if ($k -like '*AZDPS*') { 'FAIL' } else { 'FAIL' }
        $files = ($hits | Select-Object -ExpandProperty Path -Unique | ForEach-Object { $_.Replace($Root,'.') } | Select-Object -First 4) -join '; '
        Add-Result 3 "No $k in source" $sev "$n hit(s) in: $files"
    }
}

# ============================================================================
# ITEM 4 - Credential and connection security
# ============================================================================
W ""
W '===== ITEM 4: CREDENTIAL AND CONNECTION SECURITY =====' Cyan
# 4a: collector must not default to sa
$saHits = Test-InSource '(?i)(-UserName\s+["'']sa["'']|UserName\s*=\s*["'']sa["''])'
if (@($saHits).Count -eq 0) { Add-Result 4 'No sa default in collector' 'PASS' 'no hardcoded sa username' }
else { Add-Result 4 'No sa default in collector' 'FAIL' "$(@($saHits).Count) sa default(s): $((@($saHits)|Select -First 2 -Expand Path|%{$_.Replace($Root,'.')}) -join '; ')" }

# 4b: credential-by-reference resolver present
$credResolver = Find-File '(?i)Get-.*Credential.*\.ps1$'
if ($credResolver) { Add-Result 4 'Credential resolution helper' 'PASS' ($credResolver.Name -join ', ') }
else { Add-Result 4 'Credential resolution helper' 'WARN' 'no Get-*Credential*.ps1 found in source' }

# 4c: encryption posture - TrustServerCertificate=True as a shipped default is a review flag
$tscHits = Test-InSource '(?i)TrustServerCertificate\s*=\s*(true|yes)'
$encHits = Test-InSource '(?i)Encrypt\s*=\s*(true|yes)'
if (@($tscHits).Count -gt 0 -and @($encHits).Count -eq 0) {
    Add-Result 4 'Connection encryption posture' 'WARN' "TrustServerCertificate=True present with no Encrypt=True default - acceptable for lab, flagged by enterprise security review"
} elseif (@($encHits).Count -gt 0) {
    Add-Result 4 'Connection encryption posture' 'PASS' 'Encrypt=True posture present'
} else {
    Add-Result 4 'Connection encryption posture' 'MANUAL' 'no explicit encryption directive found - confirm intended default'
}

# ============================================================================
# ITEM 5 - Least-privilege install / uninstall
# ============================================================================
W ""
W "===== ITEM 5: LEAST-PRIVILEGE INSTALL / UNINSTALL =====" Cyan
$uninstall = Find-File '(?i)^(uninstall|remove|teardown)\.(ps1|sh|cmd|js|sql)$'
if ($uninstall) { Add-Result 5 'Uninstall script present' 'PASS' ($uninstall.Name -join ', ') }
else { Add-Result 5 'Uninstall script present' 'FAIL' 'no uninstall/teardown script in source' }

$permDoc = $srcFiles | Where-Object { $_.Name -match '(?i)(permission|least.?priv|security|grants?)' -and $_.Extension -in '.md','.txt' } | Select-Object -First 3
if ($permDoc) { Add-Result 5 'Permissions/least-privilege doc' 'PASS' (($permDoc | ForEach-Object { $_.Name }) -join ', ') }
else { Add-Result 5 'Permissions/least-privilege doc' 'FAIL' 'no permissions/security doc found' }

# installer must not grant sysadmin
$sysadminHits = $srcText | Where-Object { $_.Name -notin @('Verify-ShipReady.ps1', 'Find-ShipGaps.ps1', 'Confirm-ShipReady.ps1') } |
    Select-String -Pattern '(?i)(ALTER SERVER ROLE\s*\[?sysadmin|sp_addsrvrolemember.*sysadmin|EXEC sp_addsrvrolemember)' -AllMatches -ErrorAction SilentlyContinue
if (@($sysadminHits).Count -eq 0) { Add-Result 5 'Installer avoids sysadmin grant' 'PASS' 'no sysadmin role grant in source' }
else { Add-Result 5 'Installer avoids sysadmin grant' 'FAIL' "$(@($sysadminHits).Count) sysadmin grant(s) in source" }

# ============================================================================
# ITEM 6 - Licensing and edition enforcement
# ============================================================================
W ""
W '===== ITEM 6: LICENSING AND EDITION ENFORCEMENT =====' Cyan
if (Have-Sql) {
    try {
        $entRows = (Q "SELECT COUNT(*) c FROM ctl.LicenseEntitlements;").c
        Add-Result 6 'License entitlements populated' $(if ($entRows -gt 0){'PASS'}else{'WARN'}) "ctl.LicenseEntitlements: $entRows row(s)"
    } catch { Add-Result 6 'License entitlements populated' 'WARN' "could not read ctl.LicenseEntitlements: $($_.Exception.Message)" }
    try {
        $enf = Q "SELECT s.name+'.'+o.name AS ObjectName FROM sys.sql_modules m JOIN sys.objects o ON o.object_id=m.object_id JOIN sys.schemas s ON s.schema_id=o.schema_id WHERE m.definition LIKE '%InstancesAllowed%';"
        if ($enf) { Add-Result 6 'Enforcement logic references InstancesAllowed' 'PASS' (($enf.ObjectName) -join ', ') }
        else { Add-Result 6 'Enforcement logic references InstancesAllowed' 'FAIL' 'no module references InstancesAllowed - entitlement may be cosmetic' }
    } catch { Add-Result 6 'Enforcement logic' 'WARN' $_.Exception.Message }
} else {
    Add-Result 6 'License enforcement (DB)' 'MANUAL' 'SqlServer module unavailable - verify enforcement manually'
}
# edition gating in code
$editionHits = Test-InSource '(?i)(licenseTier|editionTier|entitlement|InstancesAllowed|isEnterprise)'
Add-Result 6 'Edition gating referenced in code' $(if (@($editionHits).Count -gt 0){'PASS'}else{'WARN'}) "$(@($editionHits).Count) reference(s) in source"

# ============================================================================
# ITEM 7 - Multi-tenant isolation OR declared single-tenant
# ============================================================================
W ""
W "===== ITEM 7: TENANCY MODEL =====" Cyan
$tenancyDoc = $srcFiles | Where-Object { $_.Name -match '(?i)(tenan|deployment.?model|single.?tenant)' } | Select-Object -First 2
$modelDeclared = $false
if ($tenancyDoc) {
    foreach ($d in $tenancyDoc) {
        $c = Get-Content $d.FullName -Raw -ErrorAction SilentlyContinue
        if ($c -match '(?i)single[- ]tenant') { $modelDeclared = $true }
    }
}
if ($modelDeclared) { Add-Result 7 'Tenancy model declared' 'PASS' 'single-tenant-per-customer documented' }
else {
    if (Have-Sql) {
        try {
            $tc = (Q "SELECT COUNT(*) c FROM ctl.TenantIsolationConfig;").c
            if ($tc -eq 0) { Add-Result 7 'Tenancy model' 'MANUAL' 'tenant tables empty and no single-tenant declaration - declare the deployment model in docs' }
            else           { Add-Result 7 'Tenancy model' 'WARN' "TenantIsolationConfig has $tc row(s) - confirm isolation is enforced, not partial" }
        } catch { Add-Result 7 'Tenancy model' 'MANUAL' 'declare deployment model' }
    } else { Add-Result 7 'Tenancy model' 'MANUAL' 'declare deployment model in docs (single-tenant recommended for v1)' }
}

# ============================================================================
# ITEM 8 - Documentation set
# ============================================================================
W ""
W "===== ITEM 8: DOCUMENTATION SET =====" Cyan
$docChecks = @{
    'Fresh-install guide' = '(?i)(install|deploy|getting.?started)'
    'Config reference'    = '(?i)(config|configuration).*(reference|guide)'
    'Upgrade guide'       = '(?i)(upgrade|migration).*(guide|md)'
    'Uninstall guide'     = '(?i)uninstall'
    'Security/permissions'= '(?i)(security|permission|least.?priv)'
    'Troubleshooting'     = '(?i)(troubleshoot|faq|known.?issues)'
    'Diagnostics bundle'  = '(?i)(diagnostic|support.?bundle|collect.?logs)'
    'Release notes'       = '(?i)(changelog|release.?notes)'
}
foreach ($k in $docChecks.Keys) {
    $hit = $srcFiles | Where-Object { $_.Name -match $docChecks[$k] -and $_.Extension -in '.md','.txt','.ps1','.js' } | Select-Object -First 1
    if ($hit) { Add-Result 8 $k 'PASS' $hit.FullName.Replace($Root,'.') }
    else      { Add-Result 8 $k 'FAIL' 'not found' }
}

# ============================================================================
# ITEM 9 - Legal and commercial (attestation-backed)
# ============================================================================
W ""
W '===== ITEM 9: LEGAL AND COMMERCIAL =====' Cyan
$eula = $srcFiles | Where-Object { $_.Name -match '(?i)^(EULA|CUSTOMER.?EULA|CUSTOMER.?LICENSE|LICENSE.?AGREEMENT|TERMS)' -and $_.Extension -in '.md','.txt','.pdf','.docx' } | Select-Object -First 3
if ($eula) { Add-Result 9 'Customer EULA/terms present' 'PASS' (($eula|%{$_.Name}) -join ', ') }
else { Add-Result 9 'Customer EULA/terms present' 'FAIL' 'no EULA/customer-license/terms distinct from dev LICENSE.md' }

if (Have-Sql) {
    try {
        $brand = Q "SELECT ConfigKey, ConfigValue FROM config.BrandConfig WHERE ConfigKey IN ('PortalUrl','SupportEmail','SalesEmail','LegalEmail');"
        Add-Result 9 'Brand contact config present' 'PASS' (($brand | ForEach-Object { "$($_.ConfigKey)=$($_.ConfigValue)" }) -join ' | ')
    } catch { Add-Result 9 'Brand contact config' 'WARN' $_.Exception.Message }
}

# ---- Attestation file (human sign-off for what can't be auto-verified) ----
if (-not (Test-Path $attestPath)) {
@"
# DBAOps Sovereign - Ship Attestation
# Mark [x] only when personally verified. The gate verifier requires every line
# checked before it will return 100% SHIPPABLE. Do not check what isn't true.

[ ] IP ownership: product is clean-room, separable from any contract/employer work, and legally mine to sell (confirmed).
[ ] License enforcement is REAL (tested: exceeding InstancesAllowed is refused; expiry handled gracefully), not cosmetic.
[ ] Deploy-from-source cold build has passed on a fresh, non-dev instance (see Item 2 with -RunColdBuildProof).
[ ] Support and sales inboxes in config.BrandConfig are live and monitored.
[ ] PortalUrl resolves and serves the intended content.
[ ] Credential secrets resolve from a real store (Cred Manager / DPAPI / Key Vault); none stored plaintext.
[ ] Uninstall has been executed end-to-end on a test instance and fully reverses the install.
[ ] A customer-facing EULA/SLA has been reviewed (ideally by counsel).
"@ | Set-Content -Path $attestPath -Encoding UTF8
    W "  Attestation template created: $attestPath" Yellow
    W "  Edit it, check the lines you have verified, then re-run this script." Yellow
}
$attRaw = Get-Content $attestPath -Encoding UTF8 | ForEach-Object { $_ -replace '^\\(?=\[)', '' }
$attLines = $attRaw | Where-Object { $_ -match '^\[.\]' }
$attTotal = @($attLines).Count
$attDone  = @($attLines | Where-Object { $_ -match '^\[x\]' }).Count
if ($attTotal -eq 0 -and (Test-Path $attestPath)) {
    Add-Result 9 'Manual attestations signed' 'MANUAL' '0 attestation lines parsed - use [x] not \[x] in SHIP-ATTESTATION.md'
}
elseif ($attTotal -gt 0 -and $attDone -eq $attTotal) { Add-Result 9 'Manual attestations signed' 'PASS' "$attDone/$attTotal signed" }
else { Add-Result 9 'Manual attestations signed' 'MANUAL' "$attDone/$attTotal signed - complete SHIP-ATTESTATION.md" }

# ============================================================================
# SCORECARD + VERDICT
# ============================================================================
W ""
W "=============================================================" Cyan
W "SCORECARD" Cyan
W "=============================================================" Cyan
$byItem = $results | Group-Object Item | Sort-Object { [int]$_.Name }
$itemNames = @{
    1='Packaging & release integrity'; 2='Deploy-from-source proof'; 3='Zero hardcoded environment'
    4='Credential & connection security'; 5='Least-privilege install/uninstall'; 6='Licensing & edition enforcement'
    7='Tenancy model'; 8='Documentation set'; 9='Legal & commercial'
}
foreach ($g in $byItem) {
    $fails   = @($g.Group | Where-Object Status -eq 'FAIL').Count
    $manual  = @($g.Group | Where-Object Status -eq 'MANUAL').Count
    $warns   = @($g.Group | Where-Object Status -eq 'WARN').Count
    $itemStat = if ($fails -gt 0) { 'FAIL' } elseif ($manual -gt 0) { 'PENDING' } elseif ($warns -gt 0) { 'PASS*' } else { 'PASS' }
    $col = switch ($itemStat) { 'FAIL' {'Red'} 'PENDING' {'Yellow'} 'PASS*' {'Yellow'} default {'Green'} }
    W ("  Item {0}: {1,-34} {2}" -f $g.Name, $itemNames[[int]$g.Name], $itemStat) $col
    foreach ($r in $g.Group) {
        $rc = switch ($r.Status) { 'FAIL' {'Red'} 'MANUAL' {'Yellow'} 'WARN' {'Yellow'} default {'Green'} }
        W ("       [{0,-6}] {1} - {2}" -f $r.Status, $r.Check, $r.Evidence) $rc
    }
}

$totalFail   = @($results | Where-Object Status -eq 'FAIL').Count
$totalManual = @($results | Where-Object Status -eq 'MANUAL').Count
$totalWarn   = @($results | Where-Object Status -eq 'WARN').Count
$totalPass   = @($results | Where-Object Status -eq 'PASS').Count

W ""
W "=============================================================" Cyan
W ("TOTALS: PASS={0}  WARN={1}  MANUAL/PENDING={2}  FAIL={3}" -f $totalPass,$totalWarn,$totalManual,$totalFail) Cyan
W "=============================================================" Cyan
if ($totalFail -eq 0 -and $totalManual -eq 0) {
    if ($totalWarn -eq 0) { W "VERDICT: 100% SHIPPABLE TO PAYING CUSTOMERS." Green }
    else { W "VERDICT: SHIPPABLE with $totalWarn advisory warning(s) - review before first enterprise sale." Yellow }
} elseif ($totalFail -eq 0) {
    W ("VERDICT: NOT YET - $totalManual manual/proof item(s) outstanding (no hard failures). Complete attestations + cold-build proof." ) Yellow
} else {
    W ("VERDICT: NOT SHIPPABLE - $totalFail blocker(s) must be fixed. See FAIL lines above.") Red
}

# CSV for tracking
$csv = Join-Path $ReportDir "ShipGateVerify_$stamp.csv"
$results | Export-Csv -Path $csv -NoTypeInformation -Encoding UTF8
$log | Set-Content -Path $reportPath -Encoding UTF8
W ""
W "Report : $reportPath"
W "CSV    : $csv"
W "Attest : $attestPath"
Write-Host "`nDone. Paste the scorecard back for review." -ForegroundColor Cyan
