From f749bcf187ef8e8e970152e3d39c5091c7517ff7 Mon Sep 17 00:00:00 2001 From: Ayush Nangia Date: Sun, 16 Aug 2026 01:06:20 +0530 Subject: [PATCH 1/3] fix(harbor): validate schema_version in adapt and stamp it in export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exported task.toml carries a top-level 'version' key, but the adapter parses 'schema_version' — so the stamped version was invisible to validation and a task declaring any unknown schema_version adapted silently with wrong semantics. Define HARBOR_SCHEMA_VERSION, fail adaptation loudly for unsupported values, and stamp the field in export so round-tripped tasks validate against the same contract. --- hud/integrations/harbor/adapt.py | 16 +++++++++++ hud/integrations/harbor/export.py | 3 +++ .../harbor/tests/test_contract.py | 27 +++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/hud/integrations/harbor/adapt.py b/hud/integrations/harbor/adapt.py index 81b86bdf2..65d5f0ae8 100644 --- a/hud/integrations/harbor/adapt.py +++ b/hud/integrations/harbor/adapt.py @@ -189,6 +189,13 @@ class TaskConfig(BaseModel): steps: list[dict[str, Any]] | None = None +# The Harbor task schema this build adapts. export() stamps the same value +# into generated task.toml files so round-tripped tasks validate; a task that +# declares any other schema_version was authored against a different adapter +# and must fail loudly rather than adapt with silently wrong semantics. +HARBOR_SCHEMA_VERSION = "1.0" + + @dataclass(frozen=True, slots=True) class HarborTask: path: Path @@ -238,6 +245,15 @@ def adapt( raise ValueError( f"{task_dir.name}/task.toml is not a valid Harbor task: {error}" ) from error + if ( + config.schema_version is not None + and config.schema_version != HARBOR_SCHEMA_VERSION + ): + raise ValueError( + f"{task_dir.name}/task.toml declares unsupported Harbor schema " + f"{config.schema_version!r} — this HUD build adapts schema " + f"{HARBOR_SCHEMA_VERSION!r}" + ) unsupported = [] if config.environment.os != "linux": unsupported.append(f"os={config.environment.os!r}") diff --git a/hud/integrations/harbor/export.py b/hud/integrations/harbor/export.py index 4eb4bbe7c..c3982a03d 100644 --- a/hud/integrations/harbor/export.py +++ b/hud/integrations/harbor/export.py @@ -7,6 +7,8 @@ import shlex import shutil from pathlib import Path + +from hud.integrations.harbor.adapt import HARBOR_SCHEMA_VERSION from typing import Any from hud.environment import Environment, load_environment @@ -162,6 +164,7 @@ def ignore_export(dirpath: str, names: list[str]) -> set[str]: args_json = json.dumps(task.args) (task_dir / "task.toml").write_text( 'version = "1.0"\n' + f'schema_version = "{HARBOR_SCHEMA_VERSION}"\n' f"name = {json.dumps(slug)}\n" "\n[metadata]\n" f"hud_task = {json.dumps(task.id)}\n" diff --git a/hud/integrations/harbor/tests/test_contract.py b/hud/integrations/harbor/tests/test_contract.py index 84fb6a14f..34744c3f5 100644 --- a/hud/integrations/harbor/tests/test_contract.py +++ b/hud/integrations/harbor/tests/test_contract.py @@ -12,6 +12,7 @@ from hud.eval import Task from hud.integrations import harbor +from hud.integrations.harbor.adapt import HARBOR_SCHEMA_VERSION from .conftest import make_harbor_task, make_multi_step_task @@ -1057,3 +1058,29 @@ def test_authored_runtime_assets_are_valid_source() -> None: def test_public_surface_is_only_the_two_real_operations() -> None: assert harbor.__all__ == ["adapt", "export"] + + +def test_unknown_schema_version_fails_loudly(tmp_path: Path) -> None: + task = make_harbor_task(tmp_path, "task-a") + (task / "task.toml").write_text( + 'schema_version = "99.9"\n', encoding="utf-8" + ) + + with pytest.raises(ValueError, match="unsupported Harbor schema"): + harbor.adapt(tmp_path) + + +def test_supported_schema_version_adapts(tmp_path: Path) -> None: + task = make_harbor_task(tmp_path, "task-a") + (task / "task.toml").write_text( + f'schema_version = "{HARBOR_SCHEMA_VERSION}"\n', encoding="utf-8" + ) + + taskset = harbor.adapt(tmp_path) + assert len(list(taskset)) == 1 + + +def test_absent_schema_version_still_adapts(tmp_path: Path) -> None: + """Unversioned tasks (the historical export shape) keep adapting.""" + task = make_harbor_task(tmp_path, "task-a") + assert len(list(harbor.adapt(tmp_path))) == 1 From 8b585bf54a1fd0932dbb1dfb31e00febc9151dd2 Mon Sep 17 00:00:00 2001 From: Ayush Nangia Date: Sun, 16 Aug 2026 01:37:04 +0530 Subject: [PATCH 2/3] style(harbor): ruff format for CI lint gate --- hud/integrations/harbor/adapt.py | 5 +---- hud/integrations/harbor/tests/test_contract.py | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/hud/integrations/harbor/adapt.py b/hud/integrations/harbor/adapt.py index 65d5f0ae8..eae0ec3c7 100644 --- a/hud/integrations/harbor/adapt.py +++ b/hud/integrations/harbor/adapt.py @@ -245,10 +245,7 @@ def adapt( raise ValueError( f"{task_dir.name}/task.toml is not a valid Harbor task: {error}" ) from error - if ( - config.schema_version is not None - and config.schema_version != HARBOR_SCHEMA_VERSION - ): + if config.schema_version is not None and config.schema_version != HARBOR_SCHEMA_VERSION: raise ValueError( f"{task_dir.name}/task.toml declares unsupported Harbor schema " f"{config.schema_version!r} — this HUD build adapts schema " diff --git a/hud/integrations/harbor/tests/test_contract.py b/hud/integrations/harbor/tests/test_contract.py index 34744c3f5..641924950 100644 --- a/hud/integrations/harbor/tests/test_contract.py +++ b/hud/integrations/harbor/tests/test_contract.py @@ -1062,9 +1062,7 @@ def test_public_surface_is_only_the_two_real_operations() -> None: def test_unknown_schema_version_fails_loudly(tmp_path: Path) -> None: task = make_harbor_task(tmp_path, "task-a") - (task / "task.toml").write_text( - 'schema_version = "99.9"\n', encoding="utf-8" - ) + (task / "task.toml").write_text('schema_version = "99.9"\n', encoding="utf-8") with pytest.raises(ValueError, match="unsupported Harbor schema"): harbor.adapt(tmp_path) From df31867e8a65d507bcec0f462f7af66bc14ef95f Mon Sep 17 00:00:00 2001 From: Ayush Nangia Date: Sun, 16 Aug 2026 01:40:09 +0530 Subject: [PATCH 3/3] style(harbor): fix import sort + unused variable (ruff I001/F841) --- hud/integrations/harbor/export.py | 3 +-- hud/integrations/harbor/tests/test_contract.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/hud/integrations/harbor/export.py b/hud/integrations/harbor/export.py index c3982a03d..b304a6fd6 100644 --- a/hud/integrations/harbor/export.py +++ b/hud/integrations/harbor/export.py @@ -7,13 +7,12 @@ import shlex import shutil from pathlib import Path - -from hud.integrations.harbor.adapt import HARBOR_SCHEMA_VERSION from typing import Any from hud.environment import Environment, load_environment from hud.environment.server import TaskRunner from hud.eval import Taskset +from hud.integrations.harbor.adapt import HARBOR_SCHEMA_VERSION from hud.utils.naming import normalize_environment_name ALLOWED_PROTOCOLS = ("ssh", "mcp") diff --git a/hud/integrations/harbor/tests/test_contract.py b/hud/integrations/harbor/tests/test_contract.py index 641924950..98704d999 100644 --- a/hud/integrations/harbor/tests/test_contract.py +++ b/hud/integrations/harbor/tests/test_contract.py @@ -1080,5 +1080,5 @@ def test_supported_schema_version_adapts(tmp_path: Path) -> None: def test_absent_schema_version_still_adapts(tmp_path: Path) -> None: """Unversioned tasks (the historical export shape) keep adapting.""" - task = make_harbor_task(tmp_path, "task-a") + make_harbor_task(tmp_path, "task-a") assert len(list(harbor.adapt(tmp_path))) == 1