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
43 changes: 24 additions & 19 deletions autonerves/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,10 @@ def _below_floor_message(floor_version, library_version, workspace_root):
)


def _stale_workspace_message(floor_version, library_version, workspace_root):
def _stale_workspace_message(workspace_version, library_version, workspace_root):
return (
f"The workspace at {workspace_root} records library version "
f"{floor_version}, but the installed library is {library_version} — "
f"The workspace at {workspace_root} records release version "
f"{workspace_version}, but the installed library is {library_version} — "
f"more than {_STALENESS_WINDOW_DAYS} days newer. The workspace "
f"examples and configs may lag the installed API. Pull the latest "
f"workspace:\n\n"
Expand Down Expand Up @@ -211,8 +211,11 @@ def check_version(library_version, workspace_root=None):
An installed library **older** than the floor raises
``WorkspaceVersionMismatchError`` — the workspace's scripts may use API
the install does not have. An installed library **newer** than the floor
passes; if it is more than ``_STALENESS_WINDOW_DAYS`` days newer (by
date-version comparison) a warning suggests pulling the workspace.
passes. Freshness is checked separately against ``workspace_version``
(or legacy ``version.txt``), never against ``minimum_library_version``.
If the library is more than ``_STALENESS_WINDOW_DAYS`` days newer than
that release stamp, a warning suggests pulling the workspace. Without
a release stamp, no freshness warning is issued.
Versions that cannot be parsed as date versions (e.g. development
installs) warn on inequality rather than raising.

Expand Down Expand Up @@ -246,29 +249,28 @@ def check_version(library_version, workspace_root=None):
if _yaml_bypass_set(general_yaml):
return

floor_version = _yaml_version_value(general_yaml, "minimum_library_version")

if floor_version is None:
floor_version = _yaml_version_value(general_yaml, "workspace_version")

if floor_version is None:
workspace_version = _yaml_version_value(general_yaml, "workspace_version")
if workspace_version is None:
version_file = root / "version.txt"
if version_file.exists():
floor_version = version_file.read_text().strip()
workspace_version = version_file.read_text().strip()

floor_version = _yaml_version_value(general_yaml, "minimum_library_version")
if floor_version is None:
floor_version = workspace_version

if floor_version is None or floor_version == "":
if _is_source_checkout(root) or not _looks_like_workspace(root):
return
_warn_once(_missing_version_warning(root, library_version))
return

if floor_version == library_version:
return

floor_parsed = _parse_version(floor_version)
library_parsed = _parse_version(library_version)

if floor_parsed is None or library_parsed is None:
if floor_version == library_version:
return
_warn_once(
_unparseable_mismatch_message(floor_version, library_version, root)
)
Expand All @@ -279,12 +281,15 @@ def check_version(library_version, workspace_root=None):
_below_floor_message(floor_version, library_version, root)
)

floor_date = _version_date(floor_parsed)
workspace_parsed = _parse_version(workspace_version)
workspace_date = (
_version_date(workspace_parsed) if workspace_parsed is not None else None
)
library_date = _version_date(library_parsed)

if (
floor_date is not None
workspace_date is not None
and library_date is not None
and (library_date - floor_date).days > _STALENESS_WINDOW_DAYS
and (library_date - workspace_date).days > _STALENESS_WINDOW_DAYS
):
_warn_once(_stale_workspace_message(floor_version, library_version, root))
_warn_once(_stale_workspace_message(workspace_version, library_version, root))
66 changes: 66 additions & 0 deletions test_autonerves/test_workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,72 @@ def test_below_minimum_library_version_raises_with_upgrade_advice(tmp_path):
assert "==" not in msg


def test_old_compatibility_floor_without_release_stamp_is_not_stale(tmp_path):
_write_general_yaml(tmp_path, "version:\n minimum_library_version: 2026.7.9.1")
with warnings.catch_warnings():
warnings.simplefilter("error")
check_version("2026.10.7.1", workspace_root=tmp_path)


@pytest.mark.parametrize("stamp_source", ["yaml", "version.txt"])
@pytest.mark.parametrize("stamp", ["2026.10.7.1", "2026.9.7.1"])
def test_release_stamp_not_old_floor_controls_freshness(tmp_path, stamp_source, stamp):
body = "version:\n minimum_library_version: 2026.7.9.1\n"
if stamp_source == "yaml":
body += f" workspace_version: {stamp}\n"
else:
(tmp_path / "version.txt").write_text(stamp)
_write_general_yaml(tmp_path, body)
with warnings.catch_warnings():
warnings.simplefilter("error")
check_version("2026.10.7.1", workspace_root=tmp_path)


@pytest.mark.parametrize("floor", ["2026.7.9.1", "2026.10.7.1"])
def test_stale_stamp_warns_even_with_a_different_compatibility_floor(tmp_path, floor):
_write_general_yaml(
tmp_path,
f"version:\n minimum_library_version: {floor}\n"
" workspace_version: 2026.9.6.1\n",
)
with pytest.warns(UserWarning, match="release version 2026.9.6.1"):
check_version("2026.10.7.1", workspace_root=tmp_path)


def test_yaml_release_stamp_overrides_old_version_file_for_freshness(tmp_path):
_write_general_yaml(
tmp_path,
"version:\n minimum_library_version: 2026.7.9.1\n"
" workspace_version: 2026.10.7.1\n",
)
(tmp_path / "version.txt").write_text("2026.7.9.1")
with warnings.catch_warnings():
warnings.simplefilter("error")
check_version("2026.10.7.1", workspace_root=tmp_path)


def test_fresh_release_stamp_does_not_bypass_compatibility_floor(tmp_path):
_write_general_yaml(
tmp_path,
"version:\n minimum_library_version: 2026.10.8.1\n"
" workspace_version: 2026.10.7.1\n",
)
with pytest.raises(WorkspaceVersionMismatchError):
check_version("2026.10.7.1", workspace_root=tmp_path)


@pytest.mark.parametrize("stamp", ["dev", "2026.13.7.1", ""])
def test_unusable_release_stamp_does_not_turn_floor_into_freshness(tmp_path, stamp):
_write_general_yaml(
tmp_path,
"version:\n minimum_library_version: 2026.7.9.1\n"
f" workspace_version: '{stamp}'\n",
)
with warnings.catch_warnings():
warnings.simplefilter("error")
check_version("2026.10.7.1", workspace_root=tmp_path)


def test_unparseable_library_version_warns_not_raises(tmp_path):
(tmp_path / "version.txt").write_text("2026.7.22.1\n")
with pytest.warns(UserWarning, match="cannot be compared"):
Expand Down
Loading