feat(plane): router-addressed start signal; credentials only in the start request

Agent Substrate's golden-snapshot boot runs the same image with the same task env,
so the runner no longer starts its command by itself: after the workspaces are
ready it waits for POST /ousast/v1/start (run/task checked, once per boot, refused
after a delivered run). The reconciler sends it through svc/atenet-router with
ate-target-actor, which never addresses the golden actor (plane/router.py), and the
bound Model's secretKey variable travels only in that body, into the child's env.

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:41:35 +02:00
co-authored by Claude Opus 5.5
parent ac296ee3a4
commit 14ea48b1ea
10 changed files with 919 additions and 95 deletions
+12 -1
View File
@@ -3,11 +3,13 @@
The tests copy it into a throwaway package (``AX_RUNNER_TASK_PACKAGE``) so the runner starts it the way it starts
a real task, ``python -m <package>.stub_task``. ``STUB_MODE`` picks the outcome (``done``, ``failed``,
``unfinished``, ``crash``, ``nosummary``, ``exit``, ``sleep``); ``STUB_COUNTER`` names a file that gets one line
per run.
per run. ``STUB_SECRET_VAR`` names a credential variable: the stub records the SHA-256 of its value (or None),
never the value.
"""
from __future__ import annotations
import hashlib
import json
import os
import signal
@@ -23,6 +25,11 @@ def _get(url: str) -> tuple[int, str]:
return int(response.status), response.read().decode("utf-8")
def _secret_hash(name: str | None) -> str | None:
value = os.environ.get(name) if name else None
return hashlib.sha256(value.encode("utf-8")).hexdigest() if value else None
def main(argv: list[str]) -> None:
out = Path(os.environ["OUSAST_OUTPUT_DIR"])
out.mkdir(parents=True, exist_ok=True)
@@ -45,6 +52,7 @@ def main(argv: list[str]) -> None:
"pgid": os.getpgid(0),
"metadata_url": metadata,
"metadata_task": _get(f"{metadata}/metadata/v1alpha1/ax/task")[1] if metadata else None,
"secret_sha256": _secret_hash(os.environ.get("STUB_SECRET_VAR")),
}
(out / "facts.json").write_text(json.dumps(record, indent=2, sort_keys=True), encoding="utf-8")
if mode == "sleep": # ignore SIGTERM so only the runner's SIGKILL ends the group; the grandchild inherits it
@@ -52,6 +60,9 @@ def main(argv: list[str]) -> None:
grandchild = subprocess.Popen(["sleep", "60"])
(out / "pids.json").write_text(json.dumps([os.getpid(), grandchild.pid]), encoding="utf-8")
time.sleep(60)
if mode == "leak": # a task that prints its credential and crashes: the runner must redact it
sys.stderr.write(f"auth failed for key {os.environ.get(os.environ.get('STUB_SECRET_VAR', ''), '')}\n")
raise SystemExit(5)
if mode == "crash":
raise RuntimeError("stub exploded")
if mode == "exit":