From 12be0e7d1ec3340ffe30c77f5981b685584d3560 Mon Sep 17 00:00:00 2001 From: Rajeev Jain Date: Mon, 13 Jul 2026 17:42:39 -0500 Subject: [PATCH] Report worker UXarray version uniformly across all remote compute ops Extends the remote-provenance transparency from #71 so every compute and analysis remote function reports the worker's true UXarray version via _worker_uxarray_version. _run_on_hpc surfaces it as remote_uxarray_version and emits a drift warning when it differs from the local version. Newly covered: inspect_variable, azimuthal_mean, grid_facts (the compute paths that previously omitted it). Verified live on Chrysalis (inspect_variable + get_capabilities report remote_uxarray_version and the drift warning; internal key does not leak into results). Adds a regression test asserting every compute remote function reports the version, plus a _run_on_hpc drift-warning unit test. --- docs/provenance.md | 12 ++-- src/uxarray_mcp/remote/compute_functions.py | 3 + tests/test_hpc_safety.py | 79 +++++++++++++++++++++ 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/docs/provenance.md b/docs/provenance.md index 8771248..a1ba335 100644 --- a/docs/provenance.md +++ b/docs/provenance.md @@ -19,11 +19,13 @@ Every tool result includes a `_provenance` block that records what ran, when, wh | `validation_summary` | Dataset validation results (if applicable) | For remote runs, `remote_uxarray_version` records the version that actually -computed the result on the worker. When it differs from the local -`uxarray_version`, a **version-drift warning** is added to `warnings` so that -silent numerical differences between local and remote execution are surfaced. -`curl`/`divergence` additionally add **vector-component warnings** here when the -inputs do not look like genuine vector components. +computed the result on the worker. This is reported uniformly across every +compute and analysis operation (inspect, area, zonal mean/anomaly, gradient, +curl, divergence, azimuthal mean, remap/regrid, and capability discovery). When +it differs from the local `uxarray_version`, a **version-drift warning** is added +to `warnings` so that silent numerical differences between local and remote +execution are surfaced. `curl`/`divergence` additionally add **vector-component +warnings** here when the inputs do not look like genuine vector components. ## Example diff --git a/src/uxarray_mcp/remote/compute_functions.py b/src/uxarray_mcp/remote/compute_functions.py index 3385f49..79397db 100644 --- a/src/uxarray_mcp/remote/compute_functions.py +++ b/src/uxarray_mcp/remote/compute_functions.py @@ -304,6 +304,7 @@ def remote_inspect_variable( "n_node": int(uxds.uxgrid.n_node), "n_edge": int(uxds.uxgrid.n_edge), }, + "_worker_uxarray_version": getattr(ux, "__version__", "unknown"), } @@ -1310,6 +1311,7 @@ def remote_calculate_azimuthal_mean( "radii_deg": radii, "azimuthal_mean_values": values, "n_face": int(uxds.uxgrid.n_face), + "_worker_uxarray_version": getattr(ux, "__version__", "unknown"), } @@ -1350,6 +1352,7 @@ def remote_grid_facts( "n_face": int(grid.n_face) if hasattr(grid, "n_face") else 0, "n_node": int(grid.n_node) if hasattr(grid, "n_node") else 0, "n_edge": int(grid.n_edge) if hasattr(grid, "n_edge") else 0, + "_worker_uxarray_version": getattr(ux, "__version__", "unknown"), } if data_path is not None: diff --git a/tests/test_hpc_safety.py b/tests/test_hpc_safety.py index 5a5f38f..ec8f8c1 100644 --- a/tests/test_hpc_safety.py +++ b/tests/test_hpc_safety.py @@ -396,3 +396,82 @@ def test_validate_dataset_provenance(synthetic_mesh_with_data): assert "_provenance" in result assert result["_provenance"]["tool"] == "validate_dataset" assert isinstance(result["_provenance"]["warnings"], list) + + +# ----------------------------------------------------------------------------- +# Worker-version provenance + drift warning +# ----------------------------------------------------------------------------- + + +class TestWorkerVersionProvenance: + """All compute/analysis remote functions report the worker's UXarray version, + and _run_on_hpc surfaces it plus a drift warning.""" + + COMPUTE_REMOTE_FUNCS = [ + "remote_inspect_mesh", + "remote_calculate_area", + "remote_inspect_variable", + "remote_calculate_zonal_mean", + "remote_calculate_zonal_anomaly", + "remote_calculate_gradient", + "remote_calculate_curl", + "remote_calculate_divergence", + "remote_calculate_azimuthal_mean", + "remote_grid_facts", + "remote_remap_variable", + "remote_regrid_dataset", + "remote_remap_to_rectilinear", + ] + + def test_all_compute_functions_emit_worker_version(self): + """Guard against regressions: every compute remote function's source + includes the _worker_uxarray_version key.""" + import inspect as _inspect + + from uxarray_mcp.remote import compute_functions as cf + + for name in self.COMPUTE_REMOTE_FUNCS: + fn = getattr(cf, name) + src = _inspect.getsource(fn) + assert "_worker_uxarray_version" in src, ( + f"{name} does not report _worker_uxarray_version" + ) + + @requires_globus + def test_run_on_hpc_surfaces_worker_version_and_drift(self): + """_run_on_hpc records remote_uxarray_version and warns on drift.""" + import asyncio + + from uxarray_mcp.remote.agent import UXarrayComputeAgent + from uxarray_mcp.remote.config import HPCConfig + + agent = UXarrayComputeAgent( + HPCConfig(endpoint_id="fake", endpoint_name="test", execution_mode="hpc") + ) + + # Fake executor: returns a payload with a worker version that differs + # from whatever is installed locally. + class _Fut: + def result(self, timeout=None): + return { + "n_face": 1, + "_worker_uxarray_version": "0.0.0-worker-different", + } + + class _Exec: + def submit(self, func, *a, **k): + return _Fut() + + agent._executor = _Exec() + + def _fake_func(): + return None + + _fake_func.__name__ = "remote_inspect_mesh" + result = asyncio.run(agent._run_on_hpc(_fake_func)) + + prov = result["_provenance"] + assert prov["remote_uxarray_version"] == "0.0.0-worker-different" + assert any("drift" in w for w in prov["warnings"]) + # Internal key must not leak into the user-facing result. + assert "_worker_uxarray_version" not in result