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
4 changes: 4 additions & 0 deletions config/repos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ repos:
owner: PyAutoLabs
- name: PyAutoCTI
owner: PyAutoLabs
# Polled for health/CI, but NOT part of the release-validation gate yet:
# PyAutoCTI is mid-resurrection (not released), so it has no release
# evidence to confirm. Flip to true (or drop this key) when CTI ships.
release_gate: false

build_workflow:
- name: PyAutoBuild
Expand Down
34 changes: 28 additions & 6 deletions heart/readiness.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,36 @@


def load_library_names(config_path: Path | str = CONFIG_PATH) -> list[str]:
"""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."""
"""Return every polled library — repos.libraries[].name from the policy
file. Strict: the file is in-repo and the group is load-bearing — a missing
or empty group is a config bug that must fail loudly.

This is the full *polled* set (what the tick monitors). The *release gate*
is a subset — see ``load_release_gate_names``."""
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 load_release_gate_names(config_path: Path | str = CONFIG_PATH) -> list[str]:
"""The release-gating libraries: polled libraries minus those a library
marks ``release_gate: false``. A library can be polled for health/CI
(monitoring) without being part of the release-validation gate — e.g. a
library mid-resurrection that is not yet released and so has no release
evidence to confirm. Absent key ⇒ gating (the default); only an explicit
``false`` opts out. Strict: an empty result is a config bug."""
cfg = yaml.safe_load(Path(config_path).read_text()) or {}
libs = [
r["name"] for r in cfg["repos"]["libraries"]
if r.get("release_gate", True)
]
if not libs:
raise ValueError(f"no release-gating libraries in {config_path}")
return libs


def _as_int(v: Any, default: int = 0) -> int:
try:
return int(v)
Expand All @@ -163,8 +183,10 @@ def _as_int(v: Any, default: int = 0) -> int:
VALIDATION_STALE_DAYS = 7

# 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 = tuple(load_library_names())
# recorded commit_shas for the release-validation gate to go GREEN. This is the
# release-gating subset (a resurrecting, not-yet-released library is polled but
# excluded — see load_release_gate_names).
_GATE_SHA_LIBS = tuple(load_release_gate_names())


def _sha_eq(a: Any, b: Any) -> bool:
Expand Down Expand Up @@ -206,7 +228,7 @@ def compute(
) -> dict[str, Any]:
"""Pure verdict function. Never raises on missing/partial data."""
snapshot = snapshot or {}
libs = list(libraries) if libraries is not None else load_library_names()
libs = list(libraries) if libraries is not None else load_release_gate_names()
req_wf = required_workflows if required_workflows is not None else load_required_workflows()
repos = snapshot.get("repos", {}) or {}
ref = _parse_ts(snapshot.get("ts")) or datetime.datetime.now(datetime.timezone.utc)
Expand Down
17 changes: 14 additions & 3 deletions tests/test_repo_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,21 @@ def test_thresholds_have_expected_fields(config):
assert thresholds["script_timing"]["baseline_window"] >= 3


def test_22_repos_polled(config):
"""Sanity check the polled count — bumps need a deliberate update."""
def test_25_repos_polled(config):
"""Sanity check the polled count — bumps need a deliberate update.
(25 since the CTI resurrection added PyAutoCTI + autocti_workspace +
autocti_workspace_test to the polled registry.)"""
total = sum(len(v) for v in config["repos"].values())
assert total == 22, f"expected 22 polled repos, got {total}"
assert total == 25, f"expected 25 polled repos, got {total}"


def test_cti_polled_but_not_release_gating(config):
"""A resurrecting library is polled for health but excluded from the
release-validation gate until it ships (release_gate: false)."""
from heart import readiness

assert "PyAutoCTI" in readiness.load_library_names()
assert "PyAutoCTI" not in readiness.load_release_gate_names()


def _all_names(config):
Expand Down
Loading