name: Unit Tests on: # PR 完整验证;合并推送仅在代码树和成功证明匹配时复用 pull_request: branches: - v3 push: branches: - v3 # 允许手动触发 workflow_dispatch: permissions: contents: read concurrency: group: unit-tests-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: reuse: name: Check reusable PR verification runs-on: ubuntu-latest timeout-minutes: 3 permissions: contents: read actions: read pull-requests: read outputs: reused: ${{ steps.check.outputs.reuse }} steps: - name: Checkout code uses: actions/checkout@v7 - name: Test reuse decision run: node --test .github/scripts/reuse.test.mjs - name: Check successful PR merge tree id: check uses: actions/github-script@v9 with: script: | const { reuse } = await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/reuse.mjs`); await reuse({ github, context, core }); architecture: needs: reuse if: needs.reuse.outputs.reused != 'true' runs-on: ubuntu-latest name: Architecture Contract Gate timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v10.0.1 with: python-version: '3.14' enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock - name: Install dependencies run: uv sync --locked - name: Check event semantic policy run: | uv run --locked --no-sync pytest \ tests/test_architecture_dependencies.py \ tests/test_architecture_adapter_imports.py \ tests/test_architecture_egress.py \ tests/test_architecture_event_facts.py \ tests/test_architecture_event_policy.py -q - name: Check host architecture snapshot run: | uv run --locked --no-sync pytest \ tests/test_architecture_contract_baseline.py \ tests/test_architecture_baseline_cli.py -q uv run --locked --no-sync python \ scripts/architecture/baseline.py --check-host # 与 architecture job 并行执行,两者合计即完整的宿主架构门禁 architecture-ratchets: needs: reuse if: needs.reuse.outputs.reused != 'true' runs-on: ubuntu-latest name: Architecture Ratchet Gate timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v10.0.1 with: python-version: '3.14' enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock - name: Install dependencies run: uv sync --locked - name: Check exact typing compatibility exceptions run: uv run --locked --no-sync python scripts/architecture/mypy_policy.py - name: Check all host Python types run: uv run --locked --no-sync mypy --config-file mypy.ini - name: Check structural complexity ratchet run: >- uv run --locked --no-sync python scripts/architecture/complexity.py --report complexity-report.json - name: Upload complexity and source size report if: always() uses: actions/upload-artifact@v7 with: name: complexity-report path: complexity-report.json if-no-files-found: warn retention-days: 7 - name: Check native concurrency ownership run: uv run --locked --no-sync python scripts/architecture/concurrency.py - name: Check async blocking ratchet run: uv run --locked --no-sync python scripts/architecture/async_blocking.py - name: Check background task ownership run: uv run --locked --no-sync python scripts/architecture/task_ownership.py - name: Check process runtime service locators run: uv run --locked --no-sync python scripts/architecture/service_locator.py - name: Check Ruff diagnostic ratchet run: uv run --locked --no-sync python scripts/architecture/ruff_ratchet.py - name: Check mypy error ratchet run: uv run --locked --no-sync python scripts/architecture/mypy_ratchet.py - name: Check startup performance contract run: >- uv run --locked --no-sync python scripts/startup/performance.py --check --repeat 3 coverage-shard: needs: reuse if: needs.reuse.outputs.reused != 'true' runs-on: ubuntu-latest name: Unit Tests with Coverage (${{ matrix.shard }}) timeout-minutes: 15 strategy: fail-fast: false matrix: include: - shard: '1/6' index: '1' - shard: '2/6' index: '2' - shard: '3/6' index: '3' - shard: '4/6' index: '4' - shard: '5/6' index: '5' - shard: '6/6' index: '6' steps: - name: Checkout code uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v10.0.1 with: python-version: '3.14' enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock - name: Install dependencies run: uv sync --locked - name: Generate coverage data timeout-minutes: 10 run: uv run --locked --no-sync python -m coverage run --parallel-mode tests/run.py --exclude-architecture-gate --shard "${{ matrix.shard }}" - name: Upload coverage data uses: actions/upload-artifact@v7 with: name: coverage-data-${{ matrix.index }} path: .coverage.* include-hidden-files: true if-no-files-found: error retention-days: 7 coverage-report: runs-on: ubuntu-latest name: Coverage Report needs: coverage-shard timeout-minutes: 10 steps: - name: Checkout code uses: actions/checkout@v7 - name: Set up uv uses: astral-sh/setup-uv@v10.0.1 with: python-version: '3.14' enable-cache: true cache-dependency-glob: | pyproject.toml uv.lock - name: Install dependencies run: uv sync --locked - name: Download coverage data uses: actions/download-artifact@v8 with: pattern: coverage-data-* path: coverage-data merge-multiple: true - name: Combine coverage data and check ratchet run: | uv run --locked --no-sync python -m coverage combine coverage-data uv run --locked --no-sync python -m coverage report uv run --locked --no-sync python -m coverage json uv run --locked --no-sync python -m coverage xml uv run --locked --no-sync python scripts/architecture/coverage_ratchet.py - name: Upload coverage report uses: actions/upload-artifact@v7 with: name: coverage-report path: | coverage.xml coverage.json retention-days: 7 proof: name: CI proof (${{ github.sha }}) needs: [architecture, architecture-ratchets, coverage-shard, coverage-report] if: github.event_name == 'pull_request' && needs.architecture.result == 'success' && needs.architecture-ratchets.result == 'success' && needs.coverage-shard.result == 'success' && needs.coverage-report.result == 'success' runs-on: ubuntu-latest timeout-minutes: 1 steps: - name: Record fully verified merge commit run: echo 'All required checks passed for this PR merge commit.'