From 2e7e0550b2740b20f3fa657337ba0b0e175b1825 Mon Sep 17 00:00:00 2001 From: Marius Ciepluch <11855163+norandom@users.noreply.github.com> Date: Tue, 29 Sep 2026 19:17:19 +0200 Subject: [PATCH] fix(plane): runner drops the fields ax's server sets before validating On the live cluster ax hands AX_TASK_YAML with metadata.creationTimestamp and a status block; the strict authoring schema rejected it, readiness never turned green and every resume timed out. Other unknown fields still fail. up.sh retries the first resume while the golden snapshot builds. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo --- ops/ax/up.sh | 2 +- src/openultrasast/plane/runner.py | 19 +++++++++++++++++-- tests/test_plane_runner.py | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ops/ax/up.sh b/ops/ax/up.sh index 29fda05..ef41785 100755 --- a/ops/ax/up.sh +++ b/ops/ax/up.sh @@ -66,7 +66,7 @@ step "smoke task" ax delete task ousast-smoke >/dev/null 2>&1 || true envsubst < "$(dirname "$0")/smoke-task.yaml.tmpl" > "$SRC/smoke-task.yaml" ax apply -f "$SRC/smoke-task.yaml" -ax resume task ousast-smoke || true +for i in 1 2 3 4 5 6; do ax resume task ousast-smoke && break; sleep 20; done # first resume may time out while the golden snapshot builds ax get tasks step "footprint" diff --git a/src/openultrasast/plane/runner.py b/src/openultrasast/plane/runner.py index f3c47c5..f81eb8a 100644 --- a/src/openultrasast/plane/runner.py +++ b/src/openultrasast/plane/runner.py @@ -119,6 +119,21 @@ class DeliveryError(RuntimeError): # --- manifests --------------------------------------------------------------------------------------------------- +# Fields ax's API server sets on what it hands the runner (ax.proto: ObjectMeta.creation_timestamp, Task.status). +# They are not authoring fields, so they are dropped before the strict schema sees the document; any other +# unknown field still fails. +_SERVER_META = ("creationTimestamp", "creation_timestamp") + + +def _without_server_fields(document: object) -> object: + if not isinstance(document, dict): + return document + cleaned = {k: v for k, v in document.items() if k != "status"} + meta = cleaned.get("metadata") + if isinstance(meta, dict): + cleaned["metadata"] = {k: v for k, v in meta.items() if k not in _SERVER_META} + return cleaned + def load_task(text: str) -> Task: """Parse ``AX_TASK_YAML``; raise :class:`RunnerError` unless it is exactly one Task.""" @@ -129,7 +144,7 @@ def load_task(text: str) -> Task: if len(documents) != 1: raise RunnerError(f"AX_TASK_YAML must hold exactly one Task document, found {len(documents)}") try: - manifest = parse_manifest(documents[0], source="AX_TASK_YAML") + manifest = parse_manifest(_without_server_fields(documents[0]), source="AX_TASK_YAML") except ManifestError as exc: raise RunnerError(str(exc)) from exc if not isinstance(manifest, Task): @@ -146,7 +161,7 @@ def load_workspaces(text: str) -> dict[str, Workspace]: workspaces: dict[str, Workspace] = {} for index, document in enumerate(documents): try: - manifest = parse_manifest(document, source=f"AX_WORKSPACES_YAML[{index}]") + manifest = parse_manifest(_without_server_fields(document), source=f"AX_WORKSPACES_YAML[{index}]") except ManifestError as exc: raise RunnerError(str(exc)) from exc if not isinstance(manifest, Workspace): diff --git a/tests/test_plane_runner.py b/tests/test_plane_runner.py index 0e2b015..b4ca7cf 100644 --- a/tests/test_plane_runner.py +++ b/tests/test_plane_runner.py @@ -590,3 +590,22 @@ def test_git_commits_annotation_round_trips_to_the_checkout( checkout = workspace / "code" assert git("rev-parse", "HEAD", cwd=checkout) == pin, "the pinned commit, not the branch tip" assert (checkout / "hello.py").read_text() == "print('v1')\n" + + +def test_the_runner_accepts_what_ax_hands_it() -> None: + """ax's server adds metadata.creationTimestamp and status (live cluster, 2026-09-29); other fields stay strict.""" + from openultrasast.plane.runner import RunnerError, load_task, load_workspaces + + task = load_task( + "apiVersion: ax.io/v1alpha1\nkind: Task\nmetadata:\n name: t\n atespace: default\n" + " creationTimestamp: '2026-09-29T17:11:58Z'\nspec:\n command: [repo-facts]\n" + "status:\n phase: Running\n id: task-t-1\n" + ) + assert task.metadata.name == "t" + spaces = load_workspaces( + "apiVersion: ax.io/v1alpha1\nkind: Workspace\nmetadata:\n name: w\n" + " creationTimestamp: '2026-09-29T17:11:58Z'\nspec:\n files:\n - path: a.py\n content: x\n" + ) + assert list(spaces) == ["w"] + with pytest.raises(RunnerError, match="uid"): + load_task("apiVersion: ax.io/v1alpha1\nkind: Task\nmetadata:\n name: t\n uid: x\nspec: {}\n")