Skip to content

feat: bind Issue #62 runtime inventory to Codex 0.146 #542

feat: bind Issue #62 runtime inventory to Codex 0.146

feat: bind Issue #62 runtime inventory to Codex 0.146 #542

Workflow file for this run

name: CI
on:
pull_request:
branches:
- dev
- main
push:
branches:
- dev
- main
workflow_dispatch:
schedule:
- cron: '17 3 * * 0'
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_TOOLCHAIN: 1.97.1
RUSTUP_TOOLCHAIN: 1.97.1
jobs:
classifier:
name: CI classifier
runs-on: windows-2025
timeout-minutes: 10
outputs:
python_core: ${{ steps.plan.outputs.python_core }}
python_synthetic: ${{ steps.plan.outputs.python_synthetic }}
frontend: ${{ steps.plan.outputs.frontend }}
rust: ${{ steps.plan.outputs.rust }}
rust_safe_file_linux: ${{ steps.plan.outputs.rust_safe_file_linux }}
release_flavor: ${{ steps.plan.outputs.release_flavor }}
full: ${{ steps.plan.outputs.full }}
classifier_failed: ${{ steps.plan.outputs.classifier_failed }}
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Python 3.13
uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Classify changed paths
id: plan
shell: pwsh
env:
CI_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
CI_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
$raw = & python scripts/ci/ci_change_plan.py --output-json
$exitCode = $LASTEXITCODE
if (-not $raw) {
throw 'CI classifier produced no plan output.'
}
$plan = $raw | ConvertFrom-Json
foreach ($key in @('python_core', 'python_synthetic', 'frontend', 'rust', 'rust_safe_file_linux', 'release_flavor', 'full', 'classifier_failed')) {
$value = [bool]$plan.PSObject.Properties[$key].Value
"$key=$($value.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
}
Write-Host ($plan | ConvertTo-Json -Compress)
if ($exitCode -ne 0) {
throw "CI classifier exited unexpectedly with code $exitCode."
}
- name: Fail closed when path acquisition failed
if: steps.plan.outputs.classifier_failed == 'true'
shell: pwsh
run: throw 'CI classifier could not acquire changed paths; full matrix was selected but the gate must fail.'
python-core:
name: Python core
needs: classifier
if: always() && (needs.classifier.outputs.python_core == 'true' || needs.classifier.outputs.full == 'true')
runs-on: windows-2025
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Python 3.13
uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Create isolated Python environment
shell: pwsh
run: |
python -m venv .venv-ci
- name: Install test dependencies
run: .\.venv-ci\Scripts\python.exe -m pip install --upgrade pip pytest
- name: Run Python core partition
run: |
.\.venv-ci\Scripts\python.exe -m pytest -q --ignore=tests/test_real_client_e2e.py --junitxml=.pytest-results/junit-core.xml --durations=0
- name: Upload Python core JUnit evidence
if: always()
uses: actions/upload-artifact@v7
with:
name: python-core-junit
path: .pytest-results/junit-core.xml
if-no-files-found: ignore
python-synthetic:
name: Synthetic real-client contract
needs: classifier
if: always() && (needs.classifier.outputs.python_synthetic == 'true' || needs.classifier.outputs.full == 'true')
runs-on: windows-2025
timeout-minutes: 75
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Set up Python 3.13
uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Create isolated Python environment
shell: pwsh
run: |
python -m venv .venv-ci
- name: Install test dependencies
run: .\.venv-ci\Scripts\python.exe -m pip install --upgrade pip pytest
- name: Run or skip synthetic partition
shell: pwsh
run: |
.\.venv-ci\Scripts\python.exe tests/fixtures/real_client_e2e/run-with-windows-watchdog.py `
--timeout-seconds 3600 `
-- .\.venv-ci\Scripts\python.exe -m pytest -q tests/test_real_client_e2e.py `
--junitxml=.pytest-results/junit-synthetic.xml --durations=0
- name: Upload synthetic JUnit evidence
if: always()
uses: actions/upload-artifact@v7
with:
name: python-synthetic-junit
path: .pytest-results/junit-synthetic.xml
if-no-files-found: ignore
frontend:
name: Frontend build and UI contract
needs: classifier
if: always() && (needs.classifier.outputs.frontend == 'true' || needs.classifier.outputs.full == 'true')
runs-on: windows-2025
timeout-minutes: 30
defaults:
run:
working-directory: frontend
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Node.js 22
uses: actions/setup-node@v7
with:
node-version: '22'
- name: Install dependencies
run: npm ci
- name: Build frontend
run: npm run build
- name: Run UI contract tests
run: npm run test:ui-contract
rust-tests:
name: Rust tests (${{ matrix.flavor }})
needs: classifier
if: always() && (needs.classifier.outputs.rust == 'true' || needs.classifier.outputs.full == 'true')
runs-on: windows-2025
timeout-minutes: 75
strategy:
fail-fast: false
matrix:
flavor:
- normal
- debug
env:
CODEXHUB_BUILD_FLAVOR: ${{ matrix.flavor }}
defaults:
run:
working-directory: src-tauri
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust 1.97.1 toolchain
shell: pwsh
run: |
rustup toolchain install $env:RUST_TOOLCHAIN --profile minimal --component clippy
rustup default $env:RUST_TOOLCHAIN
rustc --version
cargo --version
- name: Cache Cargo dependencies
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-deps-v2-test-${{ hashFiles('src-tauri/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-deps-v2-test-
- name: Prepare Tauri resource placeholder
shell: pwsh
run: |
New-Item -ItemType Directory -Force resources/python | Out-Null
Set-Content -Path resources/python/.ci-placeholder -Value ''
- name: Run cargo tests
shell: pwsh
run: |
if ("${{ matrix.flavor }}" -eq "debug") {
cargo test --locked --features debug-diagnostics -- --test-threads=1
} else {
cargo test --locked -- --test-threads=1
}
- name: Build release-optimized flavor
shell: pwsh
run: |
if ("${{ matrix.flavor }}" -eq "debug") {
cargo build --release --locked --features debug-diagnostics
} else {
cargo build --release --locked
}
rust-safe-file-linux:
name: Rust safe_file Linux compile and tests
needs: classifier
if: always() && (needs.classifier.outputs.rust_safe_file_linux == 'true' || needs.classifier.outputs.full == 'true')
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Set up Python 3.13
uses: actions/setup-python@v7
with:
python-version: '3.13'
- name: Install Rust 1.97.1 toolchain and musl target
run: |
rustup toolchain install "${RUST_TOOLCHAIN}" --profile minimal --component clippy
rustup target add x86_64-unknown-linux-musl --toolchain "${RUST_TOOLCHAIN}"
rustup default "${RUST_TOOLCHAIN}"
rustc --version
cargo --version
rustup component list --installed | grep -qx 'clippy-x86_64-unknown-linux-gnu'
rustup target list --installed | grep -qx 'x86_64-unknown-linux-musl'
test -x "$(rustc --print sysroot)/lib/rustlib/x86_64-unknown-linux-gnu/bin/rust-lld"
- name: Lint safe_file with Clippy for Linux
env:
CARGO_MANIFEST_DIR: ${{ github.workspace }}/src-tauri
run: rustup run "${RUST_TOOLCHAIN}" clippy-driver --target x86_64-unknown-linux-musl -C linker=rust-lld --edition=2021 --test src-tauri/src/safe_file.rs -o safe-file-linux-lint -D warnings
- name: Compile safe_file test harness for Linux
env:
CARGO_MANIFEST_DIR: ${{ github.workspace }}/src-tauri
run: rustc +"${RUST_TOOLCHAIN}" --target x86_64-unknown-linux-musl -C linker=rust-lld --edition=2021 --test src-tauri/src/safe_file.rs -o safe-file-linux-tests
- name: Run safe_file Linux tests
env:
PYTHONPATH: ${{ github.workspace }}/src-python
run: ./safe-file-linux-tests
release-flavor-contract:
name: Release flavor contract
needs: classifier
if: always() && (needs.classifier.outputs.release_flavor == 'true' || needs.classifier.outputs.full == 'true')
runs-on: windows-2025
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Verify normal default and debug parity
shell: pwsh
run: |
$normalDefault = & scripts/build-windows-portable.ps1 -DryRun | ConvertFrom-Json
$normal = & scripts/build-windows-portable.ps1 -Flavor normal -DryRun | ConvertFrom-Json
$debug = & scripts/build-windows-portable.ps1 -Flavor debug -DryRun | ConvertFrom-Json
if ($normalDefault.flavor -ne "normal") { throw "Omitted -Flavor must produce normal." }
if ($normal.version -ne $debug.version -or $normal.source_revision -ne $debug.source_revision) {
throw "Normal and debug must come from the same version and source revision."
}
if ($normal.generated_config.productName -ne $debug.generated_config.productName -or
$normal.generated_config.identifier -ne $debug.generated_config.identifier -or
$normal.generated_config.title -ne $debug.generated_config.title) {
throw "Normal and debug must preserve one application identity."
}
if ($normal.updater_manifest -ne "latest.json" -or $debug.updater_manifest -ne "latest-debug.json") {
throw "Flavor updater manifests are not selected correctly."
}
if ($normal.installer_name -eq $debug.installer_name -or -not $debug.debug_diagnostics_enabled) {
throw "Debug artifact identity or diagnostic capability is missing."
}
$replacement = & scripts/Test-BuildFlavorReplacement.ps1 -DryRun | ConvertFrom-Json
if (($replacement.sequence -join ",") -ne "normal,debug,normal") {
throw "The same-version replacement sequence is not normal -> debug -> normal."
}
if ($replacement.application_identity.identifier -ne $normal.generated_config.identifier -or
$replacement.runtime.gateway_port -ne $normal.generated_config.gatewayPort -or
$replacement.runtime.expected_gateway_owner_count -ne 1) {
throw "Flavor replacement no longer preserves one application and Gateway owner."
}
rust-clippy:
name: Rust clippy
needs: classifier
if: always() && (needs.classifier.outputs.rust == 'true' || needs.classifier.outputs.full == 'true')
runs-on: windows-2025
timeout-minutes: 45
defaults:
run:
working-directory: src-tauri
steps:
- name: Check out repository
uses: actions/checkout@v7
- name: Install Rust 1.97.1 toolchain
shell: pwsh
run: |
rustup toolchain install $env:RUST_TOOLCHAIN --profile minimal --component clippy
rustup default $env:RUST_TOOLCHAIN
rustc --version
cargo --version
- name: Cache Cargo dependencies
uses: actions/cache@v6
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-deps-v2-clippy-${{ hashFiles('src-tauri/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-deps-v2-clippy-
- name: Prepare Tauri resource placeholder
shell: pwsh
run: |
New-Item -ItemType Directory -Force resources/python | Out-Null
Set-Content -Path resources/python/.ci-placeholder -Value ''
- name: Run clippy
shell: pwsh
run: cargo clippy --locked --all-targets -- -D warnings
gate:
name: CI / gate
if: always()
needs:
- classifier
- python-core
- python-synthetic
- frontend
- rust-tests
- rust-safe-file-linux
- release-flavor-contract
- rust-clippy
runs-on: windows-2025
timeout-minutes: 10
steps:
- name: Evaluate selected checks
shell: pwsh
run: |
$classifierResult = '${{ needs.classifier.result }}'
if ($classifierResult -in @('failure', 'cancelled')) {
throw "CI classifier did not complete successfully ($classifierResult)."
}
$checks = @(
@('python_core', "${{ needs['python-core'].result }}", "${{ needs.classifier.outputs.python_core }}"),
@('python_synthetic', "${{ needs['python-synthetic'].result }}", "${{ needs.classifier.outputs.python_synthetic }}"),
@('frontend', "${{ needs.frontend.result }}", "${{ needs.classifier.outputs.frontend }}"),
@('rust_tests', "${{ needs['rust-tests'].result }}", "${{ needs.classifier.outputs.rust }}"),
@('rust_safe_file_linux', "${{ needs['rust-safe-file-linux'].result }}", "${{ needs.classifier.outputs.rust_safe_file_linux }}"),
@('release_flavor', "${{ needs['release-flavor-contract'].result }}", "${{ needs.classifier.outputs.release_flavor }}"),
@('rust_clippy', "${{ needs['rust-clippy'].result }}", "${{ needs.classifier.outputs.rust }}")
)
foreach ($check in $checks) {
$name = $check[0]
$result = $check[1]
$selected = $check[2] -eq 'true'
if (-not $selected) {
Write-Host "$name is intentionally skipped."
continue
}
if ($result -in @('failure', 'cancelled')) {
throw "$name was selected but finished with $result."
}
if ($result -ne 'success') {
throw "$name was selected but finished with unexpected result '$result'."
}
Write-Host "$name passed."
}