checkpoint: multi-repo Gitea ingestion, per-project repos, rotation anchor_sha fix

Pre-existing uncommitted work found at session start: rotation now emits
anchor_sha on TaskReleased (previously silently dropped by store.Append
validation), multi-repo Gitea provider support, per-project git worktree
roots, and associated test coverage. Committing as a checkpoint before
starting remediation work tracked in AUDIT.md.
This commit is contained in:
kami
2026-07-27 18:15:02 +04:00
parent 325c684eb0
commit ce6f02f9e6
31 changed files with 2717 additions and 320 deletions
+66
View File
@@ -0,0 +1,66 @@
{
"projects": [
{
"id": "correx",
"machine_affinity": ["mainframe"],
"repo": "/var/lib/orchestra/repos/correx.git",
"worktree_root": "/var/lib/orchestra/worktrees/correx"
},
{
"id": "maven",
"machine_affinity": ["mainframe", "satellite"]
}
],
"machines": [
{
"id": "mainframe",
"address": "10.0.0.10:9145"
},
{
"id": "satellite",
"address": "10.0.0.11:9145"
}
],
"herdrs": [
{
"id": "mainframe-claude-1",
"machine_id": "mainframe",
"harness": "claude",
"protocol": "1",
"capabilities": ["code", "review"],
"concurrency": 2,
"quota_limit_5h": 50,
"quota_limit_weekly": 500
},
{
"id": "satellite-claude-1",
"machine_id": "satellite",
"address": "10.0.0.11:9245",
"harness": "claude",
"protocol": "1",
"capabilities": ["code"],
"concurrency": 1,
"quota_limit_5h": 20,
"quota_limit_weekly": 200
},
{
"id": "mainframe-codex-1",
"machine_id": "mainframe",
"harness": "codex",
"protocol": "1",
"capabilities": ["code"],
"concurrency": 1,
"quota_limit_weekly": 300
},
{
"id": "satellite-opencode-1",
"machine_id": "satellite",
"address": "10.0.0.11:9345",
"harness": "opencode",
"protocol": "1",
"capabilities": ["code", "review"],
"concurrency": 1,
"quota_limit_weekly": 300
}
]
}
+78
View File
@@ -0,0 +1,78 @@
// Annotated reference for config.example.json (registry.Config, internal/registry/registry.go).
// This file is NOT valid JSON (it has comments) and is not loaded by orchestra —
// it exists purely to document fields. Copy config.example.json, not this file.
{
// Static project topology. One entry per project the fleet routes tasks for.
"projects": [
{
"id": "correx", // Project id; tasks/events are tagged with this.
"machine_affinity": ["mainframe"], // Machine ids (below) this project may run on.
// Required — a project with no affinity can't be routed.
"repo": "/var/lib/orchestra/repos/correx.git", // Optional per-project git repo path.
// Overrides the global ORCHESTRA_REPO default.
"worktree_root": "/var/lib/orchestra/worktrees/correx" // Optional per-project worktree dir.
// Overrides global ORCHESTRA_WORKTREE_ROOT.
},
{
"id": "maven",
"machine_affinity": ["mainframe", "satellite"] // Multiple affinities: routable to either machine.
// repo/worktree_root omitted here: falls back to the deployment's global default.
}
],
// Physical/logical machines in the fleet. herdrs.machine_id below must reference one of these.
"machines": [
{ "id": "mainframe", "address": "10.0.0.10:9145" }, // address: host:port this machine's orchestra API listens on.
{ "id": "satellite", "address": "10.0.0.11:9145" }
],
// Herdrs: individual harness worker slots that execute tasks.
"herdrs": [
{
"id": "mainframe-claude-1", // Unique herdr id.
"machine_id": "mainframe", // Which machine (above) this herdr runs on.
// "address" omitted: falls back to the parent machine's address (used here since
// this herdr's harness listens on the machine's default port).
"harness": "claude", // Harness adapter to use: "claude" | "codex" | "opencode".
"protocol": "1", // Herdr wire protocol version. Falls back to
// ORCHESTRA_HERDR_PROTOCOL if omitted.
"capabilities": ["code", "review"], // Task capability tags this herdr can accept.
"concurrency": 2, // Max simultaneous sessions this herdr will run.
"quota_limit_5h": 50, // Rolling 5-hour usage quota (harness-specific units).
"quota_limit_weekly": 500 // Rolling weekly usage quota.
// "quota_limit" (deprecated): if set without quota_limit_5h, treated as weekly-only,
// to preserve old configs' historical meaning without inventing a 5h cap.
},
{
"id": "satellite-claude-1",
"machine_id": "satellite",
"address": "10.0.0.11:9245", // Explicit override: this herdr listens on a non-default
// port on its machine (e.g. multiple herdrs per machine).
"harness": "claude",
"protocol": "1",
"capabilities": ["code"],
"concurrency": 1,
"quota_limit_5h": 20,
"quota_limit_weekly": 200
},
{
"id": "mainframe-codex-1",
"machine_id": "mainframe",
"harness": "codex", // OpenAI Codex CLI harness adapter.
"protocol": "1",
"capabilities": ["code"],
"concurrency": 1,
"quota_limit_weekly": 300 // codex has no separate 5h window tracked here; weekly only.
},
{
"id": "satellite-opencode-1",
"machine_id": "satellite",
"address": "10.0.0.11:9345",
"harness": "opencode", // OpenCode CLI harness adapter.
"protocol": "1",
"capabilities": ["code", "review"],
"concurrency": 1,
"quota_limit_weekly": 300
}
]
}
+18
View File
@@ -0,0 +1,18 @@
[
{
"project": "correx",
"base_url": "https://gitea.example.internal",
"owner": "kami",
"repo": "correx",
"token": "REPLACE_ME",
"webhook_secret": "REPLACE_ME"
},
{
"project": "maven",
"base_url": "https://gitea.example.internal",
"owner": "kami",
"repo": "maven",
"token": "REPLACE_ME",
"webhook_secret": "REPLACE_ME"
}
]
+69
View File
@@ -0,0 +1,69 @@
# Copy to /etc/orchestra/orchestra.env (chmod 600, owned by the orchestra
# user) and fill in the values you need. Referenced by orchestra.service via
# EnvironmentFile=. Every var below is read directly from os.Getenv in
# cmd/orchestra/main.go and the packages it wires up — grep ORCHESTRA_ in the
# repo if this list ever needs re-deriving.
# --- Core ---
ORCHESTRA_DATA=/var/lib/orchestra/data
ORCHESTRA_PORT=9145
# Static project/machine/herdr topology (registry.Load). Required for
# routing across more than one machine; validated at startup.
ORCHESTRA_CONFIG=/etc/orchestra/config.json
# --- Git worktrees (global default; per-project repo/worktree_root in
# ORCHESTRA_CONFIG overrides this per project — see registry.Project) ---
ORCHESTRA_REPO=/var/lib/orchestra/repo.git
ORCHESTRA_WORKTREE_ROOT=/var/lib/orchestra/worktrees
# Protocol version fallback for herdrs that don't set "protocol" in
# ORCHESTRA_CONFIG. Prefer setting it per-herdr in the config; only use this
# if every herdr on the fleet truly matches.
#ORCHESTRA_HERDR_PROTOCOL=1
# Hard rotation occupancy threshold (0 < x < 1). Default 0.75 if unset/invalid.
ORCHESTRA_OCCUPANCY_HARD=0.75
# --- Providers ---
# Local JSONL task ingestion (baseline adapter).
#ORCHESTRA_JSONL=/var/lib/orchestra/tasks.jsonl
# Gitea issue ingestion + terminal-state reflection.
#
# Multiple repos (one per project) — preferred if you have more than one
# Gitea-backed project. Points at a JSON array of
# {project,base_url,owner,repo,token,webhook_secret}; project is the
# registry project id ingested tasks are tagged with. Each source gets its
# own webhook path: /v1/providers/gitea/webhook/{project}.
#ORCHESTRA_GITEA_CONFIG=/etc/orchestra/gitea.json
#
# Single repo (legacy) — all four required together. Ignored if
# ORCHESTRA_GITEA_CONFIG is set. Webhook path is the unprefixed
# /v1/providers/gitea/webhook. Ingested tasks are tagged with project =
# ORCHESTRA_GITEA_REPO.
#ORCHESTRA_GITEA_URL=https://gitea.example.internal
#ORCHESTRA_GITEA_TOKEN=
#ORCHESTRA_GITEA_OWNER=
#ORCHESTRA_GITEA_REPO=
#ORCHESTRA_GITEA_WEBHOOK_SECRET=
# --- Delivery (notify-only surfaces) ---
# Telegram: both required together.
#ORCHESTRA_TELEGRAM_BOT_TOKEN=
#ORCHESTRA_TELEGRAM_CHAT_ID=
# ntfy: topic required, token/url optional (self-hosted ntfy).
#ORCHESTRA_NTFY_TOPIC=
#ORCHESTRA_NTFY_TOKEN=
#ORCHESTRA_NTFY_URL=https://ntfy.sh
# --- Bus authorization tokens (bearer auth per surface; a surface with no
# token set has no auth requirement — set these once you have real clients) ---
#ORCHESTRA_TUI_TOKEN=
#ORCHESTRA_WEB_TOKEN=
#ORCHESTRA_MCP_TOKEN=
#ORCHESTRA_MAVEN_TOKEN=
# Telegram/ntfy tokens above double as their surface auth tokens
# (ORCHESTRA_TELEGRAM_TOKEN is the inbound bearer token if you also expose an
# endpoint they poll, separate from the bot token used to send messages).
#ORCHESTRA_TELEGRAM_TOKEN=
+7 -3
View File
@@ -6,16 +6,20 @@ Wants=network-online.target
[Service]
Type=simple
User=orchestra
Group=orchestra
WorkingDirectory=/var/lib/orchestra
Environment=ORCHESTRA_DATA=/var/lib/orchestra/data
Environment=ORCHESTRA_PORT=9145
EnvironmentFile=/etc/orchestra/orchestra.env
ExecStart=/usr/local/bin/orchestra
Restart=on-failure
RestartSec=5s
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/lib/orchestra
ProtectHome=true
PrivateTmp=true
ReadWritePaths=/var/lib/orchestra
# ORCHESTRA_WORKTREE_ROOT / per-project worktree_root paths and
# ORCHESTRA_REPO must live under one of these, or under /var/lib/orchestra —
# add further ReadWritePaths= lines here if you keep repos elsewhere.
[Install]
WantedBy=multi-user.target
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
repo_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
tmp_bin="$(mktemp)"
trap 'rm -f -- "$tmp_bin"' EXIT
cd "$repo_dir"
echo "Building Orchestra..."
go build -o "$tmp_bin" ./cmd/orchestra
echo "Installing /usr/local/bin/orchestra..."
sudo install -o root -g root -m 0755 "$tmp_bin" /usr/local/bin/orchestra
echo "Restarting orchestra.service..."
sudo systemctl restart orchestra.service
sudo systemctl --no-pager --lines=8 status orchestra.service