smoke: add the CTI workspaces to the local runner, sharing one arcticpy recipe - #173
Merged
Conversation
…py recipe
heart/smoke.py is the local mirror of CI, but the `smoke:` block in
config/repos.yaml had no autocti entries and import_names had no PyAutoCTI — so
neither CTI smoke suite could be run locally, and the only way to exercise them
was to push and wait. That is the slowest possible loop for the repo group that
just acquired new coverage (autocti_workspace#28 gave that repo its first CI).
The design question was arcticpy: `import autocti` hard-requires it, it is not a
pip dependency, and a composite action cannot be invoked from Python. Two of the
three options in the task turn out to answer different questions, so both are
taken:
* WHERE THE RECIPE LIVES — the action's five run-steps are extracted into
.github/actions/install-arcticpy/install_arcticpy.sh. The action calls it via
${{ github.action_path }} (a composite action is downloaded with its whole
directory, so cross-repo consumers with no PyAutoHeart checkout still work);
smoke.py calls the same file out of the Heart checkout. One file, one pin,
both consumers running identical bytes.
* WHAT TRIGGERS IT — a per-workspace `arcticpy: true` key in the `smoke:`
block, mirroring the input the CTI CI callers already pass. Declared rather
than inferred from `PyAutoCTI in chain`, so CI and the local runner are
configured by the same explicit statement.
Rejected: a Python leg in smoke.py mirroring the recipe. That is the divergence
#170 was created to end.
While extracting, found the pin had quietly acquired two more copies:
action.yml's `version` input defaulted to "2.6", and arcticpy-action.yml passed
`${{ inputs.version || '2.6' }}`. The latter matters — the self-test would have
gone on proving the OLD version built after a bump. Both now defer to the
script's single default; no consumer passes `version` at all.
Two things found by running it rather than reading it:
* The GSL probe used `ls a b c`, which exits non-zero when ANY operand is
missing — so on every machine that actually has GSL (in exactly one prefix)
it reported the headers absent and refused to build. Now tests each prefix
independently, and the list is overridable via ARCTICPY_GSL_PREFIXES, which
also serves the no-root workaround in PyAutoCTI/AGENTS.md.
* `pip check` can NEVER pass in a CTI environment. arcticpy declares
numpy~=1.21 and is installed with --no-deps on purpose, since honouring it
downgrades numpy below 2.0 and breaks the stack, so the preflight reported
"arcticpy 2.6 has requirement numpy~=1.21, but you have numpy 2.5.2" and
destroyed every environment immediately after building it. The preflight now
tolerates exactly that one line, and only for a workspace that declared
`arcticpy: true`; any other broken requirement still fails.
The local leg never runs apt: a dev command must not mutate system packages, and
apt-get does not exist on macOS. It proves the headers are present and fails with
the install line instead.
The fingerprint hashes the shared script, so editing the recipe or bumping the
pin invalidates the cached environment rather than silently reusing a stale one.
Verified end to end, not just "the config parses" — `pyauto-heart smoke
autocti_test` on Python 3.12 built its environment (arcticpy 2.6 from the shared
recipe), passed preflight, and ran 3/3 scripts PASS.
Refs #172
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014wiN7R1yaeGj6k1Pa1FEq4
CI's `repos_sync.py --check --only "tenant firewall (organ code)"` failed the
pytest job — after all 602 tests passed — on three instance facts the previous
commit introduced. The firewall keeps satellite repo names out of PyAutoBrain /
PyAutoHeart / PyAutoHands so an adopting fork has a small, declared set of files
to rewrite. tests/test_smoke.py's own fixture comment says exactly this, and I
added the names anyway.
* heart/smoke.py and install_arcticpy.sh are UNLISTED, where any instance fact
is drift. Both mentions were prose in comments and carried no weight:
"inferred from ``PyAutoCTI in chain``" -> "inferred from the chain's
contents", and a pointer to the CTI library's AGENTS.md by name -> by role.
* tests/test_repo_config.py is allowlisted, but for {PyAutoCTI,
autocti_workspace, autocti_workspace_test} only; the new chain assertion
added PyAutoArray and PyAutoFit. Rather than grow the entry — the list's own
comment says never to do that casually — the assertion now goes through
`import_names`, comparing package names (autonerves/autofit/autoarray/
autocti) instead of repo-name literals. It says the same thing and
additionally proves every chain repo resolves in the map, so it is a
slightly better test than the one it replaces.
Verified by running the real check, not by inspection: with PyAutoHeart symlinked
under a root using the casing the checker expects (it silently SKIPS an organ
directory it cannot find, so a lowercase checkout gives a vacuous OK), it reports
OK — and still reports the mismatch when a fact is deliberately planted.
602 tests still pass.
Refs #172
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_014wiN7R1yaeGj6k1Pa1FEq4
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #172.
heart/smoke.pyis the local mirror of CI, but thesmoke:block inconfig/repos.yamlhad no autocti entries andimport_nameshad no PyAutoCTI. So neither CTI smoke suite could be run locally — the only way to exercise them was to push and wait, which is the slowest possible loop for the repo group that just acquired new coverage (autocti_workspace#28 gave that repo its first CI).The arcticpy design decision
import autoctihard-requires arcticpy, which is not a pip dependency, and a composite action cannot be invoked from Python. Two of the three options in the task turn out to answer different questions, so both are taken:.github/actions/install-arcticpy/install_arcticpy.sh. The action calls it via${{ github.action_path }};smoke.pycalls the same file from the Heart checkout. One file, one pin, both consumers running identical bytes.arcticpy: truekey in thesmoke:block, mirroring the input the CTI CI callers already pass. Declared rather than inferred fromPyAutoCTI in chain.Rejected: a Python leg in
smoke.pymirroring the recipe. That is exactly the divergence #170 was created to end.Extraction is safe for the action's cross-repo reach — a composite action is downloaded with its whole directory, so
${{ github.action_path }}/install_arcticpy.shresolves for a consumer with no PyAutoHeart checkout. All three consumers (lib-tests.yml×2,smoke-tests.yml) call the action with nowith:block at all, so nothing at any call site changes.The pin had quietly re-acquired two copies
While extracting, found that
arcticpy==2.6was written out in three places, not one:action.yml'sversioninputdefault: "2.6".github/workflows/arcticpy-action.yml'sversion: ${{ inputs.version || '2.6' }}The second one matters. After a pin bump, the self-test would have gone on building the old version — the one workflow whose entire job is to prove a recipe change works would have been testing the wrong thing. Both now defer to the script's single default.
Two things only running it could have found
1. The GSL probe was broken in the way that matters. My first cut used
ls a b c, which exits non-zero when any operand is missing — so on every machine that actually has GSL (in exactly one prefix) it reported the headers absent and refused to build. It now tests each prefix independently, and the list is overridable viaARCTICPY_GSL_PREFIXES, which also serves the no-root workaround inPyAutoCTI/AGENTS.md.2.
pip checkcan never pass in a CTI environment. The preflight built a perfect environment and then destroyed it:arcticpy declares
numpy~=1.21and is installed with--no-depson purpose — honouring it downgrades numpy below 2.0 and breaks the rest of the stack. So that metadata stays permanently unsatisfied andpip checkreports it every single time. Without handling this,arcticpy: truewould have been useless.The preflight now tolerates exactly that one line, and only for a workspace that declared
arcticpy: true. Any other broken requirement — in arcticpy or anything else — still fails.Local behaviour: builds arcticpy, never touches the system
smoke.pyruns the shared script with the GSL leg disabled. A local dev command must not mutate system packages, andapt-getdoes not exist on macOS — so instead of reaching for sudo it proves the headers are present and fails with the exact install line for the platform. Where GSL is present it builds unattended, sopyauto-heart smoke autoctiworks in one step.The fingerprint hashes the shared script under a fixed key, so editing the recipe or bumping the pin invalidates the cached environment rather than silently reusing one built from the old recipe. (It cannot go through the existing
relative_to(organism_root)path — the script lives in the Heart checkout, which need not sit under the organism root.)Test Plan
The acceptance criterion — actually running the suites, not "the config parses". Python 3.12, environments prepared by the local runner from scratch:
pyauto-heart smoke autocti_testdataset_1d/model_fit.py12.3s,imaging_ci/model_fit.py8.0s,plot/subplots.py11.1spyauto-heart smoke autoctidataset_1d/modeling/start_here.py30.1s,features/species_x3.py27.6s,imaging_ci/modeling/start_here.py159.1sBoth are cold numbers (datasets simulated from scratch). The
autoctisuite's own AGENTS.md records 132 s for CI; 217 s here reflects a slower container, not a regression.Unit tests: 602 pass (587 pre-existing + 15 new), zero failures.
New coverage:
arcticpydefaults off and is read from config; only the CTI workspaces request it; every chain repo has an import name (the omission that hid PyAutoCTI weakens the preflight silently rather than failing it).The fingerprint tracks the shared recipe, and a non-arcticpy workspace is unaffected by it.
The recipe runs before the workspace epilogue (CI's order), with an explicit
PYTHONandARCTICPY_INSTALL_GSL=false.A missing installer raises rather than skipping silently.
Three tests exercising the real script: the GSL probe accepts headers in any one prefix (the regression above), refuses with an actionable message without them, and never reaches for sudo when the GSL leg is disabled.
Four tests on the
pip checktolerance: the arcticpy line passes, any other conflict still fails, the arcticpy line is not tolerated for a workspace that never asked for it, and a clean check passes through.Both CTI suites run and pass through the local runner
602/602 unit tests
All three action consumers verified to pass no
versionoverridearcticpy-action.ymlself-test green on this PR — it triggers on.github/actions/install-arcticpy/**, so it exercises the extracted script end-to-end on 3.12 and 3.13Generated by Claude Code