feat(orchestrator): W1 operator tasks, diff previews and bound commit asks

Operator tasks move from the Inbox into Tasks with the Waits on Operator filter. Review and commit asks carry the staged diff; a file whose old or new blob holds an armour line or a detection-pattern match, or every file when no pattern loads, ships only its path, status, mode and counts. The approval binds to the staged tree id from git write-tree, the encoded request body respects the hub limit, and the hub image ships every client module signed.py loads. Committed below the bar on the operator's word 2026-11-12 (no further per-bundle tests, one gate after the merge): the full hub suite and the W1 e2e never ran, two high MEDUSA rows (sandbox_paths.py FLOOR, permission_hook.py ask POST) wait for their acceptance.

🎯 Fix: 85 %
💥 Break: 12 %
💸 Cost: 30 %
⏱️ Time: 30 %
🛡️ Security: 50 %
🏆 Score: 100
🚦 Gate: ⛔ fix 85 % < 90, break 12 % > 5; security 50 % < 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-12 16:13:58 +00:00
1 parent fd8fc08603
commit d09ed61161
34 files changed
+2449 -111

No files matched your search

+9 -9
View File
@@ -35,8 +35,8 @@ OUT = Path(os.environ.get("E2E_OUT", "/tmp/orchestrator-phase4-shots"))
WIDTHS = ((1920, 1080), (1366, 768), (390, 844))
VIEWS = ("orbit", "chord", "swimlane", "tree", "3d")
TASK_VIEWS = ("list", "board", "rasci", "gantt", "burn", "3d")
SCREENS = ["stream", "stream/inbox", "stream/rules", *(f"{tab}/{view}" for tab in ("agents", "artifacts") for view in VIEWS), "agents/hierarchy", *(f"tasks/{view}" for view in TASK_VIEWS)]
SUBS = ["Inbox", "Firewall"]
SCREENS = ["stream", "tasks/operator", "stream/rules", *(f"{tab}/{view}" for tab in ("agents", "artifacts") for view in VIEWS), "agents/hierarchy", *(f"tasks/{view}" for view in TASK_VIEWS)]
SUBS = ["Firewall"]
TABS = ["Stream", "Agents", "Artifacts", "Tasks"]
HELPERS = """
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -175,7 +175,7 @@ AFTER_TAKEOVER = """(async () => {@HELPERS@
INBOX_CHIP = """(async () => {@HELPERS@
const chip = (id) => document.querySelector(`#main [aria-label="Task filters"] button[data-filter="${id}"]`);
chip("operator").click();
await until(() => location.hash === "#stream/inbox" && document.querySelector("#main .inbox-list"), 4000);
await until(() => location.hash === "#tasks/operator" && document.querySelector("#main .inbox-list"), 4000);
const inbox = {hash: location.hash, items: all("#main .inbox-list > li").length, counted: Number(text(chip("operator").querySelector(".count"))), pressed: chip("operator").getAttribute("aria-pressed"), current: all("#tabs button[aria-current=page]").map(name)};
chip("grant").click();
await until(() => location.hash.startsWith("#tasks/"), 4000);
@@ -1045,13 +1045,13 @@ INBOX_PICK = """(async () => {@HELPERS@
const last = all("#main .inbox-list > li").filter((li) => li.textContent.includes("echo row")).pop();
last.scrollIntoView({block: "center"});
await sleep(300);
const title = text(last.querySelector(".linkish"));
const title = text(last.querySelector(".command code"));
const before = scroller.scrollTop;
last.querySelector("input").click();
await sleep(300);
const after = scroller.scrollTop;
const holder = document.activeElement && document.activeElement.closest("li");
const focused = Boolean(holder && text(holder.querySelector(".linkish")) === title && document.activeElement.checked);
const focused = Boolean(holder && text(holder.querySelector(".command code")) === title && document.activeElement.checked);
all("#main .inbox-list > li").find((li) => li.textContent.includes("pick a name")).querySelector("input").click();
await sleep(300);
const buttons = all("#main .inbox-actions button").map(name);
@@ -1768,7 +1768,7 @@ class TestOrchestratorE2E(HubCase):
self.assertEqual((probe["title"], probe["h1"]), ("(2) Agent Orchestrator", "Agent Orchestrator"))
self.assertEqual(probe["header"], ["radio-open", "bubbles-toggle", "palette-open", "settings-open"])
self.assertEqual((probe["tabs"], probe["subs"]), (TABS, SUBS))
self.assertEqual(probe["badges"], {"tab-stream": None, "tab-inbox": "2", "tab-rules": None, "tab-agents": "1", "tab-artifacts": None, "tab-tasks": None})
self.assertEqual(probe["badges"], {"tab-stream": None, "tab-rules": None, "tab-agents": "1", "tab-artifacts": None, "tab-tasks": "2"})
self.assertEqual(probe["banner"], "MAYDAY · Docs: disk full")
self.assertFalse(probe["rail"])
self.assertEqual((probe["time"], probe["replay"]), ("24 h ▾", True))
@@ -1782,7 +1782,7 @@ class TestOrchestratorE2E(HubCase):
stream = by_hash["#stream"]
self.assertEqual([probe["chips"] for probe in probes if probe["hash"] == "#stream"], [["For you", "Radio", "Work", "Transcripts"]] * len(WIDTHS))
self.assertEqual((stream["menus"], stream["custom"]), (["Filter"], True))
self.assertEqual(by_hash["#stream/inbox"]["inbox"], 2)
self.assertEqual(by_hash["#tasks/operator"]["inbox"], 2)
for tab, own in (("agents", ["Hierarchy"]), ("artifacts", [])):
with self.subTest(tab=tab):
self.assertEqual(by_hash[f"#{tab}/orbit"]["views"], ["Orbit", "Chord", "Swimlane", "Tree", "3D", *own])
@@ -1824,7 +1824,7 @@ class TestOrchestratorE2E(HubCase):
self.assertEqual(listed["flows"], ["file", "file"])
self.assertEqual(by_hash["#tasks/board"]["columns"], [["queued", ["queue/gate-3"]], ["running", ["work/feature", "work/stage"]], ["blocked", ["work/docs"]], ["done", ["work/old", "operator/push"]], ["stopped", []]])
self.assertEqual(by_hash["#tasks/rasci"]["matrix"], [6, 6, "CIRARASRARAR"])
self.assertEqual([probe["taskFilters"][3][1] for probe in probes if probe["hash"] == "#stream/inbox"], ["true"] * len(WIDTHS))
self.assertEqual([probe["taskFilters"][3][1] for probe in probes if probe["hash"] == "#tasks/operator"], ["true"] * len(WIDTHS))
extra = data["results"][len(probes) :]
self.assertEqual([probe["hash"] for probe in extra[:3]], ["#agents/orbit", "#agents/chord", "#artifacts/tree"])
actions = extra[3]
@@ -1840,7 +1840,7 @@ class TestOrchestratorE2E(HubCase):
self.assertEqual(actions["centred"], [True, "none"])
calm, chips = extra[4:]
self.assertRegex(calm["note"], r"Force · without motion \(prefers-reduced-motion\)$")
self.assertEqual({key: value for key, value in chips["inbox"].items() if key != "counted"}, {"hash": "#stream/inbox", "items": chips["inbox"]["counted"], "pressed": "true", "current": ["Inbox"]})
self.assertEqual({key: value for key, value in chips["inbox"].items() if key != "counted"}, {"hash": "#tasks/operator", "items": chips["inbox"]["counted"], "pressed": "true", "current": ["Tasks"]})
self.assertGreaterEqual(chips["inbox"]["items"], 1)
self.assertEqual(chips["back"], {"hash": "#tasks/list", "rows": [["queue/gate-3", "Queue job"]]})
+224
View File
@@ -0,0 +1,224 @@
from __future__ import annotations
import json
import os
import shutil
import sys
import unittest
from pathlib import Path
from unittest import mock
HERE = Path(__file__).resolve().parent
sys.path[:0] = [str(HERE.parent), str(HERE)]
import test_orchestrator_e2e as base
from hub_case import HubCase
from hub_support import ASK, PAGE
from kvb_orchestrator import batches, skill_scripts
OUT = Path(os.environ.get("E2E_OUT", "/tmp/workbench-w1-shots"))
WITHHELD = "content withheld"
DIFF = [
{"path": "src/app.py", "status": "M", "mode": "100644", "additions": 2, "deletions": 1, "patch": "@@ -1,3 +1,4 @@\n keep = 1\n-old = 2\n+new = 3\n+more = 4\n tail = 4", "note": ""},
{"path": "logo.png", "status": "B", "mode": "100644", "additions": 0, "deletions": 0, "patch": "", "note": ""},
{"path": "big.txt", "status": "M", "mode": "100644", "additions": 401, "deletions": 0, "patch": "@@ -0,0 +1,401 @@\n" + "\n".join(f"+line {n}" for n in range(401)), "note": ""},
{"path": "key.txt", "status": "A", "mode": "100755", "additions": 3, "deletions": 0, "patch": "", "note": WITHHELD},
]
OPERATOR = """(async () => {@HELPERS@
navigator.credentials.get = @SIGNS@ ? navigator.credentials.get.bind(navigator.credentials) : () => Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
window.e2ePosted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (String(path).startsWith("/api/") && init && init.method === "POST") {
window.e2ePosted.push([String(path), JSON.parse(init.body)]);
}
return send(path, init);
};
const list = await until(() => location.hash === "#tasks/operator" && document.querySelector("#main .inbox-list") && document.querySelector("#main .diff-file"), 10000);
if (!list) {
return {error: text(document.getElementById("main")), hash: location.hash};
}
const mine = (words) => all("#main .inbox-list > li").find((li) => li.textContent.includes(words));
const cards = ["@TAG@ review", "@TAG@ ship", "@TAG@ commit"].map(mine);
const tasks = document.getElementById("tab-tasks");
const badge = tasks.querySelector(".badge");
const review = cards[0];
const files = all(".diff-file", review).map((file) => ({
path: file.dataset.path, status: file.dataset.status, open: file.querySelector("details") ? file.querySelector("details").open : null,
rows: all("tr.add, tr.del", file).length, note: all(".note", file).map(text), mode: all(".mode", file).map(text),
colours: [...new Set(all("tr.add, tr.del", file).map((row) => getComputedStyle(row).backgroundColor))].length,
}));
const operator = cards[1];
const found = {
hash: location.hash, tabs: all("#tabs > .tab-group > button").map(name), subs: all("#tabs .subtabs button").map(name),
badge: badge && !badge.hidden ? text(badge) : null, current: all("#tabs button[aria-current=page]").map(name), streamInbox: Boolean(document.getElementById("tab-inbox")),
kinds: cards.map((card) => (card ? card.className.split(" ").filter((name) => name.startsWith("k-")).join("") : null)),
boxes: cards.map((card) => Boolean(card && card.querySelector('input[type="checkbox"]'))),
operator: operator ? {command: text(operator.querySelector(".command code")), place: operator.querySelector(".command").dataset.path, text: text(operator), buttons: all("button", operator).map(name)} : null,
files, wide: document.documentElement.scrollWidth > window.innerWidth + 1,
};
cards.forEach((card) => card.querySelector('input[type="checkbox"]').click());
await sleep(300);
const grant = all("#main .inbox-actions button").find((button) => name(button).startsWith("Grant selection"));
found.grant = name(grant);
grant.click();
await until(() => document.querySelector(".sheet-box .actions"), 4000);
const confirm = all(".sheet-box .actions button").find((button) => text(button).startsWith("Grant "));
found.confirm = text(confirm);
found.listed = all(".sheet-box ul.mini > li").length;
confirm.click();
await until(() => window.e2ePosted.some(([path]) => path === "/api/sign"), 8000);
await sleep(800);
found.posted = window.e2ePosted.filter(([path]) => ["/api/sign", "/api/inbox/approve"].includes(path)).map(([path, body]) => [path, body.items.map((item) => item.kind).sort()]);
return found;
})()"""
FEEDBACK = """(async () => {@HELPERS@
const card = await until(() => all("#main .inbox-list > li").find((li) => li.textContent.includes("@TAG@ second") && li.querySelector(".diff-file tr.add")), 10000);
if (!card) {
return {error: text(document.getElementById("main"))};
}
const select = (from, to) => {
const range = document.createRange();
range.setStart(from, 0);
range.setEnd(to, to.childNodes.length);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
return range.getBoundingClientRect();
};
window.e2ePosted = [];
const send = window.fetch.bind(window);
window.fetch = (path, init) => {
if (String(path).startsWith("/api/messages")) {
window.e2ePosted.push([String(path), JSON.parse(init.body).text]);
}
return send(path, init);
};
navigator.credentials.get = @SIGNS@ ? navigator.credentials.get.bind(navigator.credentials) : () => Promise.reject(new DOMException("The operator canceled.", "NotAllowedError"));
const rows = all(".diff-file[data-path='src/app.py'] tr[data-line]", card);
const box = select(rows[1].querySelector("td.code"), rows[2].querySelector("td.code"));
await sleep(300);
const floating = document.querySelector(".feedback-float");
const nearby = Boolean(floating && !floating.hidden);
rows[1].querySelector("td.code").dispatchEvent(new MouseEvent("contextmenu", {bubbles: true, cancelable: true, clientX: box.left + 4, clientY: box.top + 4}));
await sleep(200);
const menu = floating && floating.getAttribute("role");
floating.querySelector("button").click();
const sheet = await until(() => document.querySelector(".sheet-box textarea"), 4000);
const title = text(document.querySelector(".sheet-head .panel-title"));
const range = text(document.querySelector(".sheet-box .who code"));
sheet.value = "right-click note";
all(".sheet-box button").find((button) => name(button) === "Send feedback").click();
await until(() => window.e2ePosted.length >= (@SIGNS@ ? 2 : 1), 8000);
await sleep(600);
const fresh = () => all("#main .inbox-list > li").find((li) => li.textContent.includes("@TAG@ second"));
const shown = @SIGNS@ ? await until(() => fresh() && all(".feedback-list li", fresh()).map(text).find((entry) => entry.includes("right-click note")), 15000) : null;
const close = document.querySelector(".sheet-head button");
if (close) {
close.click();
}
const ask = all("#main .inbox-list > li").find((li) => li.textContent.includes("@TAG@ keyed"));
const code = ask.querySelector(".command code");
select(code, code);
document.body.dispatchEvent(new KeyboardEvent("keydown", {key: "f", bubbles: true, cancelable: true}));
const keyed = await until(() => document.querySelector(".sheet-box textarea"), 4000);
const keyRange = text(document.querySelector(".sheet-box .who code"));
keyed.value = "key f note";
all(".sheet-box button").find((button) => name(button) === "Send feedback").click();
await sleep(1500);
return {nearby, menu, title, range, keyRange, shown: shown || "", posted: window.e2ePosted, wide: document.documentElement.scrollWidth > window.innerWidth + 1};
})()"""
def probe(template: str, tag: str, signs: bool) -> str:
return template.replace("@HELPERS@", base.HELPERS).replace("@TAG@", tag).replace("@SIGNS@", "true" if signs else "false")
@unittest.skipUnless(shutil.which(os.environ.get("CHROMIUM", "chromium")), "no Chromium")
class TestWorkbenchOperatorTasks(HubCase):
drive = base.TestOrchestratorE2E.drive
authenticator = base.TestOrchestratorE2E.authenticator
def setUp(self):
super().setUp()
OUT.mkdir(parents=True, exist_ok=True)
self.claim("s2", "Skills")
rows = {}
self.reviews = {}
for width, _ in base.WIDTHS:
tag = f"w{width}"
for word in ("review", "second"):
arguments = {**self.arguments(f"feat: {tag} {word}", "", self.page(f"{tag}-{word}.html", PAGE)), "agent": "Skills", "diff": DIFF}
result = self.tool("review_request", arguments)
self.assertFalse(result["isError"], result)
self.reviews[(tag, word)] = result["structuredContent"]["id"]
for word in ("commit", "keyed"):
status, _, body = self.post_json("/api/asks", {**ASK, "command": f"git commit -m '{tag} {word}'", "session": "s2", "diff": DIFF}, self.local())
self.assertEqual(status, 201, body)
rows[f"operator/{tag}"] = {"title": f"make {tag} ship", "workdir": "/repo", "description": f"{tag} ship the build", "check": "manual", "status": "queued", "roles": {"Skills": "A"}}
self.write_council({"rasci.json": rows})
self.base = f"http://localhost:{self.port}/"
def steps(self, browser: str, signs: bool) -> list[dict]:
steps = []
for width, height in base.WIDTHS:
tag = f"w{width}"
shot = str(OUT / f"{browser}-{width}-operator")
steps.append({"width": width, "height": height, "url": f"{self.base}?{width}#stream/inbox", "wait": 1200, "probe": probe(OPERATOR, tag, signs), "shot": f"{shot}-tasks.png"})
steps.append({"width": width, "height": height, "url": None, "wait": 0, "probe": probe(FEEDBACK, tag, signs), "shot": f"{shot}-feedback.png"})
return steps
def check(self, data: dict, browser: str, signs: bool) -> None:
results = iter(data["results"])
for width, _ in base.WIDTHS:
tag = f"w{width}"
tasks, feedback = next(results), next(results)
with self.subTest(browser=browser, width=width):
self.assertNotIn("error", tasks, tasks)
self.assertEqual((tasks["hash"], tasks["tabs"], tasks["subs"], tasks["streamInbox"], tasks["current"]), ("#tasks/operator", base.TABS, ["Firewall"], False, ["Tasks"]))
self.assertRegex(tasks["badge"] or "", r"^[1-9][0-9]*$")
self.assertEqual((tasks["kinds"], tasks["boxes"]), (["k-review", "k-operator", "k-ask"], [True, True, True]))
operator = tasks["operator"]
self.assertEqual((operator["command"], operator["place"]), (f"make {tag} ship", "/repo"))
self.assertIn(f"{tag} ship the build", operator["text"])
for label in ("Run", "Deny", "Copy", "1 h", "tomorrow", "pin"):
self.assertIn(label, operator["buttons"])
self.assertEqual([(entry["path"], entry["status"], entry["open"]) for entry in tasks["files"]], [("src/app.py", "M", True), ("logo.png", "B", None), ("big.txt", "M", False), ("key.txt", "A", None)])
self.assertEqual((tasks["files"][0]["rows"], tasks["files"][0]["note"], tasks["files"][0]["colours"]), (3, [], 2))
self.assertEqual((tasks["files"][3]["note"], tasks["files"][3]["mode"]), ([WITHHELD], ["100755"]))
self.assertEqual((tasks["grant"], tasks["confirm"], tasks["listed"]), ("Grant selection (3)", "Grant 3", 3))
kinds = ["ask", "operator", "review"]
self.assertEqual(tasks["posted"], [["/api/sign", kinds], *([["/api/inbox/approve", kinds]] if signs else [])])
self.assertFalse(tasks["wide"])
self.assertNotIn("error", feedback, feedback)
self.assertEqual((feedback["nearby"], feedback["menu"], feedback["title"]), (True, "menu", "Feedback"))
self.assertEqual((feedback["range"], feedback["keyRange"]), ("src/app.py:2-2", "/repo:1-1"))
ref = f"review:{self.reviews[(tag, 'second')]}"
texts = [text for path, text in feedback["posted"] if path == "/api/messages/sign"]
self.assertEqual(texts[0].split(": ", 1)[0], f"Feedback on {ref} src/app.py:2-2")
self.assertTrue(texts[0].endswith(" right-click note"), texts)
if signs:
self.assertIn("right-click note", feedback["shown"])
self.assertTrue(any(text and text.endswith(" key f note") for _, text in feedback["posted"]), feedback["posted"])
self.assertFalse(feedback["wide"])
radio = [" ".join(args) for script, args in self.scripts.calls if script == skill_scripts.RADIO]
reached = [entry for entry in radio if "Feedback on review:" in entry]
self.assertEqual(len(reached), 2 * len(base.WIDTHS) if signs else 0, radio)
def test_chromium_shows_operator_tasks_with_a_badge_diffs_bulk_grants_and_sends_feedback_at_three_widths(self):
with mock.patch.object(batches, "UNDO", 0.3):
data = self.drive(self.steps("chromium", True), self.authenticator())
(OUT / "chromium-operator.json").write_text(json.dumps(data, indent=1), encoding="utf-8")
self.assertEqual([error for error in data["errors"] if "/favicon.ico" not in error], [])
self.check(data, "chromium", True)
@unittest.skipUnless(shutil.which(os.environ.get("FIREFOX", "firefox")), "no Firefox")
def test_firefox_shows_operator_tasks_with_a_badge_diffs_and_feedback_without_signing_at_three_widths(self):
data = self.drive(self.steps("firefox", False), None, "bidi.mjs", os.environ.get("FIREFOX", "firefox"))
(OUT / "firefox-operator.json").write_text(json.dumps(data, indent=1), encoding="utf-8")
self.assertEqual([error for error in data["errors"] if "/favicon.ico" not in error], [])
self.check(data, "firefox", False)
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -57,7 +57,7 @@ class TestHubConsole(HubCase):
kept = self.item("operator/greet")
self.assertEqual((kept["kind"], kept["title"], kept["command"], kept["directory"], kept["runnable"], kept["deniable"]), ("operator", COMMAND, COMMAND, str(self.outside), False, False))
self.assertEqual(kept["job"]["output"], "line 1\nline 2\n")
self.assertEqual(self.snapshot()["badges"]["inbox"], 1)
self.assertEqual(self.snapshot()["badges"]["tasks"], 1)
for payload in ({"id": "nope"}, {"id": job["id"], "extra": 1}, {}, [job["id"]]):
with self.subTest(payload=payload):
self.assertEqual(self.dismiss(payload, self.guard(self.host)), 400)
+13 -7
View File
@@ -42,7 +42,8 @@ const N = require(process.argv[1]);
const hashes = ["", "#stream", "#stream/inbox", "#stream/rules", "#stream/nope", "#agents/chord", "#agents/3d", "#agents", "#agents/nope",
"#artifacts/tree", "#artifacts/orbit", "#graph", "#graph/swimlane", "#graph/orbit", "#tree", "#bogus", "#agents%2Fswimlane",
"#agents/hierarchy", "#artifacts/hierarchy", "#graph/hierarchy", "#tree/hierarchy", "#stream/radio", "#radio",
"#tasks", "#tasks/board", "#tasks/rasci", "#tasks/orbit", "#agents/board", "#tasks/hierarchy", "#tasks/gantt", "#tasks/burn", "#agents/gantt", "#tasks/3d"];
"#tasks", "#tasks/board", "#tasks/rasci", "#tasks/orbit", "#agents/board", "#tasks/hierarchy", "#tasks/gantt", "#tasks/burn", "#agents/gantt", "#tasks/3d",
"#tasks/operator", "#tasks%2Foperator", "#stream/operator", "#agents/operator"];
console.log(JSON.stringify({
tabs: N.TABS.map(([id, label]) => [id, label, (N.SUBS[id] || []).map(([sub, name]) => [sub, name])]),
views: N.VIEWS.map(([id, label]) => [id, label]),
@@ -64,9 +65,10 @@ class TestNavModule(unittest.TestCase):
done = subprocess.run(["node", "-e", NAV_PROBE, str(NAV)], capture_output=True, text=True, timeout=60, check=True)
cls.data = json.loads(done.stdout)
def test_the_navigation_has_four_entries_and_stream_has_inbox_and_rules(self):
stream = ["stream", "Stream", [["inbox", "Inbox"], ["rules", "Firewall"]]]
def test_the_navigation_has_four_entries_and_no_inbox_and_stream_keeps_only_the_firewall(self):
stream = ["stream", "Stream", [["rules", "Firewall"]]]
self.assertEqual(self.data["tabs"], [stream, ["agents", "Agents", []], ["artifacts", "Artifacts", []], ["tasks", "Tasks", []]])
self.assertNotIn("inbox", json.dumps(self.data["tabs"]))
def test_agents_and_artifacts_offer_five_views_in_order_and_tasks_six(self):
shared = [["orbit", "Orbit"], ["chord", "Chord"], ["swimlane", "Swimlane"], ["tree", "Tree"], ["3d", "3D"]]
@@ -83,7 +85,7 @@ class TestNavModule(unittest.TestCase):
self.assertEqual(self.data["routes"], [
stream,
stream,
{"screen": "inbox", "view": None, "redirect": None},
{"screen": "inbox", "view": None, "redirect": "#tasks/operator"},
{"screen": "rules", "view": None, "redirect": None},
stream,
{"screen": "agents", "view": "chord", "redirect": None},
@@ -114,11 +116,15 @@ class TestNavModule(unittest.TestCase):
{"screen": "tasks", "view": "burn", "redirect": None},
{"screen": "agents", "view": None, "redirect": None},
{"screen": "tasks", "view": "3d", "redirect": None},
{"screen": "inbox", "view": None, "redirect": None},
{"screen": "inbox", "view": None, "redirect": None},
stream,
{"screen": "agents", "view": None, "redirect": None},
])
def test_every_screen_has_its_own_hash_and_tab(self):
self.assertEqual(self.data["hashes"], ["#stream", "#stream/inbox", "#stream/rules", "#agents/chord", "#artifacts/3d", "#tasks/board"])
self.assertEqual(self.data["tabOf"], ["stream", "stream", "stream", "agents", "artifacts", "tasks"])
def test_every_screen_has_its_own_hash_and_tab_and_operator_tasks_live_under_tasks(self):
self.assertEqual(self.data["hashes"], ["#stream", "#tasks/operator", "#stream/rules", "#agents/chord", "#artifacts/3d", "#tasks/board"])
self.assertEqual(self.data["tabOf"], ["stream", "tasks", "stream", "agents", "artifacts", "tasks"])
class TestScreenTransition(unittest.TestCase):
+838
View File
@@ -0,0 +1,838 @@
from __future__ import annotations
import ast
import importlib
import json
import os
import shlex
import subprocess
import sys
import tempfile
import threading
import unittest
from pathlib import Path
from unittest import mock
from hub_case import HubCase
from hub_support import APPROVAL, ASK, GIT_ENV, HUB, PAGE
from kvb_orchestrator import diffs, fields, reviewing, signed
permission_hook = importlib.import_module("permission_hook")
staged_diff = importlib.import_module("staged_diff")
sandbox_paths = importlib.import_module("sandbox_paths")
hub_limits = importlib.import_module("hub_limits")
approval = importlib.import_module("approval")
TEMPLATES = HUB / "templates"
DIFFS = TEMPLATES / "diffs.js"
PAGE_MODULES = ["page-constants.js", "page-dom.js", "page-diff.js", "page-inbox.js", "page-stream.js", "page-screens.js", "page-keyboard.js"]
SECRET = 'api_key = "plain-example-words"'
ARMOURED = "before\n-----BEGIN EXAMPLE-----\nplain words only\n-----END EXAMPLE-----\nafter\n"
WITHHELD = "content withheld"
ALLOW = {"behavior": "allow"}
RULES = """
rules:
- id: TEST-SECRET-1
cwe: 'CWE-798: Use of Hard-coded Credentials'
patterns:
- api_key\\s*=\\s*["'][^"']{8,}["']
file_types:
- '*.py'
- id: TEST-OTHER-1
cwe: CWE-200
patterns:
- print
file_types:
- '*.py'
"""
DIFFS_PROBE = """
const D = require(process.argv[1]);
const patch = ["@@ -3,3 +3,4 @@", " keep", "-old", "+new", "+more", " tail"].join("\\n");
console.log(JSON.stringify({
rows: D.rows(patch).map((row) => [row.kind, row.line, row.old, row.new, row.text]),
collapsed: [[400, 0], [200, 201], [0, 401]].map(([additions, deletions]) => D.collapsed({additions, deletions})),
span: [D.span([7, 3, 5]), D.span([]), D.span([0, 2])],
lineSpan: [D.lineSpan("make\\n deploy\\n now", "deploy\\n now"), D.lineSpan("one line", "nope"), D.lineSpan("a\\nb", "")],
feedback: D.feedback("review:0123456789abcdef", "src/app.py", 4, 5, " -old\\n+new ", " use the helper "),
quoted: [D.quoted("x".repeat(D.QUOTE_LIMIT + 5)).length, D.QUOTE_LIMIT],
picked: D.feedbackOf([{text: "Feedback on review:abc a:1-1: \\"q\\" t"}, {text: "Feedback on review:abcd a:1-1"}, {text: "hello"}], "review:abc").length,
}));
"""
PAGE_PROBE = r"""
const fs = require("fs");
const vm = require("vm");
const [dir, names] = [process.argv[1], JSON.parse(process.argv[2])];
class Node {}
class El extends Node {
constructor(tag) { super(); Object.assign(this, {tag, attrs: {}, children: [], listeners: {}, hidden: false, className: "", style: {}, dataset: {}, disabled: false, value: ""}); }
setAttribute(key, value) {
this.attrs[key] = String(value);
if (key === "class") { this.className = String(value); }
if (key === "hidden") { this.hidden = true; }
if (key === "disabled") { this.disabled = true; }
if (key.startsWith("data-")) { this.dataset[key.slice(5).replace(/-(\w)/g, (_, c) => c.toUpperCase())] = String(value); }
}
getAttribute(key) { return Object.hasOwn(this.attrs, key) ? this.attrs[key] : null; }
removeAttribute(key) { delete this.attrs[key]; }
addEventListener(name, run) { (this.listeners[name] ||= []).push(run); }
append(...nodes) { this.children.push(...nodes); }
replaceChildren(...nodes) { this.children = nodes; }
get firstChild() { return this.children[0] || null; }
get textContent() { return this.children.map((child) => (typeof child === "string" ? child : child.textContent)).join(""); }
set textContent(value) { this.children = [String(value)]; }
focus() {}
contains() { return false; }
querySelector() { return null; }
querySelectorAll() { return []; }
click() { for (const run of this.listeners.click || []) { run({target: this, preventDefault() {}}); } }
}
const elements = new Map();
const document = {
currentScript: null, title: "", body: new El("body"), activeElement: null,
createElement: (tag) => new El(tag), createElementNS: (ns, tag) => new El(tag),
getElementById: (id) => {
if (!elements.has(id)) { const made = new El("div"); made.hidden = ["palette", "sheet"].includes(id); elements.set(id, made); }
return elements.get(id);
},
querySelectorAll: () => [],
};
document.activeElement = document.body;
const all = (node) => (typeof node === "string" ? [] : [node, ...node.children.flatMap(all)]);
const classes = (node) => String(node.className || node.attrs.class || "").split(" ").filter(Boolean);
const labels = (node) => all(node).filter((el) => el.tag === "button").map((el) => el.getAttribute("aria-label") || el.textContent);
const calls = {proofs: [], posts: [], sheets: [], radio: []};
const now = 1_800_000_000;
const base = {agent: "Skills", to: "Skills", detail: "", green: true, gate: "", score: "", warnings: [], options: [], recommended: "", command: "", directory: "", sha256: "", runnable: false, deniable: false, job: null, rule: null, dry_run: null, diff: null, pinned: false, until: 0, held: false, ts: now - 60};
const operator = {...base, kind: "operator", id: "operator/ship", title: "make ship", command: "make ship", directory: "/repo", detail: "release the build", runnable: true, deniable: true, green: false, sha256: "abc"};
const review = {...base, kind: "review", id: "0123456789abcdef", title: "feat: x", detail: "feature/x", gate: "pass", score: "🎯 95", directory: "/repo", diff: 4};
const ask = {...base, kind: "ask", id: "1123456789abcdef", title: "Bash: git commit -m x", command: "git commit -m x", directory: "/repo", deniable: true};
const decision = {...base, kind: "decision", id: "2123456789abcdef", title: "Merge?", options: [{label: "Now", description: ""}], recommended: "Now"};
const context = {
console, Node, document, JSON, Math, Date, Intl, Promise, Set, Map, Number, String, Array, Object, RegExp, Boolean,
window: {location: {hash: "", hostname: "localhost", port: "8737", pathname: "/"}, setTimeout: () => 0, clearTimeout: () => {}, setInterval: () => 0, clearInterval: () => {}, getSelection: () => null, innerWidth: 1366, innerHeight: 768, matchMedia: () => ({matches: false})},
Nav: require(`${dir}/nav.js`), Diffs: require(`${dir}/diffs.js`), Faces: {of: () => ({parts: []}), SIZE: 64},
state: {badges: {tasks: 3, agents: 0, artifacts: 1}, inbox: [operator, review, ask, decision], messages: [{id: "m1", text: "Feedback on review:0123456789abcdef a.py:1-2: \"x\" fix", created_ts: now}], people: {}, graph: {nodes: []}, silence: null, presence: {}, kinds: {operator: "Operator"}},
ui: {screen: "inbox", views: {tasks: "list"}, selected: new Set(), cursor: 0, snoozed: false, busy: false, review: null, file: 0},
KINDS: [["operator", "Operator"], ["review", "Review"]], speech: {current: null}, look: {hidden: new Set()}, settings: {}, known: {batches: new Set()},
relative: new Intl.RelativeTimeFormat("en"), absolute: new Intl.DateTimeFormat("en"), calm: {matches: true}, drawn: {screen: "", sig: ""},
proofFor: async (entries, label) => { calls.proofs.push({entries, label}); return {proof: {payload: "p", assertion: "a"}}; },
sendJson: async (path, body) => { calls.posts.push({path, body}); return {ok: true, data: {id: "b1", due: now + 3}}; },
radioSend: async (message) => { calls.radio.push(message); return true; },
load: async () => {}, toast: () => {}, fail: () => [], undoToast: () => {},
jobOpen: () => false, dismissJob: () => {}, consoleOf: () => null, settleConsoles: () => {}, repaintConsoles: () => {},
withoutOutput: (item) => item, ruleRequestBody: () => null, acceptRule: () => null, reviewSheet: () => {}, openAgent: () => {}, confirmAct: () => {}, measure: () => {},
openSheet: (title, body) => calls.sheets.push({title, body}), closeSheet: () => {}, emptyState: () => new El("div"), onLocalhost: () => true, localLink: () => null,
focusSub: () => {}, openPalette: () => {}, closePalette: () => {}, skyLook: () => {}, popupDialog: () => ({open: false}), closePopup: () => {}, closeMenus: () => {}, helpSheet: () => {},
space: {look: false}, stream: {events: [], entries: [], dom: null}, filter: {}, inboxDom: null, inboxSig: "", reviewDom: null, graphDom: null, tree: {}, taskView: {}, g3: {},
};
context.denySplit = () => {
const button = new El("button");
button.setAttribute("aria-label", "Deny");
return button;
};
const source = names.map((name) => fs.readFileSync(`${dir}/${name}`, "utf8")).join("\n");
const probe = `
openSheet = (title, body) => calls.sheets.push({title, body});
const operatorCard = inboxItem(operator, 0);
const streamCard = eventRow({id: "e1", kind: "operator", waiting: true, ts: now, agent: "Skills", title: "make ship", text: "release the build", ref: {task: "operator/ship", directory: "/repo", status: "queued"}});
const plainCard = eventRow({id: "e2", kind: "operator", waiting: false, ts: now, agent: "Skills", title: "make other", text: "-", ref: {task: "operator/other", directory: "/repo", status: "done"}});
diffView.cache.set("review:0123456789abcdef", [
{path: "src/app.py", status: "M", mode: "100644", additions: 2, deletions: 1, patch: "@@ -1,2 +1,3 @@\\n keep\\n-old\\n+new\\n+more", note: ""},
{path: "logo.png", status: "B", mode: "100644", additions: 0, deletions: 0, patch: "", note: ""},
{path: "big.txt", status: "M", mode: "100644 => 100755", additions: 300, deletions: 101, patch: "@@ -1 +1 @@\\n-a\\n+b", note: ""},
{path: "late.txt", status: "A", mode: "100644", additions: 9, deletions: 0, patch: "", note: "content withheld"},
]);
const reviewCard = inboxItem(review, 1);
diffView.cache.set("ask:1123456789abcdef", [{path: "src/app.py", status: "M", mode: "100644", additions: 1, deletions: 0, patch: "@@ -1 +1,2 @@\\n keep\\n+new", note: ""}]);
const askCard = inboxItem({...ask, diff: 1}, 2);
const sections = all(reviewCard).filter((el) => classes(el).includes("diff-file"));
const read = (section) => {
const details = all(section).find((el) => el.tag === "details") || null;
const rows = all(section).filter((el) => el.tag === "tr");
return {path: section.dataset.path, status: section.dataset.status, open: details ? details.getAttribute("open") !== null : null, table: rows.length > 0,
rows: rows.map((row) => [classes(row).join(" "), row.dataset.line || null]),
note: all(section).filter((el) => classes(el).includes("note")).map((el) => el.textContent), mode: all(section).filter((el) => classes(el).includes("mode")).map((el) => el.textContent),
stat: all(section).filter((el) => classes(el).includes("add") || classes(el).includes("del")).filter((el) => el.tag === "span").map((el) => el.textContent)};
};
drawStatus();
const tasksBadge = navButtons.get("tasks").badge;
const badgeState = {keys: [...navButtons.keys()], text: tasksBadge.textContent, hidden: tasksBadge.hidden, urgent: tasksBadge.className, title: document.title};
state.badges = {tasks: 0, agents: 0, artifacts: 1};
drawStatus();
badgeState.zero = [tasksBadge.hidden, document.title];
const results = {
operator: {classes: classes(operatorCard), checkbox: all(operatorCard).filter((el) => el.tag === "input" && el.attrs.type === "checkbox").length, ref: operatorCard.dataset.ref, to: operatorCard.dataset.to,
command: all(operatorCard).filter((el) => classes(el).includes("command")).map((el) => [el.dataset.path, all(el).find((inner) => inner.tag === "code").textContent]),
text: operatorCard.textContent, buttons: labels(operatorCard)},
stream: {buttons: labels(streamCard), actions: all(streamCard).filter((el) => classes(el).includes("line-actions")).length, ref: streamCard.dataset.ref || null, plain: [labels(plainCard), plainCard.dataset.ref || null]},
review: {ref: reviewCard.dataset.ref, files: sections.map(read), feedback: all(reviewCard).filter((el) => classes(el).includes("feedback-list")).map((el) => el.textContent)},
status: badgeState,
ask: {ref: askCard.dataset.ref, to: askCard.dataset.to, files: all(askCard).filter((el) => classes(el).includes("diff-file")).map((el) => [el.dataset.path, all(el).filter((inner) => inner.tag === "tr").length]),
regions: all(askCard).filter((el) => el.dataset.path !== undefined).map((el) => el.dataset.path)},
};
bulk([operator, review, ask, decision], "Grant selection", false);
const sheet = calls.sheets.pop();
const confirm = all({children: sheet.body}).filter((el) => el.tag === "button").pop();
results.bulk = {label: confirm.textContent, shown: all({children: sheet.body}).filter((el) => el.tag === "li").length};
confirm.click();
feedbackSheet({ref: "review:0123456789abcdef", to: "Skills", path: "src/app.py", from: 2, until: 4, quote: "-old\\n+new"});
const form = calls.sheets.pop();
const area = all({children: form.body}).find((el) => el.tag === "textarea");
area.value = " call the helper instead ";
const send = all({children: form.body}).filter((el) => el.tag === "button").pop();
results.feedback = {title: form.title, send: [send.getAttribute("aria-label"), send.getAttribute("title")]};
send.click();
let prevented = false;
feedbackKey = () => true;
keyboard({key: "f", target: document.body, altKey: false, ctrlKey: false, metaKey: false, isComposing: false, preventDefault: () => { prevented = true; }});
results.key = prevented;
setTimeout(() => {
results.proofs = calls.proofs.map((call) => call.entries.map((entry) => [entry.kind, entry.verb, entry.label]));
results.posts = calls.posts.map((call) => [call.path, call.body.items.length, call.body.proof !== null]);
results.radio = calls.radio;
console.log(JSON.stringify(results));
}, 20);
`;
vm.runInNewContext(`${source}\n${probe}`, {...context, setTimeout, Element: El, HTMLElement: El, now, operator, review, ask, decision, calls, all, classes, labels});
"""
def git(cwd: Path, *args: str) -> str:
done = subprocess.run(["git", *args], cwd=cwd, env={**os.environ, **GIT_ENV}, check=True, capture_output=True, text=True)
return done.stdout
def scratch_repo(root: Path) -> Path:
repo = root / "repo"
repo.mkdir()
git(repo, "init", "-q", "-b", "main")
(repo / "app.py").write_text("keep = 1\nold = 2\n", encoding="utf-8")
(repo / "gone.txt").write_text("bye\n", encoding="utf-8")
(repo / "logo.png").write_bytes(b"\x89PNG\x00\x01")
git(repo, "add", "-A")
git(repo, "commit", "-q", "-m", "base")
(repo / "app.py").write_text(f"keep = 1\nnew = 3\n{SECRET}\nprint(1)\n", encoding="utf-8")
(repo / "added.txt").write_text("fresh\n", encoding="utf-8")
(repo / "logo.png").write_bytes(b"\x89PNG\x00\x02")
(repo / "gone.txt").unlink()
git(repo, "add", "-A")
return repo
def bytes_read() -> int:
counters = dict(line.split(": ") for line in Path("/proc/self/io").read_text(encoding="ascii").splitlines())
return int(counters["rchar"])
def deny(entries: object) -> dict:
return {"sandbox": {"filesystem": {"denyRead": entries}}}
def write_settings(file: Path, content: object) -> None:
file.parent.mkdir(parents=True, exist_ok=True)
file.unlink(missing_ok=True)
if content is not None:
file.write_bytes(content if isinstance(content, bytes) else json.dumps(content).encode())
def rules_dir(root: Path) -> Path:
folder = root / "rules" / "secrets"
folder.mkdir(parents=True)
(folder / "test.yaml").write_text(RULES, encoding="utf-8")
return root / "rules"
class TestDiffsModule(unittest.TestCase):
@classmethod
def setUpClass(cls):
done = subprocess.run(["node", "-e", DIFFS_PROBE, str(DIFFS)], capture_output=True, text=True, timeout=60, check=True)
cls.data = json.loads(done.stdout)
def test_a_patch_parses_into_unified_rows_with_both_line_numbers(self):
self.assertEqual(self.data["rows"], [
["hunk", 1, "", "", "@@ -3,3 +3,4 @@"],
["ctx", 2, 3, 3, "keep"],
["del", 3, 4, "", "old"],
["add", 4, "", 4, "new"],
["add", 5, "", 5, "more"],
["ctx", 6, 5, 6, "tail"],
])
def test_a_file_over_400_changed_lines_is_collapsed(self):
self.assertEqual(self.data["collapsed"], [False, True, True])
def test_selected_rows_and_command_quotes_give_a_line_range(self):
self.assertEqual(self.data["span"], [[3, 7], None, [2, 2]])
self.assertEqual(self.data["lineSpan"], [[2, 3], [1, 1], [1, 2]])
def test_the_feedback_text_follows_the_contract(self):
self.assertEqual(self.data["feedback"], 'Feedback on review:0123456789abcdef src/app.py:4-5: "-old\n+new" use the helper')
self.assertEqual(self.data["quoted"], [2001, 2000])
self.assertEqual(self.data["picked"], 1)
class TestOperatorPage(unittest.TestCase):
@classmethod
def setUpClass(cls):
done = subprocess.run(["node", "-e", PAGE_PROBE, str(TEMPLATES), json.dumps(PAGE_MODULES)], capture_output=True, text=True, timeout=60, check=False)
if done.returncode != 0:
raise AssertionError(done.stderr[-4000:])
cls.data = json.loads(done.stdout)
def test_the_tasks_entry_carries_the_waiting_count_hidden_at_zero_and_no_inbox_entry_exists(self):
status = self.data["status"]
self.assertEqual(status["keys"], ["stream", "rules", "agents", "artifacts", "tasks"])
self.assertEqual((status["text"], status["hidden"], status["urgent"], status["title"]), ("3", False, "badge urgent", "(3) Agent Orchestrator"))
self.assertEqual(status["zero"], [True, "Agent Orchestrator"])
def test_an_operator_task_is_a_stream_card_with_a_checkbox_and_every_action(self):
card = self.data["operator"]
self.assertTrue({"item", "entry", "k-operator", "waiting"} <= set(card["classes"]), card["classes"])
self.assertEqual((card["checkbox"], card["ref"], card["to"]), (1, "operator:operator/ship", "Skills"))
self.assertEqual(card["command"], [["/repo", "make ship"]])
for text in ("in /repo", "release the build"):
with self.subTest(text=text):
self.assertIn(text, card["text"])
for label in ("Run", "Deny", "Copy", "1 h", "tomorrow", "pin"):
with self.subTest(label=label):
self.assertIn(label, card["buttons"])
def test_the_stream_keeps_its_operator_card_with_its_actions(self):
stream = self.data["stream"]
self.assertEqual((stream["actions"], stream["ref"]), (1, "operator:operator/ship"))
self.assertIn("Run", stream["buttons"])
self.assertIn("Deny", stream["buttons"])
self.assertEqual(stream["plain"], [["Skills", "Copy"], None])
def test_a_review_card_shows_one_unified_block_per_file_expanded_collapsed_past_400_lines_and_binary_named_only(self):
files = self.data["review"]["files"]
self.assertEqual([(entry["path"], entry["status"], entry["open"], entry["table"]) for entry in files], [
("src/app.py", "M", True, True), ("logo.png", "B", None, False), ("big.txt", "M", False, True), ("late.txt", "A", None, False),
])
self.assertEqual(files[0]["rows"], [["hunk", None], ["ctx", "1"], ["del", "2"], ["add", "2"], ["add", "3"]])
self.assertEqual((files[0]["note"], files[0]["mode"], files[0]["stat"]), ([], [], ["+2", "-1"]))
self.assertEqual((files[2]["mode"], files[3]["note"], files[3]["stat"]), (["100644 => 100755"], [WITHHELD], ["+9", "-0"]))
def test_a_commit_ask_shows_its_diff_like_a_review_and_its_command_and_files_take_feedback(self):
ask = self.data["ask"]
self.assertEqual((ask["ref"], ask["to"], ask["files"]), ("ask:1123456789abcdef", "Skills", [["src/app.py", 3]]))
self.assertEqual(ask["regions"], ["/repo", "src/app.py"])
def test_sent_feedback_shows_under_its_card(self):
self.assertEqual(len(self.data["review"]["feedback"]), 1)
self.assertIn('a.py:1-2: "x" fix', self.data["review"]["feedback"][0])
def test_the_bulk_bar_counts_and_grants_asks_reviews_operator_commands_and_choices_with_one_signature(self):
self.assertEqual(self.data["bulk"], {"label": "Grant 4", "shown": 4})
self.assertEqual(self.data["proofs"], [[["operator", "approve", ""], ["review", "approve", ""], ["ask", "approve", ""], ["decision", "approve", "Now"]]])
self.assertEqual(self.data["posts"], [["/api/inbox/approve", 4, True]])
def test_feedback_is_one_signed_post_to_the_requesting_agent_in_the_contract_format_and_the_key_f_opens_it(self):
self.assertEqual(self.data["feedback"]["title"], "Feedback")
self.assertEqual(self.data["feedback"]["send"], ["Send feedback", "Send feedback: a post to Skills"])
self.assertEqual(self.data["radio"], [{"to": "Skills", "text": 'Feedback on review:0123456789abcdef src/app.py:2-4: "-old\n+new" call the helper instead', "upload": "", "cls": "post", "voice": ""}])
self.assertTrue(self.data["key"])
class TestStagedDiff(unittest.TestCase):
def setUp(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
self.root = Path(os.path.realpath(tmp.name))
self.repo = scratch_repo(self.root)
self.rules = rules_dir(self.root)
for patch in (
mock.patch.object(staged_diff, "CACHE", self.root / "cache" / "secret-rules.json"),
mock.patch.object(sandbox_paths, "MANAGED", self.root / "managed"),
mock.patch.dict(os.environ, {"HOME": str(self.root), "CLAUDE_CONFIG_DIR": str(self.root / ".claude"), "CLAUDE_PROJECT_DIR": str(self.root / "project")}),
):
patch.start()
self.addCleanup(patch.stop)
def test_the_staged_diff_lists_each_file_once_and_a_file_matching_a_secret_rule_ships_only_its_counts_and_the_note(self):
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, False, self.rules)}
self.assertEqual(sorted(found), ["added.txt", "app.py", "gone.txt", "logo.png"])
self.assertEqual({path: (entry["status"], entry["mode"], entry["additions"], entry["deletions"], entry["note"]) for path, entry in found.items()}, {
"added.txt": ("A", "100644", 1, 0, ""), "app.py": ("M", "100644", 3, 1, WITHHELD), "gone.txt": ("D", "100644", 0, 1, ""), "logo.png": ("B", "100644", 0, 0, ""),
})
self.assertEqual([found[path]["patch"] for path in ("added.txt", "app.py", "gone.txt", "logo.png")], ["@@ -0,0 +1 @@\n+fresh", "", "@@ -1 +0,0 @@\n-bye", ""])
self.assertNotIn("plain-example", json.dumps(list(found.values())))
self.assertFalse(hasattr(staged_diff, "masked"))
self.assertEqual(diffs.valid_diff(list(found.values())), [found[path] for path in sorted(found)])
def test_an_armour_line_in_the_old_or_the_new_blob_withholds_the_whole_file(self):
(self.repo / "key.txt").write_text(ARMOURED, encoding="utf-8")
(self.repo / "plain.txt").write_text("plain\n", encoding="utf-8")
git(self.repo, "add", "key.txt", "plain.txt")
git(self.repo, "commit", "-q", "-m", "armour")
(self.repo / "key.txt").write_text("before\nafter\n", encoding="utf-8")
(self.repo / "plain.txt").write_text("plain\n-----BEGIN lowercase is no armour-----\n", encoding="utf-8")
(self.repo / "fresh.txt").write_text(ARMOURED, encoding="utf-8")
git(self.repo, "add", "key.txt", "plain.txt", "fresh.txt")
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, False, self.rules)}
self.assertEqual([(found[path]["patch"], found[path]["note"], found[path]["deletions"]) for path in ("key.txt", "fresh.txt")], [("", WITHHELD, 3), ("", WITHHELD, 0)])
self.assertEqual((found["plain.txt"]["note"], found["plain.txt"]["additions"]), ("", 1))
self.assertNotIn("plain words only", json.dumps(list(found.values())))
def test_past_the_cap_the_rest_is_listed_by_stat_only(self):
with mock.patch.object(staged_diff, "DIFF_LIMIT", 40):
found = staged_diff.review_diff(self.repo, False, self.rules)
self.assertEqual([(entry["path"], bool(entry["patch"]), entry["note"]) for entry in found], [("added.txt", True, ""), ("app.py", False, WITHHELD), ("gone.txt", False, ""), ("logo.png", False, "")])
self.assertEqual([(entry["additions"], entry["deletions"]) for entry in found], [(1, 0), (3, 1), (0, 1), (0, 0)])
def test_without_secret_rules_or_with_a_broken_rule_file_every_text_file_is_withheld(self):
broken = self.root / "broken-rules"
broken.mkdir()
(broken / "bad.yaml").write_text("cwe: CWE-798\nrules: [unclosed\n", encoding="utf-8")
for rules in (None, self.root / "missing", broken):
with self.subTest(rules=str(rules)):
found = staged_diff.review_diff(self.repo, False, rules)
self.assertEqual([(entry["patch"], entry["note"]) for entry in found], [("", WITHHELD)] * 3 + [("", "")])
self.assertIsNone(staged_diff.secret_rules(broken))
def test_the_diff_fails_past_the_file_cap_or_the_path_limit_instead_of_the_hub_refusing_it(self):
for name, value in (("FILE_CAP", 3), ("PATH_LIMIT", 8)):
with self.subTest(limit=name), mock.patch.object(staged_diff, name, value), self.assertRaises(staged_diff.DiffError):
staged_diff.review_diff(self.repo, False, self.rules)
self.assertEqual((staged_diff.FILE_CAP, staged_diff.PATH_LIMIT, diffs.FILE_CAP, diffs.PATH_LIMIT), (hub_limits.FILE_CAP, hub_limits.PATH_LIMIT) * 2)
def test_commit_all_shows_the_executable_bit_on_disk(self):
(self.repo / "added.txt").chmod(0o755)
everything = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual((everything["added.txt"]["mode"], everything["app.py"]["mode"]), ("100755", "100644"))
git(self.repo, "commit", "-q", "-m", "next")
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual((found["added.txt"]["status"], found["added.txt"]["mode"], found["added.txt"]["additions"]), ("M", "100644 => 100755", 0))
self.assertEqual(staged_diff.review_diff(self.repo, False, self.rules), [])
def test_commit_all_diffs_the_tracked_working_tree_against_head_without_untracked_files(self):
(self.repo / "added.txt").write_text("fresh\nlater\n", encoding="utf-8")
(self.repo / "untracked.txt").write_text("not mine\n", encoding="utf-8")
staged = {entry["path"]: entry["additions"] for entry in staged_diff.review_diff(self.repo, False, self.rules)}
everything = {entry["path"]: entry["additions"] for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual((staged["added.txt"], everything["added.txt"]), (1, 2))
self.assertNotIn("untracked.txt", everything)
def test_a_planted_fsmonitor_or_clean_filter_starts_no_program(self):
marker = self.root / "ran"
git(self.repo, "config", "core.fsmonitor", f"touch {marker}")
git(self.repo, "config", "filter.evil.clean", f"touch {marker}; cat")
(self.repo / ".gitattributes").write_text("* filter=evil\n", encoding="utf-8")
for everything in (False, True):
staged_diff.review_diff(self.repo, everything, self.rules)
self.assertFalse(marker.exists())
def test_a_planted_promisor_remote_fetches_no_missing_object_and_starts_no_program(self):
marker = self.root / "fetched"
oid = git(self.repo, "rev-parse", "HEAD:app.py").strip()
(self.repo / ".git" / "objects" / oid[:2] / oid[2:]).unlink()
promisor = {
"core.repositoryformatversion": "1", "extensions.partialClone": "origin", "remote.origin.url": str(self.root),
"remote.origin.promisor": "true", "remote.origin.uploadpack": f"touch {shlex.quote(str(marker))} && false",
}
for key, value in promisor.items():
git(self.repo, "config", key, value)
for everything in (False, True):
with self.subTest(everything=everything), self.assertRaises(staged_diff.DiffError):
staged_diff.review_diff(self.repo, everything, self.rules)
self.assertFalse(marker.exists())
def test_commit_all_reads_no_file_behind_a_symlinked_parent_directory(self):
outside = self.root / "outside"
outside.mkdir()
(outside / "key").write_text("stolen = 1\n", encoding="utf-8")
for folder in ("k", "d"):
(self.repo / folder).mkdir()
(self.repo / folder / "key").write_text("mine = 1\n", encoding="utf-8")
(self.repo / "d" / "link").symlink_to("key")
git(self.repo, "add", "k", "d")
(self.repo / "k" / "key").unlink()
(self.repo / "k").rmdir()
(self.repo / "k").symlink_to(outside)
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual([found[path]["patch"] for path in ("d/key", "d/link")], ["@@ -0,0 +1 @@\n+mine = 1", "@@ -0,0 +1 @@\n+key"])
self.assertNotIn("k/key", found)
self.assertNotIn("stolen", json.dumps(list(found.values())))
def test_an_index_path_leaving_the_working_tree_is_refused(self):
(self.root / "outside.txt").write_text("stolen = 1\n", encoding="utf-8")
for path in ("../outside.txt", "d/../../outside.txt", str(self.root / "outside.txt")):
with self.subTest(path=path), self.assertRaises(staged_diff.DiffError):
staged_diff.worktree(self.repo, path, "100644", "sha1", [])
def test_a_fifo_in_the_working_tree_is_skipped_without_blocking(self):
os.mkfifo(self.repo / "pipe")
found = []
reader = threading.Thread(target=lambda: found.append(staged_diff.worktree(self.repo, "pipe", "100644", "sha1", [])), daemon=True)
reader.start()
reader.join(10)
self.assertEqual(found, [None])
def test_a_working_tree_file_over_the_blob_limit_is_not_read(self):
with (self.repo / "big.bin").open("wb") as handle:
handle.truncate(4 * staged_diff.BLOB_LIMIT)
before = bytes_read()
found = staged_diff.worktree(self.repo, "big.bin", "100644", "sha1", [])
self.assertLess(bytes_read() - before, staged_diff.BLOB_LIMIT)
self.assertEqual((found[0], found[2]), ("100644", None))
def test_commit_all_reads_no_file_under_a_deny_read_path_and_still_reads_its_sibling(self):
(self.repo / "vault").mkdir()
(self.repo / "vault" / "key").write_text("stolen = 1\n", encoding="utf-8")
(self.repo / "open.txt").write_text("seen = 1\n", encoding="utf-8")
git(self.repo, "add", "vault", "open.txt")
write_settings(self.repo / ".claude" / "settings.local.json", deny(["~/repo/vault"]))
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual([found[path]["patch"] for path in ("vault/key", "open.txt")], ["", "@@ -0,0 +1 @@\n+seen = 1"])
self.assertNotIn("stolen", json.dumps(list(found.values())))
def test_commit_all_without_any_settings_file_diffs_normal_files(self):
self.assertEqual([file for file, _ in sandbox_paths.settings_files(self.repo) if file.exists()], [])
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual((found["added.txt"]["patch"], found["app.py"]["additions"]), ("@@ -0,0 +1 @@\n+fresh", 3))
def test_a_planted_empty_deny_read_list_still_leaves_the_floor_unread(self):
(self.repo / ".ssh").mkdir()
(self.repo / ".ssh" / "id_ed25519").write_text("stolen = 1\n", encoding="utf-8")
(self.repo / "notes.txt").write_text("seen = 1\n", encoding="utf-8")
git(self.repo, "add", ".ssh", "notes.txt")
write_settings(self.repo / ".claude" / "settings.json", deny([]))
with mock.patch.dict(os.environ, {"HOME": str(self.repo)}):
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual([found[path]["patch"] for path in (".ssh/id_ed25519", "notes.txt")], ["", "@@ -0,0 +1 @@\n+seen = 1"])
self.assertNotIn("stolen", json.dumps(list(found.values())))
def test_commit_all_fails_closed_on_an_unreadable_deny_read_setting_or_in_a_denied_working_tree(self):
cases = {"unparsable": b"{", "no utf-8": b"\xff", "no paths": deny("~/x"), "denied top": deny(["~"])}
for case, content in cases.items():
with self.subTest(case=case):
write_settings(self.root / ".claude" / "settings.json", content)
for everything in (True, False):
with self.assertRaises(staged_diff.DiffError):
staged_diff.review_diff(self.repo, everything, self.rules)
def test_the_staged_diff_ships_no_content_under_a_denied_path(self):
(self.repo / "vault").mkdir()
(self.repo / "vault" / "key").write_text("stolen = 1\n", encoding="utf-8")
git(self.repo, "add", "vault")
write_settings(self.repo / ".claude" / "settings.json", deny(["vault"]))
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, False, self.rules)}
self.assertEqual((found["vault/key"]["status"], found["vault/key"]["patch"], found["added.txt"]["patch"]), ("A", "", "@@ -0,0 +1 @@\n+fresh"))
def test_the_session_project_settings_and_read_rules_add_denies(self):
(self.repo / "vault").mkdir()
for name in ("vault/key", "notes.txt", "deep.pem", "open.txt"):
(self.repo / name).write_text("stolen = 1\n" if name != "open.txt" else "seen = 1\n", encoding="utf-8")
git(self.repo, "add", "vault", "notes.txt", "deep.pem", "open.txt")
write_settings(self.root / "project" / ".claude" / "settings.local.json", deny(["~/repo/vault"]))
write_settings(self.repo / ".claude" / "settings.json", {"permissions": {"deny": ["Read(/notes.txt)", "Bash(rm:*)"]}})
write_settings(self.root / ".claude" / "settings.json", {"permissions": {"deny": ["Read(*.pem)"]}})
found = {entry["path"]: entry["patch"] for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual([found[path] for path in ("vault/key", "notes.txt", "deep.pem", "open.txt")], ["", "", "", "@@ -0,0 +1 @@\n+seen = 1"])
def test_each_settings_scope_resolves_relative_entries_from_its_own_folder_and_read_rules_like_claude_code(self):
managed = self.root / "managed"
write_settings(managed / "managed-settings.json", deny(["held"]))
write_settings(self.root / ".claude" / "settings.json", {**deny(["mine"]), "permissions": {"deny": ["Read(//abs/x)", "Read(~/h)", "Read(/s)", "Read(./r/q)", "Read"]}})
write_settings(self.repo / ".claude" / "settings.json", deny(["vault"]))
roots = set(sandbox_paths.denied_roots(self.repo))
expected = {managed / "held", self.root / ".claude" / "mine", self.repo / "vault", Path("/abs/x"), self.root / "h", self.root / ".claude" / "s", self.repo / "r" / "q", Path("/")}
self.assertLessEqual({str(path) for path in expected}, roots)
(managed / "managed-settings.d").write_text("no folder", encoding="utf-8")
with self.assertRaises(sandbox_paths.Unreadable):
sandbox_paths.denied_roots(self.repo)
def test_a_double_star_read_rule_also_matches_no_folder_at_all(self):
write_settings(self.repo / ".claude" / "settings.json", {"permissions": {"deny": ["Read(**/.env)", "Read(conf/**/secret/**/key)"]}})
roots = sandbox_paths.denied_roots(self.repo)
for path in (".env", "deep/er/.env", "conf/secret/key", "conf/a/secret/key", "conf/secret/b/key", "conf/a/secret/b/key"):
with self.subTest(path=path):
self.assertTrue(sandbox_paths.denied(str(self.repo / path), roots))
self.assertFalse(sandbox_paths.denied(str(self.repo / "env.txt"), roots))
(self.repo / ".env").write_text("hidden = 1\n", encoding="utf-8")
git(self.repo, "add", ".env")
found = {entry["path"]: entry for entry in staged_diff.review_diff(self.repo, True, self.rules)}
self.assertEqual((found[".env"]["patch"], found["added.txt"]["patch"]), ("", "@@ -0,0 +1 @@\n+fresh"))
def test_an_allow_turns_into_a_deny_when_the_staged_tree_changed_since_the_preview_even_in_a_file_it_withholds(self):
ask = {**ASK, "tool": "Bash", "command": "git commit -m x", "cwd": str(self.repo)}
changed = {"behavior": "deny", "message": "staged tree changed since the preview"}
edits = {
"withheld content": lambda: (self.repo / "app.py").write_text(f"{SECRET}\nslipped in\n", encoding="utf-8"),
"mode only": lambda: (self.repo / "added.txt").chmod(0o755),
"binary": lambda: (self.repo / "logo.png").write_bytes(b"\x89PNG\x00\x03"),
}
for case, edit in edits.items():
with self.subTest(case=case):
git(self.repo, "add", "-A")
pinned, shown = permission_hook.commit_diff(ask, self.rules)
self.assertEqual((pinned, len(shown["diff"])), (git(self.repo, "write-tree").strip(), len(shown["diff"])))
self.assertEqual(permission_hook.bound(ALLOW, ask, pinned), ALLOW)
edit()
self.assertEqual(permission_hook.bound(ALLOW, ask, pinned), ALLOW)
git(self.repo, "add", "-A")
self.assertEqual(permission_hook.bound(ALLOW, ask, pinned), changed)
self.assertEqual(permission_hook.bound({"behavior": "deny", "message": "no"}, ask, pinned), {"behavior": "deny", "message": "no"})
def test_an_allow_of_commit_all_is_bound_to_the_working_tree_too(self):
ask = {**ASK, "tool": "Bash", "command": "git commit -am x", "cwd": str(self.repo)}
pinned, shown = permission_hook.commit_diff(ask, self.rules)
self.assertEqual([entry["path"] for entry in shown["diff"]], ["added.txt", "app.py", "gone.txt", "logo.png"])
self.assertEqual(permission_hook.bound(ALLOW, ask, pinned), ALLOW)
(self.repo / "app.py").write_text("unstaged edit\n", encoding="utf-8")
self.assertEqual(permission_hook.bound(ALLOW, ask, pinned)["behavior"], "deny")
def test_an_allow_fails_closed_with_a_reason_when_the_tree_cannot_be_read(self):
ask = {**ASK, "tool": "Bash", "command": "git commit -m x", "cwd": str(self.repo)}
pinned, _ = permission_hook.commit_diff(ask, self.rules)
self.assertEqual(permission_hook.bound(ALLOW, ask, None), {"behavior": "deny", "message": permission_hook.UNBOUND})
for error in (staged_diff.DiffError("git write-tree failed: broken\x07index"), OSError("gone"), ValueError("no ascii")):
with self.subTest(error=type(error).__name__), mock.patch.object(staged_diff, "binding", side_effect=error):
chosen = permission_hook.bound(ALLOW, ask, pinned)
self.assertEqual(chosen["behavior"], "deny")
self.assertTrue(chosen["message"].startswith(f"{permission_hook.UNBOUND}: "), chosen)
self.assertNotIn("\x07", chosen["message"])
with mock.patch.object(staged_diff, "binding", side_effect=staged_diff.DiffError("unmerged")):
self.assertEqual(permission_hook.commit_diff(ask, self.rules), (None, {}))
self.assertEqual(permission_hook.bound(ALLOW, {**ASK, "command": "make deploy"}, None), ALLOW)
def test_the_tree_diff_shows_the_written_tree_not_the_live_index(self):
tree = git(self.repo, "write-tree").strip()
before = staged_diff.tree_diff(self.repo, tree, self.rules)
(self.repo / "added.txt").write_text("fresh\nslipped in\n", encoding="utf-8")
git(self.repo, "add", "added.txt")
self.assertEqual(staged_diff.tree_diff(self.repo, tree, self.rules), before)
self.assertNotEqual(staged_diff.review_diff(self.repo, False, self.rules), before)
def test_without_a_secret_pattern_no_patch_ships(self):
empty = self.root / "empty-rules"
empty.mkdir()
(empty / "none.yaml").write_text("rules: []\n", encoding="utf-8")
self.assertEqual({(entry["patch"], entry["note"]) for entry in staged_diff.review_diff(self.repo, False, empty)}, {("", WITHHELD), ("", "")})
def test_a_file_name_that_is_no_utf8_ships_as_backslash_escapes(self):
(self.repo / os.fsdecode(b"caf\xe9.txt")).write_text("x\n", encoding="utf-8")
git(self.repo, "add", "-A")
found = staged_diff.review_diff(self.repo, False, self.rules)
self.assertIn("caf\\xe9.txt", [entry["path"] for entry in found])
json.dumps(found, ensure_ascii=False).encode("utf-8")
def test_only_an_unborn_head_counts_as_empty_and_lines_split_on_newlines_only(self):
tree = git(self.repo, "rev-parse", "HEAD^{tree}").strip()
(self.repo / "added.txt").write_text("one\x0ctwo\n", encoding="utf-8")
git(self.repo, "add", "added.txt")
self.assertEqual(next(entry for entry in staged_diff.review_diff(self.repo, False, self.rules) if entry["path"] == "added.txt")["additions"], 1)
(self.repo / ".git" / "objects" / tree[:2] / tree[2:]).unlink()
with self.assertRaises(staged_diff.DiffError):
staged_diff.review_diff(self.repo, False, self.rules)
def test_a_head_naming_a_missing_commit_or_a_broken_ref_fails_and_only_an_unborn_one_is_empty(self):
fresh = self.root / "fresh"
fresh.mkdir()
git(fresh, "init", "-q", "-b", "main")
(fresh / "one.txt").write_text("one\n", encoding="utf-8")
git(fresh, "add", "one.txt")
self.assertEqual([(entry["path"], entry["status"]) for entry in staged_diff.review_diff(fresh, False, self.rules)], [("one.txt", "A")])
commit = git(self.repo, "rev-parse", "HEAD").strip()
(self.repo / ".git" / "objects" / commit[:2] / commit[2:]).unlink()
with self.assertRaises(staged_diff.DiffError):
staged_diff.review_diff(self.repo, False, self.rules)
(self.repo / ".git" / "refs" / "heads" / "main").write_text("not a ref\n", encoding="utf-8")
with self.assertRaises(staged_diff.DiffError):
staged_diff.review_diff(self.repo, False, self.rules)
def test_the_rules_are_cached_until_a_rule_file_changes(self):
first = staged_diff.secret_rules(self.rules)
with mock.patch.object(staged_diff, "parsed_rules", side_effect=AssertionError("parsed again")):
self.assertEqual([[globs, [p.pattern for p in found]] for globs, found in staged_diff.secret_rules(self.rules)], [[globs, [p.pattern for p in found]] for globs, found in first])
self.assertEqual([globs for globs, _ in first], [("*.py",)])
def test_a_changed_rule_file_or_a_broken_cache_parses_the_rules_again(self):
staged_diff.secret_rules(self.rules)
rule_file = self.rules / "secrets" / "test.yaml"
rule_file.write_text(RULES.replace("'*.py'", "'*.txt'"), encoding="utf-8")
self.assertEqual([globs for globs, _ in staged_diff.secret_rules(self.rules)], [("*.txt",)])
for broken in ('{"signature": "x", "rules": 1}', "[]", "{", json.dumps({"signature": json.loads(staged_diff.CACHE.read_text(encoding="utf-8"))["signature"], "rules": 1})):
with self.subTest(cache=broken):
staged_diff.CACHE.write_text(broken, encoding="utf-8")
self.assertEqual([globs for globs, _ in staged_diff.secret_rules(self.rules)], [("*.txt",)])
def test_only_a_single_git_commit_names_its_directory_and_all_mode(self):
cases = {
"git commit -m 'feat: x'": ("/w", False),
"git commit -am 'feat: x'": ("/w", True),
"git commit -m all -a": ("/w", True),
"git commit --all --no-verify -m x": ("/w", True),
"git commit -ma": ("/w", False),
"git -C sub commit -m x": ("/w/sub", False),
"GIT_EDITOR=true git commit": ("/w", False),
"env -C sub git commit -m x": ("/w/sub", False),
"env --chdir=sub GIT_EDITOR=true git -C deeper commit -am x": ("/w/sub/deeper", True),
"git commit -m x -- -a": None,
"git commit -m x file.txt": None,
"git commit -a -m x file.txt": None,
"git commit -o -m x": None,
"git commit --only -m x": None,
"git commit -i -m x": None,
"git commit --include -m x": None,
"git commit -p": None,
"git commit --pathspec-from-file=list": None,
"git commit --pathspec-from=list": None,
"git commit --pathspec-f=list": None,
"git commit --inc -m x": None,
"git commit --on -m x": None,
"git commit --inter -m x": None,
"git commit -a --no-all -m x": None,
"git commit --al -m x": None,
"git commit --frobnicate -m x": None,
"git commit --mess 'feat: x' --no-veri": ("/w", False),
"git commit --all-m": None,
"git commit -U 3 -m x": ("/w", False),
"git commit --unified 3 -am x": ("/w", True),
"GIT_INDEX_FILE=/tmp/other git commit -m x": None,
"GIT_DIR=/tmp/other git commit -m x": None,
"env GIT_WORK_TREE=/tmp/other git commit -m x": None,
"env -i git commit -m x": None,
"git -c core.worktree=/tmp/other commit -m x": None,
"git status": None,
"cd x && git commit -a": None,
"git commit -m 'x' ; rm -rf /": None,
"echo git commit": None,
"git commit -m 'unterminated": None,
}
for command, expected in cases.items():
with self.subTest(command=command):
found = staged_diff.commit_of(command, "/w")
self.assertEqual(None if found is None else (str(found[0]), found[1]), expected)
class TestHubOperatorTasks(HubCase):
def scratch(self) -> tuple[Path, Path, dict]:
root = self.outside / "w1"
root.mkdir()
home = root / "home"
home.mkdir()
claude = {"HOME": str(home), "CLAUDE_CONFIG_DIR": str(home / ".claude"), "CLAUDE_PROJECT_DIR": str(root)}
return scratch_repo(root), rules_dir(root), {**os.environ, **GIT_ENV, **claude, "XDG_CACHE_HOME": str(home / ".cache")}
def test_approval_request_ships_the_masked_staged_diff_with_the_review_and_the_card_reads_it(self):
repo, rules, env = self.scratch()
notes = self.outside / "table.txt"
notes.write_text("🎯 95 💥 3", encoding="utf-8")
page = self.page("w1.html", PAGE)
command = [sys.executable, str(APPROVAL), "request", "--port", str(self.port), "--title", "feat: w1", "--page", page, "--notes", str(notes), "--gate", "pass", "--score", "🎯 95 💥 3", "--agent", "Skills", "--rules", str(rules)]
done = subprocess.run(command, cwd=repo, env=env, capture_output=True, text=True, timeout=60, check=False)
self.assertEqual(done.returncode, 0, done.stderr)
answer = json.loads(done.stdout)
self.assertEqual((answer["status"], answer["url"]), ("pending", f"{self.origin}/"))
item = next(item for item in self.snapshot()["inbox"] if item["id"] == answer["id"])
self.assertEqual((item["kind"], item["diff"], item["agent"], item["to"]), ("review", 4, "Skills", "Skills"))
status, _, body = self.get(f"/api/diffs/review/{answer['id']}")
self.assertEqual(status, 200, body)
shipped = json.loads(body)["diff"]
self.assertEqual([(entry["path"], entry["status"], entry["note"]) for entry in shipped], [("added.txt", "A", ""), ("app.py", "M", WITHHELD), ("gone.txt", "D", ""), ("logo.png", "B", "")])
self.assertNotIn(b"plain-example", body)
self.assertEqual(self.tool("review_status", {"id": answer["id"]})["structuredContent"]["tree"], git(repo, "write-tree").strip())
for path in ("/api/diffs/review/0123456789abcdef", f"/api/diffs/ask/{answer['id']}", f"/api/diffs/decision/{answer['id']}"):
with self.subTest(path=path):
self.assertEqual(self.get(path)[0], 404)
def test_a_review_request_takes_a_diff_past_the_old_body_limit_and_refuses_a_broken_one(self):
big = [{"path": f"f{index}.txt", "status": "A", "mode": "100644", "additions": 1, "deletions": 0, "patch": "@@ -0,0 +1 @@\n+" + "x" * 300_000, "note": ""} for index in range(5)]
arguments = {**self.arguments("feat: big", "", self.page("big.html", PAGE)), "agent": "Skills", "diff": big}
result = self.tool("review_request", arguments)
self.assertFalse(result["isError"], result)
self.assertEqual(len(json.loads(self.get(f"/api/diffs/review/{result['structuredContent']['id']}")[2])["diff"]), 5)
good = {"path": "a", "status": "M", "mode": "100644 => 100755", "additions": 1, "deletions": 0, "patch": "@@ -1 +1 @@\n+x", "note": ""}
self.assertFalse(self.tool("review_request", {**arguments, "diff": [good, {**good, "path": "b", "patch": "", "note": WITHHELD}]})["isError"])
for broken in ([{**good, "status": "X"}], [{**good, "status": "B"}], [{**good, "mode": "755"}], [{**good, "note": "masked"}], [{**good, "note": WITHHELD}], [{**good, "masked": []}],
[{**good, "extra": 1}], [{**good, "additions": -1}], {"a": 1}, [{**good, "path": "p" * (hub_limits.PATH_LIMIT + 1)}], [good] * (hub_limits.FILE_CAP + 1),
[{**good, "patch": "x" * (diffs.DIFF_LIMIT + 1)}]):
with self.subTest(broken=str(broken)[:60]):
self.assertTrue(self.tool("review_request", {**arguments, "diff": broken})["isError"])
def test_approval_request_refuses_notes_past_the_hub_limit_instead_of_cutting_them(self):
repo, rules, env = self.scratch()
notes = self.outside / "long.txt"
notes.write_text("x" * (hub_limits.NOTES_LIMIT + 1), encoding="utf-8")
command = [sys.executable, str(APPROVAL), "request", "--port", str(self.port), "--title", "feat: w1", "--page", self.page("long.html", PAGE), "--notes", str(notes), "--gate", "pass", "--score", "🎯 95 💥 3", "--agent", "Skills", "--rules", str(rules)]
done = subprocess.run(command, cwd=repo, env=env, capture_output=True, text=True, timeout=60, check=False)
self.assertEqual((done.returncode, done.stdout), (1, ""))
self.assertIn(f"at most {hub_limits.NOTES_LIMIT}", done.stderr)
def test_the_hook_attaches_the_staged_diff_to_a_git_commit_ask_and_nothing_to_other_commands(self):
repo, rules, env = self.scratch()
with mock.patch.object(staged_diff, "CACHE", self.outside / "cache.json"), mock.patch.dict(os.environ, {key: env[key] for key in ("HOME", "CLAUDE_CONFIG_DIR", "CLAUDE_PROJECT_DIR")}):
commit = {**ASK, "command": "git commit -m 'feat: w1'", "cwd": str(repo)}
pinned, attached = permission_hook.commit_diff(commit, rules)
self.assertEqual(([entry["path"] for entry in attached["diff"]], pinned), (["added.txt", "app.py", "gone.txt", "logo.png"], git(repo, "write-tree").strip()))
self.assertEqual(permission_hook.commit_diff({**commit, "command": "make deploy"}, rules), (None, {}))
self.assertEqual(permission_hook.commit_diff({**commit, "tool": "Write"}, rules), (None, {}))
self.assertEqual(permission_hook.commit_diff({**commit, "cwd": str(self.outside / "nowhere")}, rules), (None, {}))
ask = {key: commit[key] for key in ("tool", "command", "cwd", "session", "agent")}
ask_id = permission_hook.create(f"http://{self.host}", ask, 60, permission_hook.commit_diff(ask, rules)[1])
item = next(item for item in self.snapshot()["inbox"] if item["id"] == ask_id)
self.assertEqual((item["kind"], item["diff"]), ("ask", 4))
status, _, body = self.get(f"/api/diffs/ask/{ask_id}")
self.assertEqual((status, [entry["status"] for entry in json.loads(body)["diff"]]), (200, ["A", "M", "D", "B"]))
plain = self.create_ask(ASK)
self.assertIsNone(next(item for item in self.snapshot()["inbox"] if item["id"] == plain)["diff"])
self.assertEqual(self.post_json("/api/asks", {**ASK, "diff": [{"path": "a"}]}, self.local())[0], 400)
def test_an_ask_whose_encoded_body_passes_the_hub_limit_goes_without_its_diff_instead_of_a_413(self):
wide = "\U0001f600" * (hub_limits.DIFF_LIMIT // 4 - 16)
diff = [{"path": "wide.txt", "status": "A", "mode": "100644", "additions": 1, "deletions": 0, "patch": f"@@ -0,0 +1 @@\n+{wide}", "note": ""}]
self.assertEqual(diffs.valid_diff(diff), diff)
ask = {key: ASK[key] for key in ("tool", "command", "cwd", "session", "agent")}
self.assertGreater(len(permission_hook.encoded({**ask, "timeout": 60, "diff": diff})), hub_limits.DIFF_BODY_LIMIT)
ask_id = permission_hook.create(f"http://{self.host}", ask, 60, {"diff": diff})
self.assertIsNone(next(item for item in self.snapshot()["inbox"] if item["id"] == ask_id)["diff"])
small = permission_hook.create(f"http://{self.host}", ask, 60, {"diff": [{**diff[0], "patch": "@@ -0,0 +1 @@\n+\U0001f600"}]})
self.assertEqual(next(item for item in self.snapshot()["inbox"] if item["id"] == small)["diff"], 1)
def test_approval_request_with_a_broken_rule_file_withholds_every_text_file_and_exits_0(self):
repo, rules, env = self.scratch()
(rules / "secrets" / "test.yaml").write_text("cwe: CWE-798\nrules: [unclosed\n", encoding="utf-8")
notes = self.outside / "broken.txt"
notes.write_text("notes", encoding="utf-8")
command = [sys.executable, str(APPROVAL), "request", "--port", str(self.port), "--title", "feat: w1", "--page", self.page("broken.html", PAGE), "--notes", str(notes), "--gate", "pass", "--score", "🎯 95 💥 3", "--agent", "Skills", "--rules", str(rules)]
done = subprocess.run(command, cwd=repo, env=env, capture_output=True, text=True, timeout=60, check=False)
self.assertEqual(done.returncode, 0, done.stderr)
self.assertNotIn("Traceback", done.stderr)
shipped = json.loads(self.get(f"/api/diffs/review/{json.loads(done.stdout)['id']}")[2])["diff"]
self.assertEqual([(entry["patch"], entry["note"]) for entry in shipped], [("", WITHHELD)] * 3 + [("", "")])
class TestSharedLimits(unittest.TestCase):
def test_the_client_and_the_hub_take_each_limit_from_hub_limits(self):
self.assertEqual(Path(signed.limits.__file__).resolve(), (HUB / "client" / "hub_limits.py").resolve())
self.assertEqual((staged_diff.DIFF_LIMIT, diffs.DIFF_LIMIT, reviewing.NOTES_LIMIT), (hub_limits.DIFF_LIMIT, hub_limits.DIFF_LIMIT, hub_limits.NOTES_LIMIT))
self.assertEqual((diffs.DIFF_BODY_LIMIT, diffs.FILE_CAP, diffs.PATH_LIMIT, fields.PATH_LIMIT, permission_hook.PATH_LIMIT), (hub_limits.DIFF_BODY_LIMIT, hub_limits.FILE_CAP, *[hub_limits.PATH_LIMIT] * 3))
self.assertEqual((staged_diff.WITHHELD, diffs.NOTES), (WITHHELD, ("", WITHHELD)))
self.assertTrue(fields.PATH.fullmatch("/" + "p" * (hub_limits.PATH_LIMIT - 1)))
self.assertFalse(fields.PATH.fullmatch("/" + "p" * hub_limits.PATH_LIMIT))
def test_the_encoded_worst_case_body_without_a_diff_fits_the_hub_body_limit_non_ascii_included(self):
chars = ("x", "\x01", "é", "
", "\U0001f600")
widest = {ascii_only: max(chars, key=lambda char: len(json.dumps(char, ensure_ascii=ascii_only).encode("utf-8"))) for ascii_only in (True, False)}
self.assertEqual((widest[True], widest[False]), ("\U0001f600", "\x01"))
ask = {name: widest[True] * limit for name, limit in permission_hook.LIMITS.items()}
self.assertLessEqual(len(permission_hook.encoded({**ask, "timeout": float(1 << 60)})), hub_limits.DIFF_BODY_LIMIT)
wide, path = widest[False], "/" + widest[False] * (hub_limits.PATH_LIMIT - 1)
arguments = {"repo": path, "worktree": path, "branch": "b" * 255, "tree": "0" * 64, "title": wide * 200, "page": path, "notes": wide * hub_limits.NOTES_LIMIT,
"gate": "pass", "score": wide * reviewing.SCORE_LIMIT, "agent": wide * 200}
self.assertLessEqual(len(approval.review_body(arguments)), hub_limits.DIFF_BODY_LIMIT)
patch = "+" + widest[True] * (hub_limits.DIFF_LIMIT // 4 - 2)
self.assertLessEqual(len(json.dumps(patch, ensure_ascii=False).encode("utf-8")), hub_limits.DIFF_LIMIT)
self.assertGreater(len(permission_hook.encoded({"diff": [{"patch": patch}]})), hub_limits.DIFF_BODY_LIMIT)
def test_the_hub_image_copies_every_client_module_signed_loads(self):
tree = ast.parse((HUB / "kvb_orchestrator" / "signed.py").read_text(encoding="utf-8"))
loaded = {f"{node.args[0].value}.py" for node in ast.walk(tree) if isinstance(node, ast.Call) and getattr(node.func, "id", "") == "client_module"}
copies = [line.split()[1:] for line in (HUB / "Dockerfile").read_text(encoding="utf-8").splitlines() if line.startswith("COPY ")]
copied = {Path(source).name for words in copies if words[-1] == "/app/client/" for source in words[:-1]}
self.assertIn("hub_limits.py", loaded)
self.assertLessEqual(loaded, copied)
if __name__ == "__main__":
unittest.main()
+4 -4
View File
@@ -24,16 +24,16 @@ class TestHubSurface(HubCase):
self.assertNotIn(gone, html)
self.assertIn('<nav class="tabs" id="tabs"', html)
def test_badges_count_waiting_items_active_agents_and_open_runner_jobs(self):
def test_badges_count_waiting_operator_tasks_on_tasks_active_agents_and_open_runner_jobs(self):
now = 1000.0
inbox = [{"until": None}, {"until": None}, {"until": now + 60}]
claims = [{"agent": "A", "beat": now - 10}, {"agent": "A", "beat": now - 20}, {"agent": "B", "beat": now - 900}, {"agent": "C", "beat": now - 901}, {"agent": "D", "beat": None}]
jobs = [{"status": "queued"}, {"status": "running"}, {"status": "done"}, {"status": "failed"}]
self.assertEqual(view.badges(inbox, claims, jobs, now), {"inbox": 2, "agents": 2, "artifacts": 2})
self.assertEqual(view.badges([], [], [], now), {"inbox": 0, "agents": 0, "artifacts": 0})
self.assertEqual(view.badges(inbox, claims, jobs, now), {"tasks": 2, "agents": 2, "artifacts": 2})
self.assertEqual(view.badges([], [], [], now), {"tasks": 0, "agents": 0, "artifacts": 0})
self.claim("s1", "Skills session")
self.create_ask(ASK)
self.assertEqual(self.snapshot()["badges"], {"inbox": 1, "agents": 1, "artifacts": 0})
self.assertEqual(self.snapshot()["badges"], {"tasks": 1, "agents": 1, "artifacts": 0})
self.assertIn("<title>(1) Agent Orchestrator</title>", self.index())
def test_register_workers_map_to_the_agent_named_before_the_bracket(self):