plane(2.1): Delivery -- presigned store URLs, collection, state mirrored to the store

src/openultrasast/plane/delivery.py: Delivery(store, run, base) mints a presigned
PUT for runs/<run>/<task>/output.tar and a GET per declared input
(runs/<run>/<producer>/<artifact>), expiry OUSAST_TASK_TIMEOUT + 600 s;
delivered() asks the store for the exact key; collect() downloads, extracts
under the run directory with the receiver's member checks (no symlink, hard
link or escape; nothing written on a rejection), re-puts the declared outputs
as single objects and writes done.json; put_input() for inputs no task
produces; save_state()/load_state() mirror state.json. The reconciler opens the
store first (no fallback; the CLI reports a store that cannot open and submits
nothing), mirrors state.json after every change and seeds a rerun with no local
state from the store (the 2.1 bullet's state mirroring is what gives the new
module its importer). The reconciler crossed 500 lines here (517) rather than at
2.4 as 1.6 projected: the bound moves to 550 with the reason in the assertion,
as 1.6 decided. tests/plane_fake_store.py is the S3-shaped fake (objects in a
dict, presigned URLs on a local server); tests/test_plane_delivery.py covers the
body, expiry, polling, rejection, declared-only re-put, state seeding and that
no URL reaches a log. Module-audit manifest regenerated (148/5, no orphan).

