Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
185 commits
Select commit Hold shift + click to select a range
7a290a5
Issue #151: Design plan for making Clustrix a data mover
jeremymanning Aug 19, 2026
8ddf813
Issue #122: delete enhanced_notebook_widget, stop silent credential-s…
jeremymanning Aug 19, 2026
e8490fb
Add measured local-execution tutorial notebook
jeremymanning Aug 19, 2026
bb265e6
Issue #148: record rationale for the host-key fix swept into bb7fbd0
jeremymanning Aug 19, 2026
aa545e0
Issue #117: replace the three named mock offenders with real execution
jeremymanning Aug 19, 2026
70e32ef
Docs: describe current behaviour only in introduction, index and limi…
jeremymanning Aug 19, 2026
d174ae3
Docs: correct the API reference pages
jeremymanning Aug 19, 2026
29c6e46
Docs: present-tense rewrite of the prose and tutorial pages
jeremymanning Aug 19, 2026
9719280
Issue #151: data packages -- ship declared data to the worker via HF …
jeremymanning Aug 19, 2026
e495b50
README: describe the current version only, and correct four false claims
jeremymanning Aug 19, 2026
5c233ca
CLAUDE.md: record #152, #151 and the cluster_type-is-not-a-keyword trap
jeremymanning Aug 19, 2026
ab9a31a
Docs: present-tense rewrite of the tutorial notebooks
jeremymanning Aug 19, 2026
a8b25aa
Docs: drop the remaining marketing adjectives from the intros
jeremymanning Aug 19, 2026
54352a2
Docs: rewrite peripheral reference docs for current behaviour
jeremymanning Aug 19, 2026
5cca5ab
Docs: use example.edu placeholders for every sample hostname
jeremymanning Aug 19, 2026
423fff6
Issue #151: one atomic commit per package, and stop tests writing to …
jeremymanning Aug 19, 2026
c5fe240
Docs: data staging landed (#151); describe it as declaration-only
jeremymanning Aug 19, 2026
382dfb7
Issue #151: do not carry a materialisation path across a pickle
jeremymanning Aug 19, 2026
3769387
Issue #151: avoid the literal policy name in a docstring, and drop th…
jeremymanning Aug 19, 2026
ca55f02
Issue #151: translate a rate-limited or failed stage into a StagingError
jeremymanning Aug 19, 2026
f6e6b50
Stop --include-notes executing historical examples
jeremymanning Aug 19, 2026
411b6aa
Issue #154: remove the shell from filesystem.py, quote what is left
jeremymanning Aug 19, 2026
0c74784
Issue #151: record the limitation a user hits first -- non-HF users g…
jeremymanning Aug 19, 2026
95b523e
Stop the test suite writing to the developer's real ~/.ssh/known_hosts
jeremymanning Aug 19, 2026
187a77f
Issue #151: reach a real HF token through the new $HOME isolation
jeremymanning Aug 19, 2026
8faee19
Notes: suspend checkpoint for the priorities-and-docs branch
jeremymanning Aug 19, 2026
68674cc
Docs: correct six verified factual errors in the configuration pages
jeremymanning Aug 19, 2026
bf00bc6
Describe the absent schedulers without dating the absence
jeremymanning Aug 19, 2026
ff3cdab
Make the known_hosts path genuinely one definition
jeremymanning Aug 19, 2026
ba9059e
Issue #154 follow-up: make the injection guard see, and make remote m…
jeremymanning Aug 19, 2026
45fb302
Data packages: stop delete() removing directories it does not own
jeremymanning Aug 19, 2026
f2c513c
Fix six defects an adversarial review found in the SSH test seam
jeremymanning Aug 19, 2026
0144c33
auto_add accepted every host key and saved none of them
jeremymanning Aug 19, 2026
04b99f7
Use a suppressed stand-in for the wrong-password literal
jeremymanning Aug 20, 2026
7165012
Issue #123: remote_file_exists leaked a channel every time it said "no"
jeremymanning Aug 20, 2026
c48fb2f
Docs: a data-package guide, and four behaviours the pages had not cau…
jeremymanning Aug 20, 2026
06287de
Issue #111: gitignore .env and its siblings
jeremymanning Aug 20, 2026
5a62499
Issue #111: create credential files at 0600 instead of narrowing them…
jeremymanning Aug 20, 2026
71ae670
Issue #116: enforce the no-mocks-in-shipped-code rule with a test
jeremymanning Aug 20, 2026
78d2922
Issue #125: delete the orphaned stream-C progress note
jeremymanning Aug 20, 2026
6dd0985
Issue #147: pin the real-world runner's exit code
jeremymanning Aug 20, 2026
e9a57d9
Issue #150: drop MinIO, PostgreSQL and Redis from the test infrastruc…
jeremymanning Aug 20, 2026
dc1a3bd
Clean up references to removed test services and deleted modules
jeremymanning Aug 20, 2026
87062d5
scripts/aws: describe what these scripts are, not what created the mess
jeremymanning Aug 20, 2026
826a434
Remove the mock SLURM controller from the test infrastructure
jeremymanning Aug 20, 2026
8d05bfb
Issue #111/#116: fix the unguarded write-then-narrow, and invert both…
jeremymanning Aug 20, 2026
b219456
One secure writer, and config.py had drifted away from it
jeremymanning Aug 20, 2026
fa12781
Issue #158: remove the queue setting nothing ever read
jeremymanning Aug 20, 2026
f0b1218
Issue #152: cores either sizes the local pool or says it is being ign…
jeremymanning Aug 20, 2026
31df879
Issue #111: close the profile-store leak, make the write atomic, repl…
jeremymanning Aug 20, 2026
f790cdd
Issue #153: read real-world test credentials from ~/.clustrix/.env
jeremymanning Aug 20, 2026
ea46f05
Issue #157: auto_add appends one line instead of rewriting known_hosts
jeremymanning Aug 20, 2026
917c053
Issue #123: deterministic teardown, a lazy sftp_client, and answers t…
jeremymanning Aug 20, 2026
69f135e
Issue #123: a reconnect must not hand back the dead transport's channel
jeremymanning Aug 20, 2026
60f30a0
Issue #153 regression: the real-world credential helper stops exporti…
jeremymanning Aug 20, 2026
0df81ca
Issue #111: a widget save, and a credential lookup, stop leaking
jeremymanning Aug 20, 2026
4126a03
Issue #152: cores bounds the pool, and the bound is now reachable
jeremymanning Aug 20, 2026
ddd0a39
Issue #166: the documentation checker now opens the notebooks
jeremymanning Aug 20, 2026
481597f
Issue #164: a named cluster environment now reaches the job script
jeremymanning Aug 20, 2026
f9879d2
Documentation: correct five false claims, and finish the overhaul
jeremymanning Aug 20, 2026
56ca935
Issue #165: the widget's Apply button forwards settings, not the profile
jeremymanning Aug 20, 2026
50a93d9
Round 7: a credential goes to one host, and a config file is an allow…
jeremymanning Aug 20, 2026
214fbce
Issue #123: the import stops reading your home directory, and 21 swal…
jeremymanning Aug 20, 2026
1005244
Issue #152: the cores fix now fails when it is broken
jeremymanning Aug 20, 2026
d22a9fa
Issue #164: the named environment could not have worked, and four sma…
jeremymanning Aug 20, 2026
bc15217
Issue #165: a cleared box clears the setting, and a stale key is named
jeremymanning Aug 20, 2026
f364c61
Round 8: a cloned repository stops choosing who gets your cluster pas…
jeremymanning Aug 20, 2026
5970455
Issue #123: the fix for the lazy search had the defect it was fixing
jeremymanning Aug 20, 2026
1422862
Issue #164: VENV2 stops being handed VENV1's interpreter, and six sma…
jeremymanning Aug 20, 2026
0a3e6fb
Issue #165: a credential channel is not a backend setting
jeremymanning Aug 20, 2026
87c8393
Issue #152: the config route into the pool, and a warning nobody heard
jeremymanning Aug 20, 2026
c32fe56
Round 9: untrusted stays untrusted, whatever it is passed through
jeremymanning Aug 20, 2026
908fec8
Issue #165: the backend-only line is about targets, not about secrecy
jeremymanning Aug 20, 2026
282fd63
Issue #152: a warning a NullHandler swallowed, and a shape nobody pinned
jeremymanning Aug 20, 2026
cf9a1e5
Issue #152: the three gates nobody pinned, and a filter that ate the …
jeremymanning Aug 20, 2026
b01771e
Round 10: a file is a file, whichever loader read it
jeremymanning Aug 20, 2026
e0b1d39
Issue #164: the submission seam gets tests, and the conda probe was d…
jeremymanning Aug 20, 2026
feb1fd9
Issue #165: how the menu is widened, and why one menu is not
jeremymanning Aug 20, 2026
fa289a4
Round 11: the Load menu is a discovery, and a thread is not an escape
jeremymanning Aug 20, 2026
aa1345f
Issue #152: the mutant that turned repetition into silence, and the m…
jeremymanning Aug 20, 2026
fa21f72
Issue #164: the branch was red on its own new file, and three uncover…
jeremymanning Aug 20, 2026
7f82333
Round 12: a passing overlap is not a verdict on a hostname
jeremymanning Aug 20, 2026
1bf4654
Issue #167: give the credential decision one home
jeremymanning Aug 20, 2026
673e444
Issue #167: every SSH reader asks the gate
jeremymanning Aug 20, 2026
08ea58a
Issue #167 route 6: delete ClusterConfig.get_env_password
jeremymanning Aug 20, 2026
f3a27f2
Round 13: provenance has to survive the file it is written to
jeremymanning Aug 20, 2026
6399773
Issue #167: privatise the store, so the gate is the only way in
jeremymanning Aug 20, 2026
5db9631
Issue #123: seven swallows were annotated, not fixed, and the guard w…
jeremymanning Aug 20, 2026
d289afa
Issue #167 route 7: a write is a release decision too
jeremymanning Aug 20, 2026
45d29fe
Issue #167 route 3: provenance becomes an argument
jeremymanning Aug 20, 2026
bbc4c5f
Issue #167: an eighth route cannot be added silently
jeremymanning Aug 20, 2026
4e1551e
Issue #167 route 5: the widget records what it found, when it finds it
jeremymanning Aug 20, 2026
480afe7
Issue #167: record the rule where a reader will look
jeremymanning Aug 20, 2026
771c54d
Issue #123: pin the two fixes that a mutant walked straight through
jeremymanning Aug 20, 2026
3956d1f
Issue #164: the GPU arm was single-valued, and the invariant trusted …
jeremymanning Aug 20, 2026
40af31a
Round 14: silence is not provenance, and a rename is not a hostname
jeremymanning Aug 20, 2026
9ec9b6b
Issue #167 G1: provenance is derived, not declared
jeremymanning Aug 20, 2026
a197847
Issue #167 G2: close the three public paths into the store
jeremymanning Aug 20, 2026
3c28311
Issue #167 G3/G5: gate config.key_file and config.password; M10
jeremymanning Aug 20, 2026
9d0e568
Round 15: copying a configuration is not choosing a hostname
jeremymanning Aug 20, 2026
ec0194b
Issue #123: the narrowing was undone one frame out, by this issue's o…
jeremymanning Aug 20, 2026
56b0a70
Issue #167 G4: convert route 9, the SSH-key fallback call site
jeremymanning Aug 20, 2026
238167a
Issue #167 G6: the enforcement test stops exempting what it should watch
jeremymanning Aug 20, 2026
f5cd22b
Issue #123: the while-loop sibling, the source tuple, and prose nobod…
jeremymanning Aug 20, 2026
720e363
Round 16: Save is not adoption, and the disk is where it stopped bein…
jeremymanning Aug 20, 2026
f2a152d
Issue #164: the probe names the host now, so the assertion must too
jeremymanning Aug 20, 2026
3c68348
Issue #166: four nested-markup instances that Markdown made invisible
jeremymanning Aug 20, 2026
049470a
Issue #159: campaign status — merge plan re-derived, DoD bullet 4 ver…
jeremymanning Aug 20, 2026
9973d82
Issue #167: a save records where it came from, whoever wrote it
jeremymanning Aug 20, 2026
ea8aedf
Issue #172: a GPU reported on the strength of output nobody could read
jeremymanning Aug 20, 2026
e0c90bd
Issue #169: a required check that only reports when the right files c…
jeremymanning Aug 20, 2026
de84a38
Issue #159: record round results, the #165 false alarm, and the stray…
jeremymanning Aug 20, 2026
f966920
Issue #159: settle merge sequencing by measuring four orderings
jeremymanning Aug 20, 2026
732a5f0
Issue #172: the setup message had two branches for three outcomes
jeremymanning Aug 20, 2026
f31a98f
Issue #167: a refusal that still authenticates is not a refusal (rout…
jeremymanning Aug 20, 2026
b30ee5f
Issue #167: record route 13 progress — five sites found, four closed,…
jeremymanning Aug 20, 2026
7f3ba8c
Issue #159: record #172 and #169 fixes; #169 stays open until a docs-…
jeremymanning Aug 20, 2026
fa54e2c
Issue #111: scan pushable history for secrets, not just the working tree
jeremymanning Aug 20, 2026
e208108
Issue #172: record RT-1 — the third state is unreachable when lspci f…
jeremymanning Aug 20, 2026
301d6a1
Issue #159: hunt the RT-1 shape repo-wide — one known, one unverified…
jeremymanning Aug 20, 2026
9a7e54f
Issue #167: the widget's Test button asks the gate, and a rule keeps …
jeremymanning Aug 20, 2026
ab9b301
Issue #167: route 13 — six sites closed, seventh (ssh-copy-id) dispat…
jeremymanning Aug 20, 2026
ab99700
Issue #172: the third state was unreachable, because a fallback inven…
jeremymanning Aug 20, 2026
25640bd
Issue #169: a required check can also be silenced one level below the…
jeremymanning Aug 20, 2026
2cc2792
Issue #172: record RT-1/2/3 fixes and the shared-scratchpad collision
jeremymanning Aug 20, 2026
be846ff
Issue #159: record the second leftovers red-team — five #169 bypasses…
jeremymanning Aug 20, 2026
fcd922a
Issue #123: five error reports, a prose test that read no prose, and …
jeremymanning Aug 20, 2026
0cbce38
Issue #123: record fix round three; correct my stray-config finding; …
jeremymanning Aug 20, 2026
19a5026
Issue #167: the ssh-copy-id subprocess obeys the gate and the host ke…
jeremymanning Aug 20, 2026
da3aca9
Issue #159: CHANGELOG draft ready; record three merge actions it surf…
jeremymanning Aug 20, 2026
982742b
Issue #167: route 13 closed at seven sites; record the ssh_host_key_p…
jeremymanning Aug 20, 2026
da5bed8
Issue #172: a GPU claimed from the vendor name, and a CUDA build boug…
jeremymanning Aug 20, 2026
4e76040
Issue #168: an unreadable file is not an empty one, and dill's reason…
jeremymanning Aug 20, 2026
3f2e7c5
Issue #168: fully closed; record the allowlist deletion the merge req…
jeremymanning Aug 20, 2026
c9ed00f
Issue #168: verify the predicted merge failure by inspection
jeremymanning Aug 20, 2026
de9e742
Issue #169: five ways to silence a required check, and an honest limit
jeremymanning Aug 20, 2026
dccfe0d
Issue #172: record the real-world CUDA-install harm and its fix; #169…
jeremymanning Aug 20, 2026
ea8fce6
Issue #159: consolidate the rehearsed merge runbook
jeremymanning Aug 20, 2026
3bfa452
Issue #171: a rename is not a delete, so a taken name is refused
jeremymanning Aug 20, 2026
126de31
Issue #171: closed; record the residual as a known limitation rather …
jeremymanning Aug 20, 2026
d63d9fa
Issue #167: record the proven compromise chain F1-F4 and the exploita…
jeremymanning Aug 20, 2026
03aae7e
Issue #167: audit the F2 field class — no merging, so the exposure is…
jeremymanning Aug 20, 2026
d1db83c
Issue #123: a count above family A, a swallow with no handler, and a …
jeremymanning Aug 20, 2026
357a7e3
Issue #123: record round four — position-independent counts, global s…
jeremymanning Aug 20, 2026
e8fa922
Issue #172: a gate nothing defended, and a listing that fails open
jeremymanning Aug 20, 2026
0b7f142
Issue #169: three more ways to silence a required check, and one it w…
jeremymanning Aug 20, 2026
172bcb6
Issue #167: an untrusted configuration may not aim a secret, and the
jeremymanning Aug 20, 2026
0f1ce41
Issue #172: the /proc fixture now holds what the driver really puts t…
jeremymanning Aug 20, 2026
7d28428
Issue #159: decide the widget-bundle question; record the rehearsed m…
jeremymanning Aug 20, 2026
dde208e
Issue #123: qualify the logging silencers by receiver, and count the …
jeremymanning Aug 21, 2026
ed76b90
Merge work/silent-failures into work/priorities-and-docs
jeremymanning Aug 21, 2026
16d0ad2
Merge work/widget-apply into work/priorities-and-docs
jeremymanning Aug 21, 2026
28b361c
Issue #123: a profile bundle is declined and named, not adopted and n…
jeremymanning Aug 21, 2026
30b9795
Merge work/named-env into work/priorities-and-docs
jeremymanning Aug 21, 2026
2c59251
Merge work/leftovers into work/priorities-and-docs
jeremymanning Aug 21, 2026
25f20e5
Issue #159: suspend point -- four merges landed, bundle decision impl…
jeremymanning Aug 21, 2026
ed04066
Merge work/fixes into work/credential-gate
jeremymanning Aug 22, 2026
fdccfd1
Merge work/credential-gate into work/priorities-and-docs
jeremymanning Aug 22, 2026
4e19e0d
Issue #127: re-execute the local-parallel notebook so its stored outp…
jeremymanning Aug 22, 2026
f2a7205
Issue #127: fold #168, #169, #171 and #172 into the 0.2.0 draft
jeremymanning Aug 22, 2026
7ae5519
Issue #161: the eleven dead ClusterConfig fields announce themselves …
jeremymanning Aug 22, 2026
47e1aa9
Issue #162: every export is documented somewhere
jeremymanning Aug 23, 2026
499b50c
Issue #163: the no-effect settings table records #161's warnings
jeremymanning Aug 23, 2026
4b1f934
Issue #127: fresh end-to-end evidence — real jobs on all three remote…
jeremymanning Aug 23, 2026
bf73225
Issue #127: refreshed use-case matrix — slurm-1 18/18, gpu-1 18/18
jeremymanning Aug 23, 2026
8780c2f
Issue #161: configure() honours the dead-field announcement too, and …
jeremymanning Aug 23, 2026
4c88b78
Issue #127: the pre-push API category skips when its target is gone, …
jeremymanning Aug 23, 2026
639be00
Issue #159: session bookkeeping
jeremymanning Aug 23, 2026
636a849
Issue #127: stop tracking session state; hermetic named-env tests; de…
jeremymanning Aug 23, 2026
023b97c
Issue #127: make the suite green on Linux and Windows CI
jeremymanning Aug 23, 2026
323c399
Issue #127: ipykernel joins the dev extra -- nbclient cannot start a …
jeremymanning Aug 23, 2026
62bb74f
Issue #127: diagnose-in-place for the named-env HOME search; runner-c…
jeremymanning Aug 23, 2026
565a7c2
Issue #127: nested-markup repairs, Windows kernel guard, working diag…
jeremymanning Aug 23, 2026
6fbd915
Issue #127: the HOME-search failure carries the shell trace
jeremymanning Aug 23, 2026
d0b7870
Issue #127: the conda probe no longer bypasses a sourced conda functi…
jeremymanning Aug 23, 2026
e684160
Issue #127: wc measures job.err through a redirect, so a directory ca…
jeremymanning Aug 23, 2026
497f86a
Issue #127: gate the job.err measurement on test -f -- GNU wc answers…
jeremymanning Aug 23, 2026
e3d135c
Issue #127: the Quick Checks step timeout grows with the suite it runs
jeremymanning Aug 23, 2026
f556edb
Issue #127: the in-process SSH server skips itself on Windows
jeremymanning Aug 23, 2026
34655df
Issue #127: module-level skip lands cleanly on Windows; the widget-si…
jeremymanning Aug 23, 2026
1342f4f
Issue #127: POSIX-semantics test modules skip themselves on Windows
jeremymanning Aug 23, 2026
d45762f
Issue #127: the last two Windows failures skip with stated reasons
jeremymanning Aug 23, 2026
6c52c82
Issue #127: the encoding-issues guard uses pytest.skip -- the class i…
jeremymanning Aug 23, 2026
987ee33
Notes: campaign close-out state
jeremymanning Aug 23, 2026
6dccfb5
Issue #127: the SSH-round-trip oracle skips macOS CI, and its job get…
jeremymanning Aug 23, 2026
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
92 changes: 0 additions & 92 deletions .claude/epics/clean-up-repository/updates/82/stream-C.md

This file was deleted.

28 changes: 21 additions & 7 deletions .github/workflows/fast_ci.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
name: Fast CI

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

local-integration:
name: Local Integration Test
Expand Down
35 changes: 35 additions & 0 deletions .github/workflows/real-world-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,41 @@ jobs:
# Test SSH connection
timeout 10 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.CLUSTRIX_USERNAME }}@localhost "echo 'SSH connection successful'"

