# 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
}