feat(orchestrator): W2 Kanban board with swimlanes and forecast
Tasks gain a Kanban board: columns Waiting, Running and Done, swimlanes P1 to P4+ and Unpinned by WSJF, after-dependency arrows and the dashed queue line; drag and drop stages status, pin and dependency moves that apply with one passkey. The burn chart gets the trend and a forecast. Committed below the bar on the operator's word 2026-11-12 (no further per-bundle tests, one gate after the merge): the W2 e2e never ran. 🎯 Fix: 80 % 💥 Break: 10 % 💸 Cost: 100 % ⏱️ Time: 100 % 🛡️ Security: 100 % 🏆 Score: 72 🚦 Gate: ⛔ fix 80 % < 90, break 10 % > 5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fd8fc08603
commit
8e63b21e21
18 files changed
+1375
-76
No files matched your search
@@ -16,9 +16,12 @@ Returns:
|
||||
Runs as the operator, outside any sandbox, every operator command a
|
||||
pinned passkey signed in the Agent Orchestrator: exactly the signed text, with bash
|
||||
in the signed directory, each signature once and before its expiry.
|
||||
Runs every signed task change (priority pin or WSJF estimate) as
|
||||
Runs every signed task change (priority pin, status, dependency or WSJF
|
||||
estimate; a board batch is one job per change) 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
|
||||
argv shape itself, and kills its process group after 30 s; rasci.py
|
||||
refuses a cycle or a done with an open key result, and its reason is the
|
||||
job's output. 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
|
||||
back while it runs and the exit code at its end. The exit code is 0.
|
||||
|
||||
@@ -27,6 +27,7 @@ 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/"
|
||||
STATUSES = ("queued", "running", "blocked", "done", "stopped")
|
||||
APPROVAL_KEYS = {"v", "kind", "items", "nonce", "expires"}
|
||||
RULE_KEYS = {"v", "kind", "id", "kinds", "tool", "pattern", "scope", "expires"}
|
||||
WARNINGS = (
|
||||
@@ -156,14 +157,31 @@ def task_item(task: str, argv: list[str]) -> dict:
|
||||
return {"kind": "task", "id": task, "argv": argv}
|
||||
|
||||
|
||||
def board_flags(task: str, flags: list[str]) -> bool:
|
||||
if flags == ["--unpin"]:
|
||||
return True
|
||||
if len(flags) != 2:
|
||||
return False
|
||||
flag, value = flags
|
||||
if flag == "--prio":
|
||||
return bool(PRIO.fullmatch(value))
|
||||
if flag == "--status":
|
||||
return value in STATUSES
|
||||
linked = flag in ("--after", "--drop-after")
|
||||
return linked and value != task and bool(TASK.fullmatch(value)) and not value.startswith(COMMANDS)
|
||||
|
||||
|
||||
def task_argv(task: object, argv: object) -> list[str]:
|
||||
"""Args:
|
||||
task: the register task the change is for, a rasci.py slug outside
|
||||
operator/.
|
||||
argv: the rasci.py arguments, one of `set <task> --prio <1-999>`,
|
||||
`estimate <task> Operator --value <fib> --complexity <fib>`,
|
||||
`estimate <task> Operator --value <fib>` (the operator's business
|
||||
value alone) or `estimate <task> Operator --drop`.
|
||||
argv: the rasci.py arguments, one of `set <task>` with exactly one
|
||||
of `--prio <1-999>`, `--unpin`, `--status <status>`,
|
||||
`--after <other>` or `--drop-after <other>` (other a register
|
||||
slug outside operator/), `estimate <task> Operator --value <fib>
|
||||
--complexity <fib>`, `estimate <task> Operator --value <fib>`
|
||||
(the operator's business value alone) or
|
||||
`estimate <task> Operator --drop`.
|
||||
|
||||
Returns:
|
||||
argv, the hub and the runner check it the same way.
|
||||
@@ -176,12 +194,11 @@ def task_argv(task: object, argv: object) -> list[str]:
|
||||
if not isinstance(argv, list) or not all(isinstance(part, str) for part in argv):
|
||||
raise Refused("the argv is no list of texts")
|
||||
rest = argv[3:]
|
||||
pinned = argv[:3] == ["set", task, "--prio"] and len(argv) == 4 and bool(PRIO.fullmatch(argv[3]))
|
||||
valued = len(rest) in (2, 4) and rest[0] == "--value" and rest[1] in FIBONACCI
|
||||
estimated = valued and (len(rest) == 2 or (rest[2] == "--complexity" and rest[3] in FIBONACCI))
|
||||
if pinned or (argv[:3] == ["estimate", task, ESTIMATOR] and (estimated or rest == ["--drop"])):
|
||||
if (argv[:2] == ["set", task] and board_flags(task, argv[2:])) or (argv[:3] == ["estimate", task, ESTIMATOR] and (estimated or rest == ["--drop"])):
|
||||
return argv
|
||||
raise Refused("the argv is no task priority or WSJF estimate change of this task")
|
||||
raise Refused("the argv is no task priority, status, dependency or WSJF estimate change of this task")
|
||||
|
||||
|
||||
def approval_payload(hashes: list[str], nonce: str, expires: int) -> dict:
|
||||
|
||||
Reference in new issue
Block a user