feat(orchestrator): S5.1 radio spot, compact menus, trend, ETA and Tasks 3D
🧪 Test / 🧪 Lock + lint (push) Canceled after 0s

Requirement 001 decisions 52 to 58 (operator 2026-11-09):

- Radio spot: the Agents toolbar radio button expands inline with the one
  composer (every channel, post and call recipients, Record, Send), a gear
  with the radio settings (tones, squelch, read aloud, moved out of the
  general settings) and Cancel mayday, which leaves the toolbar; the Radio
  tab keeps speakers, stations and the log plus a jump to the spot.
- Day/Night menu: the badge and Drain, Suspend and Resume only; each
  explanation is the button's tooltip.
- Tasks toolbar: views, filter, estimation, sort and business value in one
  wrapping row with a Request choices dropdown; the status chips stay the
  second row.
- Burndown and burnup: a trend selector (off, linear regression, velocity
  over N days) stored in the hub settings, the trend lines and the forecast
  date on the chart, and a tooltip per point with the work packages that
  started, ended or joined there.
- ETA: every list row shows its ETA (end when done, else start or now plus
  the forecast length behind its predecessors), sharing one predecessor
  walk with the Gantt.
- Tasks 3D: a sixth view on the 3D engine with the dependency edges in the
  Gantt colours, layouts Force and Time.
- Steady 3D camera: the view fits once per graph build, refreshes keep the
  operator's camera.
- e2e: Firefox runs with its profile as cwd, so its crashreporter no longer
  drops core dumps into the repository.
- A forecast beyond the last second a Date can hold is no forecast, so a
  nearly flat regression no longer aborts the Tasks render.

Evidence: make test Ran 717 OK, ruff clean, markdownlint 0 errors; e2e 30 of
30 OK in Chromium and Firefox at 1920, 1366 and 390 px; MEDUSA 0 findings
after one skeptic-accepted lexical EXA006.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-10 08:10:51 +00:00
1 parent e94f2800d5
commit 57ab29eb0d
25 files changed
+1390 -215

No files matched your search

