Skip to content

CI

CI #991

Workflow file for this run

name: CI
on:
workflow_dispatch:
schedule:
- cron: "0 * * * *"
permissions:
contents: read
actions: read
jobs:
preflight:
runs-on: ubuntu-latest
outputs:
run_ci: ${{ steps.gate.outputs.run_ci }}
steps:
- name: Decide whether to run full CI
id: gate
uses: actions/github-script@v7
with:
script: |
const eventName = context.eventName;
if (eventName === "workflow_dispatch") {
core.notice("Manual dispatch: running full CI.");
core.setOutput("run_ci", "true");
return;
}
const owner = context.repo.owner;
const repo = context.repo.repo;
const workflowId = ".github/workflows/ci.yml";
const now = Date.now();
const oneDayMs = 24 * 60 * 60 * 1000;
const currentRunId = Number(process.env.GITHUB_RUN_ID);
const { data } = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: workflowId,
event: "schedule",
per_page: 100,
status: "completed",
});
const recent = data.workflow_runs.find((run) => {
if (run.id === currentRunId) {
return false;
}
if (run.conclusion !== "success") {
return false;
}
const started = Date.parse(run.run_started_at || run.created_at);
return Number.isFinite(started) && (now - started) < oneDayMs;
});
if (recent) {
core.notice(`Skipping full CI: successful scheduled run already happened at ${recent.run_started_at || recent.created_at}.`);
core.setOutput("run_ci", "false");
} else {
core.notice("No successful scheduled run in last 24h: running full CI.");
core.setOutput("run_ci", "true");
}
skip-due-to-daily-limit:
needs: preflight
if: needs.preflight.outputs.run_ci != 'true'
runs-on: ubuntu-latest
steps:
- name: Skip heavy jobs
run: echo "CI already ran successfully in the last 24h. Exiting early."
lint-type:
needs: preflight
if: needs.preflight.outputs.run_ci == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install uv
- name: Install dependencies
run: |
python bootstrap.py --with-test --python-version "3.12"
- name: Ruff
run: .venv/bin/python -m ruff check implementation/python/voxlogica tests
- name: MyPy
run: .venv/bin/python -m mypy implementation/python/voxlogica --ignore-missing-imports
- name: Docs link check
run: |
.venv/bin/python - <<'PY'
from pathlib import Path
import re
import sys
pattern = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
failures = []
for doc in Path("doc/dev").glob("*.md"):
text = doc.read_text(encoding="utf-8")
for match in pattern.finditer(text):
target = match.group(1).strip()
if target.startswith(("http://", "https://", "#", "mailto:")):
continue
resolved = (doc.parent / target).resolve()
if not resolved.exists():
failures.append(f"{doc}: missing link target '{target}'")
if failures:
for item in failures:
print(item)
sys.exit(1)
print("Doc links OK")
PY
test-unit-contract:
needs: preflight
if: needs.preflight.outputs.run_ci == 'true'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
run: python -m pip install uv
- name: Install dependencies
run: |
python bootstrap.py --with-test --python-version "${{ matrix.python-version }}"
- name: Run unit + contract
run: .venv/bin/python -m pytest -m "unit or contract"
test-integration:
needs: preflight
if: needs.preflight.outputs.run_ci == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install uv
- name: Install dependencies
run: |
python bootstrap.py --with-test --python-version "3.12"
- name: Fetch VoxLogicA-1 binary (best-effort)
run: .venv/bin/python tests/fetch_vox1_binary.py --quiet || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run integration + regression
run: .venv/bin/python -m pytest -m "integration or regression"
test-compat-cli:
needs: preflight
if: needs.preflight.outputs.run_ci == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install uv
- name: Install dependencies
run: |
python bootstrap.py --with-test --python-version "3.12"
- name: CLI smoke
run: |
.venv/bin/python -m voxlogica.main version
.venv/bin/python -m voxlogica.main run tests/closure_basic_test.imgql --no-execute
env:
PYTHONPATH: implementation/python
coverage:
needs: preflight
if: needs.preflight.outputs.run_ci == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install uv
- name: Install dependencies
run: |
python bootstrap.py --with-test --python-version "3.12"
- name: Fetch VoxLogicA-1 binary (best-effort)
run: .venv/bin/python tests/fetch_vox1_binary.py --quiet || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run full coverage suite
run: .venv/bin/python -m pytest -m "unit or contract or integration or regression" --cov=implementation/python/voxlogica --cov-report=xml --cov-report=term-missing --cov-fail-under=60
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage.xml
perf-smoke:
needs: preflight
if: github.event_name == 'schedule' && needs.preflight.outputs.run_ci == 'true'
continue-on-error: true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
run: python -m pip install uv
- name: Install dependencies
run: |
python bootstrap.py --with-test --python-version "3.12"
- name: Fetch VoxLogicA-1 binary (best-effort)
run: .venv/bin/python tests/fetch_vox1_binary.py --quiet || true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Run perf smoke
run: .venv/bin/python -m pytest -m perf --maxfail=1