Skip to content

Workspace Validation #3

Workspace Validation

Workspace Validation #3

name: Workspace Validation
# Pulse-owned workspace-integration validation (Stage 4 Phase B).
#
# This is the "heavy validation CI" that used to live in PyAutoBuild's
# release.yml (find_scripts / generate_notebooks / run_scripts / run_notebooks /
# analyze_results). Pulse owns it now: Build is a pure executor. It runs every
# workspace's scripts AND generated notebooks against the CURRENT source `main`
# of the 5 libraries (source-shadowed via PYTHONPATH), aggregates into the same
# report.json contract, and uploads it as the `workspace-validation-report`
# artifact. Pulse's test_run check reads that run's
# conclusion + timestamp into the authoritative `readiness` verdict (with a
# staleness window).
#
# Run mechanics (script_matrix.py / run_python.py / aggregate_results.py) are
# Build's executor primitives — checked out from PyAutoBuild, not duplicated.
#
# Triggers: weekly schedule (the continuous readiness signal), manual dispatch,
# and workflow_call (so a release path can invoke it on demand). Heavy, so never
# in the <30s pulse tick.
on:
schedule:
- cron: "0 3 * * 1" # Mondays 03:00 UTC
workflow_dispatch:
workflow_call:
permissions:
contents: read
env:
# Workspace pins lag the libraries' source __version__ between releases;
# without this every script fails fast with WorkspaceVersionMismatchError.
# Same bypass run_scripts uses in release.yml.
PYAUTO_SKIP_WORKSPACE_VERSION_CHECK: "1"
jobs:
find_scripts:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.script_matrix.outputs.matrix }}
steps:
- name: Checkout PyAutoBuild (run primitives)
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBuild
path: PyAutoBuild
- name: Clone workspaces (paths match script_matrix project names)
run: |
set -e
declare -A WS=(
[autofit]=autofit_workspace [autogalaxy]=autogalaxy_workspace [autolens]=autolens_workspace
[autofit_test]=autofit_workspace_test [autogalaxy_test]=autogalaxy_workspace_test
[autolens_test]=autolens_workspace_test
[howtogalaxy]=HowToGalaxy [howtolens]=HowToLens [howtofit]=HowToFit
)
for proj in "${!WS[@]}"; do
git clone --depth 1 "https://github.com/PyAutoLabs/${WS[$proj]}" "$proj"
done
- name: Make script matrix
id: script_matrix
run: |
matrix="$(python3 PyAutoBuild/autobuild/script_matrix.py \
autofit autogalaxy autolens autofit_test autogalaxy_test autolens_test \
howtogalaxy howtolens howtofit)"
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
generate_notebooks:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Notebooks exist only for the 6 doc workspaces (the *_test workspaces
# publish none). generate.py just converts scripts → .ipynb (no lib run).
project:
- { name: autofit, workspace: autofit_workspace }
- { name: autogalaxy, workspace: autogalaxy_workspace }
- { name: autolens, workspace: autolens_workspace }
- { name: howtogalaxy, workspace: HowToGalaxy }
- { name: howtolens, workspace: HowToLens }
- { name: howtofit, workspace: HowToFit }
steps:
- name: Checkout PyAutoBuild (generate.py)
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBuild
path: PyAutoBuild
- name: Checkout workspace
uses: actions/checkout@v4
with:
repository: PyAutoLabs/${{ matrix.project.workspace }}
ref: main
path: workspace
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install notebook tooling
run: pip install jupyter ipynb-py-convert PyYAML
- name: Generate notebooks
run: |
export PYTHONPATH="$PYTHONPATH:$(pwd)/PyAutoBuild"
pushd workspace
python3 "$(pwd)/../PyAutoBuild/autobuild/generate.py" "${{ matrix.project.name }}"
- name: Upload generated notebooks
if: always()
uses: actions/upload-artifact@v4
with:
name: notebooks-${{ matrix.project.name }}
path: workspace/notebooks/
retention-days: 7
run_scripts:
runs-on: ubuntu-latest
needs: find_scripts
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
project: ${{ fromJSON(needs.find_scripts.outputs.matrix) }}
steps:
- name: Checkout PyAutoBuild (run primitives)
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBuild
path: PyAutoBuild
- name: Resolve workspace repo for ${{ matrix.project.name }}
id: ws
run: |
case "${{ matrix.project.name }}" in
autofit) echo "repo=autofit_workspace" >> "$GITHUB_OUTPUT" ;;
autogalaxy) echo "repo=autogalaxy_workspace" >> "$GITHUB_OUTPUT" ;;
autolens) echo "repo=autolens_workspace" >> "$GITHUB_OUTPUT" ;;
autofit_test) echo "repo=autofit_workspace_test" >> "$GITHUB_OUTPUT" ;;
autogalaxy_test) echo "repo=autogalaxy_workspace_test" >> "$GITHUB_OUTPUT" ;;
autolens_test) echo "repo=autolens_workspace_test" >> "$GITHUB_OUTPUT" ;;
howtogalaxy) echo "repo=HowToGalaxy" >> "$GITHUB_OUTPUT" ;;
howtolens) echo "repo=HowToLens" >> "$GITHUB_OUTPUT" ;;
howtofit) echo "repo=HowToFit" >> "$GITHUB_OUTPUT" ;;
*) echo "repo=autolens_workspace_test" >> "$GITHUB_OUTPUT" ;;
esac
- name: Checkout workspace
uses: actions/checkout@v4
with:
repository: PyAutoLabs/${{ steps.ws.outputs.repo }}
ref: main
path: workspace
- name: Checkout libraries (current main; source-shadowed via PYTHONPATH)
run: |
set -e
for lib in PyAutoConf PyAutoArray PyAutoFit PyAutoGalaxy PyAutoLens; do
git clone --depth 1 "https://github.com/PyAutoLabs/$lib" "libs/$lib"
done
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install third-party deps (libs run from source)
run: |
# Pull the full transitive dependency set (matplotlib/numba/jax/…) by
# installing the published autolens[optional]; the source checkouts
# below shadow the PyAuto packages via PYTHONPATH.
pip install "autolens[optional]"
pip install "jax>=0.7,<0.11" "jaxlib>=0.7,<0.11"
# nufftax backs the interferometer TransformerNUFFT and is NOT pulled
# transitively by autolens[optional]; without it every interferometer
# simulator (and its downstream scripts) fails fast. Matches the pin
# PyAutoArray declares (jax 0.10.x + nufftax 0.4.x is the tested combo).
pip install "nufftax>=0.4.0,<0.5.0"
- name: Run Python scripts
run: |
LIBS="$(pwd)/libs"
export PYTHONPATH="$LIBS/PyAutoConf:$LIBS/PyAutoArray:$LIBS/PyAutoFit:$LIBS/PyAutoGalaxy:$LIBS/PyAutoLens:$(pwd)/PyAutoBuild:$PYTHONPATH"
pushd workspace
python3 "$(pwd)/../PyAutoBuild/autobuild/run_python.py" \
"${{ matrix.project.name }}" "scripts/${{ matrix.project.directory }}" \
--report-dir test-results
- name: Upload script results
if: always()
uses: actions/upload-artifact@v4
with:
name: results-scripts-${{ matrix.project.name }}-${{ matrix.project.directory }}
path: workspace/test-results/
retention-days: 30
run_notebooks:
runs-on: ubuntu-latest
needs: [find_scripts, generate_notebooks]
strategy:
fail-fast: false
matrix:
python-version: ["3.12"]
project: ${{ fromJSON(needs.find_scripts.outputs.matrix) }}
steps:
- name: Gate (*_test workspaces have no notebooks)
id: gate
run: |
if [[ "${{ matrix.project.name }}" == *_test ]]; then
echo "run=false" >> "$GITHUB_OUTPUT"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi
- name: Checkout PyAutoBuild (run primitives)
if: steps.gate.outputs.run == 'true'
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBuild
path: PyAutoBuild
- name: Resolve workspace repo
if: steps.gate.outputs.run == 'true'
id: ws
run: |
case "${{ matrix.project.name }}" in
autofit) echo "repo=autofit_workspace" >> "$GITHUB_OUTPUT" ;;
autogalaxy) echo "repo=autogalaxy_workspace" >> "$GITHUB_OUTPUT" ;;
autolens) echo "repo=autolens_workspace" >> "$GITHUB_OUTPUT" ;;
howtogalaxy) echo "repo=HowToGalaxy" >> "$GITHUB_OUTPUT" ;;
howtolens) echo "repo=HowToLens" >> "$GITHUB_OUTPUT" ;;
howtofit) echo "repo=HowToFit" >> "$GITHUB_OUTPUT" ;;
*) echo "repo=autolens_workspace" >> "$GITHUB_OUTPUT" ;;
esac
- name: Checkout workspace
if: steps.gate.outputs.run == 'true'
uses: actions/checkout@v4
with:
repository: PyAutoLabs/${{ steps.ws.outputs.repo }}
ref: main
path: workspace
- name: Checkout libraries (source-shadowed via PYTHONPATH)
if: steps.gate.outputs.run == 'true'
run: |
set -e
for lib in PyAutoConf PyAutoArray PyAutoFit PyAutoGalaxy PyAutoLens; do
git clone --depth 1 "https://github.com/PyAutoLabs/$lib" "libs/$lib"
done
- name: Download generated notebooks
if: steps.gate.outputs.run == 'true'
uses: actions/download-artifact@v4
with:
name: notebooks-${{ matrix.project.name }}
path: workspace/notebooks/
- name: Set up Python ${{ matrix.python-version }}
if: steps.gate.outputs.run == 'true'
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install deps (libs from source) + jupyter
if: steps.gate.outputs.run == 'true'
run: |
pip install "autolens[optional]"
pip install "jax>=0.7,<0.11" "jaxlib>=0.7,<0.11"
pip install "nufftax>=0.4.0,<0.5.0"
pip install jupyter ipynb-py-convert
- name: Run notebooks
if: steps.gate.outputs.run == 'true'
run: |
LIBS="$(pwd)/libs"
export PYTHONPATH="$LIBS/PyAutoConf:$LIBS/PyAutoArray:$LIBS/PyAutoFit:$LIBS/PyAutoGalaxy:$LIBS/PyAutoLens:$(pwd)/PyAutoBuild:$PYTHONPATH"
pushd workspace
python3 "$(pwd)/../PyAutoBuild/autobuild/run.py" \
"${{ matrix.project.name }}" "notebooks/${{ matrix.project.directory }}" \
--report-dir test-results
- name: Upload notebook results
if: always() && steps.gate.outputs.run == 'true'
uses: actions/upload-artifact@v4
with:
name: results-notebooks-${{ matrix.project.name }}-${{ matrix.project.directory }}
path: workspace/test-results/
retention-days: 30
analyze:
runs-on: ubuntu-latest
needs: [run_scripts, run_notebooks]
if: always()
steps:
- name: Checkout PyAutoBuild (aggregate_results)
uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBuild
path: PyAutoBuild
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Download all result artifacts
uses: actions/download-artifact@v4
with:
path: all-results
pattern: results-*
merge-multiple: true
- name: Aggregate into report.json
run: |
export PYTHONPATH="$PYTHONPATH:$(pwd)/PyAutoBuild"
python3 PyAutoBuild/autobuild/aggregate_results.py all-results/ \
--output report.json --markdown report.md
- name: Post summary
if: always()
run: cat report.md >> "$GITHUB_STEP_SUMMARY" || true
- name: Upload report (consumed by pyauto-pulse readiness)
if: always()
uses: actions/upload-artifact@v4
with:
name: workspace-validation-report
path: |
report.json
report.md
retention-days: 90
- name: Fail the run if validation is not ready
run: |
python3 -c "import json,sys; sys.exit(0 if json.load(open('report.json')).get('ready') else 1)"