Skip to content

Commit d0e8dc1

Browse files
Merge pull request #173 from ContextLab/work/priorities-and-docs
v0.2.0 (honest beta): merge train complete, gates green, real jobs verified
2 parents f78d153 + 6dccfb5 commit d0e8dc1

244 files changed

Lines changed: 61614 additions & 7327 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/epics/clean-up-repository/updates/82/stream-C.md

Lines changed: 0 additions & 92 deletions
This file was deleted.

.github/workflows/fast_ci.yml

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
name: Fast CI
22

33
on:
4+
# No `paths:` filter on pull_request, deliberately.
5+
#
6+
# The `status-check` job below publishes the `CI Status` context, and
7+
# master's branch protection lists that context as required. A required
8+
# check that is never reported is not treated as passing -- GitHub blocks
9+
# the merge on "Expected -- Waiting for status to be reported", forever --
10+
# so while this trigger was path-filtered, a pull request touching only
11+
# docs/, README.md or the notebooks could never be merged by anyone
12+
# without an admin override (#169). A required check whose reporting
13+
# depends on which files changed is a trap; the filter is not worth it.
14+
#
15+
# This repository is public, so Actions minutes are free and the cost of
16+
# running the four jobs on a docs-only pull request is wall-clock time,
17+
# not money.
418
pull_request:
519
branches: [main, master, develop]
6-
paths:
7-
- 'clustrix/**'
8-
- 'tests/**'
9-
- 'setup.py'
10-
- 'pyproject.toml'
11-
- 'requirements*.txt'
1220
push:
21+
# The filter stays here. `CI Status` is not a required check for pushes
22+
# to develop, so a run that never happens blocks nothing.
1323
branches: [develop] # Only run on develop pushes to avoid duplication with main Tests workflow
1424
paths:
1525
- 'clustrix/**'
@@ -65,7 +75,11 @@ jobs:
6575
-x \
6676
--tb=short \
6777
--maxfail=3
68-
timeout-minutes: 5
78+
# Sized for the suite as it is now (~1,100 unit tests, several of
79+
# which execute notebooks and real subprocesses): the old 5-minute
80+
# step timeout was set when tests/unit held ~350 fast tests and cut
81+
# the run at 60% regardless of what passed before it.
82+
timeout-minutes: 15
6983

7084
local-integration:
7185
name: Local Integration Test

.github/workflows/real-world-tests.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,41 @@ jobs:
9292
# Test SSH connection
9393
timeout 10 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.CLUSTRIX_USERNAME }}@localhost "echo 'SSH connection successful'"
9494
95+
- name: Populate known_hosts for host key verification
96+
# Issue #148: the real-world tests no longer call
97+
# `set_missing_host_key_policy(paramiko.AutoAddPolicy())`; they go
98+
# through `clustrix.ssh_security.configure_host_key_policy`, whose
99+
# default policy is "reject". Any host absent from known_hosts now
100+
# raises HostKeyVerificationError instead of being trusted silently,
101+
# so every host these tests connect to must be scanned in first.
102+
#
103+
# localhost/127.0.0.1 is the sshd this job installs a few steps above,
104+
# and is the only host the CI run actually reaches: `test_ssh_real.py`
105+
# skips itself unless the configured host is localhost.
106+
#
107+
# There is deliberately no `secrets.*` reference for an external
108+
# cluster hostname here, because no such repository secret exists --
109+
# the only cluster secrets configured are CLUSTRIX_USERNAME,
110+
# CLUSTRIX_PASSWORD, HF_USERNAME and HF_TOKEN. Real cluster hosts are
111+
# supplied to the suite through the CLUSTRIX_TEST_{SSH,SLURM}_HOST[_2]
112+
# environment variables (see tests/real_world/credential_manager.py);
113+
# this step scans whichever of those are set so that adding them later
114+
# needs no further workflow change.
115+
run: |
116+
mkdir -p ~/.ssh
117+
chmod 700 ~/.ssh
118+
for host in localhost 127.0.0.1 \
119+
"$CLUSTRIX_TEST_SSH_HOST" "$CLUSTRIX_TEST_SSH_HOST_2" \
120+
"$CLUSTRIX_TEST_SLURM_HOST" "$CLUSTRIX_TEST_SLURM_HOST_2"; do
121+
[ -n "$host" ] || continue
122+
echo "Scanning host key for $host"
123+
ssh-keyscan -H "$host" >> ~/.ssh/known_hosts
124+
done
125+
chmod 600 ~/.ssh/known_hosts
126+
# Fail loudly rather than let the tests fail later with an opaque
127+
# HostKeyVerificationError for the host this job just created.
128+
ssh-keygen -F localhost -f ~/.ssh/known_hosts > /dev/null
129+
95130
- name: Run filesystem tests
96131
run: |
97132
python scripts/run_real_world_tests.py --filesystem

