Extended Validation #176
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: Extended Validation | |
| on: | |
| push: | |
| branches: [main] | |
| schedule: | |
| - cron: '0 7 * * *' | |
| workflow_dispatch: | |
| concurrency: | |
| group: extended-validation-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name != 'workflow_dispatch' }} | |
| permissions: | |
| contents: read | |
| env: | |
| NODE_VERSION: '20' | |
| PYTHON_VERSION: '3.12' | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| changes: | |
| name: Detect Extended Validation Scope | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| outputs: | |
| app: ${{ steps.preset.outputs.app || steps.filter.outputs.app || 'false' }} | |
| ci: ${{ steps.preset.outputs.ci || steps.filter.outputs.ci || 'false' }} | |
| extended: ${{ steps.preset.outputs.extended || steps.filter.outputs.extended || 'false' }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| if: github.event_name == 'push' | |
| with: | |
| fetch-depth: 0 | |
| - name: Run full suite for nightly or manual invocations | |
| id: preset | |
| if: github.event_name != 'push' | |
| run: | | |
| cat >>"$GITHUB_OUTPUT" <<'EOF' | |
| app=true | |
| ci=true | |
| extended=true | |
| EOF | |
| - uses: dorny/paths-filter@v4 | |
| id: filter | |
| if: github.event_name == 'push' | |
| with: | |
| filters: | | |
| app: | |
| - 'project.bootstrap.yaml' | |
| - 'AGENTS.md' | |
| - 'CLAUDE.md' | |
| - '.devcontainer/**' | |
| - '.githooks/**' | |
| - '.github/workflows/**' | |
| - 'scripts/**' | |
| - 'docs/bootstrap/**' | |
| - 'README.md' | |
| - 'docs/**' | |
| ci: | |
| - 'project.bootstrap.yaml' | |
| - 'AGENTS.md' | |
| - 'CLAUDE.md' | |
| - '.devcontainer/**' | |
| - '.githooks/**' | |
| - '.github/workflows/**' | |
| - 'scripts/**' | |
| - 'docs/bootstrap/**' | |
| - '.env.example' | |
| - 'CODEOWNERS' | |
| extended: | |
| - 'infra/**' | |
| - 'ops/**' | |
| fast-checks: | |
| name: Fast Checks | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| timeout-minutes: 15 | |
| needs: changes | |
| if: needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./actions/setup-shell-safe-node | |
| with: | |
| node-version: 24.14.1 | |
| - name: Run fast checks | |
| run: bash scripts/ci/run-fast-checks.sh | |
| extended-checks: | |
| name: Extended Checks | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| timeout-minutes: 20 | |
| needs: changes | |
| if: needs.changes.outputs.extended == 'true' || needs.changes.outputs.app == 'true' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./actions/setup-shell-safe-node | |
| with: | |
| node-version: 24.14.1 | |
| - name: Run extended validation | |
| run: bash scripts/ci/run-extended-validation.sh | |
| drift-detect: | |
| name: Drift Detection | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| timeout-minutes: 10 | |
| needs: changes | |
| if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./actions/setup-shell-safe-node | |
| with: | |
| node-version: 24.14.1 | |
| - name: Enable pnpm from shell-safe Node | |
| run: | | |
| corepack enable | |
| corepack prepare pnpm@10.32.1 --activate | |
| pnpm --version | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Compare desired and actual runner pool state | |
| env: | |
| GITHUB_PAT: ${{ secrets.RUNNER_FLEET_GITHUB_PAT }} | |
| DRIFT_THRESHOLD: ${{ vars.DRIFT_THRESHOLD }} | |
| DRIFT_NOTIFY_CHANNEL: ${{ vars.DRIFT_NOTIFY_CHANNEL }} | |
| run: | | |
| pnpm drift-detect -- --config config/pools.yaml --threshold "${DRIFT_THRESHOLD:-0}" || { | |
| echo "::warning::Runner pool drift detected; see drift-detect output for desired vs actual capacity." | |
| exit 0 | |
| } | |
| mutation-tests: | |
| name: Mutation Tests | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| timeout-minutes: 30 | |
| needs: changes | |
| if: needs.changes.outputs.extended == 'true' || needs.changes.outputs.app == 'true' | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: ./actions/setup-shell-safe-node | |
| with: | |
| node-version: 24.14.1 | |
| - name: Enable pnpm from shell-safe Node | |
| run: | | |
| corepack enable | |
| corepack prepare pnpm@10.32.1 --activate | |
| pnpm --version | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Run mutation tests | |
| run: pnpm mutation-test | |
| - name: Upload mutation report | |
| if: always() | |
| uses: actions/upload-artifact@v7 | |
| with: | |
| name: mutation-report | |
| path: reports/mutation/** | |
| if-no-files-found: ignore | |
| validate-secrets: | |
| name: Validate Secrets | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| timeout-minutes: 10 | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - name: Scan repository for secret patterns | |
| run: bash scripts/check-detect-secrets.sh --all-files | |
| extended-validation-gate: | |
| name: Extended Validation Gate | |
| runs-on: ['self-hosted', 'synology', 'shell-only', 'public'] | |
| if: always() | |
| needs: | |
| - changes | |
| - fast-checks | |
| - extended-checks | |
| - drift-detect | |
| - mutation-tests | |
| - validate-secrets | |
| steps: | |
| - name: Check extended validation jobs | |
| env: | |
| RESULTS: >- | |
| changes=${{ needs.changes.result }} | |
| fast-checks=${{ needs.fast-checks.result }} | |
| extended-checks=${{ needs.extended-checks.result }} | |
| drift-detect=${{ needs.drift-detect.result }} | |
| mutation-tests=${{ needs.mutation-tests.result }} | |
| validate-secrets=${{ needs.validate-secrets.result }} | |
| run: | | |
| failed=0 | |
| for entry in $RESULTS; do | |
| job="${entry%%=*}" | |
| status="${entry##*=}" | |
| if [[ "$status" == "success" || "$status" == "skipped" ]]; then | |
| echo "OK $job => $status" | |
| else | |
| echo "FAIL $job => $status" | |
| failed=1 | |
| fi | |
| done | |
| exit "$failed" |