Commit Graph
207 Commits
Author SHA1 Message Date
Kevin Veen-BirkenbachandClaude Opus 5.5 5556ed8a0d fix(orchestrator): one passkey request at a time
With two passkey requests open at once, the hub received one request's payload with the other request's assertion and refused it with the challenge is not the one of the payload. signedProof now refuses a second signing while one runs and tells the operator to finish the first.

🎯 Fix: 89 %
💥 Break: 4 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 85
🚦 Gate: ⛔ fix 89 % < 90: operator approved the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 08:21:53 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 57ab29eb0d 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>
2026-11-10 08:10:51 +00:00
Kevin Veen-Birkenbach e94f2800d5 Merge master into fix/rename-release-changelog
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	README.md
2026-11-09 09:27:53 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 9d4efe3bb4 feat(kvb-release-changelog): Version jumps, Contributors with URLs, net counting
Operator order 2026-11-08 (via Main), modelled on core CHANGELOG.md 16.2.3:

- Section order: Breaking, Added, Changed, Fixed, Version jumps, Upgrade
  notes, Contributors.
- Version jumps (net since X.Y.Z): in a repository that pins a version
  per role, one "*role*: old to new" line per role whose
  roles/*/meta/services.yml version differs between tag and HEAD; a
  bump twice shows once, a change and change back not at all; a change
  that is no version bump needs judgement, never a jump line. The same
  comparison covers ref and *_version keys, meta/addons/*.yml pins,
  renamed or split roles paired old to new (a rename without alias is
  also Breaking plus an Upgrade note), requirements.galaxy.yml and
  pyproject.toml pins (classes found by Main on core v16.2.3..main).
- Contributors: always, one "[Name](URL): areas" line per human author
  and Co-authored-by trailer, names through .mailmap, bots and AI
  co-authors left out, the URL from earlier CHANGELOG.md or
  packaging/debian/changelog entries, asked for when missing.
- Net counting: after a long-lived branch merge the commit list repeats
  released work under other SHAs, so every line is judged against
  git diff tag..HEAD and the earlier entries.

The details live in sections.md, read on demand, so SKILL.md stays within
its 600-word cap.

Evidence: make test 652 OK, markdownlint 0 errors
(/tmp/rename-changelog-make-test-4.log), markdownlint of sections.md
after its last edit 0 errors.

🎯 Fix: 92 %
💥 Break: 3 %
💸 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>
2026-11-09 09:11:18 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 9e2452454e refactor(skills)!: rename kvb-git-changelog to kvb-release-changelog
A new release category groups what announces a release (changelog,
article, posts); the changelog skill is its first member. The naming
test lists the category and the README organigram places the skill
under it.

BREAKING CHANGE: /kvb-git-changelog is now /kvb-release-changelog;
make install replaces the installed copy, kvb-skill-cleanup removes the
legacy directory.

Evidence: make test green, ruff and markdownlint clean
(/tmp/rename-changelog-make-test-2.log); no reference to the old name
left in the tree; MEDUSA 5 files, 0 findings.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:09:58 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 2a77e4905f chore(lock): refresh the cavecrew and caveman-compress hashes
make install pulled the current JuliusBrussee/caveman skills; their
computed hashes in skills-lock.json follow.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 09:08:05 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 d9eb121d12 feat(orchestrator): S5 Tasks with choices, Gantt, estimates and burn charts
Requirement 001 phase 6, S5:

- Tasks tab: every pending ask, rule request, operator row and choice
  card shows under "waits on Operator"; a choice request goes out on a
  fixed channel button, post and call with an agent multi-select, and a
  click on an option sends a passkey-signed answer the agent checks with
  operator_message. The unsigned /api/decisions/<id>/choose route is
  gone, and a decision, choice answer or choice recipient whose name
  holds "*" is refused, so a signed answer never reaches every agent.
- Gantt view: one bar per task from start to end (running to now), one
  arrow per explicit, branch-base and queue-order edge, hatched forecast
  bars (always, points only, real only) from the median duration or
  points times median hours per point.
- rasci.py set --after/--drop-after stores task dependencies and refuses
  unknown tasks and cycles.
- Estimation toggle (No estimate default, Story points) is stored in the
  hub settings; burndown and burnup per whole register, list filter or
  one agent or line, counting tasks or summing complexity with the
  unestimated tasks as their own line.
- New skill kvb-org-queue-estimate: "Request estimates" (story points
  mode, passkey-signed board action) or the skill sends exactly one
  newsletter listing every unestimated task with its RASCI parties; a
  party name that is no plain name shows as "unnamed party".
- Business value per task or in bulk is stored only with a verified
  passkey assertion over the exact ids and values; WSJF uses it instead
  of the median. The task list sorts by every decision-50 mode both ways.
- e2e: the walk's pending ask lives an hour instead of 60 s, and the
  CDP and BiDi drivers retry removing the browser profile while the
  browser exits (ENOTEMPTY).

Evidence: make test 709 OK, ruff and markdownlint clean
(/tmp/s5-make-test-fix-2.log); e2e 26 OK in Chromium and Firefox
(/tmp/s5-e2e-fix-2.log); MEDUSA 50 files, an independent skeptic
rejected four holes (unsigned choose route, unsigned estimates request,
free-text party names, "*" recipients incl. normalizer bypasses), all
fixed and re-verified, the 8 remaining pattern hits accepted, rescan 0
open.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 06:53:55 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 5c8b69d53d feat(skills)!: S4 night commands become drain, suspend and resume
Requirement 001 phase 6, S4 (decision 29):

- Skills renamed: kvb-org-evening -> kvb-org-drain, kvb-org-sunset ->
  kvb-org-suspend, kvb-org-sunrise -> kvb-org-resume, kvb-agent-relax
  -> kvb-agent-drain, kvb-agent-sleep -> kvb-agent-suspend,
  kvb-agent-wakeup -> kvb-agent-resume; their text uses the new verbs and
  every reference in skills, README, orchestrator and tests follows.
- night.py takes drain, suspend, resume, resume --alone and status. A
  state file written before the rename keeps working: mode evening reads
  as drain, sunset as suspend, the awake list as resumed and is rewritten
  on the next save; the hub's council.night_mode maps the same values.
  mode: day and handover_night_<session>.md keep their names.
- Orchestrator: the Day/Night menu shows Drain, Suspend and Resume, each
  confirmed, signed through /api/board/sign with the passkey and run as
  the existing argv job; the old action names are refused.
- kvb-skill-cleanup: git rates two renames at 44 and 50 % similarity,
  under its default threshold, so legacy.py detects renames from 40 %.
- tests/test_night_names.py fails on any old name in the tree outside a
  rename record "old ->".

BREAKING CHANGE: the six old skill names and night.py's clear and wake
are gone. Run make install (it reinstalls the orchestrator too), then
kvb-skill-cleanup to remove the old installed copies; until the install,
the running hub calls the old night.py path.

Evidence: make test 679 OK, ruff and markdownlint clean
(/tmp/s4-make-test-1.log); e2e 22 OK in Chromium and Firefox
(/tmp/s4-e2e-3.log); MEDUSA 23 files, the 2 findings are the
byte-identical atomic state write that moved with night.py, already
accepted after a skeptic at its old path, rescan 0 open.

🎯 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>
2026-11-08 23:18:18 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 4d88296e47 feat(orchestrator): S3 generated SVG faces whose mouths move while speaking
Requirement 001 phase 6, S3:

- faces.js derives a face from the agent's stable key (its session id,
  or name:<name> without a session), so a rename keeps the face: shape
  4, colour 7, eyes 5, brows 4, mouth 5, hair or headwear 6, accessory 5,
  84,000 combinations. It emits constant SVG tags and attributes only.
- The emoji avatar is gone: popup, Orbit and Chord nodes, the radio
  view, stream and palette draw the SVG face (the 3D views keep their
  marker).
- While the radio reads an agent's message, its faces carry the class
  speaking and the mouth runs the talk keyframes; a silent face does
  not move, and prefers-reduced-motion stops the mouth.

Evidence: make test 673 OK, ruff and markdownlint clean
(/tmp/s3-make-test-1.log); e2e 20 OK in Chromium and Firefox, none
skipped (/tmp/s3-e2e-3.log); screenshots
/tmp/orchestrator-phase4-shots/*-speaking.png; MEDUSA 13 files, 2
findings ("PLA" in "templates", the UI call openAgent) accepted after
an independent skeptic that found no markup sink or selector injection
in the face code.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 22:06:31 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 d89fb616b8 feat(orchestrator): S2 agent and node popups with leader line, node search
Requirement 001 phase 6, S2:

- Agent popup: a click on an agent opens one fading <dialog> with face,
  name, earlier names, offices, tasks, claims, subagents, history and
  the composer; Escape or a click outside closes it. The agent drawer
  and the Details side panel are gone.
- Node popup: a click on a node in a graph view enlarges it and marks it
  red, and an SVG leader line ties it to the translucent popup; a click
  elsewhere restores the node. popup.js places the popup right, left,
  below or above the node, caps its height to the room there and
  returns the line.
- Search: in the 3D views a search field with suggestions replaces the
  list of every node, one chip per distinct label.
- Fit: body.fit now applies at every width; without the side panel the
  orbit view otherwise grew a page scrollbar at 390 px.
- Two page modules had silently shadowed each other's functions of the
  same name while this was built; test_no_two_page_modules_declare_the_same_function
  now guards that. Artifact ids carry NUL, which CSS.escape turns into
  U+FFFD, so the node lookup compares dataset values.

- Composer: a message goes to the agent whose popup it was written in.
  deliver() read the global ui.agent after awaiting the upload, so
  closing the popup or opening another agent during an upload sent the
  signed message to null or to the other agent (found by the security
  skeptic, present before, easier now that a backdrop click closes).

Limits: the 3D leader line is placed when the popup opens and does not
follow the camera while the layout settles; the search keeps the first
node per label, so a second repository's main branch is found in the
graph, not through the search.

Evidence: make test 667 OK, ruff and markdownlint clean
(/tmp/s2-make-test-1.log); e2e 19 OK in Chromium and Firefox at 1920,
1366 and 390 px, none skipped, rerun after the composer fix
(/tmp/s2-e2e-4.log); screenshots in /tmp/orchestrator-phase4-shots/;
MEDUSA 22 files, 6 findings (a "PLA" pattern in "templates", the UI
names openAgent and drawAgent) accepted after an independent skeptic
that also found no XSS or selector injection in the popup code.

🎯 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>
2026-11-08 21:18:55 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 c035373518 feat(orchestrator)!: S0 English everywhere, Sécurité, NATO hashes, ICAO digits
Requirement 001 phase 6, S0:

- Speech: the security class shows as "Sécurité" and is spoken as in
  maritime radio, "say-cure-ee-tay" three times. A hex run of 7 or more
  characters with a letter and a digit (commit, session, message ids) is
  spelled in the NATO alphabet; every other digit is spoken on its own
  in ICAO pronunciation (42305: four two tree zero fife). The spelling
  runs inside clip() before the length cut, so a phrase still fits
  SPEECH_LIMIT; the displayed text keeps its digits and hashes.
- English: every orchestrator label, tooltip, message, error, script
  output, document and test string is English (Regeln -> Firewall,
  Hierarchie -> Hierarchy, Steuerung -> Controls, Ausführen -> Run, ...);
  the page declares lang="en" and formats dates as en-GB. Task filter
  ids are English. German stays only in the skill trigger phrases that
  quote the operator, and as identifiers: the view keys (view=) and the
  leitstand id.
- tests/test_english.py scans every tracked text file for umlauts and
  German words, with an allowlist of the quoted trigger phrases.

BREAKING CHANGE: the Stream search tokens art: and seit: are now kind:
and since:; an old art: or seit: token counts as a plain word.

Evidence: make test 657 OK, ruff and markdownlint clean
(/tmp/s0-make-test-1.log); e2e 17 OK, none skipped (/tmp/s0-e2e-1.log);
MEDUSA 86 files, 24 findings on reworded strings accepted after an
independent skeptic (21 path:line, 0 rejected, no behaviour change in
signing, approving, rules, denying, offices, events), rescan 0 open.

🎯 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>
2026-11-08 17:26:51 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 a388c6ef77 fix(orchestrator): S1 fit, rename once, Deutschlandfunk e2e, squelch, tree cause
Requirement 001 phase 6, S1:

- Fit: Agents and Artifacts in a graph view fill the window from 861 px
  up (body.fit flex column); the e2e walk probes #agents/orbit at every
  width for a page no taller than the window.
- Rename once: a session seen under two names by one source (two claims)
  flipped its name on every state build, so the Stream logged a rename
  each poll. Names.observe keeps only the newest sighting per session
  and source, and the timeline emits each (session, before, after) pair
  once.
- Deutschlandfunk: root cause was the signed media-src, which never
  listed the redirect target f111.rndfnk.com that st01.sslstream.dlf.de
  answers with (fixed in ba97f7c). The e2e now replays that chain, a
  302 from one https origin to a second, in Chromium and Firefox, with
  self-signed throwaway certificates the drivers accept.
- Squelch: a settings slider 0..1 scales every tone; at 0 no beep plays,
  music on or off (Voice.beep skips a silent tone). Stored settings
  without the key load with 1.0.
- Tree: the missing tree file error names `pkgmgr branch tree` and the
  release and install step that makes the timer write it.

Evidence: make test 655 OK, ruff and markdownlint clean
(/tmp/s1-make-test-1.log); e2e 17 OK, none skipped (/tmp/s1-e2e-3.log);
MEDUSA 14 files, 0 findings.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 13:37:02 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 86b55dbc91 fix(orchestrator): keep subagents behind their agent on the 3D time axis
With depth = time, #agents/3d drew subagents behind the session that
spawned them. A subagent took the mtime of its meta.json, which is
rewritten when it finishes, and a session took the moment the hub first
saw its name, or the window end when it had none.

Sessions now take the first line of their transcript and subagents the
first line of theirs, both from /api/subagents, with the old values as
fallback. SpaceView.notBeforeParent moves a spawned node without a time,
or with one before its parent's, to its parent's time, nested subagents
included, so no subagent lies before the agent that spawned it. The 3D
signature carries the time, so the nodes move once the lifetimes load.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 11:32:16 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 ba97f7cc2e fix(orchestrator): release a station's redirect targets in media-src
Deutschlandfunk stayed silent: its signed URL answers 302 to
f111.rndfnk.com, an origin the signed media-src never listed, so the
browser blocked the redirected load and reported only the station's own
URL in securitypolicyviolation.

The hub now resolves each https stream's redirect chain server side when
a settings save adds or changes it and stores the extra origins as the
stream's via list. media-src lists them next to the signed origins. A CDN
host of three or more labels widens to its siblings (*.rndfnk.com); an
IP, a two-label host and a name under a country code's generic second
level stay exact.

Hardening:

- Only the signed URL itself may lie at a non-global address; every
  redirect hop dials a global address only, checked on the address
  actually dialed (NAT64 and IPv4-compatible IPv6 count by the embedded
  IPv4 address).
- A resolve answers within three socket timeouts even for a server that
  drips its headers; a late probe goes on in a daemon thread and a url
  still in flight is not probed twice.
- A failed resolution stores via null and retries on a save ten minutes
  later; the radio page says so instead of blaming the CSP.
- A save whose settings changed while it was resolved fails instead of
  overwriting them (compare and set under the settings lock).
- A new stream URL at an already signed origin takes the passkey too.
- Settings written before via existed load as via [] and never dial.

Residuals:

- The passkey proof is spent before the resolve, so a save racing
  another one fails and the operator signs again.
- A save that hits a due retry blocks up to twelve seconds and may
  reload the page.
- A page loaded before the upgrade throws on the missing via until it
  reloads.
- A private multi-tenant suffix such as github.io still widens.
- getaddrinfo has no timeout; it runs inside the daemon thread.

Operator step after install: remove Deutschlandfunk in Radio, Sender
bearbeiten, save, add it again and save with the passkey.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 10:23:32 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 46382cee9a docs(requirements): add Gantt, estimates and burn charts to 001 S5
Records the operator's decisions 44 to 51 of 2026-11-08 for the Tasks
tab: a Gantt view with dependency arrows from an explicit rasci --after
field, the branch base and the queue order; a forecast selector; the
estimation modes No estimate (default) and Story points; burndown and
burnup with a scope selector; an estimate newsletter sent only on a
button click or /kvb-org-queue-estimate to every agent holding a RASCI
role; a passkey-signed operator business value, also as one bulk batch;
and sort modes for the task list. They join subtree S5.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 09:44:17 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 4321b97529 fix(kvb-org-queue): global budget so no queue mix can OOM the host
Root cause of the 2026-11-07 20:14 OOM: the queue granted jobs per kind
only, so several heavy kinds and direct heavy commands outside the queue
ran side by side until the kernel killed the editor.

- queue.py: every grant checks the sum of the learned RAM and CPU
  footprints of active and granted jobs against budget_percent; an
  unlearned kind takes the whole budget and runs alone. RAM used plus
  what young jobs (RAMP_SECONDS) may still take must stay below
  load_limits. Memory stage stop blocks every grant.
- watch.py: memory stage from MemAvailable, swap, PSI full and the swap
  of the container holding most of it; the block hook classifies
  builds, make install/verify-*, and pytest or unittest beyond one file
  as heavy through wrappers, sh -c, command substitutions and heredocs
  a shell reads.
- history.py: footprints per kind and signature, seed RAM for pending
  shares.
- operator.py: [h]old queues a heavy row through queue.py hold and asks
  again on its grant, with a flushed tty and a timeout; sudo rows, which
  need the terminal, stay direct.

Residuals: a shell reading code from a pipe or here-string, its own
script, a program named by a substitution, quotes inside an expanding
heredoc, an escaped backslash before a substitution; a lone job waits
while the host is over load_limits.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 06:54:09 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 09f68dd75b fix(orchestrator): run the tree timer a minute after each run
Root cause: a pkgmgr branch tree run takes 72.6 s real (55.5 s user) on
this host, while TimeoutStartSec=55 killed every run; the timer fired
again each minute, so the service burned a core and branch-tree.json
never appeared.

- kvb-orchestrator-tree.timer: OnActiveSec=30s plus
  OnUnitInactiveSec=60s instead of OnCalendar=minutely, so runs never
  overlap and the next one starts 60 s after the last ended.
- kvb-orchestrator-tree.service: TimeoutStartSec=600 and CPUQuota=50%.
- orchestrator.sh restarts the timer on install so the new schedule
  takes effect; README and requirement 001 follow.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 06:46:03 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 c1310c3322 feat(orchestrator): tasks tab with register, inbox and queue
Add a Tasks tab that lists the kvb-org-rasci register next to the inbox
and the heavy-job queue, in list, board and RASCI matrix views, each
task with its roles, WSJF, open work and the merge flow of its worktree.

- Task priority pins and WSJF estimates are signed with the operator's
  passkey and applied by the runner through rasci.py; the hub verifies
  the signature itself before it queues a job or logs the change, the
  runner verifies it again and runs only the checked argv, from /, with
  a pinned PATH and a 30 s process-group limit.
- The runner refuses task changes into a council that is a symlink, not
  owned by its uid or given by a relative path, and while the installed
  council.py, nofollow.py or rasci.py differ from the tree.
- Only tasks the hub can sign whole are editable (task slug and length).
- Page lookups keyed by register text use Object.hasOwn, so a status or
  party named like an Object.prototype member cannot break the views.
- The inbox screen renders through the tasks screen, so its task filter
  shows with "Wartet auf Operator" pressed.

Tests: 604 unit tests OK, e2e 15/15 OK (Chromium and Firefox).

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 23:43:36 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 01f791a1f4 fix(kvb-org-priority): end SKILL.md with a newline
markdownlint MD047 failed make test on master since the hierarchy commit
4540494 dropped the final newline.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 21:45:42 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 34e5d1c97e fix(install): install jscpd under ~/.local instead of the system prefix
npm install --global without a prefix writes to /usr/lib/node_modules and
fails with EACCES for a normal user, so every make install warned and
principles.py skipped its clone hits. --prefix ~/.local puts the binary in
~/.local/bin, which is on PATH.

🎯 Fix: 92 %
💥 Break: 3 %
💸 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>
2026-11-07 21:39:50 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 089f8dbb9f feat(orchestrator): operator composer with signed text and voice
Stream -> Radio gets one line per station with state, play and stop,
autoplay and CSP refusals as row state and toast, and the speaking agent
pulsing there and in the Orbit and Chord views. The composer sends on all
seven channels; push-to-talk records with MediaRecorder, whisper.cpp
transcribes off the request thread under per-minute and per-hour caps,
and the recording travels only when "Aufnahme anhängen" is ticked.

Every operator message, the mayday clear, the day and night board
actions and a settings save that adds a stream origin take the passkey;
the hub verifies each signature once against the pinned keys before it
relays or acts. The container gets TMPDIR=/scratch so that verification
no longer fails on the read-only root.

A denial now signs its recipient: the inbox and the signing step resolve
it through one function, a claim set naming several agents leaves a
direct denial without recipient, and a recipient that changed after the
inbox showed it is refused with 409. operator_message returns only the
signed reason for a denial, never the agent's own words the radio row
quotes, and the permission hook hashes the signed recipient too.

🎯 Fix: 95 %
💥 Break: 3 %
💸 Cost: 99 %
⏱️ Time: 68 %
🛡️ Security: 100 %
🏆 Score: 100
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 19:59:54 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 dbd9ec3311 docs(requirements): add the shared cache contract as subtree S8 of 001
Decision 43 records the core cache concept the operator settled on
2026-11-07 (owner IAM) as far as the orchestrator consumes it: the
image fetches models, voices and source archives through an optional
INFINITO_ARTEFACT_BASE with unchanged sha256 pins, speech uses the core
svc-ai-speech role only when configured, and make orchestrator-install
stays the standalone path. S8 follows S7 with its own criteria.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:56:40 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 a1d0397344 docs(requirements): add English and radio speech as subtree S0 of 001
Decisions 39 to 42 record the operator's order of 2026-11-07: the
security class shows as "Sécurité" and is spoken "say-cure-ee-tay" as in
maritime radio, hex hashes are spoken in the NATO alphabet, every digit
is spoken on its own in ICAO pronunciation (tree, fife, niner), and the
whole skills repository becomes English, keeping only the German trigger
phrases operators say and the rule to answer in the operator's
language. S0 runs first so S1 to S7 are written in English; the
Ressourcen tab is now Resources.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:32:38 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 58187e8323 feat(orchestrator): inbox console, deny by channel and agent rule requests
The inbox shows the console output of a command under its "Ausführen"
row, kept to the same tail the job list shows. A deny picks its radio
channel (post by default, call, newsletter, broadcast, pan-pan,
security) and an optional reason; the passkey signs denial, channel and
reason, the hub verifies the proof before it stores or sends anything,
and the PermissionRequest hook quotes a reason only when it verifies.

Agents ask for an auto-approval rule through the MCP tool rule_request;
the request shows in the inbox with a dry run over the last seven days
and is accepted only with a passkey assertion the hub verifies against
the pinned keys and the hub origin, for every rule kind. Requests lapse
with the ask lifetime and are capped per agent and in total.

The image runs as nobody by default and carries the health check
command; compose keeps the timing.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 18:14:30 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 1d311a3382 docs(requirements): record orchestrator phases 5 and 6 in 001
Requirement 001 stays the single place for the Agent Orchestrator, as
its decision 1 asks. Decisions 23 to 38 record the operator's answers of
2026-11-07: phase 5 (console, deny channel, rule requests, composer
with whisper, Hierarchie, separate agent order, Tasks tab, merge flow)
and phase 6 (Firewall with passkey-signed settings writes and groups,
asks and choices as tasks with a choice request per channel, drain /
suspend / resume, agent and node popups with search, animated faces,
fit to window, rename once, the Deutschlandfunk stream and a squelch,
the tree cause, a Ressourcen tab). A second diagram shows the serial
subtrees S1 to S7, and each gets its acceptance criteria and tests.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:55:11 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 4540494c30 feat(orchestrator): hierarchy view from council offices with a separate agent order
The Agents views gain a Hierarchie view: an org chart of the chair and
the line offices read from the council claims. The operator appoints,
hands over and recalls an office there; the page signs each change with
the passkey and a host unit (client/offices.py, offices path unit)
verifies the signature, removes a live holder itself and runs a plain
council.py claim for the successor. council.py loses the --operator
flag, so no agent can override a live holder.

The agent order is now separate from task priority: rasci.py agents
order stores it in agent-order.json beside the register through the
nofollow helpers and never writes a --prio pin. It only breaks ties of
equal task rank, also among the queue grants. The Hierarchie view edits
it as a drag list.

Host units run the installed skill scripts, so orchestrator.sh keeps the
offices unit off while the installed council.py, nofollow.py or rasci.py
differ from the tree. Job files, council files and hub records are read
with O_NOFOLLOW and O_NONBLOCK and size caps; nofollow.rewrite replaces
a claim without creating directories, so a beat never revives a released
office.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 17:46:39 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 8e180be256 fix(kvb-org-rasci): open the register and its lock without following links
The council root is writable by every agent, and operator.py runs
rasci.database() outside the sandbox. A symlinked rasci.json made it
read any JSON file the operator can read and write it back into the
council as a regular file; a symlinked rasci.lock made it create files
at the link target; a FIFO hung every register call.

load() now reads through nofollow.read_text and database() locks and
writes through nofollow.locked and nofollow.replace. A register that is
no object of task objects, or nested past the recursion limit, exits 2
instead of a traceback. nofollow.open_below also refuses a file with
more than one link, so a hard link to a file outside the council is
refused like a symlink.

🎯 Fix: 96 %
💥 Break: 3 %
💸 Cost: 99 %
⏱️ Time: 95 %
🛡️ Security: 100 %
🏆 Score: 99
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 16:38:28 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 13b360227b Merge branch 'feature/agent-orchestrator/views'
Phase 4 of requirement 001 (90c56cb): Agents and Artifacts views with spawn tree, orbit, 3D and signed branch deletes. Merged tree: 525 tests OK, listing budget 1444/1500 words.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 15:18:21 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 90c56cb786 feat(orchestrator): Agents and Artifacts views with spawn tree, orbit, 3D and signed branch deletes
Phase 4 of requirement 001. Agents and Artifacts each offer Orbit, Chord, Swimlane, Tree and 3D: the spawn tree (page-spawn.js) and the artifacts orbit (page-artifacts.js, kvb_orchestrator/artifacts.py) replace the "folgt" placeholders, 3D runs on the vendored 3d-force-graph and three.js (MIT, LICENSE files kept, served from the image under the unchanged CSP), and a Bootstrap Icons sprite (icons.py, MIT) replaces the emoji glyphs, also in the meditation page.

Signed branch deletes: the Tree view offers a delete only for merged-clean branches without a gc skip reason; the operator signs the exact item with the passkey, the hub writes a job without any command (kvb_orchestrator/cleanup.py), and the host path unit runs client/cleanup.py, which verifies the signature, dry-runs pkgmgr branch cleanup for that exact branch, refuses patterns, option names, changed tips, other worktrees and plans naming other branches, deletes, and rewrites the branch tree. pkgmgr runs with core.hooksPath=/dev/null, core.fsmonitor=false and the config hook events disabled through GIT_CONFIG_COUNT, because the cwd is a repository agents can write and pkgmgr releases before 4c8b137 delete without scan mode (test with a positive control that both hook kinds fire without it).

3D labels go into the tooltip as a text node: the vendored tooltip renders a string label as HTML.

Tests: 503 OK, Chromium (CDP) and Firefox (BiDi) walks over every screen at three widths.

MEDUSA acceptances (kvb-think-criticize skeptic 2026-11-07): WEB-FLASK-004 view.py:135 (file template, autoescape on, state via tojson), MEDUSA-MM-SCAN-058 server.py:69/:85 (substring pla in --templates and Places), RH005 client/cleanup.py:199 (raise SystemExit(main())), B704 icons.py:45 (sprite baked into the image, names checked against it), EXA002 page-artifacts.js:221 (openAgent only opens the drawer), STG002 page-constants.js:64 (U+2212 in a static label), PL002 page-graph3d.js:44 (CSS class space-tools; the real tooltip sink fixed), B603 client/cleanup.py:75 (argv without shell; hook hole closed).

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 15:03:14 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 05cc891998 feat(kvb-check-ansible): rank where an Ansible run spends its time
New skill with ansible_tasks.py: gaps, durations, kinds, kind and task rank a run's log by wall-clock gaps or profile_tasks lines; profile-report merges cProfile dumps of the controller and its forked workers (sitecustomize hook); bench-carrier and the bench playbook measure per-task overhead against variable size. kvb-code-tune hands its Ansible timing to it.

🎯 Fix: 92 %
💥 Break: 2 %
💸 Cost: new
⏱️ Time: new
🛡️ Security: 100 %
🏆 Score: 90
🚦 Gate: cost new, time new: cleared by the operator's commit word for kvb-check-ansible (2026-11-07)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:57:47 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 8b0c287ada fix(skills): trim 22 descriptions back under the listing budget
The skill listing stood at 1498 of 1500 words; the trimmed descriptions bring it to 1420, so the new kvb-check-ansible description fits without pushing others out.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 10:47:08 +00:00
Kevin Veen-Birkenbach c61125c421 Merge branch 'fix/kvb-cleanup/sandbox-worktrees' 2026-11-07 09:40:16 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 fea80f40f7 fix(kvb-cleanup): hand sandbox worktree removal to the operator
An agent sandbox mounts every .git/worktrees/<id> read-only, so git worktree remove unregisters the worktree and then fails with EBUSY; container-owned files fail with EACCES. The worktree rules now forbid removal inside the sandbox and hand the operator the ownership count, chown and removal command in one message. A dirty worktree is a candidate only when all its changed and untracked content is preserved at a branch tip, and an untagged image with RepoDigests counts as a medium-risk digest pull. Both files were trimmed so the skill text does not grow.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 09:34:03 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 6b4a8aca50 Merge branch 'feature/agent-orchestrator/tree'
🧪 Test / 🧪 Lock + lint (push) Canceled after 0s
🎯 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 09:20:41 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 db3c5264f2 Merge branch 'feature/agent-orchestrator/tree'
🎯 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:52:21 +00:00
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
Kevin Veen-BirkenbachandClaude Opus 5.5 9ef2fb6327 Merge branch 'feature/agent-orchestrator/surface'
🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 101 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 87
🚦 Gate: ⚠️ cost 101 % > 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:42:31 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 8f50874afb test(watch): pin the thresholds in the invalid-state block hook test
test_the_block_hook_reads_a_state_that_is_no_object_as_none measured the real disk under $TMPDIR against the default limits, so it failed with exit 2 whenever the host had less than 40 GB free or a falling trend. It now sets every threshold to 0 like its neighbours and proves only that a state file that is no object reads as none.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:41:53 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 de574d5c3d fix(orchestrator): ship no bytecode cache from the repository into the install
install copied the gitignored __pycache__ directories of the sandbox-writable tree into ~/.local/share/kvb-orchestrator with cp -a. Python loads a cached .pyc whose header matches the source mtime and size, and cp -a keeps the mtime, so a .pyc planted by a sandboxed agent would have run in the host runner and PermissionRequest hook without showing in any diff. install now removes every __pycache__ right after the copy; the compose build reads the same cleaned directory.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:40:46 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 6f940b4415 feat(orchestrator): rename kvb-hub to Agent Orchestrator with the phase 2 surface
Surface (requirement 001 phase 2): navigation Stream (Inbox, Regeln) / Agents / Artifacts with keys 1-3 and hash routes (#graph and #tree redirect), header with title, command palette and settings only, server-side badges (unsnoozed inbox, beats within 900 s, open jobs), a Mayday banner with "aufheben", four stream chips with agent:/art:/seit: search, Filter and time menus, the inbox in main, an agents toolbar (Funk an alle with Newsletter, Broadcast, Pan-Pan, Security, Mayday; Priorität; Tag/Nacht), Post or Call in the agent panel, Ambiente in the settings and read-aloud over the seven classes with Mayday locked.

Rename (decision 9): hub/ is orchestrator/, the package kvb_orchestrator, the unit kvb-orchestrator-runner.service, scripts/orchestrator.sh, environment KVB_ORCHESTRATOR_*, make targets orchestrator-*, the MCP server, image and compose project kvb-orchestrator, the guard header X-Kvb-Orchestrator and the message marker [kvb-orchestrator msg <id>]. install runs migrate first: it stops the old container, removes its image and code, moves state, approvals and pinned keys, swaps the MCP registration, the PermissionRequest hook and the runner unit, and changes nothing on a second run (scratch-HOME test with stub docker, claude and systemctl). Saved views fall back to the old localStorage key once.

MEDUSA acceptances carried to the renamed paths: 82 rows whose lines are unchanged (reason prefixed "renamed from <old path>, line unchanged"), and 7 rows whose text changed only in the product name, the package name, a temp prefix or a new class argument, each upheld by one kvb-think-criticize skeptic (approval.py:60, messaging.py:53, server.py:56, tests/hub_support.py:64, passkey.py:205, page-agent.js:75, language.py:46).

Cost 101 %: the longer name adds 15 tokens of skill text; the operator approved it with the commit.

🎯 Fix: 92 %
💥 Break: 4 %
💸 Cost: 101 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 87
🚦 Gate: ⚠️ cost 101 % > 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 22:20:06 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 54f3725aa1 fix(skills): fit the listing budget with kvb-git-diff
kvb-git-diff pushed the skill descriptions to 1517 of 1500 words. Its own description and those of kvb-git-commit-next, kvb-ci-triage and kvb-code-tune are shorter now, so the total is 1498.

🎯 Fix: 95 %
💥 Break: 2 %
💸 Cost: 99 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 94
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:56:49 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 5417ca33b4 fix(hub): pad short passkey coordinates and name the KeePassXC localhost switch
KeePassXC 2.7.12 drops leading zero bytes of the P-256 x and y coordinates, so about one key in 128 failed registration. p256_point now left-pads 1 to 32 bytes and still rejects empty, oversized, out-of-field and off-curve points. KeePassXC also refuses passkeys on http://localhost until its Allow using localhost with passkeys option is on; the passkey page now says where to tick it. The zero-led test key search is capped at 4096 tries.

🎯 Fix: 92 %
💥 Break: 3 %
💸 Cost: 396 %
⏱️ Time: 147 %
🛡️ Security: 100 %
🏆 Score: 15
🚦 Gate: ⚠️ cost 396 % > 100, time 147 % > 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:46:40 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 249265cf1b fix(hub): give the voice and whisper directories the search bit
ADD --chmod=0644 also set mode 0644 on /opt/voices and /opt/whisper, so the non-root hub found no voice and no whisper.cpp and the radio stayed silent. The settings page and the 503 replies now name the missing piece.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:46:40 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 c69a496c86 fix(hub): never lazy-fetch a missing object in the worktree scan
Agents write the repos the tree view scans. A repo with remote.<name>.promisor=true and a ref to a missing object made for-each-ref fetch it, and that fetch ran the repo's remote.<name>.uploadpack through sh inside the hub container. The scan's git env now sets GIT_NO_LAZY_FETCH=1 (the image ships git 2.47, which honours it); the new test proves no uploadpack runs.

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

```
/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
Kevin Veen-BirkenbachandClaude Opus 5.5 afd96fcd72 fix(hub): close seven bugs found in the refactor review
The PermissionRequest hook posted asks cut to the hub's field limits, so a signature covered less than Claude Code would run; it now posts nothing it cannot sign in full and leaves the decision to Claude Code with a reason on stderr. fields.seconds names the field the caller used in its error. Serving a stored page maps an OSError to 500 instead of a dropped connection. The natural-language line marks the register as seen before it builds the inbox, like the page does. do_POST runs every header guard (host, origin, client, content type, MCP) before it reads the body, so a refused request never costs its upload. The runner's reader closes stdout at EOF, because a close from the executing thread blocked on the buffer lock and could SIGPIPE a command still running. The page title counts only inbox items that are not snoozed.

Committed on the operator's word despite the gate: cost and time are estimates under 98 % other load and include eight new test cases.

🎯 Fix: 88 %
💥 Break: 3 %
💸 Cost: 106 %
⏱️ Time: 113 %
🛡️ Security: 100 %
🏆 Score: 71
🚦 Gate: ⛔ fix 88 % < 90; cost 106 % > 100, time 113 % > 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
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
Kevin Veen-BirkenbachandClaude Opus 5.5 305ccdb1c2 Merge branch 'feature/agent-orchestrator/radio'
🎯 Fix: 90 %
💥 Break: 5 %
💸 Cost: 104 %
⏱️ Time: 100 %
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 82
🚦 Gate: ⚠️ cost 104 % > 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:45:06 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 367fe56dd4 feat(kvb-org-radio): split radio and post, add kvb-org-update
The radio (call, broadcast, security, pan-pan, mayday) reaches its addressee at the next tool step and wakes it. The post (post with --to, newsletter without, call receipts) arrives through a new Stop hook when the turn ends and never wakes. Legacy rows map onto the two channels and send direct stays as an alias. A PreToolUse hook holds the mayday's radio silence for writing tools and Bash. Only the sender, helpers who join with radio.py assist, bare radio.py calls and the config's allowed commands pass, and any shell operator or parse error blocks. kvb-org-update announces changed skills as one newsletter. Callers moved to send call --to.

🎯 Fix: 90 %
💥 Break: 5 %
💸 Cost: 104 %
⏱️ Time: 100 %
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 82
🚦 Gate: ⚠️ cost 104 % > 100: ask the operator before the commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:08:10 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 50315a8bdb docs(requirements): add 001 Agent Orchestrator and 002 Cosmos
001 fixes the radio and post model, the orchestrator surface and rename, the pkgmgr branch tree with gc states and delete-by-click, the Agents and Artifacts views with 3D, Bootstrap Icons and kvb-org-update. 002 plans the cosmos extraction as an Infinito.Nexus custom role after 001.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:15:59 +00:00
Kevin Veen-BirkenbachandClaude Opus 5.5 5cb5da81ec style(kvb-org-organize): drop the hardening bundles' rationale comments
🧪 Test / 🧪 Lock + lint (push) Canceled after 0s
The symlink, FIFO and tokenizer reasons live in the commit messages of
the bundles that added them, so the module docstrings of nofollow.py and
test_nofollow.py and three rationale lines in watch.py and queue.py go.
watch.py gets back the space after ROOT = that the PATH bundle lost.

🎯 Fix: 98 %
💥 Break: 1 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 97
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

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