- name: Populate known_hosts for host key verification
# Issue #148: the real-world tests no longer call
# `set_missing_host_key_policy(paramiko.AutoAddPolicy())`; they go
# through `clustrix.ssh_security.configure_host_key_policy`, whose
# default policy is "reject". Any host absent from known_hosts now
# raises HostKeyVerificationError instead of being trusted silently,
# so every host these tests connect to must be scanned in first.
#
# localhost/127.0.0.1 is the sshd this job installs a few steps above,
# and is the only host the CI run actually reaches: `test_ssh_real.py`
# skips itself unless the configured host is localhost.
#
# There is deliberately no `secrets.*` reference for an external
# cluster hostname here, because no such repository secret exists --
# the only cluster secrets configured are CLUSTRIX_USERNAME,
# CLUSTRIX_PASSWORD, HF_USERNAME and HF_TOKEN. Real cluster hosts are
# supplied to the suite through the CLUSTRIX_TEST_{SSH,SLURM}_HOST[_2]
# environment variables (see tests/real_world/credential_manager.py);
# this step scans whichever of those are set so that adding them later
# needs no further workflow change.
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
for host in localhost 127.0.0.1 \
"$CLUSTRIX_TEST_SSH_HOST" "$CLUSTRIX_TEST_SSH_HOST_2" \
"$CLUSTRIX_TEST_SLURM_HOST" "$CLUSTRIX_TEST_SLURM_HOST_2"; do
[ -n "$host" ] || continue
echo "Scanning host key for $host"
ssh-keyscan -H "$host" >> ~/.ssh/known_hosts
done
chmod 600 ~/.ssh/known_hosts
# Fail loudly rather than let the tests fail later with an opaque
# HostKeyVerificationError for the host this job just created.
ssh-keygen -F localhost -f ~/.ssh/known_hosts > /dev/null

