From ba56d75ed25f6efc8ef587292bcaeeb77d8295cb Mon Sep 17 00:00:00 2001 From: Ryan Cheley Date: Wed, 8 Jul 2026 20:06:27 -0700 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20ci:=20move=20Python=203.15=20tests?= =?UTF-8?q?=20off=20the=20per-PR=20critical=20path=20(#736)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Python 3.15 is a prerelease with no prebuilt native-extension wheels, so tox-uv compiles cryptography/pydantic-core from source and the job runs ~3x slower (~137s vs ~45s) — the long pole on every PR. Drop 3.15 from the per-PR test matrix and run it in a dedicated `test-py315` job on pushes to main instead. 3.14 already covers newest-stable behavior on every PR; 3.15 breakage still surfaces on each merge. Reversible in one edit once cp315 wheels ship. No branch-protection change needed: the required `test` check is the summary aggregation job, not an individual matrix leg. Spec Kit artifacts under specs/002-ci-py315-speedup/. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_0166iS8CRLfRiyqCBaYtRDaR --- .github/workflows/ci.yml | 37 ++++- .../checklists/requirements.md | 40 +++++ specs/002-ci-py315-speedup/plan.md | 102 ++++++++++++ specs/002-ci-py315-speedup/quickstart.md | 55 +++++++ specs/002-ci-py315-speedup/research.md | 80 ++++++++++ specs/002-ci-py315-speedup/spec.md | 151 ++++++++++++++++++ specs/002-ci-py315-speedup/tasks.md | 82 ++++++++++ 7 files changed, 546 insertions(+), 1 deletion(-) create mode 100644 specs/002-ci-py315-speedup/checklists/requirements.md create mode 100644 specs/002-ci-py315-speedup/plan.md create mode 100644 specs/002-ci-py315-speedup/quickstart.md create mode 100644 specs/002-ci-py315-speedup/research.md create mode 100644 specs/002-ci-py315-speedup/spec.md create mode 100644 specs/002-ci-py315-speedup/tasks.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b406efa..37f7928 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,10 +23,17 @@ jobs: runs-on: ubuntu-latest strategy: matrix: + # 3.15 is a prerelease with no prebuilt native-extension wheels + # (cryptography, pydantic-core, ...), so tox-uv compiles them from + # source and the job runs ~3x slower (~137s vs ~45s) — the long pole on + # every PR (issue #736). 3.14 already covers newest-stable behavior, so + # 3.15 is exercised by the post-merge `test-py315` job below instead. + # To fold it back once cp315 wheels ship: add "3.15" here and delete the + # `test-py315` job. # 3.15t (free-threaded) is omitted until cryptography ships free-threaded # 3.15 wheels; building from source yields an abi3 wheel that the # free-threaded ABI can't load. Re-add once upstream wheels exist. - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "3.15"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4 @@ -217,3 +224,31 @@ jobs: env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} continue-on-error: true + + # Post-merge early-warning for Python 3.15 (prerelease). Kept off the per-PR + # matrix because its source-built deps make it ~3x slower (issue #736); running + # it on pushes to main still surfaces 3.15-specific breakage on every merge. + # Remove this job (and re-add "3.15" to the test matrix) once cp315 wheels ship. + test-py315: + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v4 + with: + persist-credentials: false + + - name: Install uv + uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3 # v4 + with: + version: "latest" + enable-cache: true + cache-dependency-glob: "uv.lock" + + - name: Set up Python 3.15 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v5 + with: + python-version: "3.15" + allow-prereleases: true + + - name: Run tests with tox-uv + run: uvx --with tox-uv tox -e py315 diff --git a/specs/002-ci-py315-speedup/checklists/requirements.md b/specs/002-ci-py315-speedup/checklists/requirements.md new file mode 100644 index 0000000..d47468f --- /dev/null +++ b/specs/002-ci-py315-speedup/checklists/requirements.md @@ -0,0 +1,40 @@ +# Specification Quality Checklist: Speed up the Python 3.15 CI test job + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-07-08 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- The spec deliberately leaves the *choice of mechanism* (reduced cadence vs. + caching the built env) to the plan phase; both are documented as options in + Assumptions. This is intentional and does not constitute an unresolved + clarification, since either satisfies the functional requirements. +- "Technology-agnostic" is interpreted for a CI/infrastructure feature: CI + jobs, matrices, and required checks are the domain nouns here, not leaked + implementation detail. diff --git a/specs/002-ci-py315-speedup/plan.md b/specs/002-ci-py315-speedup/plan.md new file mode 100644 index 0000000..1551017 --- /dev/null +++ b/specs/002-ci-py315-speedup/plan.md @@ -0,0 +1,102 @@ +# Implementation Plan: Speed up the Python 3.15 CI test job + +**Branch**: `ci-py315-speedup-736` | **Date**: 2026-07-08 | **Spec**: [spec.md](./spec.md) + +**Input**: Feature specification from `specs/002-ci-py315-speedup/spec.md` + +## Summary + +Python 3.15 is a prerelease with no prebuilt native-extension wheels, so its +per-PR `test` job compiles dependencies from source and takes ~137s — ~3x the +stable versions and the long pole on every PR. Remove `3.15` from the per-PR +`test` matrix and instead run a dedicated Python 3.15 job on pushes to `main` +(post squash-merge), preserving early-warning coverage off the PR critical path. +Single-file change to `.github/workflows/ci.yml`; no branch-protection edit is +needed because the required `test` check is the summary aggregation job, not an +individual matrix leg. + +## Technical Context + +**Language/Version**: N/A (CI configuration change; affects GitHub Actions YAML) + +**Primary Dependencies**: GitHub Actions (`actions/setup-python`, +`astral-sh/setup-uv`), `tox` / `tox-uv`, `uv` + +**Storage**: N/A + +**Testing**: The change is validated by CI behavior itself — PR runs (no 3.15 +leg) and push-to-`main` runs (3.15 job present). Existing `pytest`/`tox` suite +is unchanged. + +**Target Platform**: `ubuntu-latest` GitHub-hosted runners + +**Project Type**: Single-project Python CLI (change is confined to CI config) + +**Performance Goals**: Per-PR test gate's slowest job under ~60s (down from +~137s); ~1.5 min removed from the PR critical path. + +**Constraints**: Must keep stable-version coverage (3.10–3.14, 3.14t) on every +PR; must keep the `test` required check reporting; must keep 3.15 exercised on +every merge to `main`; must be reversible in one obvious edit. + +**Scale/Scope**: One file (`.github/workflows/ci.yml`); ~1 line removed from the +matrix + ~1 new job (~20 lines) + comment updates. + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- **I. Code Quality**: No application code changes. `zizmor` reviews GitHub + Actions — the workflow security job (`uvx zizmor .github/workflows/`) must + stay clean for the new job. Pinned action SHAs reused from existing steps. + ✅ PASS. +- **II. Testing Standards (NON-NEGOTIABLE)**: No test code changes; the full + `pytest`/`tox` suite still runs — on 3.10–3.14/3.14t per PR and on 3.15 per + merge to `main`. Total test coverage of the code is unchanged; only the + *cadence* of the 3.15 leg moves. This is a CI-timing change, not a coverage + reduction (spec SC-002). ✅ PASS. +- **III. User Experience Consistency**: No CLI surface change. ✅ N/A. +- **IV. Performance Requirements**: This *improves* CI responsiveness (the + spirit of the principle) without regressing any runtime latency. Performance + impact stated here and to be restated in the PR. ✅ PASS. +- **Workflow**: Change lives on feature branch `ci-py315-speedup-736`, tied to + issue #736; `docs/` reviewed for CI references (see Phase 1). ✅ PASS. + +No violations. Complexity Tracking not required. + +## Project Structure + +### Documentation (this feature) + +```text +specs/002-ci-py315-speedup/ +├── plan.md # This file +├── spec.md # Feature spec (+ Clarifications) +├── research.md # Phase 0 output +├── quickstart.md # Phase 1 output (validation guide) +├── checklists/ +│ └── requirements.md # Spec quality checklist +└── tasks.md # Phase 2 output (/speckit-tasks) +``` + +Not generated (justified): `data-model.md` — no data entities; this is a CI +config change. `contracts/` — no external/API/CLI interface changes. + +### Source Code (repository root) + +```text +.github/workflows/ci.yml # ONLY file changed +``` + +**Structure Decision**: Single-file CI change. Two edits to `ci.yml`: +1. Remove `"3.15"` from the `test` job's `strategy.matrix.python-version`, + updating the adjacent comment to explain 3.15 now runs post-merge and how to + fold it back when cp315 wheels ship. +2. Add a push-only job (`if: github.event_name == 'push'`) named e.g. + `test-py315` that sets up uv + Python 3.15 (`allow-prereleases: true`) and + runs `uvx --with tox-uv tox -e py315`, mirroring the existing + `coverage-baseline` job's step structure. + +## Complexity Tracking + +No constitution violations — section intentionally empty. diff --git a/specs/002-ci-py315-speedup/quickstart.md b/specs/002-ci-py315-speedup/quickstart.md new file mode 100644 index 0000000..55d1efd --- /dev/null +++ b/specs/002-ci-py315-speedup/quickstart.md @@ -0,0 +1,55 @@ +# Quickstart / Validation Guide: Python 3.15 CI cadence change + +**Feature**: 002-ci-py315-speedup + +This change is entirely in `.github/workflows/ci.yml`. Validate it by inspecting +the workflow locally and observing CI behavior on a PR and on `main`. + +## Prerequisites + +- `uv` installed (repo already uses it). +- On the feature branch `ci-py315-speedup-736`. + +## Local validation (before pushing) + +1. **Workflow security lint** (constitution: zizmor must stay clean): + ```sh + uvx zizmor .github/workflows/ + ``` + Expected: no new findings. + +2. **YAML sanity** — confirm the two edits landed: + ```sh + # 3.15 removed from the per-PR test matrix: + grep -n 'python-version: \["3.10"' .github/workflows/ci.yml # line should NOT contain "3.15" + # a push-only 3.15 job exists running tox -e py315: + grep -n 'tox -e py315' .github/workflows/ci.yml + ``` + Expected: matrix line ends at `"3.14t"`; a `tox -e py315` line exists in a + `github.event_name == 'push'` job. + +3. **Tox env exists** (the job invokes `tox -e py315`): + ```sh + uvx --with tox-uv tox -l | grep -x py315 || echo "py315 env resolves from generative config" + ``` + Note: envs are generated from the version list in `tox.ini`/`pyproject.toml`; + `tox -e py315` works as long as 3.15 is a declared env (it already is, since + the per-PR job invoked `py315` before this change). + +## CI validation (after pushing) + +- **On the PR** (SC-001, SC-002): the `test` matrix shows legs for + 3.10, 3.11, 3.12, 3.13, 3.14, 3.14t — **no** `test (3.15)` leg. The slowest + test leg finishes in ~40–60s. The required `test` check reports success. +- **After squash-merge to `main`** (SC-003): a `test-py315` job runs on the push + and executes the suite on Python 3.15. A green run confirms 3.15 coverage is + retained off the PR path. (To prove failure visibility, a deliberate + 3.15-only break would show this job red on `main`.) +- **Required check (SC-004)**: a normal green PR is mergeable; no check is stuck + "expected" waiting on a job that no longer runs. + +## Reversal (when cp315 wheels ship upstream) + +1. Add `"3.15"` back to the `test` job matrix `python-version` list. +2. Delete the `test-py315` push-only job. +3. Update the matrix comment. Re-run `uvx zizmor .github/workflows/`. diff --git a/specs/002-ci-py315-speedup/research.md b/specs/002-ci-py315-speedup/research.md new file mode 100644 index 0000000..efc4796 --- /dev/null +++ b/specs/002-ci-py315-speedup/research.md @@ -0,0 +1,80 @@ +# Phase 0 Research: Speed up the Python 3.15 CI test job + +**Feature**: 002-ci-py315-speedup | **Date**: 2026-07-08 + +## R1. Root cause of the ~137s 3.15 job + +**Decision**: Treat the slowness as dependency source-builds under the 3.15 +prerelease, not test execution. + +**Rationale**: The test suite runs in <2s locally; the ~90s gap between 3.15 +(~137s) and stable versions (~40–55s) is install/build time. Python 3.15 is a +prerelease, so native-extension dependencies (e.g. `cryptography`, +`pydantic-core`) have no prebuilt cp315 wheels on PyPI yet and are compiled from +source under `tox-uv`. Stable versions download prebuilt wheels. The workflow +already carries a note that `cryptography` lacks free-threaded 3.15 wheels, +corroborating the wheel-gap theory. + +**Alternatives considered**: Slow test execution (ruled out — <2s locally); +`setup-uv` cache miss as sole cause (contributing, but source builds dominate; +even a warm cache re-pays compilation on resolver differences for a prerelease). + +## R2. Mechanism to remove 3.15 from the PR critical path + +**Decision**: Drop `3.15` from the per-PR `test` matrix and run a dedicated +Python 3.15 job on pushes to `main` (post squash-merge). (Confirmed in spec +Clarifications, 2026-07-08.) + +**Rationale**: Smallest, most reversible change that fully removes the long +pole. 3.14 (newest stable) already exercises the newest stable Python behavior +on every PR, so per-PR 3.15 gives no extra correctness confidence. Running 3.15 +on every merge to `main` preserves early-warning coverage: any 3.15-specific +break surfaces as a failed run on `main`. + +**Alternatives considered**: +- *Cache the built native-extension env per-PR*: keeps per-PR 3.15 but is + cache-key fragile, still cold-builds on cache miss / upstream churn, and adds + config complexity. Rejected per clarification. +- *Add a scheduled/cron 3.15 run*: more coverage, but post-merge-on-`main` + already runs on every merge; a cron adds config for marginal benefit. + Rejected per clarification. +- *Drop 3.15 entirely*: loses the early-warning signal. Rejected. + +## R3. Branch-protection / required-check safety + +**Decision**: No branch-protection change is required. + +**Rationale**: Verified via `gh api .../branches/main/protection` that the +required status checks are `test`, `lint`, `type-check`, `security`. The `test` +context is the **summary** job `tests-complete` (`name: test`), which aggregates +`needs: [test, lint, type-check, security, documentation]` — it does **not** +name individual matrix legs like `test (3.15)`. Removing `3.15` from the matrix +therefore leaves the `test` required check intact and still reporting on PRs. +This satisfies FR-005 with no GitHub settings edit. (The summary-gate pattern +was designed precisely so matrix changes don't break required checks.) + +## R4. Where the post-merge 3.15 job lives + +**Decision**: Add a new job gated on `if: github.event_name == 'push'` that runs +`uvx --with tox-uv tox -e py315`, mirroring the existing `coverage-baseline` +push-only job's setup steps. + +**Rationale**: `coverage-baseline` already establishes the "runs only on push to +`main`/`develop`, sets up uv + Python, runs tox" pattern. A sibling job reuses +that pattern verbatim with `python-version: "3.15"` + `allow-prereleases: true` +and `tox -e py315`. Keeping it a separate job (not folding 3.15 into +`coverage-baseline`'s matrix) avoids double Codecov uploads and keeps the +concern — "3.15 early warning" — clearly named and independently reversible. + +**Alternatives considered**: Add `python-version` matrix to `coverage-baseline` +(would upload coverage twice and conflate coverage-baseline with 3.15 smoke +testing — rejected for clarity). + +## R5. Reversibility when cp315 wheels ship + +**Decision**: Record, in the workflow comments, that 3.15 lives in a post-merge +job and how to fold it back: add `"3.15"` to the `test` matrix and delete the +post-merge job. + +**Rationale**: FR-006/FR-007 — a future maintainer should be able to undo this +in one obvious edit once prebuilt wheels make 3.15 as fast as stable versions. diff --git a/specs/002-ci-py315-speedup/spec.md b/specs/002-ci-py315-speedup/spec.md new file mode 100644 index 0000000..e937927 --- /dev/null +++ b/specs/002-ci-py315-speedup/spec.md @@ -0,0 +1,151 @@ +# Feature Specification: Speed up the Python 3.15 CI test job + +**Feature Branch**: `ci-py315-speedup-736` + +**Created**: 2026-07-08 + +**Status**: Draft + +**Input**: User description: "CI: Python 3.15 test job is ~3x slower (~137s vs ~45s) than other matrix versions and is the long pole on every PR (issue #736). The 3.15 prerelease likely builds native-extension deps from source because no prebuilt wheels exist yet. Goal: reduce the wall-clock cost 3.15 adds to every PR's critical path without losing meaningful test coverage." + +## Clarifications + +### Session 2026-07-08 + +- Q: How should Python 3.15 be handled to remove it from the per-PR critical + path? → A: Post-merge on `main` — drop 3.15 from the per-PR matrix and run it + as a job on pushes to `main` (after squash-merge). Smallest reversible change; + no scheduled/cron run and no per-PR build caching. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Faster PR feedback (Priority: P1) + +A contributor opens a pull request. They wait for the CI test matrix to go +green before merging. Today the Python 3.15 job takes ~137s while every other +version finishes in ~40–55s, so 3.15 is the long pole and dictates how long the +contributor waits for the test gate. + +**Why this priority**: This is the entire point of the issue — the 3.15 job +adds ~1.5 minutes to the critical path of every PR with no added coverage +benefit (3.14 already exercises the newest stable Python behavior). + +**Independent Test**: Open a PR and observe the CI run. The test gate's +wall-clock time (slowest matrix job) is materially lower than the current +~137s, and coverage for supported stable Python versions is unchanged. + +**Acceptance Scenarios**: + +1. **Given** a PR is opened, **When** the CI test gate runs, **Then** the + slowest test job completes in a time comparable to the other versions + (not ~3x slower). +2. **Given** the CI test gate passes, **When** a maintainer reviews coverage, + **Then** coverage across supported stable Python versions (3.10–3.14) is + unchanged from before this change. + +--- + +### User Story 2 - Preserve early warning for Python 3.15 (Priority: P2) + +A maintainer wants to keep learning early whether the project works on the +in-development Python 3.15, so upgrades aren't a surprise when 3.15 ships — but +without paying that cost on every single PR. + +**Why this priority**: Dropping 3.15 entirely would remove a useful early +signal. The value is in keeping the signal while moving its cost off the +per-PR hot path. + +**Independent Test**: Confirm that Python 3.15 is still exercised by CI on some +cadence (e.g. on `main` and/or on a schedule), and that a 3.15-specific +failure is still surfaced to maintainers. + +**Acceptance Scenarios**: + +1. **Given** the project's code breaks specifically on Python 3.15, **When** + the reduced-cadence 3.15 job runs, **Then** the failure is visible to + maintainers (a failed run they can see). +2. **Given** a normal PR with no 3.15-specific breakage, **When** CI runs, + **Then** the contributor is not blocked waiting on the slow 3.15 build. + +--- + +### Edge Cases + +- **What happens when upstream ships prebuilt 3.15 wheels?** The change must + not make it harder to fold 3.15 back into the fast per-PR matrix later; the + reduced cadence (if chosen) is reversible with a small edit. +- **What happens when the 3.15 job fails on a scheduled/main run?** Maintainers + must be able to see the failure; it must not silently pass or be hidden. +- **What happens when 3.15 is a genuine required check today?** Branch + protection / required-check configuration must not be left pointing at a job + name that no longer runs on PRs (which would block every PR indefinitely). +- **What happens to the free-threaded 3.15t note already in the workflow?** The + existing comment explaining why 3.15t is omitted must remain accurate. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The CI configuration MUST reduce the wall-clock time that Python + 3.15 testing adds to the critical path of an individual pull request. +- **FR-002**: The CI configuration MUST continue to test the project against + all currently-supported stable Python versions (3.10 through 3.14, including + the 3.14 free-threaded variant) on every pull request, with unchanged + coverage reporting. +- **FR-003**: The project MUST continue to exercise Python 3.15 in CI on pushes + to `main` (post squash-merge) so that 3.15-specific breakage is still + detected, even though 3.15 no longer runs on individual pull requests. +- **FR-004**: A Python 3.15 failure detected by the reduced-cadence job MUST be + visible to maintainers as a failed CI run. +- **FR-005**: The change MUST NOT leave the branch-protection required-check + set referencing a job that no longer runs on pull requests, so that PRs are + not blocked waiting on a check that can never report. +- **FR-006**: The change MUST be reversible — folding Python 3.15 back into the + per-PR matrix once upstream prebuilt wheels exist MUST require only a small, + obvious configuration edit. +- **FR-007**: The reasoning for the chosen approach (why 3.15 is treated + differently, and how to undo it later) MUST be recorded in the workflow so a + future maintainer understands it without archaeology. + +### Key Entities + +- **CI test matrix**: The set of Python versions the test suite runs against on + each pull request. Currently 3.10, 3.11, 3.12, 3.13, 3.14, 3.14t, 3.15. +- **Reduced-cadence 3.15 run**: A CI execution of the test suite on Python 3.15 + that runs less often than per-PR (e.g. post-merge on `main` and/or + scheduled), providing early-warning coverage off the PR critical path. +- **Required-check set**: The branch-protection configuration naming which CI + jobs must pass before a PR can merge. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: The slowest job in the per-PR test gate completes in + approximately the time of the other matrix versions (target: under ~60s, + versus ~137s today), removing the ~1.5-minute 3.15 penalty from the PR + critical path. +- **SC-002**: Stable-version coverage is unchanged: the same Python versions + (3.10–3.14 and 3.14t) run on every PR and report coverage as before. +- **SC-003**: Python 3.15 is still executed by CI on every push to `main`, and + a deliberately introduced 3.15-only failure produces a visible failed run. +- **SC-004**: No pull request is blocked by a required check that no longer + runs; the merge gate for a normal green PR reports success without the 3.15 + job. + +## Assumptions + +- Python 3.15 is currently a prerelease with incomplete prebuilt-wheel + coverage; the slowness is dominated by source builds of native-extension + dependencies, not by test execution (tests run in <2s locally). The plan + phase will confirm the dominant cost before choosing a fix. +- 3.14 (the newest stable) provides sufficient "newest Python" coverage for + per-PR gating, so per-PR 3.15 testing is not required for correctness + confidence today. +- Moving 3.15 to post-merge-on-`main` is the accepted trade-off (see + Clarifications): early-warning coverage on every merge, off the PR critical + path. Scheduled/cron runs and per-PR build caching were considered and not + chosen. +- The repository's branch-protection required checks are configured against the + summary `test` gate job; changes must keep that gate reporting correctly for + PRs. diff --git a/specs/002-ci-py315-speedup/tasks.md b/specs/002-ci-py315-speedup/tasks.md new file mode 100644 index 0000000..461b28b --- /dev/null +++ b/specs/002-ci-py315-speedup/tasks.md @@ -0,0 +1,82 @@ +# Tasks: Speed up the Python 3.15 CI test job + +**Feature**: 002-ci-py315-speedup | **Branch**: `ci-py315-speedup-736` | **Issue**: #736 + +**Input**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md), [quickstart.md](./quickstart.md) + +**Scope note**: This feature changes exactly one file, `.github/workflows/ci.yml`. +There is no application/test code to write; "tests" here are CI-behavior +observations, not `pytest` cases. Tasks are kept minimal per the plan. + +## Phase 1: Setup + +- [X] T001 Confirm on branch `ci-py315-speedup-736` and that `.github/workflows/ci.yml` is the only target; re-read the `test` job matrix (line ~29) and the push-only `coverage-baseline` job (line ~189) in `.github/workflows/ci.yml` to anchor the two edits. + +## Phase 2: Foundational + +_None._ No blocking prerequisites — the two user stories touch independent parts +of the same file and can be applied in one pass. + +--- + +## Phase 3: User Story 1 — Faster PR feedback (Priority: P1) 🎯 MVP + +**Goal**: Remove the ~90s Python 3.15 long pole from every PR by dropping it from +the per-PR `test` matrix, while keeping stable-version coverage unchanged. + +**Independent test**: Open a PR; the `test` matrix shows legs for 3.10–3.14 and +3.14t but **no** `test (3.15)`; slowest test leg ~40–60s; required `test` check +reports success. + +- [X] T002 [US1] In `.github/workflows/ci.yml`, remove `"3.15"` from the `test` job's `strategy.matrix.python-version` list (line ~29) so it ends at `"3.14t"`. +- [X] T003 [US1] In `.github/workflows/ci.yml`, update the comment above the matrix (lines ~26–28) to explain that 3.15 was moved to a post-merge job because it is a prerelease with no prebuilt native-extension wheels (source builds cost ~90s), and note the one-line reversal (add `"3.15"` back + delete the post-merge job) once cp315 wheels ship. Keep the existing 3.15t note accurate. + +**Checkpoint**: PR runs no longer include a 3.15 leg (satisfies SC-001, SC-002, +SC-004; FR-001, FR-002, FR-005, FR-006, FR-007). + +--- + +## Phase 4: User Story 2 — Preserve early warning for Python 3.15 (Priority: P2) + +**Goal**: Keep exercising Python 3.15 on every push to `main` so 3.15-specific +breakage is still detected off the PR critical path. + +**Independent test**: After a merge to `main`, a `test-py315` job runs on the +push and executes the suite on Python 3.15; a 3.15-only break shows the job red. + +- [X] T004 [US2] In `.github/workflows/ci.yml`, add a new job `test-py315` gated on `if: github.event_name == 'push'`, mirroring the `coverage-baseline` job's steps (checkout with `persist-credentials: false`, `astral-sh/setup-uv` with cache, `actions/setup-python` with `python-version: "3.15"` and `allow-prereleases: true`), running `uvx --with tox-uv tox -e py315`. Reuse the exact pinned action SHAs already in the file. Add a short comment stating this is the post-merge 3.15 early-warning run (see US1 comment for reversal). + +**Checkpoint**: 3.15 runs on every push to `main` (satisfies SC-003; FR-003, +FR-004). + +--- + +## Phase 5: Polish & Validation + +- [X] T005 Run `uvx zizmor .github/workflows/` and confirm no new findings for the added job (constitution: GitHub Actions security gate must stay clean). +- [X] T006 Run the local checks from [quickstart.md](./quickstart.md) (grep assertions that 3.15 is gone from the matrix and that a `tox -e py315` push job exists); confirm YAML is well-formed. +- [X] T007 Review `docs/` and `README.md` for any statement about the CI Python-version matrix that names 3.15 as a per-PR version; update if present (per constitution: docs updated with the change). If none exists, note "no doc reference" in the PR. + +--- + +## Dependencies + +- **T001** (setup) before all. +- **US1 (T002–T003)** and **US2 (T004)** are independent edits to the same file; + do US1 first (MVP) then US2, or both together. +- **Phase 5 (T005–T007)** after US1 and US2 are applied. + +## Parallel opportunities + +Minimal — all edits are in one file, so they are applied sequentially to avoid +conflicting edits. T005, T006, T007 (validation/docs) are independent of each +other and can be done in any order once the edits land. [P] not marked because +the edit tasks share `ci.yml`. + +## Implementation strategy + +- **MVP = User Story 1** (T002–T003): removing 3.15 from the per-PR matrix alone + delivers the entire performance win the issue asks for. If US2 were deferred, + the only loss is 3.15 early-warning coverage. +- **Full delivery**: US1 + US2 + Phase 5 — the intended scope, matching the + clarified decision (post-merge on `main`, no cron, no per-PR caching).