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>
88 lines
4.4 KiB
Python
88 lines
4.4 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import subprocess
|
|
import unittest
|
|
|
|
from hub_support import AMBIENCE, STREAMS
|
|
|
|
SLOTS = [(0, 420, "Morgen"), (0, 539, "Morgen"), (0, 540, None), (1, 419, None), (5, 480, None), (5, 1380, "Nacht"), (6, 90, "Nacht"), (6, 120, None), (4, 1380, None), (6, 1380, None)]
|
|
PROBE = """
|
|
const A = require(process.argv[1]);
|
|
const at = (text) => Date.parse(text);
|
|
const pick = (body, keys) => Object.fromEntries(keys.map((key) => [key, body[key]]));
|
|
const surfaces = [];
|
|
for (const mode of ["light", "dark"]) {
|
|
for (const surface of ["bg", "panel"]) {
|
|
for (const sky of A.SKY.flatMap(([, top, bottom]) => [top, bottom])) {
|
|
const behind = A.mix(A.hex(sky), A.hex(A.THEMES[mode][surface]), A.GLASS);
|
|
surfaces.push({mode, surface, sky, text: A.contrast(A.hex(A.THEMES[mode].text), behind), muted: A.contrast(A.hex(A.THEMES[mode].muted), behind)});
|
|
}
|
|
}
|
|
}
|
|
const slots = JSON.parse(process.argv[3]).map(([day, minute]) => ({day, minute}));
|
|
console.log(JSON.stringify({
|
|
sun: pick(A.sun(at("1992-10-13T00:00:00Z"), 0, 0), ["ra", "dec", "eot"]),
|
|
solstice: A.sun(at("2024-06-20T20:51:00Z"), 0, 0).dec,
|
|
london: A.sun(at("2024-06-21T03:43:00Z"), 51.5074, -0.1278).alt,
|
|
moon: pick(A.moon(at("1992-04-12T00:00:00Z"), 0, 0), ["ra", "dec", "lambda", "beta", "distance", "lit", "limb", "waxing"]),
|
|
full: A.moon(at("2024-04-23T23:49:00Z"), 52.5, 13.4).lit,
|
|
fresh: A.moon(at("2024-04-08T18:21:00Z"), 52.5, 13.4).lit,
|
|
quarter: pick(A.moon(at("2024-04-15T19:13:00Z"), 52.5, 13.4), ["lit", "waxing"]),
|
|
sidereal: [A.sidereal(at("1987-04-10T00:00:00Z")), A.sidereal(at("1987-04-10T19:21:00Z"))],
|
|
stages: [-30, -15, -9, -3, 3, 20].map(A.stage),
|
|
modes: [-10, 3.9, 4, 40].map(A.modeAt),
|
|
black: A.contrast([0, 0, 0], [255, 255, 255]),
|
|
surfaces,
|
|
slot: A.slotOf(new Date(2024, 5, 21, 7, 30)),
|
|
playing: slots.map((slot) => (A.activeStream(JSON.parse(process.argv[2]), slot) || {name: null}).name),
|
|
}));
|
|
"""
|
|
|
|
|
|
class TestAmbience(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
command = ["node", "-e", PROBE, str(AMBIENCE), json.dumps(STREAMS), json.dumps([slot[:2] for slot in SLOTS])]
|
|
done = subprocess.run(command, capture_output=True, text=True, timeout=60, check=True)
|
|
cls.data = json.loads(done.stdout)
|
|
|
|
def test_the_sun_matches_meeus_and_noaa_references(self):
|
|
sun = self.data["sun"]
|
|
self.assertAlmostEqual(sun["ra"], 198.38083, delta=0.01)
|
|
self.assertAlmostEqual(sun["dec"], -7.78507, delta=0.01)
|
|
self.assertAlmostEqual(sun["eot"], 13.71, delta=0.1)
|
|
self.assertAlmostEqual(self.data["solstice"], 23.438, delta=0.01)
|
|
self.assertAlmostEqual(self.data["london"], -0.833, delta=0.3)
|
|
self.assertAlmostEqual(self.data["sidereal"][0], 197.693195, delta=0.001)
|
|
self.assertAlmostEqual(self.data["sidereal"][1], 128.7378734, delta=0.001)
|
|
|
|
def test_the_moon_matches_meeus_within_the_low_precision_series(self):
|
|
moon = self.data["moon"]
|
|
for key, expected, delta in (("lambda", 133.162655, 0.5), ("beta", -3.229126, 0.3), ("ra", 134.68847, 0.5), ("dec", 13.768368, 0.3), ("distance", 368409.7, 3000), ("lit", 0.6786, 0.01), ("limb", 285.0, 2.0)):
|
|
with self.subTest(key=key):
|
|
self.assertAlmostEqual(moon[key], expected, delta=delta)
|
|
self.assertTrue(moon["waxing"])
|
|
self.assertGreater(self.data["full"], 0.99)
|
|
self.assertLess(self.data["fresh"], 0.01)
|
|
self.assertAlmostEqual(self.data["quarter"]["lit"], 0.5, delta=0.03)
|
|
self.assertTrue(self.data["quarter"]["waxing"])
|
|
|
|
def test_sky_stages_modes_and_text_contrast_hold_in_every_sky(self):
|
|
self.assertEqual(self.data["stages"], ["night", "astronomical", "nautical", "civil", "golden", "day"])
|
|
self.assertEqual(self.data["modes"], ["dark", "dark", "light", "light"])
|
|
self.assertAlmostEqual(self.data["black"], 21.0, places=6)
|
|
self.assertEqual(len(self.data["surfaces"]), 88)
|
|
for row in self.data["surfaces"]:
|
|
with self.subTest(mode=row["mode"], surface=row["surface"], sky=row["sky"]):
|
|
self.assertGreaterEqual(row["text"], 7.0)
|
|
self.assertGreaterEqual(row["muted"], 4.5)
|
|
|
|
def test_streams_play_on_their_weekdays_and_hours(self):
|
|
self.assertEqual(self.data["slot"], {"day": 4, "minute": 450})
|
|
self.assertEqual(self.data["playing"], [slot[2] for slot in SLOTS])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|