- name: Run filesystem tests
run: |
python scripts/run_real_world_tests.py --filesystem
Expand Down
12 changes: 10 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
# This is a bound on a genuinely longer job, not a relaxed check: every
# test still has to pass, and pytest's own --timeout=120 still bounds any
# individual test that wedges.
timeout-minutes: 30
timeout-minutes: 45
strategy:
# Show every platform's failures in one run. With the default
# fail-fast, one job failing cancelled the other six, so a
Expand Down Expand Up @@ -212,7 +212,15 @@ jobs:
run: |
cd docs
make html


# Sphinx inline markup does not nest: a ``literal`` inside a **bold**
# span renders as plain text, backticks and all, and `sphinx -W` builds
# it happily. Only the built HTML shows it, and only the built HTML has
# the .rst, the docstring and the nbsphinx-converted notebook cases in
# one place -- so this checks the output of the step above.
- name: Check documentation for nested inline markup
run: python scripts/check_docs_markup.py docs/build/html

- name: Test notebook execution
run: |
pip install jupyter nbconvert
Expand Down
14 changes: 12 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,17 @@ docs/build/
**/clustrix-*.key
**/*-credentials.json
**/*-service-account.json
.env.local
.env.validation
# Credential-bearing dotenv files (see #111). `clustrix credentials setup`
# writes ~/.clustrix/.env, but an operator following the docs can easily end
# up with one in the working tree, and `git add .` would have committed it.
.env
.env.*
# ...except a checked-in, secret-free example, if one is ever added.
!.env.example
# direnv's file. `.env.*` above does not match it -- no dot after "env" --
# and it routinely holds exported credentials.
.envrc
.envrc.*
**/validation-secrets.json
**/.op/
**/op-session-*
Expand All @@ -76,3 +85,4 @@ docs/build/
tests/real_world/screenshots/
tests/real_world/temp/
.omc/
.omo/run-continuation/
103 changes: 103 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# PROJECT KNOWLEDGE BASE

