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>
This commit is contained in:
1 parent
9ef2fb6327
commit
912a3dc07f
17 files changed
+453
-251
No files matched your search
+69
-5
@@ -2,8 +2,9 @@
|
||||
# Usage: orchestrator.sh install | migrate | check | pin <blob> | runner | hook
|
||||
# install migrate, then copy orchestrator/ to ~/.local/share/kvb-orchestrator,
|
||||
# write its .env, build and start the container, register the MCP
|
||||
# server for Claude Code; re-enable the runner and re-register the
|
||||
# hook when the migration took them over
|
||||
# server for Claude Code, build pkgmgr from the newest signed tag of
|
||||
# PKGMGR_REPO and enable the tree timer; re-enable the runner and
|
||||
# re-register the hook when the migration took them over
|
||||
# migrate move every trace of the earlier name to the kvb-orchestrator names
|
||||
# once: container, image, code, state, approvals, pinned keys, MCP
|
||||
# server, PermissionRequest hook, runner unit; a second run changes
|
||||
@@ -31,6 +32,9 @@ SETTINGS="${HOME}/.claude/settings.json"
|
||||
LOCAL_SETTINGS="${HOME}/.claude/settings.local.json"
|
||||
HOOK_TIMEOUT=110
|
||||
RUNNER="${NAME}-runner.service"
|
||||
TREE_UNIT="${NAME}-tree"
|
||||
TREE_DIR="${HOME}/.local/state/pkgmgr/tree"
|
||||
PKGMGR_REPO="${PKGMGR_REPO:-https://github.com/kevinveenbirkenbach/package-manager.git}"
|
||||
IMAGE="${NAME}:local"
|
||||
FLOOR_GB=15
|
||||
FAILED=0
|
||||
@@ -137,7 +141,7 @@ install_hub() {
|
||||
port="$(port_of "${REPO_ROOT}/orchestrator/.env")"
|
||||
log "skills: starting the Agent Orchestrator on http://localhost:${port}/"
|
||||
rm -rf "${HUB}"
|
||||
mkdir -p "${HUB}" "${STATE}" "${APPROVALS}" "${COUNCIL}" "${HOME}/.claude/projects" "${HOME}/Repositories" "${HOME}/.local/share/worktrees" "${UNITS}" "${APPROVERS}"
|
||||
mkdir -p "${HUB}" "${STATE}" "${APPROVALS}" "${COUNCIL}" "${HOME}/.claude/projects" "${TREE_DIR}" "${UNITS}" "${APPROVERS}"
|
||||
chmod 700 "${APPROVERS}"
|
||||
cp -a "${REPO_ROOT}/orchestrator/." "${HUB}/"
|
||||
# Python loads a cached .pyc whose header matches the source mtime and size, and cp -a keeps the mtime: one planted in the sandbox-writable tree would run on the host
|
||||
@@ -145,8 +149,8 @@ install_hub() {
|
||||
chmod +x "${HUB}/client/permission_hook.py" "${HUB}/client/runner.py" "${HUB}/client/approval.py"
|
||||
log "skills: the PermissionRequest hook client is ${HUB}/client/permission_hook.py"
|
||||
log "skills: approvals need a pinned passkey: settings, 'Passkey registrieren', then 'make orchestrator-pin CREDENTIAL=<blob>'; until then every executor refuses"
|
||||
printf 'KVB_ORCHESTRATOR_USER=%s:%s\nKVB_ORCHESTRATOR_STATE=%s\nKVB_ORCHESTRATOR_COUNCIL=%s\nKVB_ORCHESTRATOR_SKILLS=%s\nKVB_ORCHESTRATOR_PROJECTS=%s\nKVB_ORCHESTRATOR_REPOS=%s\nKVB_ORCHESTRATOR_WORKTREES=%s\nKVB_ORCHESTRATOR_TIMERS=%s\nKVB_ORCHESTRATOR_APPROVERS=%s\n' \
|
||||
"$(id -u)" "$(id -g)" "${STATE}" "${COUNCIL}" "${HOME}/.claude/skills" "${HOME}/.claude/projects" "${HOME}/Repositories" "${HOME}/.local/share/worktrees" "${UNITS}" "${APPROVERS}" >>"${HUB}/.env"
|
||||
printf 'KVB_ORCHESTRATOR_USER=%s:%s\nKVB_ORCHESTRATOR_STATE=%s\nKVB_ORCHESTRATOR_COUNCIL=%s\nKVB_ORCHESTRATOR_SKILLS=%s\nKVB_ORCHESTRATOR_PROJECTS=%s\nKVB_ORCHESTRATOR_TREE=%s\nKVB_ORCHESTRATOR_TIMERS=%s\nKVB_ORCHESTRATOR_APPROVERS=%s\n' \
|
||||
"$(id -u)" "$(id -g)" "${STATE}" "${COUNCIL}" "${HOME}/.claude/skills" "${HOME}/.claude/projects" "${TREE_DIR}" "${UNITS}" "${APPROVERS}" >>"${HUB}/.env"
|
||||
if ! docker compose --file "${HUB}/compose.yml" up -d --build; then
|
||||
warn "skills: docker compose failed; the Agent Orchestrator stays down until 'make orchestrator-install' succeeds."
|
||||
fi
|
||||
@@ -162,9 +166,64 @@ install_hub() {
|
||||
fi
|
||||
}
|
||||
|
||||
# Args: scratch directory. Prints the newest v* tag of PKGMGR_REPO that a fully
|
||||
# trusted key signed and unpacks its tree into <scratch>/src. A fresh clone,
|
||||
# because the local checkout's config is sandbox-writable and git runs its
|
||||
# gpg.program, hooks and fsmonitor on the host.
|
||||
fetch_pkgmgr() {
|
||||
local scratch="$1" tag
|
||||
if ! git clone --quiet --bare --filter=blob:none "${PKGMGR_REPO}" "${scratch}/mirror"; then
|
||||
warn "skills: cloning ${PKGMGR_REPO} failed; the tree timer stays off."
|
||||
return 1
|
||||
fi
|
||||
for tag in $(git -C "${scratch}/mirror" tag --list 'v*' --sort=-v:refname); do
|
||||
if git -C "${scratch}/mirror" -c gpg.minTrustLevel=fully verify-tag "${tag}" &>/dev/null; then
|
||||
mkdir "${scratch}/src"
|
||||
git -C "${scratch}/mirror" archive "${tag}" | tar -x -C "${scratch}/src"
|
||||
printf '%s\n' "${tag}"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
warn "skills: no v* tag of ${PKGMGR_REPO} carries a fully trusted signature; the tree timer stays off."
|
||||
return 1
|
||||
}
|
||||
|
||||
build_pkgmgr() {
|
||||
local scratch tag venv="${HUB}/pkgmgr" built=1
|
||||
scratch="$(mktemp -d)"
|
||||
if tag="$(fetch_pkgmgr "${scratch}")"; then
|
||||
if ! grep -qs '"tree"' "${scratch}/src/src/pkgmgr/cli/parser/branch_cmd.py"; then
|
||||
warn "skills: pkgmgr ${tag} has no 'branch tree'; the tree timer stays off until a release has it."
|
||||
else
|
||||
rm -rf "${venv}"
|
||||
if python3 -m venv "${venv}" && "${venv}/bin/pip" install --quiet "${scratch}/src[keyring]"; then
|
||||
log "skills: built pkgmgr ${tag} into ${venv}"
|
||||
built=0
|
||||
else
|
||||
warn "skills: installing pkgmgr ${tag} into ${venv} failed; the tree timer stays off."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
rm -rf "${scratch}"
|
||||
return "${built}"
|
||||
}
|
||||
|
||||
tree_timer() {
|
||||
if ! has_systemd; then
|
||||
warn "skills: no systemd user session; the pkgmgr tree file stays unwritten."
|
||||
return 0
|
||||
fi
|
||||
build_pkgmgr || return 0
|
||||
cp -f "${HUB}/systemd/${TREE_UNIT}.service" "${HUB}/systemd/${TREE_UNIT}.timer" "${UNITS}/"
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now "${TREE_UNIT}.timer"
|
||||
log "skills: ${TREE_UNIT}.timer writes ${TREE_DIR}/branch-tree.json every 60 s while the Agent Orchestrator page polls"
|
||||
}
|
||||
|
||||
install_all() {
|
||||
migrate || return 0
|
||||
install_hub
|
||||
tree_timer
|
||||
if ((TOOK_RUNNER)); then runner || true; fi
|
||||
if ((TOOK_HOOK)); then
|
||||
if [[ -f "${HUB}/client/permission_hook.py" ]]; then hook; else warn "skills: run 'make orchestrator-hook' once the Agent Orchestrator is installed"; fi
|
||||
@@ -223,6 +282,11 @@ check() {
|
||||
else
|
||||
todo "runner off: make orchestrator-runner"
|
||||
fi
|
||||
if [[ "$(systemctl --user is-active "${TREE_UNIT}.timer" 2>/dev/null || true)" == "active" ]]; then
|
||||
ok "${TREE_UNIT}.timer active"
|
||||
else
|
||||
todo "${TREE_UNIT}.timer off: make orchestrator-install with a signed pkgmgr tag that has 'branch tree'"
|
||||
fi
|
||||
free="$(df --output=avail -BG / | tail -1 | tr -dc '0-9')"
|
||||
if ((free >= FLOOR_GB)); then ok "/ has ${free} GB free"; else fail "/ has ${free} GB free, floor ${FLOOR_GB} GB"; fi
|
||||
return "${FAILED}"
|
||||
|
||||
Reference in new issue
Block a user