+1 -1
View File
@@ -414,7 +414,7 @@ The hub page loads nothing from the network: its icons come from Bootstrap Icons
The Agents view "Hierarchy" draws the council's formal offices as a collapsible org chart: Operator, chair, dimensions with their line representatives, organisers (`cluster/<slug>`) and presidents (`work/<slug>`); the chair in every graph is the holder of the `chair` office, not a fixed name. Appointing, handing over or recalling an office there asks for the passkey signature; the hub writes a job (office, signed holder, successor session, work; no command) into `~/.local/state/kvb-orchestrator/offices/jobs`, and the `kvb-orchestrator-offices.path` user unit runs `client/offices.py` on the host, which verifies the signature, refuses a council directory others can write, a link on the path or a holder that changed since the signature. For a takeover or recall it moves the office aside and removes it only while the claim there still names the signed holder; an appoint or takeover then runs a plain `council.py claim <office>` for the successor by argv. council.py itself has no flag that overrides a live holder. The same view holds the operator's agent order as a drag list with up and down buttons: `rasci.py agents order` stores it in `agent-order.json` beside the register, where it breaks ties of equal task priority and orders the chair's grants without touching any task's `--prio` pin.
The Tasks tab (key 4, `#tasks/list`, `#tasks/board`, `#tasks/rasci`) shows the `kvb-org-rasci` register as a list, a board by status and a RASCI matrix; queue jobs from `council/queue.json` appear as "waiting for a grant", and the filter "Waits on Operator" is the inbox, which the Inbox entry under Stream opens: every pending review, ask, rule request, operator row and choice card, with no Choices column of its own. "Request choices" has one button per channel, post and call to the agents ticked under "Agents", newsletter, broadcast, pan-pan, Sécurité and mayday to all, that sends the fixed request "send me your open decisions as choices" without a text field, one passkey assertion over every recipient (`/api/choices/sign`, then `/api/choices`). Agents answer with `decision_request`, shown as choice cards, and a click on an option is the passkey-signed answer: after the 3 s undo the hub verifies the signature and posts the answer to the asking agent, whose `decision_wait` returns it as `message` and `answer` for `operator_message`. A task's sheet pins its priority (`rasci.py set <task> --prio <n>`) or sets and drops the operator's WSJF estimate (`rasci.py estimate <task> Operator --value <fib> --complexity <fib>` or `--drop`) after a passkey signature; the hub writes an argv-only job, and the runner, started with `--rasci <rasci.py> --council <council root>`, verifies it like an operator command and runs `rasci.py` from `/` without a shell, killing it after 30 s. Agent order stays untouched. The install copies the fresh runner unit and restarts the runner when it is enabled, and only while the installed `council.py`, `nofollow.py` and `rasci.py` match this tree: otherwise it disables the runner and asks for `make install`. The runner unit pins `PATH` to `/usr/bin:/bin`, so a signed command names any other program by its full path; a runner started without `--rasci` and `--council` keeps running signed commands and refuses task changes, and so does one whose council is a symlink or another uid's. Each worktree in the tree file may carry `"flow": {"pull": [...], "push": [...], "source": "file"}` or `null`, which the Tree, the Chord edges, the detail panel and the matching task show; a malformed flow shows as `invalid`. Worktrees and tasks with open work (uncommitted, unpushed, unmerged, task not done) carry a warning bar.
The Tasks tab (key 4, `#tasks/list`, `#tasks/board`, `#tasks/rasci`) shows the `kvb-org-rasci` register as a list, a board by status and a RASCI matrix; queue jobs from `council/queue.json` appear as "waiting for a grant", and the filter "Waits on Operator" is the inbox, which the Inbox entry under Stream opens: every pending review, ask, rule request, operator row and choice card, with no Choices column of its own. The Tasks toolbar is one row that wraps on narrow screens (views, filter, estimation, sort and business value, trend on the burn view), the status chips the second; every list row shows its ETA, and `#tasks/3d` draws the tasks and their dependencies on the 3D engine. Its "Request choices" dropdown has one button per channel, post and call to the agents ticked in it, newsletter, broadcast, pan-pan, Sécurité and mayday to all, that sends the fixed request "send me your open decisions as choices" without a text field, one passkey assertion over every recipient (`/api/choices/sign`, then `/api/choices`). Agents answer with `decision_request`, shown as choice cards, and a click on an option is the passkey-signed answer: after the 3 s undo the hub verifies the signature and posts the answer to the asking agent, whose `decision_wait` returns it as `message` and `answer` for `operator_message`. A task's sheet pins its priority (`rasci.py set <task> --prio <n>`) or sets and drops the operator's WSJF estimate (`rasci.py estimate <task> Operator --value <fib> --complexity <fib>` or `--drop`) after a passkey signature; the hub writes an argv-only job, and the runner, started with `--rasci <rasci.py> --council <council root>`, verifies it like an operator command and runs `rasci.py` from `/` without a shell, killing it after 30 s. Agent order stays untouched. The install copies the fresh runner unit and restarts the runner when it is enabled, and only while the installed `council.py`, `nofollow.py` and `rasci.py` match this tree: otherwise it disables the runner and asks for `make install`. The runner unit pins `PATH` to `/usr/bin:/bin`, so a signed command names any other program by its full path; a runner started without `--rasci` and `--council` keeps running signed commands and refuses task changes, and so does one whose council is a symlink or another uid's. Each worktree in the tree file may carry `"flow": {"pull": [...], "push": [...], "source": "file"}` or `null`, which the Tree, the Chord edges, the detail panel and the matching task show; a malformed flow shows as `invalid`. Worktrees and tasks with open work (uncommitted, unpushed, unmerged, task not done) carry a warning bar.
Without reinstalling the skills, `make orchestrator-install` installs, builds and starts only the hub, `make orchestrator-check` checks the installed hub (image, container, logs, HTTP, pinned keys, hook, runner, tree timer, disk; exit 1 on a failure), `make orchestrator-pin CREDENTIAL=<blob>` pins the passkey that "Register passkey" shows, `make orchestrator-runner` enables the runner service, and `make orchestrator-hook` registers the PermissionRequest hook in `~/.claude/settings.json`, replacing an earlier Agent Orchestrator entry and keeping the file mode, after a copy to `settings.json.before-kvb-orchestrator-hook` (Claude Code reads `~/.claude/settings.local.json` only in sessions started in the home directory). Agents that reach the Docker socket are root-equivalent: until the agent VMs of core requirement 093 isolate them, the signatures do not hold against such an agent. `make watch-check` shows whether the resource watch hook is stuck (running hooks, holders of the kept queue history, age of the stored stage).