ek-ui 0.9.37

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
# One-command quality gate for ek-ui.
# Runs: cargo fmt --check / cargo test / cargo clippy -D warnings / cargo doc
#       / scripts/check-package.ps1
# Usage: powershell -ExecutionPolicy Bypass -File scripts/gate.ps1 [-AllowDirty]
# NOTE: ASCII-only on purpose (Windows PowerShell 5.1 decodes BOM-less .ps1 as ANSI).
[CmdletBinding()]
param(
    [switch]$AllowDirty
)

$ErrorActionPreference = 'Continue'

$root = Split-Path -Parent $PSScriptRoot
Push-Location $root
try {
    # MSVC 19.50 cl.exe exits 2 with D8050 ("cannot execute c1.dll") when the compile
    # asks for debug info AND cl was started by a build tool instead of a shell. The C
    # code here is third-party (ring via egui-charts, libsql-ffi via ek-cfg); this repo's
    # Rust code is not involved. The gate only checks correctness, not debuggability, so
    # drop debug info for the C side. Root cause + experiments:
    # docs/ek-3modes-report-2026-11-01.md section 5.
    $env:CARGO_PROFILE_DEV_DEBUG = '0'

    # Temp: in restricted sandboxes %TEMP% can be unwritable, which makes tests/launch.rs
    # fail with os error 5 (it creates its scratch dir under std::env::temp_dir()).
    # Pin TEMP/TMP inside the workspace so the gate does not depend on host temp ACLs.
    $tmp = Join-Path $root 'target\test-tmp'
    New-Item -ItemType Directory -Force -Path $tmp | Out-Null
    $env:TEMP = $tmp
    $env:TMP = $tmp

    Write-Host "toolchain:" -ForegroundColor Cyan
    & rustfmt --version
    & rustc --version
    & cargo --version

    $results = New-Object System.Collections.ArrayList

    function Invoke-Gate {
        param([string]$Name, [scriptblock]$Body)
        Write-Host ""
        Write-Host ("=== " + $Name + " ===") -ForegroundColor Cyan
        & $Body
        $code = $LASTEXITCODE
        if ($null -eq $code) { $code = 0 }
        if ($code -eq 0) {
            Write-Host ("PASS  " + $Name) -ForegroundColor Green
            [void]$results.Add([pscustomobject]@{ Name = $Name; Ok = $true })
        } else {
            Write-Host ("FAIL  " + $Name + "  (exit " + $code + ")") -ForegroundColor Red
            [void]$results.Add([pscustomobject]@{ Name = $Name; Ok = $false })
        }
    }

    Invoke-Gate 'cargo fmt --check' { cargo fmt --check }
    Invoke-Gate 'cargo test' { cargo test }
    Invoke-Gate 'cargo clippy --all-targets -- -D warnings' { cargo clippy --all-targets -- -D warnings }
    # The native feature is COMPILE-ONLY here: the live CTP tests are all `#[ignore]`d,
    # so they cannot run in a normal gate, but they must still COMPILE -- otherwise a
    # breakage like the SecretString migration sits silently with zero coverage
    # (2026-11-01: five test files had never been compiled, the live surface was empty).
    Invoke-Gate 'cargo check --tests --features ctp-native' { cargo check --tests --features ctp-native }
    Invoke-Gate 'cargo doc --no-deps' { cargo doc --no-deps }

    # Data contract: src/ must not build structs with `..*Data::default()` -- that makes
    # a new ek-data field silently default and the compiler stay quiet (`traded_price`
    # sat wrong for several releases that way). Only src/ is checked: examples and tests
    # legitimately build minimal objects that way.
    $contractOk = $true
    $fallbacks = Get-ChildItem -Recurse -Filter *.rs -Path src |
        Select-String -Pattern '^\s*\.\.\w*Data::default\(\)'
    if ($fallbacks) {
        $contractOk = $false
        Write-Host "[gate] FAIL data contract: ..*Data::default() in src/" -ForegroundColor Red
        $fallbacks | ForEach-Object {
            Write-Host ("  {0}:{1}  {2}" -f (Split-Path $_.Path -Leaf), $_.LineNumber, $_.Line.Trim())
        }
    } else {
        Write-Host "[gate] OK data contract: no ..*Data::default() in src/" -ForegroundColor Green
    }
    [void]$results.Add([pscustomobject]@{ Name = 'data contract (no ..*Data::default in src/)'; Ok = $contractOk })

    $guard = Join-Path $PSScriptRoot 'check-package.ps1'
    if ($AllowDirty) {
        Invoke-Gate 'scripts/check-package.ps1 -AllowDirty' { & $guard -AllowDirty }
    } else {
        Invoke-Gate 'scripts/check-package.ps1 (strict)' { & $guard }
    }

    # ---- G1 (no-ui mode must stay egui-free) ------------------------------
    # The daemon binary (ek-station) must never pull egui in: "headless" is a
    # compile-time property, not a runtime switch. Two checks that fail loudly
    # if someone enables the `ui` feature on a plugin from the station side.
    $g1ok = $true
    $stationToml = Join-Path (Split-Path -Parent $root) 'ek-station\Cargo.toml'
    if (Test-Path $stationToml) {
        $hits = Select-String -Path $stationToml -Pattern 'features\s*=\s*\[[^\]]*"ui"' -ErrorAction SilentlyContinue
        if ($hits) {
            $g1ok = $false
            Write-Host "[G1] FAIL: ek-station/Cargo.toml enables a plugin ui feature" -ForegroundColor Red
            $hits | ForEach-Object { Write-Host ("  " + $_.LineNumber + ": " + $_.Line.Trim()) }
        }
    }
    # Run the tree INSIDE the daemon repo: from ek-ui the resolution would unify
    # ek-ui's own egui/eframe features into the answer (false positive).
    $stDir = Join-Path (Split-Path -Parent $root) 'ek-station'
    $tree = ''
    if (Test-Path $stDir) {
        Push-Location $stDir
        try { $tree = & cargo tree -e features 2>&1 | Out-String } finally { Pop-Location }
    }
    if ($tree -match 'egui') {
        $g1ok = $false
        Write-Host "[G1] FAIL: ek-station dependency graph contains egui" -ForegroundColor Red
        ($tree -split "`n" | Select-String -Pattern 'egui' | Select-Object -First 5) | ForEach-Object { Write-Host ("  " + $_.Line.Trim()) }
    }
    if ($g1ok) { Write-Host "[G1] OK: ek-station is egui-free" -ForegroundColor Green }
    [void]$results.Add([pscustomobject]@{ Name = 'G1 no-ui mode has no egui'; Ok = $g1ok })

    # ---- G2 (no orphan plugin faces) --------------------------------------
    $faces = Join-Path $PSScriptRoot 'check_plugin_faces.ps1'
    Invoke-Gate 'G2 no orphan plugin faces' { & $faces }
    Write-Host ""
    Write-Host "===== gate summary =====" -ForegroundColor Cyan
    $failed = 0
    foreach ($r in $results) {
        if ($r.Ok) {
            Write-Host ("  PASS  " + $r.Name) -ForegroundColor Green
        } else {
            Write-Host ("  FAIL  " + $r.Name) -ForegroundColor Red
            $failed++
        }
    }

    if ($failed -eq 0) {
        Write-Host "ALL GATES PASSED" -ForegroundColor Green
        exit 0
    } else {
        Write-Host ("GATES FAILED: " + $failed) -ForegroundColor Red
        exit 1
    }
} finally {
    Pop-Location
}