You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
heart/smoke.py is the local mirror of what CI does — "one isolated environment per workspace, prepared from the workspace-owned installer". Which workspaces it can prepare comes from the smoke: block in config/repos.yaml, and the CTI repos are absent from it entirely: import_names has no PyAutoCTI, and workspaces has no autocti or autocti_test.
So pyauto-heart smoke autocti cannot work, and neither can a local run of autocti_workspace_test. As of 2026-08-24 there are two CTI smoke surfaces — autocti_workspace_test (3 scripts, ~20 s) and autocti_workspace (new in autocti_workspace#28, 3 curated scripts, ~132 s cold) — and the only way to exercise either is to push and wait for CI. That is the slowest possible loop for the repo group that just acquired new coverage.
Plan
Add PyAutoCTI: autocti to smoke.import_names so the preflight can prove a CTI environment at all.
Add autocti and autocti_test workspace entries with chain [PyAutoNerves, PyAutoFit, PyAutoArray, PyAutoCTI].
Verify by actually preparing a CTI environment locally and running a suite through it, not by "the config parses".
The arcticpy decision
import autocti hard-requires arcticpy, which is not a pip dependency. In CI this is solved: .github/actions/install-arcticpy owns the recipe and the single arcticpy==2.6 pin, and Heart's smoke-tests.yml runs it behind the arcticpy: true input before the workspace epilogue. A composite action cannot be invoked from heart/smoke.py, so the local runner needs an equivalent.
The prompt offered three options. Two of them turn out to be answers to different questions, so both are taken:
question
answer
where does the recipe live?
Extract the action's steps into .github/actions/install-arcticpy/install_arcticpy.sh. The action calls it via ${{ github.action_path }}; smoke.py calls the same file from the Heart checkout. One file, one pin, both consumers execute identical bytes.
what triggers it locally?
A per-workspace arcticpy: true key in the smoke: block, mirroring the arcticpy: true input the CI callers already pass. Explicit rather than inferred from PyAutoCTI in chain.
The rejected option is the prompt's middle one — a Python leg in smoke.py mirroring the recipe. That is precisely the divergence #170 was created to end (the recipe had drifted into four copies), and it would put a second copy of the 2.6 pin in the tree.
Extraction is safe for the action's reach: a composite action is downloaded with its whole directory, so ${{ github.action_path }}/install_arcticpy.sh resolves for a consumer that has no checkout of PyAutoHeart — the property that made a composite action the right shape in the first place. Its version / sudo / install-gsl inputs keep working, passed through as environment variables.
How far the local runner goes (decided)
It builds arcticpy, but never touches system packages.smoke.py runs the shared script with the GSL leg disabled: the script probes for gsl/gsl_version.h and, if absent, fails with the exact apt-get line to run. Where GSL is present it builds and installs arcticpy into the isolated venv unattended, so pyauto-heart smoke autocti works in one step.
Rejected: running apt-get (a local dev command should not mutate system packages, and it is a hard no-op on macOS); and preflight-only "arcticpy must already be importable" (leaves the manual step this task exists to remove).
.github/actions/install-arcticpy/install_arcticpy.sh — new. Carries the recipe verbatim (GSL, build deps, runtime deps, the pinned build, the importlib.metadata verify) plus the long WHY comment. Env contract: ARCTICPY_VERSION (default 2.6 — the pin), ARCTICPY_INSTALL_GSL, ARCTICPY_SUDO, PYTHON (default python). Writes version= to $GITHUB_OUTPUT only when that variable is set, so it is equally usable outside Actions.
.github/actions/install-arcticpy/action.yml — its five run-steps collapse to one bash "${{ github.action_path }}/install_arcticpy.sh" with the inputs mapped to those variables. Inputs, defaults and the version output are unchanged, so every consumer is byte-identical at the call site.
config/repos.yaml — PyAutoCTI: autocti in import_names; autocti and autocti_test entries carrying arcticpy: true.
heart/smoke.py —
WorkspaceSpec gains arcticpy: bool (default False); load_smoke_config reads it.
_install_environment runs the shared script before the workspace epilogue when the flag is set — the same order as CI.
environment_fingerprint hashes the shared script under a fixed key when the flag is set, so editing the recipe or bumping the pin invalidates the cached environment instead of silently reusing a stale one. (It cannot go through the existing relative_to(organism_root) path — the script lives in the Heart checkout, not the organism root.)
GSL probe with an actionable error naming the apt line.
Tests — tests/test_smoke.py / tests/test_repo_config.py for the new key, the fingerprint sensitivity, and the ordering.
Verification (the acceptance criterion)
Prepare a CTI environment through the local runner and run a suite through it. autocti_workspace_test is the cheap one (~20 s); autocti_workspace is the new 132 s suite.
--no-deps suppresses arcticpy's runtime imports too: arcticpy/read_noise.py imports scipyandmatplotlib, and __init__.py imports it.
arcticpy exposes no__version__. arcticpy.__version__ raises AttributeError on a healthy install; use importlib.metadata.version("arcticpy").
Original Prompt
Click to expand starting prompt
Heart's local smoke runner cannot run any CTI workspace — no autocti entry
Type: maintenance
Target: pyautoheart
Repos:
@PyAutoHeart
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Filed: 2026-08-24
PyAutoHeart/heart/smoke.py is the local smoke runner — "one isolated
environment per workspace, prepared from the workspace-owned installer", the
local mirror of what CI does. Which workspaces it can prepare is declared in config/repos.yaml under the smoke: block, and the CTI repos are absent
from it entirely.
So pyauto-heart smoke autocti cannot work, and neither can a local smoke run of autocti_workspace_test — the only way to exercise CTI smoke is to push and let
CI do it. That is a slow loop for the repo group that has just acquired two smoke
suites.
Work
Add PyAutoCTI: autocti to smoke.import_names.
Add the two workspace entries with the correct chain
([PyAutoNerves, PyAutoFit, PyAutoArray, PyAutoCTI]).
Handle arcticpy. This is the real design question. A composite action
cannot be invoked from heart/smoke.py, so the local runner needs an
equivalent. Decide deliberately between: factoring the recipe into a shell
script that both the action and smoke.py call; a small Python leg in smoke.py that mirrors it; or a per-workspace arcticpy: true flag in the smoke: block that smoke.py honours.
Whatever is chosen, there must remain exactly one place the recipe and the 2.6 pin live. Re-creating a second copy would undo PyAutoHeart#170.
Verify by actually running it — prepare a CTI environment through the
local runner and run both suites, not just "the config parses".
Context
PyAutoMind/complete/2026/08/arcticpy-install-standardisation.md — why the
recipe has one owner and what breaks when it does not.
Overview
heart/smoke.pyis the local mirror of what CI does — "one isolated environment per workspace, prepared from the workspace-owned installer". Which workspaces it can prepare comes from thesmoke:block inconfig/repos.yaml, and the CTI repos are absent from it entirely:import_nameshas noPyAutoCTI, andworkspaceshas noautoctiorautocti_test.So
pyauto-heart smoke autocticannot work, and neither can a local run ofautocti_workspace_test. As of 2026-08-24 there are two CTI smoke surfaces —autocti_workspace_test(3 scripts, ~20 s) andautocti_workspace(new in autocti_workspace#28, 3 curated scripts, ~132 s cold) — and the only way to exercise either is to push and wait for CI. That is the slowest possible loop for the repo group that just acquired new coverage.Plan
PyAutoCTI: autoctitosmoke.import_namesso the preflight can prove a CTI environment at all.autoctiandautocti_testworkspace entries with chain[PyAutoNerves, PyAutoFit, PyAutoArray, PyAutoCTI].The arcticpy decision
import autoctihard-requires arcticpy, which is not a pip dependency. In CI this is solved:.github/actions/install-arcticpyowns the recipe and the singlearcticpy==2.6pin, and Heart'ssmoke-tests.ymlruns it behind thearcticpy: trueinput before the workspace epilogue. A composite action cannot be invoked fromheart/smoke.py, so the local runner needs an equivalent.The prompt offered three options. Two of them turn out to be answers to 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 execute identical bytes.arcticpy: truekey in thesmoke:block, mirroring thearcticpy: trueinput the CI callers already pass. Explicit rather than inferred fromPyAutoCTI in chain.The rejected option is the prompt's middle one — a Python leg in
smoke.pymirroring the recipe. That is precisely the divergence #170 was created to end (the recipe had drifted into four copies), and it would put a second copy of the2.6pin in the tree.Extraction is safe for the action's reach: a composite action is downloaded with its whole directory, so
${{ github.action_path }}/install_arcticpy.shresolves for a consumer that has no checkout of PyAutoHeart — the property that made a composite action the right shape in the first place. Itsversion/sudo/install-gslinputs keep working, passed through as environment variables.How far the local runner goes (decided)
It builds arcticpy, but never touches system packages.
smoke.pyruns the shared script with the GSL leg disabled: the script probes forgsl/gsl_version.hand, if absent, fails with the exactapt-getline to run. Where GSL is present it builds and installs arcticpy into the isolated venv unattended, sopyauto-heart smoke autoctiworks in one step.Rejected: running
apt-get(a local dev command should not mutate system packages, and it is a hard no-op on macOS); and preflight-only "arcticpy must already be importable" (leaves the manual step this task exists to remove).Detailed implementation plan
Affected Repositories
Branch Survey
Suggested branch:
feature/heart-smoke-table-autoctiImplementation Steps
.github/actions/install-arcticpy/install_arcticpy.sh— new. Carries the recipe verbatim (GSL, build deps, runtime deps, the pinned build, theimportlib.metadataverify) plus the long WHY comment. Env contract:ARCTICPY_VERSION(default2.6— the pin),ARCTICPY_INSTALL_GSL,ARCTICPY_SUDO,PYTHON(defaultpython). Writesversion=to$GITHUB_OUTPUTonly when that variable is set, so it is equally usable outside Actions..github/actions/install-arcticpy/action.yml— its five run-steps collapse to onebash "${{ github.action_path }}/install_arcticpy.sh"with the inputs mapped to those variables. Inputs, defaults and theversionoutput are unchanged, so every consumer is byte-identical at the call site.config/repos.yaml—PyAutoCTI: autoctiinimport_names;autoctiandautocti_testentries carryingarcticpy: true.heart/smoke.py—WorkspaceSpecgainsarcticpy: bool(defaultFalse);load_smoke_configreads it._install_environmentruns the shared script before the workspace epilogue when the flag is set — the same order as CI.environment_fingerprinthashes the shared script under a fixed key when the flag is set, so editing the recipe or bumping the pin invalidates the cached environment instead of silently reusing a stale one. (It cannot go through the existingrelative_to(organism_root)path — the script lives in the Heart checkout, not the organism root.)tests/test_smoke.py/tests/test_repo_config.pyfor the new key, the fingerprint sensitivity, and the ordering.Verification (the acceptance criterion)
Prepare a CTI environment through the local runner and run a suite through it.
autocti_workspace_testis the cheap one (~20 s);autocti_workspaceis the new 132 s suite.Key Files
.github/actions/install-arcticpy/{action.yml,install_arcticpy.sh}config/repos.yaml— thesmoke:blockheart/smoke.py—WorkspaceSpec,load_smoke_config,_install_environment,environment_fingerprintTwo traps not to rediscover
--no-depssuppresses arcticpy's runtime imports too:arcticpy/read_noise.pyimportsscipyandmatplotlib, and__init__.pyimports it.__version__.arcticpy.__version__raisesAttributeErroron a healthy install; useimportlib.metadata.version("arcticpy").Original Prompt
Click to expand starting prompt
Heart's local smoke runner cannot run any CTI workspace — no autocti entry
Type: maintenance
Target: pyautoheart
Repos:
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised
Filed: 2026-08-24
PyAutoHeart/heart/smoke.pyis the local smoke runner — "one isolatedenvironment per workspace, prepared from the workspace-owned installer", the
local mirror of what CI does. Which workspaces it can prepare is declared in
config/repos.yamlunder thesmoke:block, and the CTI repos are absentfrom it entirely.
So
pyauto-heart smoke autocticannot work, and neither can a local smoke run ofautocti_workspace_test— the only way to exercise CTI smoke is to push and letCI do it. That is a slow loop for the repo group that has just acquired two smoke
suites.
Work
Add
PyAutoCTI: autoctitosmoke.import_names.Add the two workspace entries with the correct chain
(
[PyAutoNerves, PyAutoFit, PyAutoArray, PyAutoCTI]).Handle arcticpy. This is the real design question. A composite action
cannot be invoked from
heart/smoke.py, so the local runner needs anequivalent. Decide deliberately between: factoring the recipe into a shell
script that both the action and
smoke.pycall; a small Python leg insmoke.pythat mirrors it; or a per-workspacearcticpy: trueflag in thesmoke:block thatsmoke.pyhonours.Whatever is chosen, there must remain exactly one place the recipe and the
2.6pin live. Re-creating a second copy would undo PyAutoHeart#170.Verify by actually running it — prepare a CTI environment through the
local runner and run both suites, not just "the config parses".
Context
PyAutoMind/complete/2026/08/arcticpy-install-standardisation.md— why therecipe has one owner and what breaks when it does not.