Settle blocked Turns without spend and back off the exact Todo #7774
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: Python Tests | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] | |
| workflow_dispatch: | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - ".github/workflows/python-tests.yml" | |
| - ".github/workflows/sonarcloud.yml" | |
| - "sonar-project.properties" | |
| - "apps/**" | |
| - "loopx/**" | |
| - "scripts/**" | |
| - "tests/**" | |
| - "examples/**" | |
| - "package.json" | |
| - "package-lock.json" | |
| - "pyproject.toml" | |
| - "setup.py" | |
| - "MANIFEST.in" | |
| - "tsconfig.control-plane.json" | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: python-tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 3 | |
| outputs: | |
| core_tests: ${{ steps.classify.outputs.core_tests }} | |
| change_kind: ${{ steps.classify.outputs.change_kind }} | |
| python_tests: ${{ steps.classify.outputs.python_tests }} | |
| stage2c_tests: ${{ steps.classify.outputs.stage2c_tests }} | |
| presentation_tests: ${{ steps.classify.outputs.presentation_tests }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Validate merge gate semantics | |
| run: python -m unittest discover -s scripts/ci -p 'test_*.py' | |
| - name: Classify the exact pull-request change | |
| id: classify | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| CHECKOUT_SHA: ${{ github.sha }} | |
| FORCE_FULL: ${{ contains(github.event.pull_request.labels.*.name, 'ci:full') }} | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| extra=() | |
| if [[ "$FORCE_FULL" == true ]]; then extra+=(--force-full); fi | |
| if [[ "$EVENT_NAME" == pull_request ]]; then | |
| python scripts/ci/review_gate.py classify --base "$BASE_SHA" --head "$HEAD_SHA" --plan impact-plan.json "${extra[@]}" >> "$GITHUB_OUTPUT" | |
| else | |
| python scripts/ci/review_gate.py classify --base "$CHECKOUT_SHA" --head "$CHECKOUT_SHA" --non-pr --plan impact-plan.json >> "$GITHUB_OUTPUT" | |
| fi | |
| python - <<'PY' | |
| import json, os | |
| from pathlib import Path | |
| plan = json.loads(Path("impact-plan.json").read_text()) | |
| with open(os.environ["GITHUB_STEP_SUMMARY"], "a") as summary: | |
| summary.write("## CI job exemptions\n\n") | |
| summary.write(f"Change kind: **{plan['change_kind']}**; Python shards: **{plan['python_shards']}**.\n\n") | |
| summary.write(plan['reason'] + ".\n\n") | |
| summary.write(f"Python: {plan['python_tests']}; Stage2c: {plan['stage2c_tests']}; frontend: {plan['presentation_tests']}.\n\n") | |
| summary.write(plan["coverage_scope"] + ". Unknown/mixed changes and main run full. Label ci:full forces full qualification.\n") | |
| PY | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: ci-impact-plan | |
| path: impact-plan.json | |
| if-no-files-found: error | |
| retention-days: 7 | |
| chat-bundle: | |
| needs: changes | |
| if: needs.changes.outputs.core_tests == 'true' || needs.changes.outputs.python_tests == 'true' || needs.changes.outputs.presentation_tests == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: apps/presentation/dashboard/package-lock.json | |
| - run: python scripts/chat_bundle.py build --install | |
| - run: python scripts/chat_bundle.py verify --source | |
| - name: Qualify the actual compiled UI before saving the artifact | |
| working-directory: apps/presentation/dashboard | |
| run: | | |
| ./node_modules/.bin/playwright install --with-deps chromium | |
| npm run smoke:personal-workspace-packaged | |
| npm run smoke:chat-upgrade | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| if-no-files-found: error | |
| retention-days: 7 | |
| kernel-static-checks: | |
| needs: [changes, chat-bundle] | |
| if: needs.changes.outputs.core_tests == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Set up the TypeScript Effect runtime | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install test dependencies | |
| run: python -m pip install --disable-pip-version-check -e ".[test]" | |
| - name: Verify file authority on the core runtime | |
| run: >- | |
| node --no-warnings --experimental-strip-types --test | |
| tests/control_plane_ts/authority_store.test.ts | |
| tests/control_plane_ts/local_authority_runtime.test.ts | |
| tests/control_plane_ts/sqlite_runtime_admission.test.ts | |
| - name: Set up the qualified SQLite runtime | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.22.3" | |
| - name: Qualify the TypeScript Effect core | |
| env: | |
| LOOPX_TEST_REQUIRE_SQLITE_QUALIFIED: "1" | |
| run: | | |
| npm ci --ignore-scripts | |
| npm run typecheck:control-plane | |
| npm run test:control-plane:coverage | |
| - name: Upload TypeScript control-plane coverage | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: typescript-control-plane-coverage | |
| path: coverage/control-plane/lcov.info | |
| if-no-files-found: error | |
| retention-days: 3 | |
| - name: Lint test suite | |
| run: >- | |
| python -m ruff check | |
| tests | |
| loopx/canary | |
| loopx/control_plane | |
| loopx/domain_packs | |
| loopx/presentation | |
| - name: Type-check kernel contracts | |
| run: python -m mypy | |
| - name: Qualify agent-facing CLI output | |
| env: | |
| LOOPX_CLI_OUTPUT_BASE_REF: origin/${{ github.event.pull_request.base.ref || 'main' }} | |
| run: python examples/control_plane/cli-output-budget-regression-smoke.py | |
| dashboard-acceptance: | |
| needs: [changes, chat-bundle] | |
| if: needs.changes.outputs.core_tests == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v7 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| - name: Set up the qualified Node.js runtime | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.22.3" | |
| cache: npm | |
| cache-dependency-path: | | |
| package-lock.json | |
| apps/presentation/dashboard/package-lock.json | |
| - name: Install Dashboard and coverage dependencies | |
| run: | | |
| npm ci --ignore-scripts | |
| npm ci --ignore-scripts --prefix apps/presentation/dashboard | |
| - name: Measure Dashboard unit and browser interactions | |
| env: | |
| LOOPX_DASHBOARD_COVERAGE: "1" | |
| LOOPX_PLAYWRIGHT_PACKAGE: ${{ github.workspace }}/apps/presentation/dashboard/node_modules/playwright | |
| run: | | |
| npm run test:dashboard:coverage | |
| cd apps/presentation/dashboard | |
| npx playwright install --with-deps chromium --only-shell | |
| node ../../../examples/personal-workspace-browser-smoke.mjs | |
| - name: Upload Dashboard coverage | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: dashboard-coverage | |
| path: coverage/dashboard/*.info | |
| if-no-files-found: error | |
| retention-days: 3 | |
| checks: | |
| # Preserve the required check name while exposing independent failure lanes. | |
| if: always() && needs.changes.outputs.core_tests == 'true' | |
| needs: [changes, kernel-static-checks, dashboard-acceptance] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| steps: | |
| - name: Require kernel and Dashboard qualification | |
| env: | |
| DASHBOARD_RESULT: ${{ needs.dashboard-acceptance.result }} | |
| KERNEL_RESULT: ${{ needs.kernel-static-checks.result }} | |
| run: | | |
| test "$KERNEL_RESULT" = success | |
| test "$DASHBOARD_RESULT" = success | |
| node-minimum-compatibility: | |
| needs: changes | |
| if: needs.changes.outputs.core_tests == 'true' | |
| runs-on: ubuntu-latest | |
| # The full suite runs under this exact Node version in kernel-static-checks. | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.22.3" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Qualify the public minimum Node.js runtime | |
| env: | |
| LOOPX_TEST_REQUIRE_SQLITE_QUALIFIED: "1" | |
| run: | | |
| npm ci --ignore-scripts | |
| npm run typecheck:control-plane | |
| # Prove the minimum itself admits SQLite and executes both provider | |
| # lifecycle rows. The qualified core lane runs the complete suite. | |
| node --no-warnings --experimental-sqlite --experimental-strip-types --test \ | |
| tests/control_plane_ts/sqlite_runtime_admission.test.ts \ | |
| tests/control_plane_ts/deferred_hard_lease_lifecycle.test.ts | |
| node-forward-compatibility: | |
| needs: changes | |
| if: needs.changes.outputs.core_tests == 'true' | |
| continue-on-error: true | |
| runs-on: ubuntu-latest | |
| # This runs the full core conformance suite. Allow forward-runtime/runner | |
| # variance without dropping cases or changing individual test deadlines. | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "26" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Probe the next Node.js runtime | |
| run: | | |
| npm ci --ignore-scripts | |
| npm run typecheck:control-plane | |
| npm run test:control-plane | |
| test-shard: | |
| needs: [changes, chat-bundle] | |
| if: needs.changes.outputs.python_tests == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: package-lock.json | |
| - name: Install test dependencies | |
| run: | | |
| python -m pip install --disable-pip-version-check -e ".[test]" | |
| npm ci --ignore-scripts | |
| - name: Prepare native shell isolation | |
| # Ubuntu 24.04 may need an executable-scoped userns profile for bwrap. | |
| # Do not disable AppArmor or the system-wide unprivileged-userns guard. | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y bubblewrap | |
| if ! bwrap --unshare-all --ro-bind / / /bin/true; then | |
| sudo tee /etc/apparmor.d/loopx-qualification-bwrap >/dev/null <<'PROFILE' | |
| abi <abi/4.0>, | |
| include <tunables/global> | |
| profile loopx-qualification-bwrap /usr/bin/bwrap flags=(unconfined) { | |
| userns, | |
| } | |
| PROFILE | |
| sudo apparmor_parser -r /etc/apparmor.d/loopx-qualification-bwrap | |
| fi | |
| bwrap --unshare-all --ro-bind / / /bin/true | |
| - name: Run test shard | |
| # Split the whole collection, not a hand-maintained list of directories. | |
| # Without timing history least_duration alternates equal-weight tests. | |
| # Each runner retains the measured two-worker pool. | |
| run: >- | |
| python -m pytest -q -n 2 -m "not stage2c_e2e" | |
| --splits 4 --group ${{ matrix.shard }} | |
| --splitting-algorithm least_duration | |
| --durations=25 --durations-min=1 | |
| --junitxml=junit.xml | |
| --cov=loopx | |
| --cov-report=term | |
| - name: Upload full-shard outcomes for selection audit | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: python-junit-${{ matrix.shard }} | |
| path: junit.xml | |
| if-no-files-found: error | |
| retention-days: 7 | |
| - name: Upload shard coverage | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: python-coverage-${{ matrix.shard }} | |
| path: .coverage | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| retention-days: 3 | |
| pytest: | |
| # Keep the required check name; a skipped/failed shard must not turn it green. | |
| if: always() && needs.changes.outputs.python_tests == 'true' | |
| needs: [changes, checks, test-shard] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Require every upstream check | |
| env: | |
| CHECKS_RESULT: ${{ needs.checks.result }} | |
| SHARDS_RESULT: ${{ needs.test-shard.result }} | |
| run: | | |
| test "$CHECKS_RESULT" = success | |
| test "$SHARDS_RESULT" = success | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - run: python -m pip install --disable-pip-version-check -e ".[test]" | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| pattern: python-coverage-* | |
| path: coverage-shards | |
| - name: Combine complete coverage and enforce the existing floor | |
| run: | | |
| shards=() | |
| for shard in 1 2 3 4; do | |
| path="coverage-shards/python-coverage-${shard}/.coverage" | |
| test -s "$path" | |
| shards+=("$path") | |
| done | |
| python -m coverage combine "${shards[@]}" | |
| python -m coverage report --fail-under=19.6 | |
| python -m coverage xml -o coverage.xml | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: python-coverage-xml | |
| path: coverage.xml | |
| if-no-files-found: error | |
| retention-days: 3 | |
| sonar: | |
| needs: pytest | |
| uses: ./.github/workflows/sonarcloud.yml | |
| secrets: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| stage2c-suite: | |
| needs: [changes, chat-bundle] | |
| if: needs.changes.outputs.stage2c_tests == 'true' | |
| name: stage2c (${{ matrix.suite }} ${{ matrix.shard }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - {suite: e2e, shard: 1} | |
| - {suite: e2e, shard: 2} | |
| - {suite: mutants, shard: 0} | |
| - {suite: installed, shard: 0} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| cache-dependency-path: tests/requirements-stage2c-linux-py311.txt | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| - name: Install locked test and package build tools | |
| run: | | |
| python -m pip install --disable-pip-version-check --require-hashes --only-binary=:all: -r tests/requirements-stage2c-linux-py311.txt | |
| npm ci --ignore-scripts | |
| - name: Build and verify the checked-out source package | |
| run: | | |
| python -m build --no-isolation --wheel --outdir .local/stage2c-source-install | |
| python - <<'PYTHON' | |
| import hashlib | |
| from pathlib import Path | |
| directory = Path(".local/stage2c-source-install").resolve() | |
| wheels = list(directory.glob("*.whl")) | |
| if len(wheels) != 1: | |
| raise SystemExit("Expected exactly one wheel from the checked-out source") | |
| wheel = wheels[0] | |
| digest = hashlib.sha256(wheel.read_bytes()).hexdigest() | |
| (directory / "requirements.txt").write_text( | |
| f"loopx @ {wheel.as_uri()} --hash=sha256:{digest}\n", encoding="utf-8" | |
| ) | |
| PYTHON | |
| python -m pip install --disable-pip-version-check --require-hashes --no-deps --no-index -r .local/stage2c-source-install/requirements.txt | |
| python -m pip check | |
| # Remove the generated source copy before pytest's normal discovery. | |
| python -c "import shutil; shutil.rmtree('build')" | |
| - name: Qualify real CLI, mixed writers, process death, and recovery | |
| if: matrix.suite == 'e2e' | |
| env: | |
| LOOPX_SHADOW_COMPARISON_OUTPUT: .local/stage2c-observables | |
| # Keep each module's shared workspace and ordered parity rows on one worker. | |
| run: python -m pytest -q -n 2 --dist loadfile -m stage2c_e2e -p scripts.ci.module_shard --ci-module-shards 2 --ci-module-shard ${{ matrix.shard }} --durations=20 --junitxml=stage2c-e2e.xml | |
| - name: Reject deliberate correctness regressions | |
| if: matrix.suite == 'mutants' | |
| run: python examples/shared-goal-authority-e2e/mutants.py --output .local/stage2c-mutants | |
| - name: Build independently installed distributions | |
| if: matrix.suite == 'installed' | |
| run: python -m build --no-isolation | |
| - name: Qualify wheel outside the repository | |
| if: matrix.suite == 'installed' | |
| run: python examples/shared-goal-authority-e2e/installed.py --artifact dist/*.whl --report-json installed-wheel.json | |
| - name: Qualify sdist outside the repository | |
| if: matrix.suite == 'installed' | |
| run: python examples/shared-goal-authority-e2e/installed.py --artifact dist/*.tar.gz --report-json installed-sdist.json | |
| - name: Retain bounded acceptance evidence | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: stage2c-correctness-evidence-${{ matrix.suite }}-${{ matrix.shard }} | |
| include-hidden-files: true | |
| if-no-files-found: error | |
| path: | | |
| stage2c-e2e.xml | |
| .local/stage2c-observables/ | |
| installed-wheel.json | |
| installed-sdist.json | |
| .local/stage2c-mutants/ | |
| stage2c-correctness-e2e: | |
| # Preserve the public check name and reject failed, cancelled or skipped lanes. | |
| if: always() && needs.changes.outputs.stage2c_tests == 'true' | |
| needs: [changes, stage2c-suite] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| steps: | |
| - name: Require every Stage 2C lane | |
| env: | |
| STAGE2C_RESULT: ${{ needs.stage2c-suite.result }} | |
| run: test "$STAGE2C_RESULT" = success | |
| windows-powershell: | |
| needs: [changes, chat-bundle] | |
| if: needs.changes.outputs.python_tests == 'true' | |
| runs-on: windows-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Check out repository | |
| uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| - name: Set up Python | |
| uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| cache: pip | |
| - name: Set up the TypeScript Effect runtime | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: npm | |
| cache-dependency-path: apps/presentation/dashboard/package-lock.json | |
| - name: Install test dependencies | |
| run: python -m pip install --disable-pip-version-check -e ".[test]" | |
| - name: Verify native Windows test Python discovery | |
| run: node --no-warnings --experimental-strip-types --test tests/control_plane_ts/test_python_runtime.test.ts | |
| - name: Run native Windows lifecycle tests | |
| run: >- | |
| python -m pytest -q | |
| tests/test_command_invocation.py | |
| tests/test_doctor_git_encoding.py | |
| tests/test_doctor_install_freshness.py | |
| tests/test_file_lock.py | |
| tests/test_file_lock_cross_process.py | |
| tests/control_plane/test_coordination_file_provider.py | |
| tests/control_plane/test_effect_runtime_integration.py | |
| tests/control_plane/test_local_authority_shadow_outbox.py | |
| tests/test_self_update_runtime_activation.py | |
| tests/test_windows_atomic_writes.py | |
| tests/test_windows_install.py | |
| - name: Set up the qualified SQLite runtime | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "22.22.3" | |
| - name: Run SQLite CLI integration | |
| run: python -m pytest -q tests/control_plane/test_sqlite_authority_cli.py | |
| - name: Run native scheduler and SQLite authority tests | |
| run: >- | |
| node --no-warnings --experimental-sqlite --experimental-strip-types --test | |
| tests/control_plane_ts/scheduler_heartbeat_commit.test.ts | |
| tests/control_plane_ts/scheduler_heartbeat_commit_cli.test.ts | |
| tests/control_plane_ts/sqlite_authority_store.test.ts | |
| tests/control_plane_ts/local_authority_provider.test.ts | |
| tests/control_plane_ts/sqlite_runtime_admission.test.ts | |
| - name: Install the frontend dependencies for a source rebuild | |
| working-directory: apps/presentation/dashboard | |
| run: npm ci --ignore-scripts | |
| - name: Rebuild the Chat bundle where only python.exe is usable | |
| working-directory: apps/presentation/dashboard | |
| shell: pwsh | |
| run: | | |
| $shadow = Join-Path $env:RUNNER_TEMP "python-exe-only" | |
| New-Item -ItemType Directory -Force -Path $shadow | Out-Null | |
| # Supported Windows installs may expose python.exe alone, and a | |
| # python3.exe App Execution Alias stub is not runnable. The npm entry | |
| # must resolve a real 3.11+ interpreter either way. | |
| Set-Content -Path (Join-Path $shadow "python3.exe") ` | |
| -Value "unusable python3 stand-in" -Encoding ascii | |
| $env:PATH = "$shadow;$env:PATH" | |
| npm run build:chat | |
| - name: Verify the Windows-built Chat bundle | |
| run: python scripts/chat_bundle.py verify --source | |
| presentation: | |
| needs: [changes, chat-bundle] | |
| if: needs.changes.outputs.presentation_tests == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/download-artifact@v7 | |
| with: | |
| name: chat-bundle-${{ github.sha }} | |
| path: loopx/web/chat/ | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Verify the source-bound, browser-qualified Dashboard artifact | |
| run: python scripts/chat_bundle.py verify --source | |
| merge-gate: | |
| # Always publish one stable outcome, including documentation-only PRs. | |
| if: always() | |
| needs: [changes, checks, pytest, node-minimum-compatibility, stage2c-correctness-e2e, windows-powershell, presentation] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 3 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.11" | |
| - name: Reject incomplete or unsuccessful qualification | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: python scripts/ci/review_gate.py verify |