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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-09-29 19:17:19 +02:00
co-authored by Claude Opus 5.5
parent 8e6485e577
commit 2e7e0550b2
3 changed files with 37 additions and 3 deletions
+1 -1
View File
@@ -66,7 +66,7 @@ step "smoke task"
ax delete task ousast-smoke >/dev/null 2>&1 || true ax delete task ousast-smoke >/dev/null 2>&1 || true
envsubst < "$(dirname "$0")/smoke-task.yaml.tmpl" > "$SRC/smoke-task.yaml" envsubst < "$(dirname "$0")/smoke-task.yaml.tmpl" > "$SRC/smoke-task.yaml"
ax apply -f "$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 ax get tasks
step "footprint" step "footprint"
+17 -2
View File
@@ -119,6 +119,21 @@ class DeliveryError(RuntimeError):
# --- manifests --------------------------------------------------------------------------------------------------- # --- 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: def load_task(text: str) -> Task:
"""Parse ``AX_TASK_YAML``; raise :class:`RunnerError` unless it is exactly one 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: if len(documents) != 1:
raise RunnerError(f"AX_TASK_YAML must hold exactly one Task document, found {len(documents)}") raise RunnerError(f"AX_TASK_YAML must hold exactly one Task document, found {len(documents)}")
try: 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: except ManifestError as exc:
raise RunnerError(str(exc)) from exc raise RunnerError(str(exc)) from exc
if not isinstance(manifest, Task): if not isinstance(manifest, Task):
@@ -146,7 +161,7 @@ def load_workspaces(text: str) -> dict[str, Workspace]:
workspaces: dict[str, Workspace] = {} workspaces: dict[str, Workspace] = {}
for index, document in enumerate(documents): for index, document in enumerate(documents):
try: 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: except ManifestError as exc:
raise RunnerError(str(exc)) from exc raise RunnerError(str(exc)) from exc
if not isinstance(manifest, Workspace): if not isinstance(manifest, Workspace):
+19
View File
@@ -590,3 +590,22 @@ def test_git_commits_annotation_round_trips_to_the_checkout(
checkout = workspace / "code" checkout = workspace / "code"
assert git("rev-parse", "HEAD", cwd=checkout) == pin, "the pinned commit, not the branch tip" assert git("rev-parse", "HEAD", cwd=checkout) == pin, "the pinned commit, not the branch tip"
assert (checkout / "hello.py").read_text() == "print('v1')\n" 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")