Files
skills/orchestrator/server.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 912a3dc07f feat(orchestrator): read the pkgmgr branch tree file instead of running git
Phase 3 of requirement 001. The orchestrator no longer starts git: orchestrator/kvb_orchestrator/tree.py maps the document that pkgmgr branch tree writes (repos, branches, worktrees, detached heads, delete_ready) onto the Tree view, re-reads it when mtime or size change, and reports a missing or broken file as an empty tree with the error. compose.yml mounts the tree directory read-only (a file mount would keep the replaced inode). /api/state touches state/tree-heartbeat; kvb-orchestrator-tree.timer runs every minute and its oneshot service runs only while that heartbeat is younger than 5 minutes, with PATH=/usr/bin:/bin and the pkgmgr venv that the install builds from the newest v* tag a fully trusted GPG key signed, cloned fresh from PKGMGR_REPO because the local checkout's config is sandbox-writable.

Dialectic verdict 2026-11-06 (2 rounds, 5 skeptics, ~85 %): hardened git runner in pkgmgr, gc classification shared with the tree scan, forge facts only from an unlocked Secret Service token with a 600 s cache, atomic tree output refreshed by open/close/drop/gc, host units outside sandbox-writable paths, orchestrator reads the file only. Losing objections: extending the host runner (it would run bash -c on signed text), a new payload kind, copying the pkgmgr checkout, a tree limited to .worktrees. Operator answers: signed-tag venv, UI delete keeps volumes and is local only, ignored files beyond known caches block a delete, scan only while the page polls. Delete jobs, gc_item signing and the UI delete path follow in phase 4.

MEDUSA acceptances (kvb-think-criticize 2026-11-07, 1 skeptic): MEDUSA-MM-SCAN-058 orchestrator/server.py:64 and :80 false positives (case-insensitive substring pla in --templates and Places; --tree-file is a read-only JSON file, no model loading).

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 06:51:49 +00:00

96 lines
4.6 KiB
Python
Executable File

#!/usr/bin/env python3
"""Args:
--bind: address the server listens on.
--listen-port: port the server listens on.
--public-port: port browsers and agents reach the hub on; the Host and
Origin headers must name it.
--council: directory holding rasci.json, news.jsonl, queue-history.jsonl,
night.json, radio-silence.json and the claims under cluster/*/ and
work/*/; a missing file leaves its part of the state empty. The board
actions change it through the skill scripts.
--pages: directory every review page must lie in.
--state: writable directory for reviews.json, asks.json, decisions.json,
messages.json, rules.json, jobs.json, marks.json, settings.json,
log.jsonl, the page copies and uploads/<id>/<name>.
--host-state: the state directory's path on the host, named in the radio
rows that carry an upload.
--skills: installed skills directory holding the kvb-org-radio,
kvb-org-rasci and kvb-org-sunset scripts.
--transcripts: directory the Claude Code transcripts (*.jsonl) lie under.
--templates: directory holding index.html.j2 and the *.js modules it
includes.
--tree-file: the JSON file `pkgmgr branch tree --output` writes, mounted
read-only; read again whenever it changes.
--timers: directory holding the systemd user units (*.timer).
--llm-url: base URL of an OpenAI-compatible chat endpoint; empty turns the
natural-language command line off.
--llm-model: model name sent to that endpoint; empty turns it off.
--llm-key-file: file holding the endpoint's bearer key; empty file or
unreadable means no key.
--piper: the Piper executable, run as a separate process; missing turns
speech output off.
--voices: directory holding the Piper voices as <model>.onnx with
<model>.onnx.json; every speaker of a model is a voice <model>:<id>.
--operator-voice: the voice of the operator and the hub; it must be one of
the installed voices when any is installed.
--whisper: the whisper.cpp command line executable; missing turns speech
input off.
--whisper-model: the ggml model whisper.cpp loads.
--scratch: writable directory the engines use as HOME and TMPDIR.
--approvers: the pinned passkeys, mounted read-only; the hub only lists
their ids and algorithms for the page and never verifies or pins.
"""
from __future__ import annotations
import argparse
from pathlib import Path
from kvb_orchestrator import NAME
from kvb_orchestrator.engines import Engines
from kvb_orchestrator.errors import ToolError
from kvb_orchestrator.exchange import HubServer
from kvb_orchestrator.handler import Handler
from kvb_orchestrator.hub import Places, open_hub
from kvb_orchestrator.language import Model, read_key
from kvb_orchestrator.skill_scripts import Scripts
def main() -> int:
parser = argparse.ArgumentParser(prog=NAME)
parser.add_argument("--bind", required=True)
for name in ("--listen-port", "--public-port"):
parser.add_argument(name, type=int, required=True)
paths = ("--council", "--pages", "--state", "--host-state", "--skills", "--transcripts", "--templates", "--tree-file", "--timers", "--llm-key-file", "--approvers")
for name in (*paths, "--piper", "--voices", "--whisper", "--whisper-model", "--scratch"):
parser.add_argument(name, type=Path, required=True)
for name in ("--llm-url", "--llm-model", "--operator-voice"):
parser.add_argument(name, required=True)
args = parser.parse_args()
try:
hub = open_hub(
council=args.council,
pages=args.pages,
public_port=args.public_port,
state=args.state,
host_state=args.host_state,
transcripts=args.transcripts,
scripts=Scripts(skills=args.skills, council=args.council, home=args.state),
templates=args.templates,
places=Places(tree_file=args.tree_file, timers=args.timers),
model=Model(url=args.llm_url.strip(), model=args.llm_model.strip(), key=read_key(args.llm_key_file)),
engines=Engines(piper=args.piper, voices=args.voices, operator_voice=args.operator_voice, whisper=args.whisper, whisper_model=args.whisper_model, scratch=args.scratch),
approvers=args.approvers,
)
except ToolError as error:
parser.error(str(error))
server = HubServer((args.bind, args.listen_port), Handler)
server.hub = hub
print(f"{NAME}: listening on {args.bind}:{args.listen_port}, public port {args.public_port}", flush=True)
server.serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(main())