Supported OS / Python Compatibility Matrix

Source of truth. This document explains which operating systems and Python versions the repository supports. config/python-support.json is its machine-readable counterpart and the two are checked together. The policy was added by issue #333.

The rule of thumb is “supported = CI-enforced.” A combination is supported only if the project’s CI actively exercises it on a runner. Combinations that are declared but not CI-tested are called best-effort and are kept deliberately distinct so no one reads best-effort as equivalent to supported.

Runtime promise

pyproject.toml declares:

requires-python = ">=3.10,<3.14"

Python 3.10 is the root-project floor and 3.13 is the ceiling. The upper bound is intentional: it makes the promise closed rather than open-ended, so a future 3.14 cannot silently ship as “supported” without the CI matrix being extended first. A consequence of the <3.14 bound is that uv sync on a 3.14 interpreter will reject the project unless you pass --no-install-project (the pattern CI already uses), so local development on a newer interpreter stays possible while the supported runtime remains bounded.

Root and standalone boundaries

config/python-support.json is the machine-readable boundary. The shared root project and its uv.lock support Python 3.10 through 3.13. Packaged skills remain source-compatible with Python 3.9 when their own requirements.txt permits it. Python 3.9 clean-room jobs must not sync the root lock or install the root project; they validate only standalone artifacts. Ruff therefore retains a py39 syntax target across skills/ and shared standalone scripts.

Supported (CI-enforced)

Actively tested in CI:

OS Python Where
Linux (ubuntu-latest) 3.10 test / coverage / workflow-replay jobs
Linux (ubuntu-latest) 3.9 standalone market-calendar-compat clean room
Linux (ubuntu-latest) 3.11 existing lint / metadata / security / supply-chain jobs
Linux (ubuntu-latest) 3.13 compat-smoke (PR)
Windows (windows-latest) 3.13 compat-smoke (PR)
Windows (windows-latest) 3.10 compat-nightly (daily)
macOS (macos-latest) 3.13 compat-smoke (PR)
macOS (macos-latest) 3.10 compat-nightly (daily)

The macOS runner uses the macos-latest label. actions/setup-python provisions the requested Python version independently of the base runner image, so the 3.10 leg is available on that label.

Best-effort (NOT CI-enforced)

Any combination whose Python version satisfies >=3.10,<3.14 on ubuntu-latest / windows-latest / macos-latest, except the CI-enforced rows above.

Defined as the set-complement of the supported rows, so the two tiers never overlap. Nothing in this tier is claimed to “just work” — it is simply not ruled out at the package level.

Policy

Every job in every workflow under .github/workflows/ must satisfy the following (validated statically by scripts/check_compat_matrix.py):

  1. Its OS is in {ubuntu-latest, windows-latest, macos-latest}.
  2. Root-project jobs use Python in [3.10, 3.14). Explicit standalone clean-room jobs may use the separately declared 3.9 floor.

The drift guard globs the whole workflow directory rather than a hard-coded allowlist, so a brand-new workflow that runs a job on an unsupported OS / out-of-range Python is caught and not silently skipped. dependency-review is a third-party-action job with no setup-python step, so it is OS-checked only; single-runner jobs without a matrix axis are only OS-checked because no Python version can be pinned. Compatibility-defining jobs (compat-smoke, compat-nightly) are also checked against the documented axis mapping below.

Job → (os, python) mapping

The compatibility-defining jobs are declared here and must match the actual workflow YAML:

compat_matrix:
  python: ">=3.10,<3.14"
  supported_os: [ubuntu-latest, windows-latest, macos-latest]
  jobs:
    compat-smoke:
      os: [ubuntu-latest, windows-latest, macos-latest]
      python: ["3.13"]
    compat-nightly:
      os: [windows-latest, macos-latest]
      python: ["3.10"]

scripts/check_compat_matrix.py parses this block and compares it to the axis sets it infers from ci.yml / compat-nightly.yml. Workflow environments are not (and cannot be) exercised by the static checker.

Enforcement scope

The issue title asks to define and enforce the matrix. Enforcement here is static and has two parts:

  1. A drift guard (scripts/check_compat_matrix.py, run in the metadata CI job and as a pre-commit hook) that fails a change if the documented matrix diverges from the workflow YAML, or if any workflow job leaves the supported OS set / Python range.
  2. Declaring only supported combinations in the workflow itself.

A runtime rejection of “this unsupported combination reached a runner” is out of scope for a static checker: a static tool cannot observe the live runner. This is a deliberate, documented reduction so the gap between “enforce” and the current implementation is explicit.

Why this matrix

Two past failure classes motivated bounding the promise and testing cross-platform:

  • #64 — a Windows default-encoding Markdown failure that would not have surfaced if the suite had run on a Windows runner.
  • #311 — a standalone dependency gap discovered by running the risk/state/navigator skills outside the main project venv. The compat-smoke / compat-nightly jobs therefore run an extra isolated packaged-dependency import step: each core skill’s declared requirements.txt is installed into a throwaway environment (uv run --isolated --no-project --with <reqs>) and the skill’s top-level imports are exercised there, per OS and per Python version. This catches a missing dependency declaration that the shared dev environment would otherwise mask via packages installed transitively.

The cross-platform jobs (compat-smoke, compat-nightly) exercise the compatibility-sensitive suites — position-sizer, futures-position-sizer, trader-memory-core, drawdown-circuit-breaker, trading-skills-navigator — plus the UTF-8 / path / line-ending / temp-dir / subprocess-quoting regressions in scripts/tests/test_compat_regressions.py, so these classes no longer ship silently.

Verification record

Local verification on a macOS (aarch64) host, using uv sync --locked:

  • Python 3.10: resolves the locked environment successfully after the security-exception retirement.
  • Python 3.13: resolves the locked environment successfully (scipy==1.17.1, statsmodels==0.14.6).
  • Result: the >=3.10,<3.14 range is viable for --extra dev --extra ci on this machine.

Windows and macOS runner outcomes are validated by the PR’s GitHub Actions; they cannot be reproduced locally from a macOS host.