feat(orchestrator): W3 artifacts tree, ancestry, gates and token-free cleanup

Artifacts show the branch tree with ancestry and gate marks. The cleanup runner calls pkgmgr branch cleanup with its real argv and parses its repos[*].items output, refuses any plan with a remote-branch action, marks an item done only when pkgmgr reports it done, and offers no delete for a branch a worktree holds. Every text states what the pkgmgr command does to volumes. Committed below the bar on the operator's word 2026-11-12 (no further per-bundle tests, one gate after the merge): the W3 e2e never ran; the client switches to --local once pkgmgr ships it.

🎯 Fix: 90 %
💥 Break: 6 %
💸 Cost: 30 %
⏱️ Time: 40 %
🛡️ Security: 100 %
🏆 Score: 100
🚦 Gate: ⛔ break 6 % > 5

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:15:14 +00:00
1 parent fd8fc08603
commit f7a59d43a5
33 files changed
+1998 -107

No files matched your search

+65 -23
View File
@@ -16,12 +16,17 @@ Returns:
Runs once over every job in --jobs, as the operator on the host: per item
it verifies the operator's passkey signature over repository, branch,
worktree and tip, then in the repository runs `pkgmgr branch cleanup
--json --local --branch <name>` as a dry run and, only when that plan
names exactly this branch as merged-clean without skip reason at the
signed tip and worktree, `pkgmgr branch cleanup --apply --json --local
--branch <name>`. Every other item is refused and nothing changes for
it. Writes each item's result, rewrites the tree file after a deletion
and removes the job. The exit code is 0.
--json --branch <name>` as a dry run and, only when that plan names
exactly this branch as merged-clean without skip reason at the signed
tip and worktree and deletes no remote branch, `pkgmgr branch cleanup
--apply --json --branch <name>`. While the tree file says pkgmgr has no GitHub token
(delete_ready false), only a branch the dry run shows merged by ancestry
into the default branch and without a remote branch is deleted; the
others are refused with "token needed". Every other item is refused and
nothing changes for it. An applied item is done only when pkgmgr's
result for it is done; a skipped one or one without result is failed
with pkgmgr's reason. Writes each item's result, rewrites the tree file
after a deletion and removes the job. The exit code is 0.
"""
from __future__ import annotations
@@ -43,6 +48,9 @@ TIP = re.compile(r"[0-9a-f]{40}|[0-9a-f]{64}")
UNSAFE = re.compile(r"[*?\[\]\\\s\x00-\x1f\x7f]|^-|\.\.|@\{")
OUTPUT_LIMIT = 16 << 10
TIMEOUT = 600
DONE = "done"
REMOTE_ACTION = "remote-branch:"
TOKEN_NEEDED = "token needed: without the GitHub token only a local branch merged by ancestry into the default branch and without a remote branch is deleted"
# the cwd is a repository agents can write: its config may name hooks, and
# pkgmgr releases before 4c8b137 run git branch -D without scan mode
GIT_OVERRIDES = (
@@ -62,12 +70,18 @@ def git_overrides() -> dict[str, str]:
return env
def known_repositories(tree: Path) -> set[str]:
def tree_facts(tree: Path) -> tuple[dict[str, str], bool]:
"""Returns:
The repositories the tree file says pkgmgr knows, each with its
default branch, and whether pkgmgr found the GitHub token
(delete_ready); nothing and False for an unreadable file.
"""
try:
data = json.loads(tree.read_text(encoding="utf-8"))
return {entry["path"] for entry in data["repos"] if entry.get("known") is True and isinstance(entry.get("path"), str)}
known = {entry["path"]: entry.get("default") if isinstance(entry.get("default"), str) else "" for entry in data["repos"] if entry.get("known") is True and isinstance(entry.get("path"), str)}
return known, data.get("delete_ready") is True
except (OSError, ValueError, KeyError, TypeError, AttributeError):
return set()
return {}, False
def run(pkgmgr: Path, argv: list[str], cwd: str) -> tuple[int, str, str]:
@@ -78,15 +92,32 @@ def run(pkgmgr: Path, argv: list[str], cwd: str) -> tuple[int, str, str]:
return done.returncode, done.stdout, done.stderr[-OUTPUT_LIMIT:]
def plan_argv(branch: str, apply: bool) -> list[str]:
"""Args:
branch: the exact branch name.
apply: execute the plan instead of the dry run.
Returns:
The `pkgmgr branch cleanup` arguments for this branch's plan in JSON.
"""
return ["branch", "cleanup", *(["--apply"] if apply else []), "--json", "--branch", branch]
def plan_items(stdout: str) -> list[dict] | None:
"""Returns:
Every item of `pkgmgr branch cleanup --json`, which prints
{"host_pid_namespace", "repos": [{"repo", "default", "merged_into",
"warnings", "items"}]}; None for any other output.
"""
try:
data = json.loads(stdout)
except ValueError:
return None
items = data.get("items") if isinstance(data, dict) else data
if not isinstance(items, list) or not all(isinstance(entry, dict) for entry in items):
repos = data.get("repos") if isinstance(data, dict) else None
if not isinstance(repos, list) or not all(isinstance(repo, dict) and isinstance(repo.get("items"), list) for repo in repos):
return None
return items
items = [entry for repo in repos for entry in repo["items"]]
return items if all(isinstance(entry, dict) for entry in items) else None
def shaped(item: object) -> bool:
@@ -105,6 +136,9 @@ def mismatch(entry: dict, item: dict) -> str | None:
return f"tip changed: signed {item['tip'][:12]}, now {str(tip)[:12]}"
if (entry.get("worktree") or "") != item["worktree"]:
return f"worktree changed: signed {item['worktree'] or 'none'}, now {entry.get('worktree') or 'none'}"
remote = [str(action) for action in entry.get("actions") or [] if str(action).startswith(REMOTE_ACTION)]
if remote:
return f"the plan also deletes {', '.join(remote)[:200]}; a signed delete is local only"
return None
@@ -114,16 +148,20 @@ class Cleanup:
self.tree = tree
self.pkgmgr = pkgmgr
def item(self, item: dict, proof: object, known: set[str], now: float) -> dict:
def item(self, item: dict, proof: object, known: dict[str, str], ready: bool, now: float) -> dict:
"""Args:
item: one signed item with repository, branch, worktree and tip.
proof: the job's {"payload", "assertion"}.
known: the repositories the tree file says pkgmgr knows.
known: the repositories the tree file says pkgmgr knows, each
with its default branch.
ready: whether pkgmgr holds the GitHub token; without it only a
branch signing.token_free() accepts in the dry run is deleted.
now: the current time in epoch seconds.
Returns:
The item with status done, refused or failed and the reason;
refused and failed items changed nothing.
refused items changed nothing, failed ones are those pkgmgr did
not report done.
"""
def refused(reason: str) -> dict:
return {**item, "status": "refused", "reason": reason}
@@ -136,8 +174,7 @@ class Cleanup:
return refused("unknown repository: pkgmgr does not list it")
if UNSAFE.search(item["branch"]) or not item["branch"]:
return refused("the branch name holds pattern or option characters; only an exact branch name is deleted")
selector = ["--json", "--local", "--branch", item["branch"]]
code, stdout, stderr = run(self.pkgmgr, ["branch", "cleanup", *selector], item["repository"])
code, stdout, stderr = run(self.pkgmgr, plan_argv(item["branch"], False), item["repository"])
plan = plan_items(stdout) if code == 0 else None
if plan is None:
return refused(f"the dry run failed ({code}): {stderr.strip()[-500:]}")
@@ -149,10 +186,15 @@ class Cleanup:
reason = mismatch(plan[0], item)
if reason:
return refused(reason)
code, stdout, stderr = run(self.pkgmgr, ["branch", "cleanup", "--apply", *selector], item["repository"])
applied = plan_items(stdout)
status = "done" if code == 0 else "failed"
return {**item, "status": status, "reason": "" if code == 0 else stderr.strip()[-500:], "code": code, "applied": applied if applied is not None else []}
if not ready and not signing.token_free(plan[0], known[item["repository"]]):
return refused(TOKEN_NEEDED)
code, stdout, stderr = run(self.pkgmgr, plan_argv(item["branch"], True), item["repository"])
applied = plan_items(stdout) or []
outcome = next((entry.get("result") for entry in applied if entry.get("branch") == item["branch"]), None)
if code == 0 and isinstance(outcome, str) and outcome.split(";")[0] == DONE:
return {**item, "status": "done", "reason": "", "code": code, "applied": applied}
reason = outcome if isinstance(outcome, str) and outcome else stderr.strip()[-500:] or f"pkgmgr reported no result for {item['branch']}"
return {**item, "status": "failed", "reason": reason[:500], "code": code, "applied": applied}
def job(self, path: Path, now: float) -> dict:
try:
@@ -162,8 +204,8 @@ class Cleanup:
items = job.get("items") if isinstance(job, dict) else None
if not isinstance(items, list) or not items or not all(map(shaped, items)) or job.get("id") != path.stem:
return {"id": path.stem, "finished": now, "items": [], "error": "malformed job: refused, nothing deleted"}
known = known_repositories(self.tree)
results = [self.item(item, job.get("proof"), known, now) for item in items]
known, ready = tree_facts(self.tree)
results = [self.item(item, job.get("proof"), known, ready, now) for item in items]
result = {"id": path.stem, "finished": time.time(), "items": results}
if any(entry["status"] == "done" for entry in results):
code, _, stderr = run(self.pkgmgr, ["branch", "tree", "--format", "json", "--output", str(self.tree)], str(self.tree.parent))
+58 -10
View File
@@ -10,6 +10,9 @@
~/.claude/skills/kvb-org-rasci/scripts/rasci.py; optional.
--council: the council directory holding rasci.json, the agents'
AGENTS_COUNCIL_ROOT; optional, task changes need both.
--gates: the installed gates script, e.g.
~/.claude/skills/kvb-check-gate/scripts/gates.py; optional, gate
changes need it.
--once: handle the queued jobs once and exit.
Returns:
@@ -18,9 +21,12 @@ Returns:
in the signed directory, each signature once and before its expiry.
Runs every signed task change (priority pin or WSJF estimate) as
`python3 -I -B <rasci> <argv>` from / without a shell, after checking the
argv shape itself, and kills its process group after 30 s. Refuses task
changes without --rasci and --council or into a council that is a
symlink or another uid's, and every other job. Posts the last 200 output lines
argv shape itself, and kills its process group after 30 s. Runs every
signed gate change (rule set|drop, parents add|drop) the same way as
`python3 -I -B <gates> <argv>` with the operator's HOME, so gates.py
writes its default gates.db. Refuses task changes without --rasci and
--council or into a council that is a symlink or another uid's, gate
changes without --gates, and every other job. Posts the last 200 output lines
back while it runs and the exit code at its end. The exit code is 0.
"""
@@ -52,8 +58,10 @@ BEAT = 2.0
SAFE_PATH = "/usr/bin:/bin"
TASK_JOB = "task"
TASK_LIMIT = 30.0
GATE_JOB = "gate"
REFUSED = "refused: no valid operator signature for this command"
UNREGISTERED = "refused: this runner was started without --rasci and --council; reinstall its unit with make orchestrator-runner"
UNGATED = "refused: this runner was started without --gates; reinstall its unit with make orchestrator-runner"
class Register(NamedTuple):
@@ -113,6 +121,24 @@ def task_refusal(job: dict, trust: verifier.Trust, now: float) -> str | None:
return None
def gate_refusal(job: dict, trust: verifier.Trust, now: float) -> str | None:
"""Args:
job: the claimed gate change with argv and proof.
trust: the pinned keys, the runner's replay store and the origin.
now: the current time in epoch seconds.
Returns:
Why the change must not run, or None when signing.gate_argv()
accepts its argv and a pinned key signed it.
"""
try:
argv = signing.gate_argv(job["argv"])
verifier.check(job.get("proof"), signing.gate_item(argv), "", trust, now)
except (signing.Refused, OSError) as error:
return f"{REFUSED}: {error}"
return None
def council_refusal(council: Path) -> str | None:
"""Args:
council: the directory rasci.py writes the register into.
@@ -161,6 +187,23 @@ def change(job: dict, register: Register, report: Callable[[dict], None]) -> int
return spawn([sys.executable, "-I", "-B", str(register.rasci), *argv], "/", env, report, TASK_LIMIT)
def gate(job: dict, gates: Path, report: Callable[[dict], None]) -> int:
"""Args:
job: the claimed and verified gate change with argv.
gates: the gates script.
report: called with {"output", "code", "refused"}.
Returns:
The exit code of gates.py, run from / on the argv gate_argv() checked.
Raises:
signing.Refused for an argv of any other shape.
"""
argv = signing.gate_argv(job["argv"])
env = {"PATH": SAFE_PATH, "HOME": str(Path.home())}
return spawn([sys.executable, "-I", "-B", str(gates), *argv], "/", env, report, TASK_LIMIT)
def spawn(argv: list[str], cwd: str, env: dict[str, str] | None, report: Callable[[dict], None], limit: float | None = None) -> int:
"""Args:
argv: the program and its arguments, run without a shell.
@@ -224,22 +267,26 @@ def sender(base: str, job_id: str) -> Callable[[dict], None]:
return send
def cycle(base: str, trust: verifier.Trust, register: Register | None) -> None:
def cycle(base: str, trust: verifier.Trust, register: Register | None, gates: Path | None = None) -> None:
for job in call(base, "/api/runner/jobs", None)["jobs"]:
try:
claimed = call(base, f"/api/runner/{job['id']}/claim", {})
except urllib.error.HTTPError:
continue
send = sender(base, claimed["id"])
task = claimed.get("kind") == TASK_JOB
if not task:
reason = refusal(claimed, trust, time.time())
else:
kind = claimed.get("kind")
if kind == TASK_JOB:
reason = UNREGISTERED if register is None else council_refusal(register.council) or task_refusal(claimed, trust, time.time())
elif kind == GATE_JOB:
reason = UNGATED if gates is None else gate_refusal(claimed, trust, time.time())
else:
reason = refusal(claimed, trust, time.time())
if reason is not None:
send({"output": reason, "code": None, "refused": True})
elif task:
elif kind == TASK_JOB:
change(claimed, register, send)
elif kind == GATE_JOB:
gate(claimed, gates, send)
else:
execute(claimed, send)
@@ -252,6 +299,7 @@ def main() -> int:
parser.add_argument("--replay", type=Path, required=True)
parser.add_argument("--rasci", type=Path)
parser.add_argument("--council", type=Path)
parser.add_argument("--gates", type=Path)
parser.add_argument("--once", action="store_true")
args = parser.parse_args()
base = hub_api.base_url(args.port)
@@ -259,7 +307,7 @@ def main() -> int:
register = Register(rasci=args.rasci, council=args.council) if args.rasci and args.council else None
while True:
try:
cycle(base, trust, register)
cycle(base, trust, register, args.gates)
except (OSError, ValueError, KeyError, TypeError):
pass
if args.once:
+71
View File
@@ -27,6 +27,11 @@ PRIO = re.compile(r"[1-9][0-9]{0,2}")
FIBONACCI = ("1", "2", "3", "5", "8", "13", "21", "34", "55", "89")
ESTIMATOR = "Operator"
COMMANDS = "operator/"
GATE_LEVELS = ("none", "quick", "test", "quality-high", "e2e")
GATE_ACTIONS = ("commit", "merge", "push", "pr")
GATE_REPO = re.compile(r"/[^\x00-\x1f\x7f]{0,4095}")
GATE_BRANCH = re.compile(r"(?![-/])(?!.*\.\.)(?!.*@\{)(?!.*//)[^\x00-\x20\x7f~^:?*\[\\]{1,255}(?<![/.])")
TOKEN_FREE_MERGE = "ancestor"
APPROVAL_KEYS = {"v", "kind", "items", "nonce", "expires"}
RULE_KEYS = {"v", "kind", "id", "kinds", "tool", "pattern", "scope", "expires"}
WARNINGS = (
@@ -184,6 +189,72 @@ def task_argv(task: object, argv: object) -> list[str]:
raise Refused("the argv is no task priority or WSJF estimate change of this task")
def gate_item(argv: list[str]) -> dict:
return {"kind": "gate", "argv": argv}
def gate_argv(argv: object) -> list[str]:
"""Args:
argv: the gates.py arguments, one of `rule set --repo R --branch B
--action A --level L`, `rule drop --repo R --branch B --action A`,
`parents add --repo R --branch B --parent P` (source operator) or
`parents drop --repo R --branch B --parent P`; R an absolute path,
B and P branch names, P not B.
Returns:
argv, the hub and the runner check it the same way.
Raises:
Refused for every other argv.
"""
if not isinstance(argv, list) or not all(isinstance(part, str) for part in argv):
raise Refused("the argv is no list of texts")
head, values = argv[:2], dict(zip(argv[2::2], argv[3::2]))
names = {("rule", "set"): ("--repo", "--branch", "--action", "--level"), ("rule", "drop"): ("--repo", "--branch", "--action"), ("parents", "add"): ("--repo", "--branch", "--parent"), ("parents", "drop"): ("--repo", "--branch", "--parent")}.get(tuple(head))
if names is None or argv[2::2] != list(names) or len(argv) != 2 + 2 * len(names):
raise Refused("the argv is no gates.py rule set|drop or parents add|drop of one branch")
repo = values["--repo"]
if not GATE_REPO.fullmatch(repo) or "/../" in f"{repo}/" or "/./" in f"{repo}/":
raise Refused("the repository is no absolute path")
branches = [values[name] for name in ("--branch", "--parent") if name in values]
if not all(GATE_BRANCH.fullmatch(name) for name in branches) or len(set(branches)) != len(branches):
raise Refused("the branch or parent is no branch name, or the branch is its own parent")
if values.get("--action", GATE_ACTIONS[0]) not in GATE_ACTIONS or values.get("--level", GATE_LEVELS[0]) not in GATE_LEVELS:
raise Refused(f"the action must be one of {', '.join(GATE_ACTIONS)} and the level one of {', '.join(GATE_LEVELS)}")
return argv
def names_default(ref: object, default: object) -> bool:
"""Args:
ref: a ref as pkgmgr names it, bare such as main or behind one
remote such as origin/main, the remote-tracking ref it compares
against when the remote has a HEAD.
default: the repository's default branch as the tree file names it,
without the remote (pkgmgr tree.py strips it).
Returns:
True when ref is the default branch.
"""
if not isinstance(ref, str) or not isinstance(default, str) or not default:
return False
remote, _, name = ref.partition("/")
return ref == default or (bool(remote) and name == default)
def token_free(entry: dict, default: object) -> bool:
"""Args:
entry: a pkgmgr tree or gc plan item with merged_by, merged_into and
remotes.
default: the repository's default branch as the tree file names it.
Returns:
True for a local branch merged by ancestry into the default branch
(names_default) that has no remote branch: deleting it needs no
GitHub token.
"""
return entry.get("merged_by") == TOKEN_FREE_MERGE and names_default(entry.get("merged_into"), default) and not entry.get("remotes")
def approval_payload(hashes: list[str], nonce: str, expires: int) -> dict:
return {"v": VERSION, "kind": "approve", "items": sorted(set(hashes)), "nonce": nonce, "expires": expires}