Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,47 @@ env:
TORCH_INDEX_URL: https://download.pytorch.org/whl/cpu

jobs:
changes:
# Classify a pull request so a leaf-module edit runs a fast, deterministic
# check set instead of the full matrix (hq#14). Runs on a GitHub-hosted
# runner so it is never itself queued behind the self-hosted pool it exists
# to shorten; a `push` to main always takes the full path.
runs-on: ubuntu-latest
outputs:
leaf: ${{ steps.classify.outputs.leaf }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- id: classify
name: Classify the changed paths
env:
EVENT: ${{ github.event_name }}
BASE: ${{ github.base_ref }}
run: |
set -eu
leaf=false
if [ "$EVENT" = "pull_request" ] && [ -n "${BASE:-}" ]; then
git fetch --no-tags origin "$BASE"
changed="$(git diff --name-only "origin/$BASE...HEAD")"
echo "changed files:"
echo "$changed"
if [ -n "$changed" ]; then
leaf=true
while IFS= read -r path; do
case "$path" in
spikeforge/observability/*|tests/test_observability_*.py) ;;
*) leaf=false; break ;;
esac
done <<< "$changed"
fi
fi
echo "leaf=$leaf" >> "$GITHUB_OUTPUT"
lint:
# Static checks are interpreter-version agnostic; run them once.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -44,6 +83,8 @@ jobs:

test:
# The full suite across every supported interpreter (python_requires).
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
strategy:
fail-fast: false
Expand Down Expand Up @@ -80,6 +121,8 @@ jobs:
# SDK-backed cell must report `available: false` with a named reason — that
# is honest reporting, not a build failure. The job asserts the command
# exits 0 *and* that the JSON carries one cell per registered target.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -131,6 +174,8 @@ jobs:
# The `hub` core extra is gone: the hub now ships as the `spikeforge-hub`
# distribution (`spikeforge_hub` import root) and this job proves it installs from
# source and its offline catalog/CLI work.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -167,6 +212,8 @@ jobs:
# install-only here (the suite asserts the SDK-gated backends are
# *absent*), and `docs` builds the site. `hub` is gone too: it is the
# `spikeforge-hub` distribution, exercised by the `hub` job above.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
strategy:
fail-fast: false
Expand Down Expand Up @@ -229,6 +276,8 @@ jobs:
# *core* degrades without the server stack, not that the server runs while
# its own base deps are blocked. The 18 server-dependent test files are
# enumerated with `--ignore` below; everything else runs under the blocker.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
env:
PYTHONPATH: ${{ github.workspace }}/scripts/blocked_deps
Expand Down Expand Up @@ -297,6 +346,8 @@ jobs:
# A broken documentation link must fail the pull request, not just a local
# `build_docs.sh --check`. This gate covers the MkDocs tree in `docs/`;
# the `published-surfaces` job below covers what readers actually land on.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand All @@ -323,6 +374,8 @@ jobs:
#
# Deliberately torch-free: readme_renderer plus the stdlib is the whole
# dependency set, so this stays one of the fastest jobs in the matrix.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand All @@ -347,6 +400,8 @@ jobs:
# in capsize-games/spikeforge-dashboard (its own CI runs `npm run build`). Core keeps
# only the protocol-codegen guard so a schema change without regenerated
# types is still caught here.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand All @@ -373,6 +428,8 @@ jobs:
# The static half of the core boundary: no module-level import of a
# forbidden root, and no function-local import outside the lazy shims.
# Pure stdlib, so no dependencies are installed.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand All @@ -388,6 +445,8 @@ jobs:
# The artifact half of the core boundary: a core wheel built with NO extras
# must install, import, and pull in none of the thirteen forbidden
# distributions, and must not contain `server/`.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -450,6 +509,8 @@ jobs:
packaging-guards:
# The three lightweight topology guards: disjoint import roots (PEP 420),
# console-script ownership, and satellite pins == compatibility.json.
needs: changes
if: needs.changes.outputs.leaf != 'true'
runs-on: [self-hosted, linux, x64, spikeforge-ci]
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -479,3 +540,36 @@ jobs:
--targets-wheel packages/spikeforge-targets/dist/spikeforge_targets-*.whl \
--hub-wheel packages/spikeforge-hub/dist/spikeforge_hub-*.whl \
--io-wheel packages/spikeforge-io/dist/spikeforge_io-*.whl

leaf:
# The fast lane. The leaf paths above cannot reach torch, the server stack
# or the satellite distributions, so this covers the touched module without
# the matrix. Deliberately torch-free: the package is imported from source
# via PYTHONPATH, so no ML wheel is installed.
needs: changes
if: needs.changes.outputs.leaf == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install the leaf toolchain (no torch)
run: |
python -m pip install --upgrade pip
pip install pytest pytest-cov ruff mypy "capsize-commons>=0.1.1"

- name: Lint (ruff)
run: ruff check .

- name: Type-check the core package
run: mypy spikeforge/

- name: Leaf tests
run: PYTHONPATH=. pytest tests/test_observability_logging.py

- name: The logging surface must import without the ML stack
run: |
python -c "import sys; import spikeforge.observability.logging_setup as m; assert 'torch' not in sys.modules; print('torch-free OK:', m.LOGGER_NAME)"
Loading