Gates: pytest 2020 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:05:48 +02:00
co-authored by Claude Fable 5.1
parent 79296bb4a4
commit 35d8032c0a
8 changed files with 516 additions and 10 deletions
+109
View File
@@ -0,0 +1,109 @@
"""An S3-shaped memory store for the delivery tests: objects in a dict, presigned URLs served by a local HTTP server
that accepts PUT, GET and HEAD by key (``/<bucket>/<key>?X-Amz-Expires=N&X-Amz-Signature=fake``), the way a
presigned S3 URL looks to the runner. Nothing here is imported by the product."""
from __future__ import annotations
import threading
from collections.abc import Mapping
from datetime import timedelta
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from urllib.parse import unquote, urlsplit
from openultrasast.plane.memory import MemoryStore
class HttpObjectStore(MemoryStore):
"""``presign_*`` hand out URLs on a local server; ``requests`` records every HTTP call (method, key)."""
def __init__(self, bucket: str = "fake-bucket", host: str = "localhost") -> None:
self.objects: dict[str, bytes] = {}
self.bucket, self.host = bucket, host
self.requests: list[tuple[str, str]] = []
self.presigned: list[tuple[str, str, int]] = []
self.lock = threading.Lock()
self.server = ThreadingHTTPServer(("127.0.0.1", 0), _Handler)
self.server.store = self # type: ignore[attr-defined]
threading.Thread(target=self.server.serve_forever, daemon=True).start()
def close(self) -> None:
self.server.shutdown()
self.server.server_close()
def describe(self) -> str:
return f"fake://{self.bucket}"
def _get(self, key: str) -> tuple[bytes, str | None] | None:
with self.lock:
data = self.objects.get(key)
return None if data is None else (data, "v1")
def _put(self, key: str, data: bytes, labels: Mapping[str, str] | None = None) -> None:
with self.lock:
self.objects[key] = bytes(data)
def _delete(self, key: str) -> None:
with self.lock:
self.objects.pop(key, None)
def _keys(self, prefix: str) -> list[str]:
with self.lock:
return sorted(k for k in self.objects if k.startswith(prefix))
def _url(self, method: str, key: str, expires: timedelta) -> str:
self.presigned.append((method, key, int(expires.total_seconds())))
port = self.server.server_address[1]
return f"http://{self.host}:{port}/{self.bucket}/{key}?X-Amz-Expires={int(expires.total_seconds())}&X-Amz-Signature=fake"
def presign_put(self, key: str, expires: timedelta = timedelta(hours=1)) -> str:
return self._url("PUT", key, expires)
def presign_get(self, key: str, expires: timedelta = timedelta(hours=1)) -> str:
return self._url("GET", key, expires)
class _Handler(BaseHTTPRequestHandler):
def _key(self) -> str | None:
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
path = unquote(urlsplit(self.path).path)
prefix = f"/{store.bucket}/"
return path[len(prefix) :] if path.startswith(prefix) else None
def do_PUT(self) -> None: # noqa: N802
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
key = self._key()
body = self.rfile.read(int(self.headers.get("Content-Length") or 0))
store.requests.append(("PUT", key or self.path))
if key is None:
return self._answer(404)
store._put(key, body)
self._answer(200)
def do_GET(self) -> None: # noqa: N802
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
key = self._key()
store.requests.append(("GET", key or self.path))
got = store._get(key) if key else None
if got is None:
return self._answer(404)
self.send_response(200)
self.send_header("Content-Length", str(len(got[0])))
self.end_headers()
self.wfile.write(got[0])
def do_HEAD(self) -> None: # noqa: N802
store: HttpObjectStore = self.server.store # type: ignore[attr-defined]
key = self._key()
store.requests.append(("HEAD", key or self.path))
got = store._get(key) if key else None
self.send_response(404 if got is None else 200)
self.send_header("Content-Length", "0" if got is None else str(len(got[0])))
self.end_headers()
def _answer(self, code: int) -> None:
self.send_response(code)
self.send_header("Content-Length", "0")
self.end_headers()
def log_message(self, format: str, *args: object) -> None: # noqa: A002
pass
+145
View File
@@ -0,0 +1,145 @@
"""plane-on-kubernetes task 2.1 (Req 3.1, 2.1): ``Delivery`` against an S3-shaped fake -- presigned URLs in the start
body, expiry, polling, extraction with the receiver's member checks, declared-only re-put, state seeding, and no URL
in any log line."""
from __future__ import annotations
import io
import json
import logging
import tarfile
import urllib.request
from collections.abc import Iterator
from pathlib import Path
import pytest
from plane_fake_store import HttpObjectStore
from openultrasast.plane.delivery import GRACE_SECONDS, Delivery, DeliveryError, expiry, input_var
@pytest.fixture
def store() -> Iterator[HttpObjectStore]:
fake = HttpObjectStore()
try:
yield fake
finally:
fake.close()
def tar_of(files: dict[str, bytes], *, symlink: str | None = None) -> bytes:
buffer = io.BytesIO()
with tarfile.open(fileobj=buffer, mode="w") as tar:
for name, data in files.items():
info = tarfile.TarInfo(name)
info.size = len(data)
tar.addfile(info, io.BytesIO(data))
if symlink:
info = tarfile.TarInfo(symlink)
info.type = tarfile.SYMTYPE
info.linkname = "/etc/passwd"
tar.addfile(info)
return buffer.getvalue()
def put_via_url(url: str, data: bytes) -> int:
request = urllib.request.Request(url, data=data, method="PUT", headers={"Content-Type": "application/x-tar"})
with urllib.request.urlopen(request, timeout=10) as response: # noqa: S310 - the fake's local URL
return int(response.status)
def test_the_start_body_names_exactly_the_tar_and_the_declared_inputs(store: HttpObjectStore, tmp_path: Path, monkeypatch) -> None:
monkeypatch.setenv("OUSAST_TASK_TIMEOUT", "100")
delivery = Delivery(store, "run-1", tmp_path)
body = delivery.body("verify", {"facts": "repo-facts/facts.json", "pass a": "va/units.jsonl"})
assert set(body) == {"put", "inputs"}
assert body["put"].startswith(f"http://localhost:{store.server.server_address[1]}/fake-bucket/runs/run-1/verify/output.tar?")
assert set(body["inputs"]) == {"FACTS", "PASS_A"} and input_var("pass a") == "PASS_A"
assert "/runs/run-1/repo-facts/facts.json?" in body["inputs"]["FACTS"] and "/runs/run-1/va/units.jsonl?" in body["inputs"]["PASS_A"]
assert {m for m, _, _ in store.presigned} == {"PUT", "GET"}
assert {e for _, _, e in store.presigned} == {100 + GRACE_SECONDS}, "expiry = OUSAST_TASK_TIMEOUT + 600"
assert expiry({"OUSAST_TASK_TIMEOUT": "7200"}).total_seconds() == 7800 and expiry({}).total_seconds() == 7800
assert delivery.host == "localhost", "the hostname the egress policy must allow"
with pytest.raises(DeliveryError, match="not <producer>/<artifact>"):
delivery.input_urls({"x": "no-slash"})
def test_delivered_polls_the_store_and_collect_extracts_republishes_and_marks_done(
store: HttpObjectStore, tmp_path: Path, caplog: pytest.LogCaptureFixture
) -> None:
caplog.set_level(logging.DEBUG)
delivery = Delivery(store, "r", tmp_path / "r")
assert delivery.delivered("facts") is False
summary = {"status": "done", "units_done": 1, "units_total": 1}
tar = tar_of({"facts.json": b'{"a": 1}', "summary.json": json.dumps(summary).encode(), "scratch/notes.txt": b"not declared"})
assert put_via_url(delivery.put_url("facts"), tar) == 200, "the runner's PUT to the presigned URL"
assert delivery.delivered("facts") is True
got, size = delivery.collect("facts", ["facts.json", "summary.json", "missing.json"])
assert got == summary and size == len(tar)
assert (tmp_path / "r" / "facts" / "facts.json").read_bytes() == b'{"a": 1}'
assert (tmp_path / "r" / "facts" / "scratch" / "notes.txt").exists(), "extracted locally"
assert store.objects["runs/r/facts/facts.json"] == b'{"a": 1}' and "runs/r/facts/summary.json" in store.objects
assert "runs/r/facts/scratch/notes.txt" not in store.objects and "runs/r/facts/missing.json" not in store.objects, "declared only"
done = json.loads(store.objects["runs/r/facts/done.json"])
assert (
done["status"] == "done"
and done["bytes"] == len(tar)
and done["files"] == 3
and done["published"] == ["facts.json", "summary.json"]
)
# a consumer's presigned GET names one artifact and fetches it
urls = delivery.input_urls({"facts": "facts/facts.json"})
with urllib.request.urlopen(urls["FACTS"], timeout=10) as response: # noqa: S310
assert response.read() == b'{"a": 1}'
assert "X-Amz" not in caplog.text and "fake-bucket" not in caplog.text, "URLs never appear in logs"
@pytest.mark.parametrize("bad", ["../escape.json", "/abs.json"])
def test_a_tar_member_that_escapes_is_rejected_and_nothing_is_written(store: HttpObjectStore, tmp_path: Path, bad: str) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
store._put(delivery.key("t", "output.tar"), tar_of({bad: b"{}", "summary.json": b"{}"}))
with pytest.raises(DeliveryError, match="rejected member"):
delivery.collect("t", ["summary.json"])
assert not (tmp_path / "r" / "t").exists() and not (tmp_path / "escape.json").exists()
assert "runs/r/t/done.json" not in store.objects and "runs/r/t/summary.json" not in store.objects
def test_a_symlink_member_and_a_non_tar_are_rejected(store: HttpObjectStore, tmp_path: Path) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
store._put(delivery.key("t", "output.tar"), tar_of({"summary.json": b"{}"}, symlink="link"))
with pytest.raises(DeliveryError, match="rejected member link"):
delivery.collect("t", ["summary.json"])
store._put(delivery.key("t", "output.tar"), b"not a tar at all")
with pytest.raises(DeliveryError, match="bad tar"):
delivery.collect("t", ["summary.json"])
with pytest.raises(DeliveryError, match="not in the store"):
delivery.collect("never", [])
def test_state_is_mirrored_and_seeds_a_rerun_elsewhere(store: HttpObjectStore, tmp_path: Path) -> None:
first = Delivery(store, "r", tmp_path / "machine-a")
assert first.load_state() is None
first.save_state({"run": "r", "tasks": {"facts": {"status": "done"}}})
second = Delivery(store, "r", tmp_path / "machine-b") # another results root, the same store
assert second.load_state() == {"run": "r", "tasks": {"facts": {"status": "done"}}}
store._put(second.key("state.json"), b"not json")
assert second.load_state() is None
def test_put_input_stores_a_file_no_task_produces(store: HttpObjectStore, tmp_path: Path) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
source = tmp_path / "candidates.json"
source.write_bytes(b"[1, 2, 3]")
url = delivery.put_input("candidates.json", source)
assert store.objects["runs/r/inputs/candidates.json"] == b"[1, 2, 3]"
with urllib.request.urlopen(url, timeout=10) as response: # noqa: S310
assert response.read() == b"[1, 2, 3]"
def test_publish_reputs_only_declared_files_under_the_task_directory(store: HttpObjectStore, tmp_path: Path) -> None:
delivery = Delivery(store, "r", tmp_path / "r")
(tmp_path / "r" / "seeded").mkdir(parents=True)
(tmp_path / "r" / "seeded" / "facts.json").write_bytes(b"{}")
(tmp_path / "r" / "secret.txt").write_bytes(b"outside")
assert delivery.publish("seeded", ["facts.json", "../secret.txt", "absent.json"]) == ["facts.json"]
assert set(store.objects) == {"runs/r/seeded/facts.json"}
+36 -2
View File
@@ -832,9 +832,13 @@ def test_mark_done_sets_the_state_under_the_run_lock(tmp_path: Path) -> None:
assert not (base / "lock").exists(), "the lock is released"
def test_reconciler_is_under_500_lines_and_holds_no_pipeline_logic() -> None:
def test_reconciler_is_under_550_lines_and_holds_no_pipeline_logic() -> None:
text = RECONCILER.read_text(encoding="utf-8")
assert len(text.splitlines()) < 500, "Req 3.4: the reconciler stays under 500 lines"
assert len(text.splitlines()) < 550, (
"Req 3.4: the reconciler stays under 500 lines; plane-on-kubernetes 1.6/2.1 moved the bound to 550 while the "
"store-delivery wiring (Delivery, state mirror and seed, the start-body delivery) sits beside the receiver path "
"that task 2.6 removes -- 2.6 records the count and returns the bound to 500 if it allows"
)
for word in ("prompt(", "openai", "ChatClient", "score(", "system_prompt"):
assert word not in text, f"Req 3.4: no pipeline logic in the reconciler ({word})"
assert "Popen" not in text and "openultrasast.plane.tasks" not in text, "ax is the only executor"
@@ -979,3 +983,33 @@ def test_ax_task_names_leave_room_for_the_template_suffix() -> None:
assert other != long, "no collision between tasks sharing a long prefix"
assert reconciler._ax_name("run", "facts", limit=reconciler.TASK_NAME_LIMIT) == "run-facts"
assert re.fullmatch(r"[a-z0-9]([a-z0-9-]*[a-z0-9])?", long)
# --- the store is the state of record (plane-on-kubernetes 2.1, design section 2) -----------------------------------
def test_state_is_mirrored_to_the_store_and_seeds_a_rerun_from_an_empty_results_root(
fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
assert reconciler.run(write_run(tmp_path, "mir"), ax=str(fake.ax)) == "done"
mirrored = tmp_path / "memory" / "runs" / "mir" / "state.json" # the profile's file:// store
assert mirrored.is_file() and json.loads(mirrored.read_text()) == state_of("mir"), "mirrored after the last change"
assert json.loads(mirrored.read_text())["status"] == "done"
applies = len(fake.events("apply"))
monkeypatch.setenv("OUSAST_RESULTS", str(tmp_path / "elsewhere")) # another machine: no local state at all
assert reconciler.run(write_run(tmp_path, "mir"), ax=str(fake.ax)) == "done"
assert statuses("mir") == {"repo-facts": "done", "verify": "done", "agree": "done"}, "seeded from the store"
assert len(fake.events("apply")) == applies, "nothing was submitted again"
def test_a_store_that_cannot_open_stops_the_run_before_ax_and_leaves_no_lock(
fake: Fake, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
from openultrasast.plane.memory import MemoryStoreError
monkeypatch.setenv("OUSAST_MEMORY", "s3://nowhere") # the profile's override; no S3_* in the test environment
for key in ("S3_ENDPOINT", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"):
monkeypatch.delenv(key, raising=False)
with pytest.raises(MemoryStoreError, match="S3_ENDPOINT"):
reconciler.run(write_run(tmp_path, "nostore"), ax=str(fake.ax))
assert fake.events("apply") == [] and not (reconciler.run_dir("nostore") / "lock").exists()