Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions config/repos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,19 @@ required_workflows:
workspaces_test: ["Smoke Tests"] # smoke_tests.yml
howto: ["Smoke Tests", "Navigator Check"] # smoke_tests.yml + navigator_check.yml

# Version-skew policy: workspace pin vs installed library. Iterated directly
# by heart/checks/version_skew.py — independent of polling (includes unpolled
# repos like the assistant). An adopter replaces this with their own map.
version_skew:
autofit_workspace: {library: PyAutoFit, package: autofit}
autogalaxy_workspace: {library: PyAutoGalaxy, package: autogalaxy}
autolens_workspace: {library: PyAutoLens, package: autolens}
HowToFit: {library: PyAutoFit, package: autofit}
HowToGalaxy: {library: PyAutoGalaxy, package: autogalaxy}
HowToLens: {library: PyAutoLens, package: autolens}
euclid_strong_lens_modeling_pipeline: {library: PyAutoLens, package: autolens}
autolens_assistant: {library: PyAutoLens, package: autolens}

# Excluded — not polled. Listed here for documentation only.
excluded:
- admin_jammy # personal tooling
Expand Down
20 changes: 8 additions & 12 deletions heart/checks/version_skew.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,13 @@

_VERSION_RE = re.compile(r'^__version__\s*=\s*["\']([^"\']+)["\']', re.MULTILINE)

# workspace name -> (library repo dir, package dir holding __init__.py)
WORKSPACE_LIBRARY = {
"autofit_workspace": ("PyAutoFit", "autofit"),
"autogalaxy_workspace": ("PyAutoGalaxy", "autogalaxy"),
"autolens_workspace": ("PyAutoLens", "autolens"),
"HowToFit": ("PyAutoFit", "autofit"),
"HowToGalaxy": ("PyAutoGalaxy", "autogalaxy"),
"HowToLens": ("PyAutoLens", "autolens"),
"euclid_strong_lens_modeling_pipeline": ("PyAutoLens", "autolens"),
"autolens_assistant": ("PyAutoLens", "autolens"),
}
def workspace_library(config_path: Path | str = CONFIG_PATH) -> dict[str, tuple[str, str]]:
"""workspace name -> (library repo dir, package dir), from the policy
file's ``version_skew`` block. Strict: a missing block is a config bug
and fails loudly rather than silently checking nothing."""
cfg = yaml.safe_load(Path(config_path).read_text()) or {}
block = cfg["version_skew"]
return {ws: (spec["library"], spec["package"]) for ws, spec in block.items()}


def read_library_version(repo: str, pkg: str, root: Path = PYAUTO_ROOT) -> str | None:
Expand Down Expand Up @@ -126,7 +122,7 @@ def compare(pinned: str | None, installed: str | None) -> str:

def run(root: Path = PYAUTO_ROOT) -> dict[str, Any]:
workspaces = []
for workspace, (repo, pkg) in WORKSPACE_LIBRARY.items():
for workspace, (repo, pkg) in workspace_library().items():
yaml_v, txt_v = read_workspace_pin_sources(workspace, root)
if yaml_v is None and txt_v is None:
continue # no pin (e.g. *_test workspaces) → not a skew candidate
Expand Down
16 changes: 14 additions & 2 deletions heart/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,20 @@
STALE_AFTER_SECONDS = 3600

# Library repos, used to split the per-repo table into libraries vs workspaces
# when a repo body carries no group label.
DEFAULT_LIBRARIES = ("PyAutoConf", "PyAutoFit", "PyAutoArray", "PyAutoGalaxy", "PyAutoLens")
# when a repo body carries no group label. Derived from the policy file
# (config/repos.yaml `repos.libraries`) — dashboard cannot import readiness
# (cycle), so it reads the same file directly.
def _library_names() -> tuple:
import pathlib

import yaml

cfg_path = pathlib.Path(__file__).resolve().parents[1] / "config" / "repos.yaml"
cfg = yaml.safe_load(cfg_path.read_text()) or {}
return tuple(r["name"] for r in cfg["repos"]["libraries"])


DEFAULT_LIBRARIES = _library_names()
GATED_WORKSPACE_GROUPS = frozenset({"workspaces", "workspaces_test", "howto"})

# The public Pages board (the badge's entry point). Kept here so every surface
Expand Down
18 changes: 9 additions & 9 deletions heart/readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
CONFIG_PATH = HEART_HOME / "config" / "repos.yaml"
RELEASE_READY_FILE = state.HEART_STATE_DIR / "release_ready.json"

DEFAULT_LIBRARIES = ("PyAutoConf", "PyAutoFit", "PyAutoArray", "PyAutoGalaxy", "PyAutoLens")

# Workspace repo groups whose required-workflow conclusions on `main` HEAD are a
# hard release gate (RED on failure). Libraries are gated separately (the lib
Expand Down Expand Up @@ -137,13 +136,14 @@


def load_library_names(config_path: Path | str = CONFIG_PATH) -> list[str]:
"""Return repos.libraries[].name, or DEFAULT_LIBRARIES if unavailable."""
try:
cfg = yaml.safe_load(Path(config_path).read_text()) or {}
libs = [r["name"] for r in cfg.get("repos", {}).get("libraries", [])]
return libs or list(DEFAULT_LIBRARIES)
except (OSError, yaml.YAMLError, KeyError, TypeError):
return list(DEFAULT_LIBRARIES)
"""Return repos.libraries[].name from the policy file. Strict: the file
is in-repo and the group is load-bearing (the release gate) — a missing
or empty group is a config bug that must fail loudly."""
cfg = yaml.safe_load(Path(config_path).read_text()) or {}
libs = [r["name"] for r in cfg["repos"]["libraries"]]
if not libs:
raise ValueError(f"empty repos.libraries group in {config_path}")
return libs


def _as_int(v: Any, default: int = 0) -> int:
Expand All @@ -164,7 +164,7 @@ def _as_int(v: Any, default: int = 0) -> int:

# The library repos whose current `main` HEAD must match a validation report's
# recorded commit_shas for the release-validation gate to go GREEN.
_GATE_SHA_LIBS = DEFAULT_LIBRARIES
_GATE_SHA_LIBS = tuple(load_library_names())


def _sha_eq(a: Any, b: Any) -> bool:
Expand Down
6 changes: 4 additions & 2 deletions tests/test_version_skew.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,7 @@ def test_run_unknown_when_library_not_checked_out(tmp_path):

def test_autolens_assistant_is_a_pinned_workspace():
# Gap closed vs verify_workspace_versions.sh, which covers 8 workspaces.
assert "autolens_assistant" in vs.WORKSPACE_LIBRARY
assert vs.WORKSPACE_LIBRARY["autolens_assistant"] == ("PyAutoLens", "autolens")
# The map now lives in config/repos.yaml `version_skew` (the policy file).
mapping = vs.workspace_library()
assert "autolens_assistant" in mapping
assert mapping["autolens_assistant"] == ("PyAutoLens", "autolens")
Loading