.github/workflows/tests.yml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
# This is a bound on a genuinely longer job, not a relaxed check: every
2222
# test still has to pass, and pytest's own --timeout=120 still bounds any
2323
# individual test that wedges.
24-
timeout-minutes: 30
24+
timeout-minutes: 45
2525
strategy:
2626
# Show every platform's failures in one run. With the default
2727
# fail-fast, one job failing cancelled the other six, so a
@@ -212,7 +212,15 @@ jobs:
212212
run: |
213213
cd docs
214214
make html
215-
215+
216+
# Sphinx inline markup does not nest: a ``literal`` inside a **bold**
217+
# span renders as plain text, backticks and all, and `sphinx -W` builds
218+
# it happily. Only the built HTML shows it, and only the built HTML has
219+
# the .rst, the docstring and the nbsphinx-converted notebook cases in
220+
# one place -- so this checks the output of the step above.
221+
- name: Check documentation for nested inline markup
222+
run: python scripts/check_docs_markup.py docs/build/html
223+
216224
- name: Test notebook execution
217225
run: |
218226
pip install jupyter nbconvert

.gitignore

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,17 @@ docs/build/
6060
**/clustrix-*.key
6161
**/*-credentials.json
6262
**/*-service-account.json
63-
.env.local
64-
.env.validation
63+
# Credential-bearing dotenv files (see #111). `clustrix credentials setup`
64+
# writes ~/.clustrix/.env, but an operator following the docs can easily end
65+
# up with one in the working tree, and `git add .` would have committed it.
66+
.env
67+
.env.*
68+
# ...except a checked-in, secret-free example, if one is ever added.
69+
!.env.example
70+
# direnv's file. `.env.*` above does not match it -- no dot after "env" --
71+
# and it routinely holds exported credentials.
72+
.envrc
73+
.envrc.*
6574
**/validation-secrets.json
6675
**/.op/
6776
**/op-session-*
@@ -76,3 +85,4 @@ docs/build/
7685
tests/real_world/screenshots/
7786
tests/real_world/temp/
7887
.omc/
88+
.omo/run-continuation/

