Files
skills/hub/kvb_hub/sources.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 8ba45efd04 feat(hub): kvb-hub review hub with passkey-signed approvals
The kvb-hub is a local review hub in a container bound to 127.0.0.1: one page, JSON APIs and an MCP endpoint for the operator's inbox, asks, decisions, staged-commit reviews, the radio stream, the agent graph, the worktree tree, push-to-talk and read-aloud radio (Piper, whisper.cpp) and an optional LLM command line. The hub is only a relay. The host executors in hub/client (the runner user service, the PermissionRequest hook and the approval CLI the commit skills call) verify a WebAuthn passkey signature against keys pinned outside every sandbox, refuse replays and fail closed. scripts/hub.sh with make hub-install, hub-check, hub-pin, hub-runner and hub-hook installs and checks the hub; hub-hook writes ~/.claude/settings.json after a backup. make watch-check shows a stuck watch hook. The new kvb-git-diff skill and the commit skills route reviews through the hub; the test_hub_* suites replace test_hub.py.

Security review (kvb-think-criticize, three skeptics) fixed four holes: signed rules auto-approved chained shell commands; the client and LLM openers followed redirects, the LLM one with its bearer key; the worktree scan ran git fsmonitor and filter commands from agent-writable repos; compose set no memory, pids or health limits. Documented until core requirement 093, on the operator's word: the hub page is trusted while signing and pinning, local processes can forge the unsigned operator actions, and the container reads the host /tmp. Committed on the operator's word despite the gate: the passkey path is not yet verified live.

Accepted MEDUSA false positives (137 findings on 115 lines, rows in security-accepted.jsonl): EXA002 x17, MEDUSA-MM-SCAN-058 x12, B603 x8, MEDUSA-FTSC-SCAN-089 x7, MEDUSA-PRIV-SCAN-762 x6, AIC-EXFIL-005 x5, EXA006 x5, MEDUSA-FTSCAN-231 x5, WEB-SSRF x5, MEDUSA-INF-SCAN-2057 x3, RH005 x3, TC001 x3, LO002 x2, MEDUSA-AEXP-SCAN-096 x2, MEDUSA-CGS-SCAN-028 x2, MEDUSA-INF-SCAN-1350 x2, MEDUSA-INF-SCAN-2369 x2, MEDUSA-INF-SCAN-2440 x2, MEDUSA-INF-SCAN-2638 x2, MLSC-SERIAL-002 x2, PL002 x2, PQC005 x2, SAST-PATH-001 x2, TC004 x2, TC005 x2, TC006 x2, TC007 x2, AA-146, EXA007, FINE-050, GB-ENCODE-001, GB-ENCODE-003, MEDUSA-CGS-SCAN-029, MEDUSA-CGS-SCAN-103, MEDUSA-DP-SCAN-1034, MEDUSA-DP-SCAN-1916, MEDUSA-DP-SCAN-954, MEDUSA-EXF26-TOOL-004, MEDUSA-FT-TRIGGER-005, MEDUSA-INF-SCAN-2206, MEDUSA-INF-SCAN-2457, MEDUSA-INF-WEB-003, MEDUSA-INFRA-SCAN-030, MEDUSA-MP-SCAN-1135, MEDUSA-RLHF-POISON-002, MEDUSA-RT-SCAN-006, MM-ENCODE-001, MP-124, PQC001, PQC002, PQC006, RP001, TC009, TC010, WEB-FLASK-004.

🎯 Fix: 80 %
💥 Break: 4 %
💸 Cost: 129 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 60
🚦 Gate: ⛔ fix 80 % < 90; cost 129 % > 100: ask the operator before the commit

```
/home/kevinveenbirkenbach/Repositories/github.com/kevinveenbirkenbach/skills/.worktrees/feature/kvb-hub
```

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:46:40 +00:00

54 lines
2.7 KiB
Python

from __future__ import annotations
import time
from .council import HUMAN, agent, read_claims
from .crons import cron_state, queue_rows, timer_rows
from .errors import ToolError
from .fields import single
from .lifetimes import edge_times, lifetime, session_gone, subagent_rows
from .names import PSEUDO, SESSION_ID, Directory, title_sightings
from .records import epoch, iso_epoch
from .register import REGISTER, read_register
from .tree import assign
class Sources:
def subagents(self, query: dict[str, list[str]]) -> dict:
session = single(query, "session") or ""
if not SESSION_ID.fullmatch(session):
raise ToolError("session must be a session id")
now = time.time()
beats = [claim["beat"] for claim in read_claims(self.council) if claim["session"] == session and claim["beat"] is not None]
try:
path = next(iter(sorted(self.transcripts.root.glob(f"*/{session}.jsonl"))), None)
except OSError:
path = None
times = edge_times(path) if path is not None else (None, None, None)
life = lifetime(times, session_gone(max(beats, default=None), times[2], now))
return {"session": session, **life, "subagents": subagent_rows(self.transcripts.root, session)}
def directory(self, claims: list[dict], news: list[dict], now: float) -> Directory:
claimed = {claim["session"] for claim in claims}
sightings = title_sightings(self.transcripts.root, claimed, now) if self.names.due(now) else []
sightings += [(claim["session"], claim["agent"], iso_epoch(claim["since"]), "claim") for claim in claims if claim["agent"] and claim["session"] != "-"]
self.names.observe(sorted(sightings, key=lambda sighting: (sighting[2] is None, sighting[2] or 0.0)), now)
known = {entry["name"] for entry in self.names.listed()}
radio = []
for row in news:
name, ts = agent(row.get("from")), epoch(row.get("ts"))
if name and name != HUMAN and name not in known and ts is not None:
radio.append((f"{PSEUDO}{name}", name, ts, "radio"))
known.add(name)
self.names.observe(radio, now)
return Directory(self.names.listed())
def repositories(self) -> dict:
tasks = read_register(self.council / REGISTER)
return assign(self.tree.get(), tasks, read_claims(self.council), self.reviews.listed())
def schedules(self, claims: list[dict], now: float) -> list[dict]:
beats = {claim["session"]: claim["beat"] for claim in claims if claim["beat"] is not None}
rows = [cron_state(cron, beats, now) for cron in self.transcripts.schedules()]
return rows + timer_rows(self.timers) + queue_rows(self.council)