Skip to content

fix(monitor): unify successor routing before writeback and receipt validation #4938

fix(monitor): unify successor routing before writeback and receipt validation

fix(monitor): unify successor routing before writeback and receipt validation #4938

Workflow file for this run

name: Python Tests
on:
pull_request:
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"
- "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 }}
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_review_gate.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 }}
shell: bash
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == pull_request ]]; then
python scripts/ci/review_gate.py classify --base "$BASE_SHA" --head "$HEAD_SHA" >> "$GITHUB_OUTPUT"
else
echo 'core_tests=true' >> "$GITHUB_OUTPUT"
fi
checks:
needs: changes
if: needs.changes.outputs.core_tests == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- 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: "22.6"
cache: npm
cache-dependency-path: package-lock.json
- name: Install test dependencies
run: python -m pip install --disable-pip-version-check -e ".[test]"
- name: Qualify the TypeScript Effect core
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
test-shard:
needs: changes
if: needs.changes.outputs.core_tests == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
shard: [1, 2]
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- uses: actions/setup-python@v6
with:
python-version: "3.11"
cache: pip
- uses: actions/setup-node@v6
with:
node-version: "22.6"
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: 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 2 --group ${{ matrix.shard }}
--splitting-algorithm least_duration
--durations=25 --durations-min=1
--cov=loopx
--cov-report=term
- 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.core_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: |
test -s coverage-shards/python-coverage-1/.coverage
test -s coverage-shards/python-coverage-2/.coverage
python -m coverage combine coverage-shards/python-coverage-1/.coverage coverage-shards/python-coverage-2/.coverage
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
if: needs.changes.outputs.core_tests == 'true'
name: stage2c (${{ matrix.suite }})
runs-on: ubuntu-latest
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
suite: [e2e, mutants, installed]
steps:
- uses: actions/checkout@v7
- 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: "22.6"
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 4 --dist loadfile -m stage2c_e2e --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 }}
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.core_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
if: needs.changes.outputs.core_tests == 'true'
runs-on: windows-latest
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@v7
with:
fetch-depth: 0
- 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: "22.6"
- name: Install test dependencies
run: python -m pip install --disable-pip-version-check -e ".[test]"
- name: Run native Windows lifecycle tests
run: >-
python -m pytest -q
tests/test_command_invocation.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_install.py
- name: Run native scheduler facade tests
run: >-
node --no-warnings --experimental-strip-types --test
tests/control_plane_ts/scheduler_heartbeat_commit.test.ts
tests/control_plane_ts/scheduler_heartbeat_commit_cli.test.ts
merge-gate:
# Always publish one stable outcome, including documentation-only PRs.
if: always()
needs: [changes, pytest, stage2c-correctness-e2e, windows-powershell]
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