Files
skills/tests/test_hub_cleanup.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 90c56cb786 feat(orchestrator): Agents and Artifacts views with spawn tree, orbit, 3D and signed branch deletes
Phase 4 of requirement 001. Agents and Artifacts each offer Orbit, Chord, Swimlane, Tree and 3D: the spawn tree (page-spawn.js) and the artifacts orbit (page-artifacts.js, kvb_orchestrator/artifacts.py) replace the "folgt" placeholders, 3D runs on the vendored 3d-force-graph and three.js (MIT, LICENSE files kept, served from the image under the unchanged CSP), and a Bootstrap Icons sprite (icons.py, MIT) replaces the emoji glyphs, also in the meditation page.

Signed branch deletes: the Tree view offers a delete only for merged-clean branches without a gc skip reason; the operator signs the exact item with the passkey, the hub writes a job without any command (kvb_orchestrator/cleanup.py), and the host path unit runs client/cleanup.py, which verifies the signature, dry-runs pkgmgr branch cleanup for that exact branch, refuses patterns, option names, changed tips, other worktrees and plans naming other branches, deletes, and rewrites the branch tree. pkgmgr runs with core.hooksPath=/dev/null, core.fsmonitor=false and the config hook events disabled through GIT_CONFIG_COUNT, because the cwd is a repository agents can write and pkgmgr releases before 4c8b137 delete without scan mode (test with a positive control that both hook kinds fire without it).

3D labels go into the tooltip as a text node: the vendored tooltip renders a string label as HTML.

Tests: 503 OK, Chromium (CDP) and Firefox (BiDi) walks over every screen at three widths.

MEDUSA acceptances (kvb-think-criticize skeptic 2026-11-07): WEB-FLASK-004 view.py:135 (file template, autoescape on, state via tojson), MEDUSA-MM-SCAN-058 server.py:69/:85 (substring pla in --templates and Places), RH005 client/cleanup.py:199 (raise SystemExit(main())), B704 icons.py:45 (sprite baked into the image, names checked against it), EXA002 page-artifacts.js:221 (openAgent only opens the drawer), STG002 page-constants.js:64 (U+2212 in a static label), PL002 page-graph3d.js:44 (CSS class space-tools; the real tooltip sink fixed), B603 client/cleanup.py:75 (argv without shell; hook hole closed).

🎯 Fix: 92 %
💥 Break: 5 %
💸 Cost: 100 %
⏱️ Time: –
🛡️ Security: 100 %
🏆 Score: 87
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 15:03:14 +00:00

251 lines
16 KiB
Python

