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:
1 parent
fd8fc08603
commit
f7a59d43a5
33 files changed
+1998
-107
No files matched your search
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user