AGENTS.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# PROJECT KNOWLEDGE BASE
2+
3+
**Generated:** 2026-08-21 16:23 UTC
4+
**Commit:** 7d28428
5+
**Branch:** work/priorities-and-docs
6+
7+
## OVERVIEW
8+
9+
Clustrix is a Python distributed computing framework: `@cluster` on a function serializes it (dill/cloudpickle, by value) and runs it on a configured backend — `local`, `ssh`, `slurm`, `huggingface` (HF Jobs). Those four are the whole list (`clustrix.config.SUPPORTED_CLUSTER_TYPES`); pbs/sge/kubernetes/AWS/GCP/Azure/Lambda raise `ValueError` (issues #140#146). Python >=3.10, version 0.2.0, beta.
10+
11+
**CLAUDE.md is the deep curated knowledge base** (architecture, security invariants, mocking policy, two-venv execution). This file is the map; read CLAUDE.md before non-trivial work.
12+
13+
## STRUCTURE
14+
15+
```
16+
clustrix/
17+
├── clustrix/ # the package — flat, 34 modules (see clustrix/AGENTS.md)
18+
├── tests/ # unit/ + real_world/ + integration/ + comprehensive/ (see tests/AGENTS.md)
19+
├── scripts/ # dev/ops tooling; aws/ is operator cleanup, NOT a backend
20+
├── docs/ # source/ (Sphinx) + evidence/ (committed proof) + build/ (generated)
21+
├── notes/ # session notes; per user policy, update as work proceeds
22+
├── .claude/ # pm command system (commands/pm, scripts/pm, rules, agents)
23+
├── .github/workflows/ # tests.yml, fast_ci.yml, real-world-tests.yml
24+
├── build/ # STALE setuptools output — see NOTES
25+
├── htmlcov/, performance_test_results/, docs/build/ # generated; ignore
26+
└── pyproject.toml # the ONLY pytest/coverage config; black/mypy/flake8 too
27+
```
28+
29+
## WHERE TO LOOK
30+
31+
| Task | Location | Notes |
32+
|-|-|-|
33+
| Add a backend | `config.SUPPORTED_CLUSTER_TYPES` + `executor_core.py` dispatch + `executor_schedulers.py` | Gate: real job on real hardware, evidence committed |
34+
| Change execution flow | `executor_core.py` (ClusterExecutor), split across `executor_connections/_schedulers/_scheduler_status` | `executor.py` is a 39-line shim |
35+
| Touch serialization | `utils.py` `serialize_function`/`deserialize_function`, `generate_two_venv_execution_commands` | Keep each serialize/deserialize pair symmetric; never stdlib `pickle` |
36+
| Config change | `config.py` (ClusterConfig, configure, load_config) | No env-var overlay exists; only `CLUSTRIX_CONFIG_DIR` + `password_env_var` |
37+
| SSH/auth | `ssh_security.py` (host keys), `ssh_utils.py`, `auth_manager.py`, `credential_manager.py` | Never `AutoAddPolicy` directly |
38+
| Notebook UI | `notebook_magic_core.py` (%%remote, %clustrix), `modern_notebook_widget.py` | Widget never auto-displays on import unless `CLUSTRIX_AUTO_WIDGET=1` |
39+
| Data staging | `staging.py` (`data_package`, `materialize_packages`) | Declaration only; nothing inferred; nothing auto-deleted |
40+
| Quality gates | `scripts/pre_push_check.py` (retries 5x), `scripts/check_quality.py` | Run repeatedly until ALL pass before commit |
41+
| Regenerate backend evidence | `scripts/verify_cluster_usecases.py`, `scripts/collect_execution_evidence.py` | Output committed under `docs/evidence/` |
42+
| CI changes | `.github/workflows/` | `real-world-tests.yml` has NO push/PR trigger deliberately (credentialed jobs); secrets gate via `check-secrets` job outputs — `secrets` context is illegal in `if:` |
43+
44+
## CODE MAP
45+
46+
Centrality from codegraph (Python LSP not installed; ruff is lint-only).
47+
48+
| Symbol | Type | Location | Refs | Role |
49+
|-|-|-|-|-|
50+
| `configure` | function | `clustrix/config.py:559` | 288 | Singleton config entry point; validates all keys before applying any |
51+
| `ClusterExecutor` | class | `clustrix/executor_core.py:27` | 81 | Dispatch, submission, HMAC-verified result retrieval |
52+
| `cluster` | decorator | `clustrix/decorator.py:58` | public API | `@cluster`; extras limited to `hf_*` + `key_file``cluster_type=` is NOT accepted |
53+
| `ClusterConfig` | dataclass | `clustrix/config.py` | high | Plain-`str` `cluster_type`; no ClusterType enum |
54+
| `ClusterfyMagics` | class | `clustrix/notebook_magic_core.py:69` | 6 | `%%remote`, `%clustrix`, deprecated `%%clusterfy` alias |
55+
| `LocalExecutor` | class | `clustrix/local_executor.py` || Real local parallelism; `choose_executor_type` at :339 |
56+
| `HFJobsManager` | class | `clustrix/hf_jobs.py` || HuggingFace Jobs backend; 256 KB payload cap |
57+
| `data_package` | function | `clustrix/staging.py` || Declared data staging; large packages go to a private HF dataset repo |
58+
59+
## CONVENTIONS
60+
61+
- black line-length 88, target py310, **pinned `black==26.3.1`** (unbounded `>=` broke CI, #110); flake8 max-line 88, extend-ignore E203/W503; mypy python_version 3.10, `files=["clustrix/"]`, tests ignored, `follow_imports="skip"`.
62+
- **pyproject.toml is the only pytest config.** No pytest.ini/tox.ini/setup.cfg — the first found shadows this block (#130); `tests/unit/test_pytest_config.py` enforces it.
63+
- pytest `--strict-markers`; all 6 markers registered in pyproject. `testpaths` must never list `tests/integration` (billable, #109).
64+
- Coverage `fail_under = 66` — a floor 2 points under measured 68%, not a target (#115).
65+
- Pre-commit runs on python3.12 explicitly (system python3 may be 3.9, which the project does not support).
66+
- Version string lives in 4 places and must stay identical: `pyproject.toml`, `setup.py`, `clustrix/__init__.py`, `docs/source/conf.py`.
67+
- Comments explain *why*, often with issue refs (#109#159). Match that style; do not strip them.
68+
69+
## ANTI-PATTERNS (THIS PROJECT)
70+
71+
- Never `set_missing_host_key_policy(paramiko.AutoAddPolicy())` — go through `ssh_security.configure_host_key_policy`.
72+
- Never stdlib `pickle` in the two-venv handoffs — dill/cloudpickle only, symmetric pairs.
73+
- Never a mock as a fallback when the real thing is unavailable — fail instead. Production code must never know it is being tested (no `isinstance(x, Mock)`; `grep -rn "unittest.mock\|MagicMock" clustrix/` stays empty).
74+
- Never weaken a failing test; fix the code or explicitly rewrite a wrong assertion.
75+
- Never document unsupported backends (pbs/sge/k8s/cloud VMs), cost monitoring, or HF Spaces as working.
76+
- Never add `@cluster(cluster_type=...)` to examples — it is ignored with a warning; backend is set via `configure()`.
77+
- `auto_gpu_parallel` does nothing (deleted; it fabricated results). Don't document it as a feature.
78+
- No `cluster_put`/`cluster_get` — the `cluster_*` fs helpers are read-only by design.
79+
80+
## UNIQUE STYLES
81+
82+
- Honesty-first docs: verified vs unsupported backends stated up front; evidence transcripts committed under `docs/evidence/`.
83+
- Defensive validation with explanatory errors (removed settings raise `ValueError` naming the replacement; unknown config keys get did-you-mean).
84+
- One shared async executor per process (`decorator._shared_async_executor`), context-manager support on `SimpleAsyncClusterExecutor`.
85+
86+
## COMMANDS
87+
88+
```bash
89+
pip install -e ".[dev]" # dev env (widget extra needed for widget tests)
90+
python scripts/pre_push_check.py # black+flake8+mypy+pytest, retries until clean — run before EVERY commit
91+
pytest tests/ -m "not real_world" --ignore=tests/real_world --ignore=tests/integration # what CI runs
92+
pytest tests/unit/ -q # fast loop
93+
python scripts/collect_execution_evidence.py # real job per reachable backend; needs credentials
94+
cd docs && make html # docs build
95+
```
96+
97+
## NOTES
98+
99+
- **`build/lib/clustrix/` is stale**: it still contains `kubernetes/`, `cloud_providers/`, `pricing_clients/`, `cost_providers/` — modules deleted from the source tree. Grep results there are ghosts; exclude `build/` from searches.
100+
- `htmlcov/`, `performance_test_results/`, `docs/build/` are generated output, not source.
101+
- `tests/integration/` provisions real billable AWS resources; refuses without `CLUSTRIX_ALLOW_BILLABLE=1` (guard reads `config.args`, deliberately).
102+
- `remote_work_dir` must be on a filesystem compute nodes see — `/tmp` dies with exit 127 on SLURM.
103+
- Fresh count: 21 of 166 test modules use `unittest.mock` (issue #117 migrates them; new tests must not add to it).

0 commit comments

Comments
 (0)