from __future__ import annotations
import importlib.util
import json
import secrets
import subprocess
import sys
import tempfile
import time
import unittest
from pathlib import Path
from hub_case import HubCase
from hub_support import HUB, ORIGIN, key, signing, verifier
CLEANUP = HUB / "client" / "cleanup.py"
CLEANUP_SPEC = importlib.util.spec_from_file_location("kvb_orchestrator_cleanup_runner", CLEANUP)
host = importlib.util.module_from_spec(CLEANUP_SPEC)
CLEANUP_SPEC.loader.exec_module(host)
DONE_TIP = "5b1e6fd3a7c0e2d94f8a61b3c2d7e9f0a1b2c3d4"
OTHER_TIP = "1111111111111111111111111111111111111111"
# Param: format with python=<interpreter>; plan.json beside it is the dry-run plan, next-tree.json the rewritten tree.
FAKE_PKGMGR = """#!{python}
import json
import os
import sys
from pathlib import Path
here = Path(__file__).parent
with (here / "calls.jsonl").open("a", encoding="utf-8") as log:
log.write(json.dumps({{"argv": sys.argv[1:], "cwd": os.getcwd()}}) + "\\n")
if sys.argv[1:3] == ["branch", "tree"]:
following = here / "next-tree.json"
rewritten = following.read_text(encoding="utf-8") if following.exists() else json.dumps({{"rewritten": True}})
Path(sys.argv[sys.argv.index("--output") + 1]).write_text(rewritten, encoding="utf-8")
raise SystemExit(0)
print(json.dumps({{"items": json.loads((here / "plan.json").read_text(encoding="utf-8"))}}))
"""
class TestHubCleanup(HubCase):
def ready(self, ready: bool) -> dict:
root = self.write_tree()
data = json.loads(self.tree_file.read_text(encoding="utf-8"))
data["delete_ready"], data["delete_reason"] = ready, "" if ready else "Secret Service locked"
self.tree_file.write_text(json.dumps(data), encoding="utf-8")
return signing.cleanup_item(f"{root}/repos/github.com/me/demo", "feature/done", f"{root}/worktrees/github.com/me/demo/feature-done", DONE_TIP)
def branches(self) -> dict:
status, _, body = self.get("/api/tree")
self.assertEqual(status, 200, body)
demo = next(repo for repo in json.loads(body)["repos"] if repo["name"] == "github.com/me/demo")
return {entry["name"]: entry for entry in demo["branches"]}
def sign(self, items: list[dict]) -> tuple[int, bytes]:
status, _, body = self.post_json("/api/sign", {"items": items}, self.guard(self.host))
return status, body
def test_the_tree_offers_a_delete_only_for_merged_clean_branches_without_skip_reason(self):
item = self.ready(False)
branches = self.branches()
done = branches["feature/done"]
self.assertEqual(done["cleanup"], item)
self.assertEqual(done["plan"], [
"feature/done @ 5b1e6fd3a7c0: merged-clean, gemergt durch #12",
f"entfernt den Worktree {item['worktree']}",
"löscht den lokalen Branch feature/done",
"bleibt: remote-branch (nur lokal)",
"stoppt den Compose-Stack demo-feature-done ohne -v",
"entfernt das Image demo:feature-done",
])
self.assertEqual((done["merged_by"], done["merged_into"], done["gone"]), ("#12", "main", True))
self.assertEqual({name: entry["cleanup"] for name, entry in branches.items() if name != "feature/done"}, {"feature": None, "feature/kept": None, "main": None})
self.assertEqual(branches["feature/kept"]["skip"], "ignored files beyond known caches: notes.txt")
def test_signing_a_delete_needs_delete_ready_and_exactly_an_offered_item(self):
item = self.ready(False)
status, body = self.sign([item])
self.assertEqual((status, body.decode()), (400, "löschen gesperrt: Secret Service locked\n"))
item = self.ready(True)
kept = {**item, "branch": "feature/kept", "worktree": "", "tip": "0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d"}
for name, items in (("changed tip", [{**item, "tip": OTHER_TIP}]), ("skip reason", [kept]), ("pattern", [{**item, "branch": "feature/*"}]), ("twice", [item, item]), ("extra key", [{**item, "command": "rm -rf /"}])):
with self.subTest(name=name):
self.assertEqual(self.sign(items)[0], 400)
status, body = self.sign([item])
self.assertEqual(status, 200, body)
self.assertEqual(json.loads(body)["payload"]["items"], [signing.item_hash(item)])
def test_a_signed_delete_becomes_a_job_without_any_command(self):
item = self.ready(True)
signer = self.pin()
proof = signer.proof(self.prepare([item])["payload"], self.origin)
self.assertEqual(self.post_json("/api/cleanup", {"items": [item], "proof": None}, self.guard(self.host))[0], 400)
self.assertEqual(self.post_json("/api/cleanup", {"items": [item], "proof": proof}, {"Host": self.host})[0], 403)
status, _, body = self.post_json("/api/cleanup", {"items": [item], "proof": proof}, self.guard(self.host))
self.assertEqual(status, 202, body)
job_id = json.loads(body)["id"]
written = self.cleanup / "jobs" / f"{job_id}.json"
job = json.loads(written.read_text(encoding="utf-8"))
self.assertEqual(sorted(job), ["created", "id", "items", "proof", "v"])
self.assertEqual(job["items"], [{key_name: item[key_name] for key_name in ("repository", "branch", "worktree", "tip")}])
self.assertEqual(job["proof"], proof)
self.assertNotIn("command", written.read_text(encoding="utf-8"))
self.assertEqual((written.stat().st_mode & 0o777, (self.cleanup / "jobs").stat().st_mode & 0o777), (0o600, 0o700))
self.assertEqual(json.loads(self.get(f"/api/cleanup/{job_id}")[2])["status"], "queued")
self.assertEqual(self.get("/api/cleanup/0000000000000000")[0], 404)
result = {"id": job_id, "finished": time.time(), "items": [{**job["items"][0], "status": "done", "reason": ""}]}
(self.cleanup / "results" / f"{job_id}.json").write_text(json.dumps(result), encoding="utf-8")
self.assertEqual(json.loads(self.get(f"/api/cleanup/{job_id}")[2])["status"], "done")
self.assertEqual(json.loads(self.get("/api/tree")[2])["cleanups"], [result])
class TestHostCleanup(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
base = Path(tmp.name)
self.repo = base / "repos" / "demo"
self.repo.mkdir(parents=True)
self.jobs, self.results = base / "jobs", base / "results"
self.jobs.mkdir()
self.results.mkdir()
self.fake = base / "bin" / "pkgmgr"
self.fake.parent.mkdir()
self.fake.write_text(FAKE_PKGMGR.format(python=sys.executable), encoding="utf-8")
self.fake.chmod(0o755)
self.calls = self.fake.parent / "calls.jsonl"
self.tree = base / "tree" / "branch-tree.json"
self.tree.parent.mkdir()
self.tree.write_text(json.dumps({"repos": [{"path": str(self.repo), "known": True}, {"path": str(base / "stranger"), "known": False}]}), encoding="utf-8")
self.approvers = base / "config" / "approvers.json"
self.trust = verifier.Trust(approvers=self.approvers, replay=base / "replay" / "cleanup.json", origin=ORIGIN)
self.signer = key(-7, "main")
self.signer.pin(self.approvers, ORIGIN)
self.item = {"repository": str(self.repo), "branch": "feature/done", "worktree": f"{self.repo}/.worktrees/feature-done", "tip": DONE_TIP}
def proof(self, items: list[dict], signer) -> dict:
hashes = [signing.item_hash(signing.cleanup_item(**item)) for item in items]
return signer.proof(signing.approval_payload(hashes, secrets.token_hex(16), int(time.time()) + 600), ORIGIN)
def entry(self, **changes: object) -> dict:
return {"branch": self.item["branch"], "worktree": self.item["worktree"], "local_sha": DONE_TIP, "head": DONE_TIP, "state": "merged-clean", "skip": None, **changes}
def run_job(self, items: list[dict], proof: object, plan: list[dict]) -> tuple[dict, list[dict]]:
(self.fake.parent / "plan.json").write_text(json.dumps(plan), encoding="utf-8")
job_id = secrets.token_hex(8)
(self.jobs / f"{job_id}.json").write_text(json.dumps({"v": 1, "id": job_id, "created": "x", "items": items, "proof": proof}), encoding="utf-8")
self.assertEqual(host.cycle(self.jobs, self.results, host.Cleanup(self.trust, self.tree, self.fake)), 1)
self.assertEqual(list(self.jobs.iterdir()), [])
calls = [json.loads(line) for line in self.calls.read_text(encoding="utf-8").splitlines()] if self.calls.exists() else []
self.calls.unlink(missing_ok=True)
return json.loads((self.results / f"{job_id}.json").read_text(encoding="utf-8")), calls
def test_a_signed_merged_clean_item_is_dry_run_then_deleted_by_exact_name(self):
result, calls = self.run_job([self.item], self.proof([self.item], self.signer), [self.entry()])
self.assertEqual([(entry["status"], entry["reason"]) for entry in result["items"]], [("done", "")])
selector = ["--json", "--local", "--branch", "feature/done"]
self.assertEqual(calls, [
{"argv": ["branch", "cleanup", *selector], "cwd": str(self.repo)},
{"argv": ["branch", "cleanup", "--apply", *selector], "cwd": str(self.repo)},
{"argv": ["branch", "tree", "--format", "json", "--output", str(self.tree)], "cwd": str(self.tree.parent)},
])
self.assertEqual(json.loads(self.tree.read_text(encoding="utf-8")), {"rewritten": True})
self.assertEqual(result["tree"], {"code": 0, "error": ""})
def test_every_doubtful_item_is_refused_and_changes_nothing(self):
pattern = {**self.item, "branch": "feature/*"}
option = {**self.item, "branch": "--all"}
stranger = {**self.item, "repository": str(self.repo.parent.parent / "stranger")}
rule = self.signer.proof(signing.rule_payload(secrets.token_hex(8), ["review"], "", "*", "*", int(time.time()) + 600), ORIGIN)
cases = (
("bad signature", [self.item], self.proof([self.item], key(-7, "intruder")), [self.entry()], "no valid operator signature", 0),
("missing signature", [self.item], None, [self.entry()], "no signature", 0),
("rule instead of approval", [self.item], rule, [self.entry()], "no valid operator signature", 0),
("other item signed", [self.item], self.proof([{**self.item, "tip": OTHER_TIP}], self.signer), [self.entry()], "does not list this item", 0),
("unknown repository", [stranger], self.proof([stranger], self.signer), [self.entry()], "unknown repository", 0),
("pattern characters", [pattern], self.proof([pattern], self.signer), [self.entry(branch="feature/*")], "pattern or option characters", 0),
("option name", [option], self.proof([option], self.signer), [self.entry(branch="--all")], "pattern or option characters", 0),
("changed tip", [self.item], self.proof([self.item], self.signer), [self.entry(local_sha=OTHER_TIP)], "tip changed", 1),
("not merged-clean", [self.item], self.proof([self.item], self.signer), [self.entry(state="dirty")], "state is dirty", 1),
("skip reason", [self.item], self.proof([self.item], self.signer), [self.entry(skip="ignored files")], "gc skip reason", 1),
("other worktree", [self.item], self.proof([self.item], self.signer), [self.entry(worktree="/elsewhere")], "worktree changed", 1),
("pattern match in pkgmgr", [self.item], self.proof([self.item], self.signer), [self.entry(), self.entry(branch="feature/done-too")], "other branches too", 1),
)
for name, items, proof, plan, reason, dry_runs in cases:
with self.subTest(name=name):
result, calls = self.run_job(items, proof, plan)
self.assertEqual([entry["status"] for entry in result["items"]], ["refused"])
self.assertIn(reason, result["items"][0]["reason"])
self.assertEqual([call["argv"] for call in calls], [["branch", "cleanup", "--json", "--local", "--branch", items[0]["branch"]]] * dry_runs)
self.assertNotIn("tree", result)
def test_a_signature_deletes_once_and_a_malformed_job_nothing(self):
proof = self.proof([self.item], self.signer)
self.assertEqual(self.run_job([self.item], proof, [self.entry()])[0]["items"][0]["status"], "done")
again, calls = self.run_job([self.item], proof, [self.entry()])
self.assertEqual((again["items"][0]["status"], calls), ("refused", []))
self.assertIn("already used", again["items"][0]["reason"])
for name, items in (("command smuggled in", [{**self.item, "command": "rm -rf ~"}]), ("relative repository", [{**self.item, "repository": "demo"}]), ("no items", [])):
with self.subTest(name=name):
result, calls = self.run_job(items, proof, [self.entry()])
self.assertEqual((result["items"], calls), ([], []))
self.assertIn("malformed job", result["error"])
def test_the_unit_runs_the_script_once_over_the_job_directory(self):
(self.fake.parent / "plan.json").write_text(json.dumps([self.entry()]), encoding="utf-8")
job_id = secrets.token_hex(8)
(self.jobs / f"{job_id}.json").write_text(json.dumps({"v": 1, "id": job_id, "created": "x", "items": [self.item], "proof": self.proof([self.item], self.signer)}), encoding="utf-8")
(self.jobs / "stray.tmp").write_text("x", encoding="utf-8")
trust = ["--approvers", str(self.approvers), "--replay", str(self.trust.replay)]
command = [sys.executable, str(CLEANUP), "--port", ORIGIN.rsplit(":", 1)[1], "--jobs", str(self.jobs), "--results", str(self.results), *trust, "--tree", str(self.tree), "--pkgmgr", str(self.fake)]
done = subprocess.run(command, capture_output=True, text=True, timeout=60, check=False)
self.assertEqual(done.returncode, 0, done.stderr)
self.assertEqual(list(self.jobs.iterdir()), [])
result = json.loads((self.results / f"{job_id}.json").read_text(encoding="utf-8"))
self.assertEqual(result["items"][0]["status"], "done")
self.assertEqual((self.results / f"{job_id}.json").stat().st_mode & 0o777, 0o600)
class TestHostCleanupGitConfig(unittest.TestCase):
def test_pkgmgr_runs_no_hook_the_repository_config_names(self):
with tempfile.TemporaryDirectory() as tmp:
base = Path(tmp)
repo, hooks, marker = base / "repo", base / "hooks", base / "hook-ran"
hooks.mkdir()
(hooks / "reference-transaction").write_text(f"#!/bin/sh\necho path >> {marker}\n", encoding="utf-8")
(hooks / "reference-transaction").chmod(0o755)
git = ["git", "-c", "user.name=t", "-c", "user.email=t@example.invalid", "-c", "commit.gpgsign=false"]
subprocess.run([*git, "init", "-q", str(repo)], check=True)
subprocess.run([*git, "commit", "-q", "--allow-empty", "-m", "x"], cwd=repo, check=True)
subprocess.run(["git", "branch", "control"], cwd=repo, check=True)
subprocess.run(["git", "branch", "doomed"], cwd=repo, check=True)
subprocess.run(["git", "config", "core.hooksPath", str(hooks)], cwd=repo, check=True)
subprocess.run(["git", "config", "hook.spy.event", "reference-transaction"], cwd=repo, check=True)
subprocess.run(["git", "config", "hook.spy.command", f"echo config >> {marker}"], cwd=repo, check=True)
subprocess.run(["git", "branch", "-D", "control"], cwd=repo, check=True, capture_output=True)
self.assertEqual({line.split()[0] for line in marker.read_text(encoding="utf-8").splitlines()}, {"path", "config"})
marker.unlink()
fake = base / "pkgmgr"
fake.write_text(f"#!{sys.executable}\nimport subprocess\nsubprocess.run(['git', 'branch', '-D', '--', 'doomed'], check=True)\n", encoding="utf-8")
fake.chmod(0o755)
code, _, error = host.run(fake, ["branch", "cleanup", "--apply"], str(repo))
self.assertEqual(code, 0, error)
self.assertFalse(marker.exists())
branches = subprocess.run(["git", "branch", "--format=%(refname:short)"], cwd=repo, capture_output=True, text=True, check=True).stdout.split()
self.assertNotIn("doomed", branches)
if __name__ == "__main__":
unittest.main()