Repository navigation
feat: bind Issue #62 runtime inventory to Codex 0.146 #542
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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." | |
| } |