Skip to content

minds: design system — tokens + type / radius / spacing / elevation scales #13709

minds: design system — tokens + type / radius / spacing / elevation scales

minds: design system — tokens + type / radius / spacing / elevation scales #13709

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: ['**']
jobs:
# Changelog gate. Runs on the orchestrator (not in an offload
# sandbox) because it diffs the PR branch against its real base branch, and
# the sandbox has no base ref -- doing the diff there passes vacuously. Pure
# stdlib, so no `uv sync`; just enough git history to compute the diff.
check-changelog:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
# Shallow checkout, then deepen ONLY HEAD's ancestry (same rationale as
# the offload jobs below: a bare `--unshallow` pulls every branch). The
# merge-base with the base branch is an ancestor of HEAD, so deepening
# HEAD plus fetching the base tip is enough for a three-dot diff.
- uses: actions/checkout@v6
- name: Fetch base branch and deepen HEAD for the changelog diff
run: |
if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
git fetch --no-tags --unshallow origin "$(git rev-parse HEAD)"
fi
git fetch --no-tags origin \
"+refs/heads/${GITHUB_BASE_REF}:refs/remotes/origin/${GITHUB_BASE_REF}"
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Check changelog entries
run: python -m scripts.check_changelog_entries
# Unit + integration tests via offload on Modal
test-offload:
runs-on: ubuntu-latest
# `checks: write` is required by the report-flaky-aware-tests composite
# action: both its `mikepenz/action-junit-report@v6` invocations and its
# `gh api POST /repos/.../check-runs` call need it. `contents: write` is
# required so offload can `git push` its image-cache git notes back to
# `refs/notes/offload-images`; without it, every run is a cache miss and
# rebuilds the base image from scratch (~150 s wasted per run).
# Declaring permissions explicitly avoids depending on the repo-level
# default, which may be restricted (contents: read only).
permissions:
contents: write
checks: write
env:
MODAL_TOKEN_ID: ${{ vars.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
steps:
# Default shallow single-ref checkout, then deepen only THIS ref's
# history in the next step. offload needs the full ancestry of HEAD
# (to find its checkpoint commit and thin-diff against it), but not
# every other branch -- which `fetch-depth: 0` would also fetch, and on
# a repo with many branches that full-history-of-all-branches fetch can
# take minutes. The image-cache git notes are fetched separately below.
- uses: actions/checkout@v6
- name: Deepen current-branch history for offload
# Deepen ONLY the checked-out commit's ancestry -- all offload needs to
# find and thin-diff its checkpoint. Naming the SHA matters: a bare
# `git fetch --unshallow origin` uses the broad refspec and pulls every
# branch's full history (including a heavy orphan branch), which is most
# of the fetch cost. GitHub allows fetching reachable SHAs, and the
# image-cache git notes are fetched separately below.
run: |
if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
git fetch --no-tags --unshallow origin "$(git rev-parse HEAD)"
fi
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
- name: Install Python dependencies
run: uv sync --all-packages
- name: Install just
uses: extractions/setup-just@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache offload binary
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/offload
key: cargo-offload-0.9.7-${{ runner.os }}
- name: Install offload
run: |
if ! command -v offload &> /dev/null || ! offload --version | grep -q '0.9.7'; then
cargo install offload@0.9.7 --force
fi
- name: Restore offload test durations from previous run
uses: actions/cache/restore@v5
with:
path: test-results/junit.xml
key: offload-durations-${{ github.ref }}-${{ github.run_id }}
restore-keys: offload-durations-${{ github.ref }}-
- name: Fetch offload image-cache git notes
run: git fetch origin 'refs/notes/*:refs/notes/*' || true
- name: Configure git identity for image-cache note writes
run: |
git config user.email "dev@imbue.com"
git config user.name "imbue-dev"
- name: Run tests via offload
id: tests
run: just test-offload
- name: Save offload test durations for next run
if: always()
uses: actions/cache/save@v5
with:
path: test-results/junit.xml
key: offload-durations-${{ github.ref }}-${{ github.run_id }}
- name: Combine coverage and check per-package thresholds
if: always()
run: |
set -u
# Diagnostic: correlate per-sandbox coverage delivery vs per-sandbox junit.
# Drift in per-package coverage across runs (with zero test failures) most
# likely means some sandbox dropped its .coverage file while still
# reporting tests via junit. If these counts differ, that's the cause.
# Extract unique sandbox IDs from both the per-sandbox junit files
# (sb-<id>.xml, exactly one per sandbox) and the per-batch .coverage
# files (test-results/<sandbox-id>/<batch>/.coverage, one per batch
# within a sandbox, typically multiple per sandbox). Compare on
# UNIQUE-SANDBOX counts so the MISMATCH check is apples-to-apples.
# Raw file counts are kept as additional info but are not directly
# comparable (coverage files are per-batch, junit files are
# per-sandbox).
cov_file_count=$(find test-results/ -name ".coverage" -type f 2>/dev/null | wc -l | tr -d ' ')
junit_file_count=$(find test-results/junit-parts/ -name "sb-*.xml" 2>/dev/null | wc -l | tr -d ' ')
junit_ids=$(find test-results/junit-parts/ -name "sb-*.xml" 2>/dev/null \
| sed -E 's|.*/(sb-[^/]+)\.xml$|\1|' | sort -u)
cov_ids=$(find test-results/ -mindepth 2 -name ".coverage" -type f 2>/dev/null \
| sed -E 's|^test-results/([^/]+)/.*|\1|' | sort -u)
junit_sandbox_count=$(printf '%s\n' "$junit_ids" | awk 'NF' | wc -l | tr -d ' ')
cov_sandbox_count=$(printf '%s\n' "$cov_ids" | awk 'NF' | wc -l | tr -d ' ')
echo "=== Coverage-data delivery diagnostic ==="
echo " sandboxes with .coverage: $cov_sandbox_count"
echo " sandboxes with junit: $junit_sandbox_count"
echo " total .coverage files (per-batch): $cov_file_count"
echo " total junit files (per-sandbox): $junit_file_count"
if [ "$cov_sandbox_count" != "$junit_sandbox_count" ]; then
if [ "$junit_sandbox_count" -gt "$cov_sandbox_count" ]; then
echo " MISMATCH: $(( junit_sandbox_count - cov_sandbox_count )) sandboxes delivered junit but not .coverage"
else
echo " MISMATCH: $(( cov_sandbox_count - junit_sandbox_count )) sandboxes delivered .coverage but not junit"
fi
echo " sandboxes with junit but no .coverage:"
comm -23 <(printf '%s\n' "$junit_ids") <(printf '%s\n' "$cov_ids") | awk 'NF{print " " $0}'
echo " sandboxes with .coverage but no junit:"
comm -13 <(printf '%s\n' "$junit_ids") <(printf '%s\n' "$cov_ids") | awk 'NF{print " " $0}'
fi
i=0; for f in $(find test-results/ -name ".coverage"); do cp "$f" ".coverage.$i"; i=$((i+1)); done
uv run coverage combine .coverage.*
FAILED_PKGS=""
for pkg in libs/* apps/*; do
[ -f "$pkg/pyproject.toml" ] || continue
THRESHOLD=$(python3 -c "import tomllib,sys; d=tomllib.load(open(sys.argv[1],'rb')); n=d.get('tool',{}).get('coverage',{}).get('report',{}).get('fail_under'); print('' if n is None else n)" "$pkg/pyproject.toml")
[ -n "$THRESHOLD" ] || continue
echo "=== $pkg (fail_under=${THRESHOLD}%) ==="
if ! uv run coverage report --include="$pkg/*" --fail-under="$THRESHOLD"; then
FAILED_PKGS="$FAILED_PKGS $pkg"
fi
done
# Global backstop against regression of the combined gate.
# Packages with their own much-lower fail_under (and minds, which
# has its own coverage config) are excluded from the combined
# report via libs/mngr/pyproject.toml's [tool.coverage.report].omit
# so this 80% gate reflects the "main" package set.
echo "=== combined (fail_under=80%) ==="
uv run coverage report --rcfile=libs/mngr/pyproject.toml --fail-under=80 || FAILED_PKGS="$FAILED_PKGS [combined]"
if [ -n "$FAILED_PKGS" ]; then
echo ""
echo "FAILED coverage gates:$FAILED_PKGS"
exit 1
fi
- name: Report test results
if: always()
uses: ./.github/actions/report-flaky-aware-tests
with:
check-name: Unit + Integration Tests
junit-path: test-results/junit.xml
tests-outcome: ${{ steps.tests.outcome }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-offload
path: test-results/
# Offload pulls per-sandbox files into test-results/<sb>/.test_output/,
# which holds the flaky_tests_*.txt manifests used by the summary check.
# Default upload-artifact behavior strips hidden paths, making post-hoc
# debugging of the @flaky column impossible.
include-hidden-files: true
retention-days: 30
# Clean up old Modal test environments left behind by acceptance tests.
cleanup-modal-environments:
runs-on: ubuntu-latest
env:
MODAL_TOKEN_ID: ${{ vars.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
- name: Install Python dependencies
run: uv sync --all-packages
- name: Clean up old Modal test environments
run: uv run python scripts/cleanup_old_modal_test_environments.py --max-age-hours 1.0
# Acceptance tests via offload on Modal
test-offload-acceptance:
runs-on: ubuntu-latest
# See note on `test-offload` above: `checks: write` is required by the
# report-flaky-aware-tests composite action, and `contents: write` is
# required so offload can push its image-cache git notes back to
# `refs/notes/offload-images` (otherwise every run is a cache miss).
permissions:
contents: write
checks: write
env:
MODAL_TOKEN_ID: ${{ vars.MODAL_TOKEN_ID }}
MODAL_TOKEN_SECRET: ${{ secrets.MODAL_TOKEN_SECRET }}
steps:
# Default shallow single-ref checkout, then deepen only THIS ref's
# history in the next step. offload needs the full ancestry of HEAD
# (to find its checkpoint commit and thin-diff against it), but not
# every other branch -- which `fetch-depth: 0` would also fetch, and on
# a repo with many branches that full-history-of-all-branches fetch can
# take minutes. The image-cache git notes are fetched separately below.
- uses: actions/checkout@v6
- name: Deepen current-branch history for offload
# Deepen ONLY the checked-out commit's ancestry -- all offload needs to
# find and thin-diff its checkpoint. Naming the SHA matters: a bare
# `git fetch --unshallow origin` uses the broad refspec and pulls every
# branch's full history (including a heavy orphan branch), which is most
# of the fetch cost. GitHub allows fetching reachable SHAs, and the
# image-cache git notes are fetched separately below.
run: |
if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
git fetch --no-tags --unshallow origin "$(git rev-parse HEAD)"
fi
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
- name: Install Python dependencies
run: uv sync --all-packages
- name: Install just
uses: extractions/setup-just@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cache offload binary
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/offload
key: cargo-offload-0.9.7-${{ runner.os }}
- name: Install offload
run: |
if ! command -v offload &> /dev/null || ! offload --version | grep -q '0.9.7'; then
cargo install offload@0.9.7 --force
fi
- name: Restore offload test durations from previous run
uses: actions/cache/restore@v5
with:
path: test-results/junit.xml
key: offload-acceptance-durations-${{ github.ref }}-${{ github.run_id }}
restore-keys: offload-acceptance-durations-${{ github.ref }}-
- name: Fetch offload image-cache git notes
run: git fetch origin 'refs/notes/*:refs/notes/*' || true
- name: Configure git identity for image-cache note writes
run: |
git config user.email "dev@imbue.com"
git config user.name "imbue-dev"
- name: Run acceptance tests via offload
id: tests
run: just test-offload-acceptance
- name: Save offload test durations for next run
if: always()
uses: actions/cache/save@v5
with:
path: test-results/junit.xml
key: offload-acceptance-durations-${{ github.ref }}-${{ github.run_id }}
- name: Report test results
if: always()
uses: ./.github/actions/report-flaky-aware-tests
with:
check-name: Acceptance Tests
junit-path: test-results/junit.xml
tests-outcome: ${{ steps.tests.outcome }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
name: test-results-offload-acceptance
path: test-results/
# Same rationale as the test-offload upload step above: offload's
# per-sandbox flaky_tests_*.txt manifests live under hidden
# test-results/<sb>/.test_output/ subdirs.
include-hidden-files: true
retention-days: 30
# Docker acceptance tests - runs on a GitHub runner with a real Docker daemon.
# Temporary: these will move to offload once Docker-in-Docker support lands.
test-docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y tmux git openssh-server unison
- name: Configure Docker Hub mirror
run: |
echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
- name: Install Python dependencies
run: uv sync --all-packages
- name: Run Docker acceptance tests
# Excludes `minds_electron` so the heavyweight Electron + docker
# spin-up (apps/minds/test_desktop_client_e2e.py) does not serialize
# behind every other docker-marked test in this batch. That test
# gets its own `test-docker-electron` job below.
# Excludes `minds_snapshot_resume` because those tests only make
# sense inside a Modal sandbox booted from a pre-built snapshot
# (see scripts/snapshot_minds_e2e_state.py); they assume a
# workspace Docker container is already in /var/lib/docker, which
# is not the case on a fresh GitHub runner. Runs via
# `just test-offload-minds-snapshot <image-id>` instead.
run: PYTEST_MAX_DURATION_SECONDS=600 uv run pytest -n 0 --timeout 300 --no-cov --cov-fail-under=0 -v --tb=short -m '(docker or docker_sdk) and not release and not minds_electron and not minds_snapshot_resume'
# Sibling to `test-docker` for the single minds Electron e2e test. Split
# out because the test (~2 min end-to-end: docker pull + container start
# + bootstrap + system_interface render) is the longest single
# docker-marked test and would otherwise stretch the test-docker job's
# wall clock. Splitting lets the two jobs run in parallel; the overall
# test stage's wall clock becomes `max(test-docker, test-docker-electron)`
# instead of their sum. Also avoids pulling Node / pnpm / Electron native
# deps / xvfb into the test-docker runner where they would otherwise
# only serve this one test.
test-docker-electron:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y tmux git openssh-server unison xvfb
- name: Set up Node.js for Electron e2e test
uses: actions/setup-node@v6
with:
node-version: '24.15.0'
- name: Install pnpm
run: npm install --global pnpm@10.33.4
- name: Install Electron + minds JS deps
run: pnpm install --dir apps/minds --frozen-lockfile
- name: Configure Docker Hub mirror
run: |
echo '{"registry-mirrors": ["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"
- name: Install Python dependencies
run: uv sync --all-packages
- name: Run minds Electron e2e test
# `xvfb-run -a` provides the display server the Electron renderer
# needs; `env` sets PYTEST_MAX_DURATION_SECONDS for the wrapped
# command. Both the env-var suite-level budget and the
# `--timeout` per-test default match the test's own
# `@pytest.mark.timeout(900)`, so the suite budget cannot kill
# the pytest run before the per-test marker fires.
run: xvfb-run -a env PYTEST_MAX_DURATION_SECONDS=900 uv run pytest -n 0 --timeout 900 --no-cov --cov-fail-under=0 -v --tb=short -m 'minds_electron and not release'