**Generated:** 2026-08-21 16:23 UTC
**Commit:** 7d28428
**Branch:** work/priorities-and-docs

## OVERVIEW

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.

**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.

## STRUCTURE

```
clustrix/
├── clustrix/ # the package — flat, 34 modules (see clustrix/AGENTS.md)
├── tests/ # unit/ + real_world/ + integration/ + comprehensive/ (see tests/AGENTS.md)
├── scripts/ # dev/ops tooling; aws/ is operator cleanup, NOT a backend
├── docs/ # source/ (Sphinx) + evidence/ (committed proof) + build/ (generated)
├── notes/ # session notes; per user policy, update as work proceeds
├── .claude/ # pm command system (commands/pm, scripts/pm, rules, agents)
├── .github/workflows/ # tests.yml, fast_ci.yml, real-world-tests.yml
├── build/ # STALE setuptools output — see NOTES
├── htmlcov/, performance_test_results/, docs/build/ # generated; ignore
└── pyproject.toml # the ONLY pytest/coverage config; black/mypy/flake8 too
```

## WHERE TO LOOK

| Task | Location | Notes |
|-|-|-|
| Add a backend | `config.SUPPORTED_CLUSTER_TYPES` + `executor_core.py` dispatch + `executor_schedulers.py` | Gate: real job on real hardware, evidence committed |
| Change execution flow | `executor_core.py` (ClusterExecutor), split across `executor_connections/_schedulers/_scheduler_status` | `executor.py` is a 39-line shim |
| Touch serialization | `utils.py` `serialize_function`/`deserialize_function`, `generate_two_venv_execution_commands` | Keep each serialize/deserialize pair symmetric; never stdlib `pickle` |
| Config change | `config.py` (ClusterConfig, configure, load_config) | No env-var overlay exists; only `CLUSTRIX_CONFIG_DIR` + `password_env_var` |
| SSH/auth | `ssh_security.py` (host keys), `ssh_utils.py`, `auth_manager.py`, `credential_manager.py` | Never `AutoAddPolicy` directly |
| Notebook UI | `notebook_magic_core.py` (%%remote, %clustrix), `modern_notebook_widget.py` | Widget never auto-displays on import unless `CLUSTRIX_AUTO_WIDGET=1` |
| Data staging | `staging.py` (`data_package`, `materialize_packages`) | Declaration only; nothing inferred; nothing auto-deleted |
| Quality gates | `scripts/pre_push_check.py` (retries 5x), `scripts/check_quality.py` | Run repeatedly until ALL pass before commit |
| Regenerate backend evidence | `scripts/verify_cluster_usecases.py`, `scripts/collect_execution_evidence.py` | Output committed under `docs/evidence/` |
| 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:` |

## CODE MAP

Centrality from codegraph (Python LSP not installed; ruff is lint-only).

| Symbol | Type | Location | Refs | Role |
|-|-|-|-|-|
| `configure` | function | `clustrix/config.py:559` | 288 | Singleton config entry point; validates all keys before applying any |
| `ClusterExecutor` | class | `clustrix/executor_core.py:27` | 81 | Dispatch, submission, HMAC-verified result retrieval |
| `cluster` | decorator | `clustrix/decorator.py:58` | public API | `@cluster`; extras limited to `hf_*` + `key_file` — `cluster_type=` is NOT accepted |
| `ClusterConfig` | dataclass | `clustrix/config.py` | high | Plain-`str` `cluster_type`; no ClusterType enum |
| `ClusterfyMagics` | class | `clustrix/notebook_magic_core.py:69` | 6 | `%%remote`, `%clustrix`, deprecated `%%clusterfy` alias |
| `LocalExecutor` | class | `clustrix/local_executor.py` | — | Real local parallelism; `choose_executor_type` at :339 |
| `HFJobsManager` | class | `clustrix/hf_jobs.py` | — | HuggingFace Jobs backend; 256 KB payload cap |
| `data_package` | function | `clustrix/staging.py` | — | Declared data staging; large packages go to a private HF dataset repo |

## CONVENTIONS

- 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"`.
- **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.
- pytest `--strict-markers`; all 6 markers registered in pyproject. `testpaths` must never list `tests/integration` (billable, #109).
- Coverage `fail_under = 66` — a floor 2 points under measured 68%, not a target (#115).
- Pre-commit runs on python3.12 explicitly (system python3 may be 3.9, which the project does not support).
- Version string lives in 4 places and must stay identical: `pyproject.toml`, `setup.py`, `clustrix/__init__.py`, `docs/source/conf.py`.
- Comments explain *why*, often with issue refs (#109–#159). Match that style; do not strip them.

## ANTI-PATTERNS (THIS PROJECT)

- Never `set_missing_host_key_policy(paramiko.AutoAddPolicy())` — go through `ssh_security.configure_host_key_policy`.
- Never stdlib `pickle` in the two-venv handoffs — dill/cloudpickle only, symmetric pairs.
- 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).
- Never weaken a failing test; fix the code or explicitly rewrite a wrong assertion.
- Never document unsupported backends (pbs/sge/k8s/cloud VMs), cost monitoring, or HF Spaces as working.
- Never add `@cluster(cluster_type=...)` to examples — it is ignored with a warning; backend is set via `configure()`.
- `auto_gpu_parallel` does nothing (deleted; it fabricated results). Don't document it as a feature.
- No `cluster_put`/`cluster_get` — the `cluster_*` fs helpers are read-only by design.

## UNIQUE STYLES

- Honesty-first docs: verified vs unsupported backends stated up front; evidence transcripts committed under `docs/evidence/`.
- Defensive validation with explanatory errors (removed settings raise `ValueError` naming the replacement; unknown config keys get did-you-mean).
- One shared async executor per process (`decorator._shared_async_executor`), context-manager support on `SimpleAsyncClusterExecutor`.

## COMMANDS

```bash
pip install -e ".[dev]" # dev env (widget extra needed for widget tests)
python scripts/pre_push_check.py # black+flake8+mypy+pytest, retries until clean — run before EVERY commit
pytest tests/ -m "not real_world" --ignore=tests/real_world --ignore=tests/integration # what CI runs
pytest tests/unit/ -q # fast loop
python scripts/collect_execution_evidence.py # real job per reachable backend; needs credentials
cd docs && make html # docs build
```

## NOTES

- **`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.
- `htmlcov/`, `performance_test_results/`, `docs/build/` are generated output, not source.
- `tests/integration/` provisions real billable AWS resources; refuses without `CLUSTRIX_ALLOW_BILLABLE=1` (guard reads `config.args`, deliberately).
- `remote_work_dir` must be on a filesystem compute nodes see — `/tmp` dies with exit 127 on SLURM.
- Fresh count: 21 of 166 test modules use `unittest.mock` (issue #117 migrates them; new tests must not add to it).
Loading
Loading