Worker bridged implementer path (#431 follow-up) #1815
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: flow-next | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| # Weekly drift backstop: full ubuntu / python 3.x run (units + smokes). | |
| # The 3.x row was demoted from the per-PR matrix (it duplicated ubuntu-3.11 | |
| # almost entirely); day-to-day 3.x coverage rides python-intermediate-smoke, | |
| # and this cron catches anything a new stable Python breaks in the full gate. | |
| schedule: | |
| - cron: "0 3 * * 1" # Mondays 03:00 UTC | |
| workflow_dispatch: | |
| inputs: | |
| suite_mode: | |
| description: "Unit-suite mode for this exact ref" | |
| required: true | |
| type: choice | |
| options: | |
| - parallel | |
| - serial | |
| - shuffle | |
| default: parallel | |
| pattern: | |
| description: "Optional single-file glob relative to plugins/flow-next/tests (for example test_gate_receipt.py)" | |
| required: false | |
| type: string | |
| default: "" | |
| verbose: | |
| description: "Print per-test names and failing-file output" | |
| required: false | |
| type: boolean | |
| default: false | |
| file_timeout: | |
| description: "Per-file timeout in seconds (1-900)" | |
| required: false | |
| type: string | |
| default: "900" | |
| legacy_baseline: | |
| description: "Measurement lever (fn-155) - run the unit suite at the pre-CI-detection job count, computed per runner" | |
| required: false | |
| type: boolean | |
| default: false | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.run_id }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| units_matrix: ${{ steps.classify.outputs.units_matrix }} | |
| smokes_matrix: ${{ steps.classify.outputs.smokes_matrix }} | |
| run_smokes: ${{ steps.classify.outputs.run_smokes }} | |
| run_windows_stub: ${{ steps.classify.outputs.run_windows_stub }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Classify change and emit job matrices | |
| id: classify | |
| env: | |
| BASE_SHA: ${{ github.event_name == 'pull_request' && github.event.pull_request.base.sha || github.event.before }} | |
| HEAD_SHA: ${{ github.sha }} | |
| run: python scripts/ci/classify_changes.py | |
| # ──────────── Units (static checks + parallel unit suite) ──────────── | |
| # Split from the smokes so the two ~6-9m halves run side by side per OS | |
| # instead of back to back. Neither job depends on the other. | |
| units: | |
| timeout-minutes: 35 | |
| needs: changes | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # Explicit 3.11 rows preserve the minimum-runtime contract; the | |
| # latest-stable `3.x` full gate runs on the weekly schedule and in | |
| # python-intermediate-smoke, not per-PR. | |
| include: ${{ fromJSON(needs.changes.outputs.units_matrix) }} | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash # Git Bash on Windows; bash on Linux/macOS — unifies the matrix. | |
| steps: | |
| - name: Disable git autocrlf so checkout preserves LF on Windows | |
| run: git config --global core.autocrlf false | |
| # Default Windows-runner config converts LF→CRLF on checkout, which | |
| # changes heredoc bytes that smokes compare byte-identically against | |
| # flowctl output. Force LF on all OSes for consistent fixtures. | |
| # Reached-path B0/B1 validation reads immutable prompt sources from their | |
| # recorded commits. A shallow checkout makes valid frozen evidence look | |
| # corrupt because those commits are absent. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Configure git identity (smoke tests create commits) | |
| run: | | |
| git config --global user.email "ci@flow-next.test" | |
| git config --global user.name "flow-next CI" | |
| git config --global init.defaultBranch main | |
| # ──────────── Static checks (cheap, fail fast) ──────────── | |
| - name: Syntax check — flowctl.py | |
| run: python -m py_compile plugins/flow-next/scripts/flowctl.py | |
| # Correctness-only ruleset; see ruff.toml for what is deliberately out. | |
| # PIN THE VERSION: ruff 0.16 moved its default set from 59 rules to 413, | |
| # which turns an unpinned `ruff` into an unannounced CI break. | |
| # One row only - lint results do not vary by OS or Python version. | |
| - name: Lint — ruff | |
| if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11' | |
| run: | | |
| python -m pip install --disable-pip-version-check ruff==0.16.0 | |
| ruff check . | |
| - name: Syntax check — ralph.sh | |
| run: bash -n plugins/flow-next/skills/flow-next-ralph-init/templates/ralph.sh | |
| # ──────────── Python unit tests (file-level parallel, fn-119) ──────────── | |
| # Canonical full-suite entrypoint: scripts/run_tests_parallel.py | |
| # Shards by FILE only (never within a file). Default jobs = the runner's | |
| # full core count on CI and cores-2 locally (fn-155; the job-count policy | |
| # lives in the runner, not here). --serial is the equivalence fallback. | |
| # workflow_dispatch suite_mode supports focused, serial, and shuffled | |
| # exact-ref diagnostics without changing ordinary PR/push behavior. | |
| # workflow_dispatch.inputs.legacy_baseline is the fn-155 measurement | |
| # lever: it reproduces the pre-change job count from this same commit, | |
| # computed per runner because nothing establishes that all three OS legs | |
| # report the same core count. Inert on push and pull_request. | |
| - name: Python unit tests - parallel suite (fn-119) | |
| env: | |
| LEGACY_BASELINE: ${{ github.event.inputs.legacy_baseline }} | |
| SUITE_MODE: ${{ github.event.inputs.suite_mode }} | |
| TEST_PATTERN: ${{ github.event.inputs.pattern }} | |
| TEST_VERBOSE: ${{ github.event.inputs.verbose }} | |
| FILE_TIMEOUT: ${{ github.event.inputs.file_timeout }} | |
| run: | | |
| JOBS_ARGS=() | |
| if [ "$LEGACY_BASELINE" = "true" ]; then | |
| JOBS_ARGS=(--jobs "$(python -c 'import os; print(max(1, (os.cpu_count() or 2) - 2))')") | |
| echo "Legacy baseline (fn-155): ${JOBS_ARGS[*]}" | |
| fi | |
| MODE_ARGS=() | |
| PATTERN_ARGS=() | |
| VERBOSE_ARGS=() | |
| TIMEOUT_ARGS=() | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| case "$SUITE_MODE" in | |
| parallel) ;; | |
| serial) MODE_ARGS=(--serial) ;; | |
| shuffle) MODE_ARGS=(--shuffle --seed "$GITHUB_RUN_ID") ;; | |
| *) echo "Invalid suite_mode: $SUITE_MODE (expected parallel, serial, or shuffle)" >&2; exit 2 ;; | |
| esac | |
| if [ -n "$TEST_PATTERN" ]; then | |
| case "$TEST_PATTERN" in | |
| */*|*\\*) echo "pattern must be a filename glob relative to plugins/flow-next/tests" >&2; exit 2 ;; | |
| esac | |
| PATTERN_ARGS=(--pattern "$TEST_PATTERN") | |
| fi | |
| if [ "$TEST_VERBOSE" = "true" ]; then | |
| VERBOSE_ARGS=(--verbose) | |
| fi | |
| case "$FILE_TIMEOUT" in | |
| ''|*[!0-9]*) echo "file_timeout must be an integer from 1 to 900" >&2; exit 2 ;; | |
| esac | |
| if [ "$FILE_TIMEOUT" -lt 1 ] || [ "$FILE_TIMEOUT" -gt 900 ]; then | |
| echo "file_timeout must be an integer from 1 to 900" >&2 | |
| exit 2 | |
| fi | |
| TIMEOUT_ARGS=(--file-timeout "$FILE_TIMEOUT") | |
| echo "Diagnostic mode: $SUITE_MODE; pattern=${TEST_PATTERN:-test_*.py}; timeout=${FILE_TIMEOUT}s" | |
| fi | |
| # No Windows-only exclusions (fn-120, R8): every OS leg runs the SAME | |
| # discovered corpus. fn-119 shipped a six-file Windows EXCLUDES list | |
| # for latent incompatibilities in files that had never run on Windows | |
| # CI; fn-120.1/.2 fixed the five deterministic ones and fn-120.3 | |
| # retired the last (test_backend_spec.py - no hang reproduced on | |
| # windows-2025, and the runner now isolates + kills each shard's | |
| # process tree). Never re-add a per-OS filter here: an exclusion is | |
| # invisible debt. Bound a suspect file with --file-timeout instead. | |
| python scripts/run_tests_parallel.py "${JOBS_ARGS[@]}" "${MODE_ARGS[@]}" "${PATTERN_ARGS[@]}" "${VERBOSE_ARGS[@]}" "${TIMEOUT_ARGS[@]}" | |
| # ──────────── Smokes (cursor install + functional bash smokes) ──────────── | |
| # Runs in parallel with the units job on each OS; skipped entirely on | |
| # docs-only PR and main push ranges. | |
| smokes: | |
| timeout-minutes: 30 | |
| needs: changes | |
| if: needs.changes.outputs.run_smokes == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: ${{ fromJSON(needs.changes.outputs.smokes_matrix) }} | |
| runs-on: ${{ matrix.os }} | |
| defaults: | |
| run: | |
| shell: bash # Git Bash on Windows; bash on Linux/macOS — unifies the matrix. | |
| steps: | |
| - name: Disable git autocrlf so checkout preserves LF on Windows | |
| run: git config --global core.autocrlf false | |
| # Default Windows-runner config converts LF→CRLF on checkout, which | |
| # changes heredoc bytes that smokes compare byte-identically against | |
| # flowctl output. Force LF on all OSes for consistent fixtures. | |
| # Reached-path B0/B1 validation reads immutable prompt sources from their | |
| # recorded commits. A shallow checkout makes valid frozen evidence look | |
| # corrupt because those commits are absent. | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Configure git identity (smoke tests create commits) | |
| run: | | |
| git config --global user.email "ci@flow-next.test" | |
| git config --global user.name "flow-next CI" | |
| git config --global init.defaultBranch main | |
| # ──────────── Cursor local-install (1.9.0) ──────────── | |
| # The bash + PowerShell Cursor installers (scripts/install-cursor.{sh,ps1}) | |
| # must not drift, and must actually produce a complete + clean install on | |
| # each OS. Installer-parity unit tests ride the parallel suite in the | |
| # units job; the real-install smoke runs the platform's OWN installer | |
| # (rsync on Unix, robocopy on Windows) into ~/.cursor/plugins/local and | |
| # verifies against the source tree (component trees match 1:1; codex/ | |
| # tests/ *.pyc excluded). | |
| - name: install-cursor.sh real-install smoke (Unix) | |
| if: always() && runner.os != 'Windows' | |
| run: | | |
| bash "$GITHUB_WORKSPACE/scripts/install-cursor.sh" | |
| python "$GITHUB_WORKSPACE/scripts/ci/verify_cursor_install.py" | |
| - name: install-cursor.ps1 real-install smoke (Windows) | |
| if: always() && runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force | |
| ./scripts/install-cursor.ps1 | |
| python ./scripts/ci/verify_cursor_install.py | |
| # ──────────── Functional smokes (ordered cheap-first) ──────────── | |
| # All smokes refuse to run from the main plugin repo, so cd into the | |
| # runner's temp dir before invoking. $RUNNER_TEMP is set on every | |
| # GitHub-hosted runner (ubuntu/macos/windows). | |
| - name: pick_python_test.sh (Windows 9009-stub probe regression, POSIX/Git-Bash, fn-77.5) | |
| if: always() && runner.os != 'Windows' | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/pick_python_test.sh" | |
| # POSIX/mac-linux unit layer for the interpreter probe: a fake 9009 | |
| # `python3` stub first on PATH must be rejected by the shared resolver + | |
| # the bash launcher, PYTHON_BIN override is itself probed, py -3 preferred, | |
| # python3 still first on mac/linux (R8). The real windows-latest end-to-end | |
| # of flowctl.cmd + the bash launcher lives in the windows-python3-stub job. | |
| - name: ci_test.sh (flowctl integration, 58 cases) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/ci_test.sh" | |
| - name: resolve-pr_smoke_test.sh (58 cases, ~1s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/resolve-pr_smoke_test.sh" | |
| - name: strategy_smoke_test.sh (62 cases, ~5s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/strategy_smoke_test.sh" | |
| - name: audit_smoke_test.sh (41 cases, ~6s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/audit_smoke_test.sh" | |
| - name: glossary_smoke_test.sh (80 cases, ~7s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/glossary_smoke_test.sh" | |
| - name: prospect_smoke_test.sh (94 cases, ~60s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/prospect_smoke_test.sh" | |
| - name: make-pr_smoke_test.sh (67 cases, ~7s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/make-pr_smoke_test.sh" | |
| - name: map_smoke_test.sh (75 cases, fn-50.1 + fn-50.6 directory-level ignore + config-state --json guards) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/map_smoke_test.sh" | |
| - name: impl-review_smoke_test.sh (74 cases, ~70s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/impl-review_smoke_test.sh" | |
| - name: smoke_test.sh (130 cases, ~100s) | |
| if: always() | |
| run: | | |
| cd "$RUNNER_TEMP" | |
| bash "$GITHUB_WORKSPACE/plugins/flow-next/scripts/smoke_test.sh" | |
| # Smokes intentionally skipped (require external CLIs not on GitHub runners): | |
| # - ralph_smoke_test.sh / ralph_smoke_rp.sh — need claude / codex / rp-cli | |
| # - plan_review_prompt_smoke.sh — needs rp-cli | |
| python-intermediate-smoke: | |
| timeout-minutes: 10 | |
| name: Python ${{ matrix.python-version }} compatibility smoke | |
| needs: changes | |
| if: needs.changes.outputs.run_smokes == 'true' | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # `3.x` (latest stable) rides this cheap contract leg per-PR now that | |
| # the full ubuntu-3.x matrix row is demoted to the weekly schedule. | |
| python-version: ["3.12", "3.13", "3.x"] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Compile flowctl source and bootstrap | |
| run: | | |
| python -m py_compile \ | |
| plugins/flow-next/scripts/flowctl.py \ | |
| plugins/flow-next/scripts/flowctl_bootstrap.py | |
| - name: Runtime, launcher, and usage contracts | |
| run: | | |
| cd plugins/flow-next/tests | |
| python -m unittest \ | |
| test_startup_bootstrap \ | |
| test_bin_launcher_parity \ | |
| test_cmd_usage \ | |
| test_precheck_mode_contract \ | |
| -q | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| # fn-77.5 (R10): real windows-latest end-to-end. A DETERMINISTIC fake `python3` | |
| # stub (exit 9009 — the Microsoft Store App Execution Alias failure mode) is | |
| # forced FIRST on PATH for BOTH a Git Bash step AND a PowerShell/cmd step (NOT | |
| # reliant on the runner's own Store alias, which may be absent). A real | |
| # python.org Python (+ the py launcher, already on the hosted image) is present | |
| # via setup-python; then the bash `flowctl` launcher and the `flowctl.cmd` batch | |
| # shim must each probe past the stub, resolve a working interpreter, and run | |
| # `flowctl init` — proving the fix works in the exact shells where Claude | |
| # Desktop / native Codex / Cursor invoke flowctl on Windows. | |
| # ───────────────────────────────────────────────────────────────────────────── | |
| windows-python3-stub: | |
| needs: changes | |
| if: needs.changes.outputs.run_windows_stub == 'true' | |
| timeout-minutes: 10 | |
| runs-on: windows-latest | |
| steps: | |
| - name: Disable git autocrlf so the LF-pinned bash launcher stays LF | |
| shell: bash | |
| run: git config --global core.autocrlf false | |
| # The bash `flowctl` launcher is pinned eol=lf (.gitattributes); CRLF would | |
| # break `bash` parsing. Default Windows-runner autocrlf would rewrite it. | |
| - uses: actions/checkout@v4 | |
| - name: Setup Python (real python.org interpreter + py launcher) | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Configure git identity (flowctl init touches git) | |
| shell: bash | |
| run: | | |
| git config --global user.email "ci@flow-next.test" | |
| git config --global user.name "flow-next CI" | |
| git config --global init.defaultBranch main | |
| - name: Build a deterministic 9009 python3 stub for both shells | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| STUB="$RUNNER_TEMP/pystub" | |
| mkdir -p "$STUB" | |
| # Extensionless stub for Git Bash: MSYS resolves bare `python3` to this | |
| # shebang script (it does not append .cmd), so `python3` runs the stub. | |
| { | |
| printf '#!/bin/bash\n' | |
| printf 'echo "Python was not found; run without arguments to install from the Microsoft Store, or disable this shortcut from Settings > Apps > Advanced app settings > App execution aliases." >&2\n' | |
| printf 'exit 9009\n' | |
| } > "$STUB/python3" | |
| chmod +x "$STUB/python3" | |
| # .cmd stub for cmd.exe / PowerShell: PATHEXT resolves `python3` here. | |
| { | |
| printf '@ECHO OFF\n' | |
| printf 'ECHO Python was not found; disable the Microsoft Store App execution alias. 1>&2\n' | |
| printf 'EXIT /b 9009\n' | |
| } > "$STUB/python3.cmd" | |
| # Prove the stub really is non-functional before wiring it onto PATH. | |
| if "$STUB/python3" -c "import sys" >/dev/null 2>&1; then | |
| echo "FAIL: stub python3 should exit non-zero" >&2; exit 1 | |
| fi | |
| printf 'PYSTUB_DIR=%s\n' "$STUB" >> "$GITHUB_ENV" | |
| - name: Build deterministic working-but-too-old probes for both shells | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| OLD="$RUNNER_TEMP/pyold" | |
| mkdir -p "$OLD" | |
| for name in py python3 python; do | |
| { | |
| printf '#!/bin/bash\n' | |
| printf 'exit 3\n' | |
| } > "$OLD/$name" | |
| chmod +x "$OLD/$name" | |
| { | |
| printf '@ECHO OFF\r\n' | |
| printf 'EXIT /b 3\r\n' | |
| } > "$OLD/$name.cmd" | |
| done | |
| printf 'PYOLD_DIR=%s\n' "$OLD" >> "$GITHUB_ENV" | |
| - name: Git Bash — bash `flowctl` bypasses the 9009 stub | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| export PATH="$PYSTUB_DIR:$PATH" | |
| echo "python3 resolves to: $(command -v python3)" | |
| # The stub is genuinely first on PATH and non-functional. | |
| if python3 -c "import sys" >/dev/null 2>&1; then | |
| echo "FAIL: python3 stub should exit non-zero under Git Bash" >&2; exit 1 | |
| fi | |
| FLOWCTL="$GITHUB_WORKSPACE/plugins/flow-next/scripts/flowctl" | |
| REPO="$RUNNER_TEMP/bash-stub-repo"; mkdir -p "$REPO"; cd "$REPO"; git init -q | |
| OUT="$("$FLOWCTL" init --json 2>&1)" || { echo "FAIL: launcher exited non-zero"; echo "$OUT"; exit 1; } | |
| echo "$OUT" | |
| echo "$OUT" | grep -q '"success": true' || { echo "FAIL: flowctl init did not succeed" >&2; exit 1; } | |
| if echo "$OUT" | grep -q "Python was not found"; then | |
| echo "FAIL: stub message leaked — launcher did not bypass the stub" >&2; exit 1 | |
| fi | |
| echo "PASS: bash flowctl bypassed the 9009 python3 stub" | |
| - name: PowerShell — `flowctl.cmd` bypasses the 9009 stub | |
| shell: pwsh | |
| run: | | |
| # GitHub pwsh sets $ErrorActionPreference='Stop'; with the 7.3+ default | |
| # $PSNativeCommandUseErrorActionPreference=$true a non-zero native exit | |
| # THROWS. We invoke a stub that is MEANT to fail, so drive control flow | |
| # off $LASTEXITCODE explicitly instead. | |
| $PSNativeCommandUseErrorActionPreference = $false | |
| $env:PATH = "$env:PYSTUB_DIR;$env:PATH" | |
| Write-Host "python3 resolves to: $((Get-Command python3 -ErrorAction SilentlyContinue).Source)" | |
| # The stub is genuinely first on PATH and non-functional (native 9009). | |
| & python3 -c "import sys" 2>$null | |
| if ($LASTEXITCODE -eq 0) { Write-Error "python3 stub should exit non-zero in pwsh"; exit 1 } | |
| $flowctl = Join-Path $env:GITHUB_WORKSPACE "plugins/flow-next/scripts/flowctl.cmd" | |
| $repo = Join-Path $env:RUNNER_TEMP "cmd-stub-repo" | |
| New-Item -ItemType Directory -Force -Path $repo | Out-Null | |
| Set-Location $repo | |
| git init -q | |
| $outputLines = & $flowctl init --json 2>&1 | |
| $exitCode = $LASTEXITCODE | |
| $out = $outputLines | Out-String | |
| Write-Host $out | |
| if ($exitCode -ne 0) { Write-Error "flowctl.cmd exited $exitCode"; exit 1 } | |
| if ($out -notmatch '"success": true') { Write-Error "flowctl.cmd init did not succeed"; exit 1 } | |
| if ($out -match "Python was not found") { Write-Error "stub message leaked — flowctl.cmd did not bypass the stub"; exit 1 } | |
| Write-Host "PASS: flowctl.cmd bypassed the 9009 python3 stub" | |
| ci: | |
| name: CI | |
| if: always() | |
| needs: [changes, units, smokes, python-intermediate-smoke, windows-python3-stub] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Require every selected job | |
| env: | |
| NEEDS_JSON: ${{ toJSON(needs) }} | |
| run: | | |
| python3 - <<'PYTHON' | |
| import json, os | |
| needs = json.loads(os.environ['NEEDS_JSON']) | |
| required = {'changes', 'units'} | |
| outputs = needs['changes'].get('outputs', {}) | |
| if outputs.get('run_smokes') == 'true': | |
| required.update(('smokes', 'python-intermediate-smoke')) | |
| if outputs.get('run_windows_stub') == 'true': | |
| required.add('windows-python3-stub') | |
| for name, job in needs.items(): | |
| allowed = {'success'} if name in required else {'success', 'skipped'} | |
| if job['result'] not in allowed: | |
| raise SystemExit(f"{name}: {job['result']}") | |
| PYTHON |