diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89d5ec98b..5e952e040 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,7 @@ jobs: workflow-security: name: Workflow security runs-on: ubuntu-latest + timeout-minutes: 10 permissions: contents: read security-events: write @@ -55,6 +56,7 @@ jobs: test: name: Test Python ${{ matrix.python-version }} runs-on: ubuntu-latest + timeout-minutes: 30 strategy: matrix: python-version: ["3.10", "3.11", "3.12", "3.13"] @@ -66,6 +68,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: pyproject.toml - name: Install dependencies run: | @@ -73,28 +77,33 @@ jobs: pip install -e ".[dev]" - name: Run linter - run: | - ruff check src/ + if: matrix.python-version == '3.12' + run: ruff check src/ - name: Run type checker - run: | - mypy src/adcp/ + if: matrix.python-version == '3.12' + run: mypy src/adcp/ - name: Run adopter type-check suite - run: | - mypy --strict tests/type_checks/ + if: matrix.python-version == '3.12' + run: mypy --strict tests/type_checks/ - name: Enforce adopter type-check fixture contract - run: | - python scripts/check_type_ignore_contract.py + if: matrix.python-version == '3.12' + run: python scripts/check_type_ignore_contract.py - name: Run tests - run: | - pytest tests/ -v --cov=src/adcp --cov-report=term-missing + if: matrix.python-version != '3.12' + run: pytest tests/ -v + + - name: Run tests with coverage + if: matrix.python-version == '3.12' + run: pytest tests/ -v --cov=src/adcp --cov-report=term-missing pg-conformance: name: Postgres conformance tests (Postgres 16) runs-on: ubuntu-latest + timeout-minutes: 15 services: postgres: # CI-local ephemeral database. POSTGRES_HOST_AUTH_METHOD=trust @@ -120,6 +129,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml - name: Install dependencies (with [pg] extra) run: | @@ -141,6 +152,7 @@ jobs: conventional-commits: name: Validate conventional commit format runs-on: ubuntu-latest + timeout-minutes: 10 if: github.event_name == 'pull_request' steps: @@ -197,6 +209,7 @@ jobs: downstream-imports: name: Downstream import smoke (representative consumer symbols) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v6 @@ -205,6 +218,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.11" + cache: pip + cache-dependency-path: pyproject.toml - name: Build sdist and install its wheel run: | @@ -321,6 +336,7 @@ jobs: schema-check: name: Validate schemas are up-to-date runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v6 @@ -329,6 +345,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.11" + cache: pip + cache-dependency-path: pyproject.toml - name: Install dependencies run: | @@ -426,6 +444,7 @@ jobs: storyboard: name: AdCP storyboard runner — examples/seller_agent.py (@adcp/sdk ${{ matrix.adcp-sdk-tag }}) runs-on: ubuntu-latest + timeout-minutes: 20 # Blocking gate: examples/seller_agent.py is the Python-owned # reference target for bidirectional storyboard interop. The matrix # runs two legs: the sticky ``adcp-3.0`` tag is a fixed, reproducible @@ -443,6 +462,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml - name: Set up Node 22 uses: actions/setup-node@v6 @@ -480,8 +501,11 @@ jobs: storyboard-required-gate: name: AdCP storyboard runner — examples/seller_agent.py runs-on: ubuntu-latest + timeout-minutes: 5 needs: storyboard - if: always() + # Run after a failed matrix so the aggregate required check fails, but + # do not keep superseded workflow runs alive after concurrency cancels them. + if: ${{ !cancelled() }} steps: - name: Require all reference seller storyboard matrix runs run: | @@ -494,6 +518,7 @@ jobs: v3-reference-seller-tests: name: v3 reference seller — pytest (respx-mocked upstream) runs-on: ubuntu-latest + timeout-minutes: 15 steps: - uses: actions/checkout@v6 @@ -502,6 +527,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml - name: Install dependencies run: | @@ -523,6 +550,7 @@ jobs: storyboard-v3-reference-seller: name: AdCP storyboard runner — v3 reference seller (translator) runs-on: ubuntu-latest + timeout-minutes: 20 env: # Intentionally non-secret: gates loopback-only debug counters # inside this CI job. @@ -551,6 +579,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml - name: Set up Node 22 uses: actions/setup-node@v6 @@ -819,6 +849,7 @@ jobs: storyboard-multi-platform-seller: name: AdCP storyboard runner — examples/multi_platform_seller (PlatformRouter) runs-on: ubuntu-latest + timeout-minutes: 20 # Multi-tenant proof: one process, two tenants, one router. Each # tenant's storyboard runs against its own subdomain # (tenant-a.localhost / tenant-b.localhost). Blocking gate — both @@ -834,6 +865,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml - name: Set up Node 22 uses: actions/setup-node@v6 @@ -928,6 +961,7 @@ jobs: storyboard-sales-proposal-mode: name: AdCP storyboard runner — sales-proposal-mode (proposal_finalize) runs-on: ubuntu-latest + timeout-minutes: 20 # v1.5 ProposalManager finalize lifecycle proof. The mock seller # declares ``finalize=True`` + wires an ``InMemoryProposalStore``; # the framework's dispatch wiring intercepts ``refine[i].action='finalize'`` @@ -942,6 +976,8 @@ jobs: uses: actions/setup-python@v6 with: python-version: "3.12" + cache: pip + cache-dependency-path: pyproject.toml - name: Set up Node 22 uses: actions/setup-node@v6