plane(2.2): runner -- delivery {put, inputs} in the start body, PUT to the store

StartGate.offer validates an optional `delivery` beside the credentials: https
URLs to a hostname (never an address, no userinfo; AX_RUNNER_INSECURE_DELIVERY=1
admits http in unit tests only), names ^[A-Z][A-Z0-9_]*$, a 400 that names the
field and echoes no URL. The runner PUTs its output tar to delivery.put and
GETs each declared input from delivery.inputs[NAME] (deliver() gains a method;
log lines and DeliveryError carry the URL without its query, so no presigned
signature reaches a log, a marker or a summary). OUSAST_ARTIFACT_URL/DIAL stay
honoured until task 2.6 so both paths run in the same build; the completion
marker and the retry-only boot are unchanged, and a re-start brings a fresh
URL for the retry. Router.start_task carries `delivery` in the body and
redacts its URLs like credential values. Tests: the gate's acceptance and
refusals, an end-to-end PUT/GET against the S3-shaped fake with no URL in any
log or file, the failed-PUT-then-retry-only boot, deliver() with PUT.

Gates: pytest 2024 passed, 42 skipped, exit 0; ruff check 0; ruff format 0;
mypy 0.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
Marius Ciepluch
2026-10-02 21:12:19 +02:00
co-authored by Claude Fable 5.1
parent 35d8032c0a
commit 9506a18b56
4 changed files with 249 additions and 39 deletions
+120
View File
@@ -23,6 +23,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import pytest
from plane_fake_store import HttpObjectStore
from openultrasast.plane import egress, runner
from openultrasast.plane.manifests import GitSource
@@ -36,6 +37,7 @@ from openultrasast.plane.runner import (
clone_destination,
deliver,
find_cached_checkout,
parse_delivery,
start_health_server,
)
@@ -850,3 +852,121 @@ def test_fetch_dials_the_override_and_sends_the_url_host(tmp_path: Path) -> None
server.server_close()
assert size == 2 and (tmp_path / "x" / "a.json").read_bytes() == b"ok"
assert seen[0]["path"] == "/inputs/p/a.json" and seen[0]["Host"] == "receiver.example" and seen[0]["X-Ousast-Run"] == "r"
# --- store delivery through the start body (plane-on-kubernetes 2.2, design section 3) ---------------------------
@pytest.fixture
def object_store() -> Iterator[HttpObjectStore]:
fake = HttpObjectStore()
try:
yield fake
finally:
fake.close()
def test_the_start_body_may_carry_delivery_targets_and_refuses_unsafe_urls() -> None:
"""``delivery {put, inputs}``: https to a hostname, never an address or http; the answer echoes no URL."""
gate = StartGate()
gate.arm("r", "t")
bad = [
({"put": "http://store.example/b/k?sig=1"}, "delivery.put must be an https URL"),
({"put": "https://10.0.0.7/b/k"}, "delivery.put must name a host, not an address"),
({"put": "https://user:[email protected]/k"}, "delivery.put must be an https URL"),
({"inputs": {"FACTS": "ftp://store.example/k"}}, "delivery.inputs.FACTS must be an https URL"),
({"inputs": {"lower": "https://store.example/k"}}, "delivery.inputs must map"),
({"elsewhere": "x"}, "delivery must be an object"),
("https://store.example/k", "delivery must be an object"),
]
for delivery, message in bad:
status, text = gate.offer(json.dumps({"run": "r", "task": "t", "delivery": delivery}).encode())
assert status == 400 and message in text, (delivery, status, text)
assert "store.example" not in text and "10.0.0.7" not in text, "the answer never echoes a URL"
body = {
"run": "r",
"task": "t",
"delivery": {
"put": "https://store.example/b/t/output.tar?X-Amz-Signature=s",
"inputs": {"FACTS": "https://store.example/b/p/facts.json?X-Amz-Signature=s"},
},
}
assert gate.offer(json.dumps(body).encode()) == (202, "started")
start = gate.wait(threading.Event())
assert start is not None and start.delivery is not None
assert start.delivery.put == body["delivery"]["put"] and start.delivery.inputs == body["delivery"]["inputs"]
assert "X-Amz" not in repr(start) and "store.example" not in repr(start), "a Start never prints a URL"
assert parse_delivery(None) is None and parse_delivery({"put": "http://localhost:1/k"}, insecure=True) is not None
with pytest.raises(ValueError, match="must be an https URL"):
parse_delivery({"put": "http://localhost:1/k"})
def test_output_is_put_to_the_presigned_url_and_inputs_are_fetched_from_theirs(
env: dict[str, str], object_store: HttpObjectStore, tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
"""End to end against the S3-shaped fake: the start carries the URLs, the runner PUTs the tar and GETs the input;
no receiver, no OUSAST_ARTIFACT_URL, no URL in any log line."""
caplog.set_level(logging.DEBUG)
object_store._put("runs/run-1/repo-facts/facts.json", b'{"a.py": ["f"]}')
put_url = object_store.presign_put("runs/run-1/stub/output.tar")
facts_url = object_store.presign_get("runs/run-1/repo-facts/facts.json")
thread, environ, codes = main_in_thread(
tmp_path,
AX_RUNNER_INSECURE_DELIVERY="1",
OUSAST_TASK="stub",
OUSAST_INPUTS=json.dumps({"FACTS": "repo-facts/facts.json"}),
OUSAST_INPUT_FACTS=str(tmp_path / "ws" / "in" / "repo-facts" / "facts.json"),
)
body = {"run": "run-1", "task": "stub", "delivery": {"put": put_url, "inputs": {"FACTS": facts_url}}}
assert post_start(environ["AX_RUNNER_BOUND_PORT"], body) == (202, "started")
thread.join(timeout=30)
assert codes == [0]
assert (tmp_path / "ws" / "in" / "repo-facts" / "facts.json").read_bytes() == b'{"a.py": ["f"]}', "the input came by its URL"
assert ("GET", "runs/run-1/repo-facts/facts.json") in object_store.requests and (
"PUT",
"runs/run-1/stub/output.tar",
) in object_store.requests
with tarfile.open(fileobj=io.BytesIO(object_store.objects["runs/run-1/stub/output.tar"]), mode="r:") as tar:
names = sorted(tar.getnames())
assert "facts.json" in names and "summary.json" in names, "the whole output directory, as a tar, in the store"
marker = json.loads((tmp_path / "state" / "stub.done.json").read_text())
assert marker["delivered"] is True and marker["status"] == "done"
assert "X-Amz" not in caplog.text and "fake-bucket" not in caplog.text, "no presigned URL in any log line"
assert files_containing(tmp_path, "X-Amz-Signature") == [], "nor in any file"
def test_a_failed_put_is_retried_alone_on_the_next_start_with_a_fresh_url(
env: dict[str, str], object_store: HttpObjectStore, tmp_path: Path
) -> None:
"""The marker and retry-only boot are unchanged: a PUT that fails (wrong bucket: 404) ends the task failed with
the URL's host and path only; the next boot's start brings a working URL and only the delivery runs again."""
counter = tmp_path / "runs.txt"
wrong = object_store.presign_put("runs/run-1/stub/output.tar").replace("/fake-bucket/", "/no-such-bucket/")
thread, environ, codes = main_in_thread(
tmp_path, AX_RUNNER_INSECURE_DELIVERY="1", STUB_COUNTER=str(counter), OUSAST_DELIVERY_BACKOFF="0"
)
assert post_start(environ["AX_RUNNER_BOUND_PORT"], {"run": "run-1", "task": "stub", "delivery": {"put": wrong}})[0] == 202
thread.join(timeout=30)
assert codes == [EXIT_BY_STATUS["failed"]]
marker = json.loads((tmp_path / "state" / "stub.done.json").read_text())
assert marker["delivered"] is False and marker["status"] == "failed" and marker["exit"] == 0
assert "HTTP 404" in marker["reason"] and "X-Amz" not in marker["reason"], "the reason names the status, not the signature"
thread, environ, codes = main_in_thread(
tmp_path, AX_RUNNER_INSECURE_DELIVERY="1", STUB_COUNTER=str(counter), OUSAST_DELIVERY_BACKOFF="0"
)
good = object_store.presign_put("runs/run-1/stub/output.tar")
assert post_start(environ["AX_RUNNER_BOUND_PORT"], {"run": "run-1", "task": "stub", "delivery": {"put": good}})[0] == 202
thread.join(timeout=30)
assert codes == [0] and len(counter.read_text().splitlines()) == 1, "only the delivery is retried"
assert "runs/run-1/stub/output.tar" in object_store.objects
assert json.loads((tmp_path / "state" / "stub.done.json").read_text())["delivered"] is True
def test_deliver_puts_and_names_only_the_safe_url_on_failure(object_store: HttpObjectStore) -> None:
url = object_store.presign_put("k/output.tar")
assert runner.deliver(url, b"tar", {"Content-Type": "application/x-tar"}, backoff=0, method="PUT") == 200
assert object_store.objects["k/output.tar"] == b"tar"
with pytest.raises(runner.DeliveryError) as caught:
runner.deliver(url.replace("/fake-bucket/", "/other/"), b"x", {}, attempts=2, backoff=0, method="PUT")
assert "HTTP 404" in str(caught.value) and "X-Amz" not in str(caught.value) and "/other/k/output.tar" in str(caught.value)
assert runner.safe_url("https://h:1/p/q?X-Amz-Signature=s") == "https://h:1/p/q"