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>
96 lines
4.6 KiB
Python
Executable File
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())
|