Files
orchestra/cmd/orchestra/main.go
T
2026-07-30 14:37:34 +04:00

1368 lines
49 KiB
Go

package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net"
"net/http"
"orchestra/internal/admin"
"orchestra/internal/authz"
"orchestra/internal/buildinfo"
"orchestra/internal/delivery"
"orchestra/internal/domain"
"orchestra/internal/federation"
"orchestra/internal/herdr"
"orchestra/internal/operations"
"orchestra/internal/orchestrator"
"orchestra/internal/provider"
"orchestra/internal/registry"
"orchestra/internal/router"
"orchestra/internal/store"
"orchestra/internal/ui"
"orchestra/internal/webui"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
func id() string { return domain.NewID() }
const defaultHerdrPort = "9245"
func herdrAddress(rr registry.Registry, h registry.Herdr) string {
if h.Address != "" {
return h.Address
}
m, ok := rr.Machine(h.MachineID)
if !ok {
return ""
}
host, _, err := net.SplitHostPort(m.Address)
if err != nil {
return m.Address
}
return net.JoinHostPort(host, defaultHerdrPort)
}
type federatedAvailability struct {
base router.Availability
workers *federation.Registry
localMachine string
}
// federatedReachability keeps the legacy TCP probe for local herdrs, while
// avoiding a coordinator-side probe of a remote worker's herdr socket. A
// remote harness is reachable precisely when its worker is registered (as
// enforced by federatedAvailability); probing its raw herdr endpoint here
// would reintroduce the cross-machine Design A dependency.
type federatedReachability struct {
base registry.Reachability
remote map[string]bool
}
func (r federatedReachability) Reachable(address string, timeout time.Duration) bool {
if r.remote[address] {
return true
}
return r.base.Reachable(address, timeout)
}
func remoteHerdrAddresses(rr registry.Registry, localMachine string) map[string]bool {
remote := map[string]bool{}
for _, h := range rr.Herdrs() {
if h.MachineID != localMachine {
remote[herdrAddress(rr, h)] = true
}
}
return remote
}
// coordinatorOwnsHerdr identifies the only herdr sockets the coordinator may
// probe or adapt. In federation mode a remote pane belongs to its worker;
// reaching into that machine would turn a worker-owned health signal back
// into a misleading coordinator TCP result.
func coordinatorOwnsHerdr(h registry.Herdr, localMachine string) bool {
return localMachine == "" || h.MachineID == localMachine
}
func (a federatedAvailability) Available(h registry.Herdr) bool {
if a.base != nil && !a.base.Available(h) {
return false
}
if a.localMachine == "" || h.MachineID == a.localMachine {
return true
}
return a.workers.Available(h.ID)
}
func (a federatedAvailability) Supports(h registry.Herdr, project string) bool {
// Locally-owned herdrs keep their static registry/project affinity. A
// remote worker must additionally prove it has a local checkout for the
// project before the router can offer it a lease.
if a.localMachine == "" || h.MachineID == a.localMachine {
return true
}
return a.workers.Supports(h.ID, project)
}
func validateLocalMachine(rr registry.Registry, localMachine string) error {
machines := rr.Machines()
if len(machines) <= 1 {
return nil
}
if localMachine == "" {
return fmt.Errorf("ORCHESTRA_MACHINE_ID is required for a multi-machine registry; refusing unsafe remote-herdr coordination")
}
if _, ok := rr.Machine(localMachine); !ok {
return fmt.Errorf("ORCHESTRA_MACHINE_ID %q is not in the registry", localMachine)
}
return nil
}
type harnessCompletion struct {
store *store.Store
route func(domain.Event) error
token string
}
func (h harnessCompletion) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if h.token != "" && r.Header.Get("Authorization") != "Bearer "+h.token {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
var p struct {
TaskID string `json:"task_id"`
WorkerID string `json:"worker_id"`
LeaseEpoch string `json:"lease_epoch"`
Harness string `json:"harness"`
TranscriptPath string `json:"transcript_path"`
Report string `json:"report"`
}
if json.NewDecoder(r.Body).Decode(&p) != nil || p.TaskID == "" || p.WorkerID == "" || p.LeaseEpoch == "" || p.Report == "" || p.TranscriptPath == "" {
http.Error(w, "task_id, worker_id, lease_epoch, transcript_path, and report are required", http.StatusBadRequest)
return
}
t, ok := h.store.Task(p.TaskID)
if !ok {
http.Error(w, "task not found", http.StatusNotFound)
return
}
if t.State != domain.StateLeased || t.Lease == nil || t.Lease.HarnessID != p.WorkerID || t.Lease.Epoch != p.LeaseEpoch {
http.Error(w, "lease not owned", http.StatusConflict)
return
}
var usage herdr.Usage
var err error
switch p.Harness {
case "codex":
usage, err = herdr.CodexUsage(p.TranscriptPath)
case "opencode":
usage, err = herdr.OpenCodeUsage(p.TranscriptPath)
case "", "claude":
usage, err = herdr.ClaudeUsage(p.TranscriptPath)
default:
http.Error(w, "unknown harness: "+p.Harness, http.StatusBadRequest)
return
}
if err != nil {
http.Error(w, "reading transcript: "+err.Error(), http.StatusBadRequest)
return
}
ref, err := h.store.PutArtifact([]byte(p.Report))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
payload, _ := json.Marshal(map[string]any{"report_ref": ref, "harness_id": p.WorkerID, "lease_epoch": p.LeaseEpoch, "expected_version": t.Version, "receipt": map[string]any{
"input_tokens": usage.Input, "cache_read_tokens": usage.CacheRead,
"cache_write_tokens": usage.CacheWrite, "output_tokens": usage.Output, "numerator": usage.Numerator(),
}})
e := domain.Event{ID: id(), Type: "TaskCompleted", TaskID: p.TaskID, Version: t.Version + 1, Payload: payload, Surface: string(authz.System)}
if err := h.store.Append(e); err != nil {
http.Error(w, err.Error(), http.StatusConflict)
return
}
if h.route != nil {
if err := h.route(e); err != nil {
log.Printf("route task: %v", err)
}
}
if t.Lease != nil && t.Lease.HarnessID != "" {
qp, _ := json.Marshal(map[string]any{"harness_id": t.Lease.HarnessID, "consumed": float64(usage.Numerator())})
if err := h.store.Append(domain.Event{ID: id(), Type: "QuotaReported", TaskID: "system", Payload: qp, Surface: string(authz.System)}); err != nil {
log.Printf("quota report: %v", err)
}
}
json.NewEncoder(w).Encode(e)
}
func main() {
dir := os.Getenv("ORCHESTRA_DATA")
if dir == "" {
dir = "./data"
}
s, err := store.Open(dir)
if err != nil {
log.Fatal(err)
}
var rr registry.Registry
var rt *router.Router
var coordinator *orchestrator.Coordinator
localMachine := os.Getenv("ORCHESTRA_MACHINE_ID")
workers := &federation.Registry{AdmitToken: os.Getenv("ORCHESTRA_FEDERATION_ADMIT_TOKEN"), StatePath: filepath.Join(dir, "federation-state.json")}
if err := workers.Load(); err != nil {
log.Fatalf("load federation state: %v", err)
}
if config := os.Getenv("ORCHESTRA_CONFIG"); config != "" {
if rr, err = registry.Load(config); err != nil {
log.Fatalf("load orchestra config: %v", err)
}
if err := validateLocalMachine(rr, localMachine); err != nil {
log.Fatal(err)
}
reachability := registry.Reachability(registry.TCPReachability{})
if localMachine != "" {
reachability = federatedReachability{base: reachability, remote: remoteHerdrAddresses(rr, localMachine)}
}
rt = &router.Router{Store: s, Registry: rr, Reachability: reachability, Timeout: time.Second, Retry: router.RetryPolicy{MaxAttempts: 3, Backoff: time.Minute}}
limits := map[string]router.QuotaWindowLimits{}
for _, h := range rr.Herdrs() {
w := router.QuotaWindowLimits{FiveHour: h.QuotaLimit5h, Weekly: h.QuotaLimitWeekly}
if w.Weekly <= 0 && h.QuotaLimit > 0 {
// Back-compat: the old single-window field meant weekly.
w.Weekly = h.QuotaLimit
}
if w.FiveHour > 0 || w.Weekly > 0 {
limits[h.ID] = w
}
}
if len(limits) > 0 {
rt.Availability = router.QuotaAvailability{Store: s, Limits: limits}
}
if localMachine != "" {
rt.Availability = federatedAvailability{base: rt.Availability, workers: workers, localMachine: localMachine}
}
if repo, root := os.Getenv("ORCHESTRA_REPO"), os.Getenv("ORCHESTRA_WORKTREE_ROOT"); repo != "" && root != "" {
adapters := map[string]herdr.Adapter{}
for _, h := range rr.Herdrs() {
if !coordinatorOwnsHerdr(h, localMachine) {
log.Printf("herdr %s is worker-owned on %s; coordinator probe skipped", h.ID, h.MachineID)
continue
}
address := herdrAddress(rr, h)
if address == "" {
continue
}
client := herdr.New(address)
protocol := h.Protocol
if protocol == "" {
protocol = os.Getenv("ORCHESTRA_HERDR_PROTOCOL")
}
if err := client.CheckProtocol(context.Background(), protocol); err != nil {
log.Printf("herdr %s unavailable: %v", h.ID, err)
continue
}
log.Printf("herdr %s reachable at %s (protocol %s, harness %s)", h.ID, address, protocol, h.Harness)
switch h.Harness {
case "claude":
adapters[h.ID] = herdr.Claude(client, 200000, s)
case "opencode":
adapters[h.ID] = herdr.OpenCode(client, 200000, s)
case "codex", "":
adapters[h.ID] = herdr.Codex(client, 200000, s)
default:
log.Printf("herdr %s has unsupported harness %q", h.ID, h.Harness)
}
}
projectRepos := map[string]orchestrator.ProjectRepo{}
for _, p := range rr.Projects() {
if p.Repo != "" && p.WorktreeRoot != "" {
projectRepos[p.ID] = orchestrator.ProjectRepo{Repo: p.Repo, WorktreeRoot: p.WorktreeRoot}
}
}
worktrees := orchestrator.PerProjectGitWorktrees{
Projects: projectRepos,
Default: orchestrator.GitWorktrees{Root: root, Repo: repo},
}
coordinator = &orchestrator.Coordinator{Store: s, StatePath: filepath.Join(dir, "runtime-sessions.json"), Worktrees: worktrees, Adapters: orchestrator.AdapterFactory{Herdrs: adapters}, LocalHerdr: func(id string) bool {
h, ok := rr.Herdr(id)
return ok && coordinatorOwnsHerdr(h, localMachine)
}}
rt.OnLease = func(e domain.Event) error {
// In federated mode the coordinator must never inspect a remote
// checkout. Its worker consumes the router-issued lease event and
// performs all Git/herdr operations on that machine (§2.1).
if localMachine != "" {
var p struct {
HarnessID string `json:"harness_id"`
}
if json.Unmarshal(e.Payload, &p) == nil {
if h, ok := rr.Herdr(p.HarnessID); ok && h.MachineID != localMachine {
return nil
}
}
}
return coordinator.Start(context.Background(), e)
}
hard := 0.75
if v, parseErr := strconv.ParseFloat(os.Getenv("ORCHESTRA_OCCUPANCY_HARD"), 64); parseErr == nil && v > 0 && v < 1 {
hard = v
}
if v, parseErr := strconv.ParseFloat(os.Getenv("ORCHESTRA_OCCUPANCY_SOFT"), 64); parseErr == nil && v > 0 && v < 1 {
coordinator.Soft = v
}
go func() {
if monitorErr := coordinator.Monitor(context.Background(), hard, 30*time.Second); monitorErr != nil {
log.Printf("orchestrator monitor: %v", monitorErr)
}
}()
}
}
mux := http.NewServeMux()
// B18: the UI is a full control plane — it can create tasks, release or
// complete them, and inject approval keystrokes into live panes. It has
// one explicit operator identity and is never enabled by a missing env var.
webCredentials := authz.WebCredentials{Username: os.Getenv("ORCHESTRA_WEB_USERNAME"), PasswordHash: os.Getenv("ORCHESTRA_WEB_PASSWORD_HASH")}
if err := webCredentials.Validate(); err != nil {
log.Fatalf("web login configuration: %v", err)
}
sessions := &authz.Sessions{}
// A browser login exchanges verified credentials for an HttpOnly cookie.
mux.HandleFunc("/v1/ui/session", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodDelete {
// Expire the browser credential even if it is already absent or stale.
// The client never has access to the HttpOnly value, so this is the
// only reliable way for an operator to end a browser session.
if cookie, err := r.Cookie(authz.SessionCookie); err == nil {
sessions.Revoke(cookie.Value)
}
http.SetCookie(w, &http.Cookie{Name: authz.SessionCookie, Value: "", Path: "/", MaxAge: -1, HttpOnly: true, SameSite: http.SameSiteStrictMode, Secure: os.Getenv("ORCHESTRA_UI_INSECURE_COOKIE") == ""})
w.WriteHeader(http.StatusNoContent)
return
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
var body struct {
Username string `json:"username"`
Password string `json:"password"`
}
decoder := json.NewDecoder(http.MaxBytesReader(w, r.Body, 4096))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&body); err != nil || !webCredentials.Authenticate(body.Username, body.Password) {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
}
v, err := sessions.Issue()
if err != nil {
http.Error(w, "session unavailable", http.StatusInternalServerError)
return
}
http.SetCookie(w, &http.Cookie{
Name: authz.SessionCookie, Value: v, Path: "/",
HttpOnly: true, SameSite: http.SameSiteStrictMode,
Secure: os.Getenv("ORCHESTRA_UI_INSECURE_COOKIE") == "",
MaxAge: int((12 * time.Hour).Seconds()),
})
w.WriteHeader(http.StatusNoContent)
})
// Browser-specific endpoints intentionally present a joined read model;
// raw lifecycle endpoints below remain stable for workers and harnesses.
mux.Handle("/v1/ui/", ui.Server{Store: s, Workers: workers, Coordinator: coordinator, Route: func(e domain.Event) error {
if rt == nil {
return nil
}
_, err := rt.HandleEvent(e)
return err
}}.Handler())
mux.Handle("/", webui.Handler())
// A missed heartbeat is not relinquishment. Releasing here used to lease
// the same task to a successor while the old pane was still running. The
// authoritative lease timer performs the only automatic reassignment.
workers.OnOffline = func(w federation.Worker) { log.Printf("worker %s offline; retaining leases until expiry", w.ID) }
go func() {
ticker := time.NewTicker(15 * time.Second)
defer ticker.Stop()
for range ticker.C {
_ = workers.Snapshot()
}
}()
providerHealth := map[string]*provider.Supervisor{}
surface := func(r *http.Request) authz.Surface {
v := authz.ParseSurface(r.Header.Get("X-Orchestra-Surface"))
if v == "" || v == authz.System {
// System means "the plane itself, in-process" and is always
// FullControl with no token gate (AUDIT.md B8) — it must never
// be constructible from an HTTP request, or any LAN caller
// declaring this header gets unauthenticated full control.
return authz.Web
}
return v
}
mux.HandleFunc("/v1/tasks", func(w http.ResponseWriter, r *http.Request) {
if wid := r.Header.Get("X-Orchestra-Worker"); wid != "" {
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if err := workers.Authenticate(wid, token); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
}
if r.Method == "GET" {
json.NewEncoder(w).Encode(s.Tasks())
return
}
if r.Method != "POST" {
http.Error(w, "method not allowed", 405)
return
}
var p map[string]any
if json.NewDecoder(r.Body).Decode(&p) != nil {
http.Error(w, "invalid json", 400)
return
}
// Make the selected project's deterministic gate part of the immutable
// task contract before any worker can create TASK.md. A caller may
// override it only when it has deliberately supplied a task-specific
// gate; routing never asks a harness to choose one.
if _, set := p["quality_gate"]; !set {
if projectID, _ := p["project"].(string); projectID != "" {
if project, ok := rr.Project(projectID); ok && project.QualityGate != "" {
p["quality_gate"] = project.QualityGate
}
}
}
b, _ := json.Marshal(p)
e := domain.Event{ID: id(), Type: "TaskCreated", TaskID: id(), Version: 1, Payload: b, Surface: string(surface(r))}
if err := s.Append(e); err != nil {
if errors.Is(err, domain.ErrDuplicate) {
// (source, external_id) was already ingested. Nothing was
// appended; report the existing task idempotently rather
// than fabricating a 201 with an unrelated event (S6).
source, _ := p["source"].(string)
externalID, _ := p["external_id"].(string)
if t, ok := s.TaskBySource(source, externalID); ok {
w.WriteHeader(200)
json.NewEncoder(w).Encode(t)
return
}
}
http.Error(w, err.Error(), 400)
return
}
if rt != nil {
if _, err := rt.HandleEvent(e); err != nil {
log.Printf("route task: %v", err)
}
}
w.WriteHeader(201)
json.NewEncoder(w).Encode(e)
})
mux.HandleFunc("/v1/events", func(w http.ResponseWriter, r *http.Request) {
var n uint64
if x, err := strconv.ParseUint(r.URL.Query().Get("since"), 10, 64); err == nil {
n = x
}
json.NewEncoder(w).Encode(s.Events(n))
})
mux.HandleFunc("/v1/handoffs", func(w http.ResponseWriter, r *http.Request) {
out := make([]domain.Event, 0)
for _, e := range s.Events(0) {
if e.Type == "TaskReleased" {
out = append(out, e)
}
}
json.NewEncoder(w).Encode(out)
})
mux.HandleFunc("/v1/quotas", func(w http.ResponseWriter, r *http.Request) {
out := make([]domain.Event, 0)
for _, e := range s.Events(0) {
if e.Type == "QuotaReported" {
out = append(out, e)
}
}
json.NewEncoder(w).Encode(out)
})
mux.HandleFunc("/v1/artifacts", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if wid := r.Header.Get("X-Orchestra-Worker"); wid != "" {
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if err := workers.Authenticate(wid, token); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
}
// Reports and handoffs are content-addressed evidence. Keep uploads
// bounded because event payloads only carry their resulting hash.
r.Body = http.MaxBytesReader(w, r.Body, 4<<20)
b, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "artifact too large or unreadable", http.StatusRequestEntityTooLarge)
return
}
if len(b) == 0 {
http.Error(w, "artifact is empty", http.StatusBadRequest)
return
}
ref, err := s.PutArtifact(b)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(map[string]string{"ref": ref})
})
// Artifacts are normally write-only to public surfaces. A federation
// worker may read a handoff only after authenticating as the worker that
// will validate it against its own checkout (§2.1, §6.2).
mux.HandleFunc("/v1/artifacts/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
wid := r.Header.Get("X-Orchestra-Worker")
token := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if err := workers.Authenticate(wid, token); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
ref := strings.TrimPrefix(r.URL.Path, "/v1/artifacts/")
if len(ref) != 64 {
http.Error(w, "artifact ref required", http.StatusBadRequest)
return
}
b, err := s.Artifact(ref)
if err != nil {
http.Error(w, "artifact not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(b)
})
// /v1/harness/complete is the automatic TaskCompleted producer (AUDIT.md
// B3): a harness-side hook posts here when the agent has declared the
// task done (see deploy/hooks/orchestra-stop.sh), not on every turn
// boundary. It reads the transcript locally to build an honest receipt —
// same session-file assumption as CLIAdapter.Occupancy — rather than
// trusting a self-reported number.
harnessToken := os.Getenv("ORCHESTRA_HARNESS_TOKEN")
// The unaffiliated harness hook has no durable worker identity or fencing
// epoch, so it cannot safely mutate a leased task. Completion is accepted
// only through the authenticated federation worker endpoint below.
mux.HandleFunc("/v1/harness/complete", func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "legacy harness completion endpoint retired; use worker completion", http.StatusGone)
})
// /v1/harness/turn is the unified turn-decision endpoint (AUDIT.md Phase
// 2 items 1-2): the Face-B stop hook posts here on every ordinary turn
// boundary (report marker absent — /v1/harness/complete covers task
// completion separately) and gets back exactly one of continue /
// prepare_handoff / rotate_now / refuse, per spec §5.3. This replaces
// what would otherwise be separate ad-hoc marker-file conventions per
// decision.
mux.HandleFunc("/v1/harness/turn", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
if harnessToken != "" && r.Header.Get("Authorization") != "Bearer "+harnessToken {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
if coordinator == nil {
http.Error(w, "coordinator not configured", http.StatusServiceUnavailable)
return
}
var p struct {
TaskID string `json:"task_id"`
}
if json.NewDecoder(r.Body).Decode(&p) != nil || p.TaskID == "" {
http.Error(w, "task_id is required", http.StatusBadRequest)
return
}
decision, err := coordinator.TurnDecision(r.Context(), p.TaskID)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
json.NewEncoder(w).Encode(map[string]string{"decision": decision})
})
mux.HandleFunc("/v1/brief", func(w http.ResponseWriter, r *http.Request) {
to := time.Now().UTC()
from := to.Add(-12 * time.Hour)
if v, parseErr := time.Parse(time.RFC3339, r.URL.Query().Get("from")); parseErr == nil {
from = v
}
if v, parseErr := time.Parse(time.RFC3339, r.URL.Query().Get("to")); parseErr == nil {
to = v
}
git := map[string]operations.GitSync{}
for _, p := range rr.Projects() {
if p.Repo == "" {
continue
}
git[p.ID] = operations.GitState(p.Repo)
}
if len(git) == 0 {
if repo := os.Getenv("ORCHESTRA_REPO"); repo != "" {
git["default"] = operations.GitState(repo)
}
}
json.NewEncoder(w).Encode(operations.BuildBrief(s.Events(0), from, to, git))
})
standup := func() (domain.Event, error) {
return operations.GenerateStandupAdvisory(s, time.Now().UTC())
}
mux.HandleFunc("/v1/standup", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
json.NewEncoder(w).Encode(operations.StandupItems(s.Tasks()))
return
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
if err := authz.AuthorizeEvent(surface(r), "StandupAdvisory"); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
e, err := standup()
if err != nil {
http.Error(w, err.Error(), 400)
return
}
json.NewEncoder(w).Encode(e)
})
mux.HandleFunc("/v1/standup/approve", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
if err := authz.AuthorizeEvent(surface(r), "ApprovalGranted"); err != nil {
http.Error(w, err.Error(), 403)
return
}
var p struct {
AdvisoryID string `json:"advisory_id"`
}
if json.NewDecoder(r.Body).Decode(&p) != nil || p.AdvisoryID == "" {
http.Error(w, "advisory_id required", 400)
return
}
b, _ := json.Marshal(map[string]any{"subject_ref": p.AdvisoryID})
e := domain.Event{ID: id(), Type: "ApprovalGranted", TaskID: "system", Version: 0, Payload: b, Surface: string(surface(r))}
if err := s.Append(e); err != nil {
http.Error(w, err.Error(), 400)
return
}
json.NewEncoder(w).Encode(e)
})
mux.HandleFunc("/v1/standup/apply", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
if err := authz.AuthorizeEvent(surface(r), "TaskAmended"); err != nil {
http.Error(w, err.Error(), 403)
return
}
var p struct {
AdvisoryID string `json:"advisory_id"`
}
if json.NewDecoder(r.Body).Decode(&p) != nil || p.AdvisoryID == "" {
http.Error(w, "advisory_id required", 400)
return
}
out, err := operations.ApplyAdvisory(s, p.AdvisoryID)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
json.NewEncoder(w).Encode(out)
})
adminServer := &admin.Server{Store: s, RouterReady: rt != nil, Build: buildinfo.Current(), Providers: providerHealth, Probes: map[string]admin.ProbeFunc{
"router": func() (bool, string) { return rt != nil, "configured router" },
"gitea": func() (bool, string) {
configured := os.Getenv("ORCHESTRA_GITEA_URL") != "" || os.Getenv("ORCHESTRA_GITEA_CONFIG") != ""
if !configured {
return true, "configured provider"
}
for name := range providerHealth {
if name == "gitea" || strings.HasPrefix(name, "gitea:") {
return true, "configured provider"
}
}
return false, "configured provider"
},
"jsonl": func() (bool, string) {
return os.Getenv("ORCHESTRA_JSONL") == "" || providerHealth["jsonl"] != nil, "configured provider"
},
}}
mux.HandleFunc("/metrics", adminServer.Metrics)
mux.HandleFunc("/v1/events/subscribe", adminServer.Subscribe)
mux.HandleFunc("/v1/admin/diagnostics", adminServer.Diagnostics)
mux.HandleFunc("/v1/tasks/", func(w http.ResponseWriter, r *http.Request) {
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if r.Method == http.MethodGet && coordinator != nil && len(parts) == 4 {
taskID, view := parts[2], parts[3]
if view == "health" {
if h, ok := coordinator.MonitorHealth().Sessions[taskID]; ok {
json.NewEncoder(w).Encode(h)
return
}
http.Error(w, "session health not found", 404)
return
}
if view == "capture" {
body, err := coordinator.Capture(r.Context(), taskID, r.URL.Query().Get("source"))
if err != nil {
http.Error(w, err.Error(), 404)
return
}
json.NewEncoder(w).Encode(map[string]string{"task_id": taskID, "source": r.URL.Query().Get("source"), "text": body})
return
}
}
if len(parts) < 4 || len(parts) > 5 || r.Method != "POST" {
http.Error(w, "not found", http.StatusNotFound)
return
}
taskID, action := parts[2], parts[3]
if action == "approval" {
if err := authz.AuthorizeEvent(surface(r), map[bool]string{true: "ApprovalRequested", false: "ApprovalGranted"}[len(parts) == 4]); err != nil && len(parts) == 4 {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if len(parts) == 5 {
typ := "ApprovalGranted"
if parts[4] == "deny" {
typ = "ApprovalDenied"
}
if err := authz.AuthorizeEvent(surface(r), typ); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
if parts[4] != "grant" && parts[4] != "deny" {
http.Error(w, "unknown approval action", 404)
return
}
t, ok := s.Task(taskID)
if !ok {
http.Error(w, "task not found", 404)
return
}
by := r.Header.Get("X-Orchestra-Actor")
if by == "" {
by = "surface"
}
b, _ := json.Marshal(map[string]any{"subject_ref": taskID, "by": by})
e := domain.Event{ID: id(), Type: typ, TaskID: taskID, Version: t.Version + 1, Payload: b, Surface: string(surface(r))}
if err := s.Append(e); err != nil {
http.Error(w, err.Error(), 409)
return
}
json.NewEncoder(w).Encode(e)
return
}
var p struct {
Options []any `json:"options"`
}
if json.NewDecoder(r.Body).Decode(&p) != nil || len(p.Options) == 0 {
http.Error(w, "options required", 400)
return
}
if _, ok := s.Task(taskID); !ok {
http.Error(w, "task not found", 404)
return
}
b, _ := json.Marshal(map[string]any{"subject_ref": taskID, "options": p.Options})
t, _ := s.Task(taskID)
e := domain.Event{ID: id(), Type: "ApprovalRequested", TaskID: taskID, Version: t.Version + 1, Payload: b, Surface: string(surface(r))}
if err := s.Append(e); err != nil {
http.Error(w, err.Error(), 400)
return
}
json.NewEncoder(w).Encode(e)
return
}
var e domain.Event
var err error
actionTypes := map[string]string{"lease": "TaskLeased", "release": "TaskReleased", "complete": "TaskCompleted", "block": "TaskBlocked", "attention": "TaskNeedsAttention"}
if typ, known := actionTypes[action]; known {
if err := authz.AuthorizeEvent(surface(r), typ); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}
}
switch action {
case "lease":
var p struct {
HarnessID string `json:"harness_id"`
TTLSeconds int `json:"ttl_seconds"`
}
if json.NewDecoder(r.Body).Decode(&p) != nil || p.HarnessID == "" {
http.Error(w, "harness_id required", 400)
return
}
if p.TTLSeconds <= 0 {
p.TTLSeconds = 1800
}
e, err = s.Lease(taskID, p.HarnessID, time.Duration(p.TTLSeconds)*time.Second)
case "release", "complete", "block", "attention":
t, ok := s.Task(taskID)
if !ok {
http.Error(w, "task not found", 404)
return
}
types := map[string]string{"release": "TaskReleased", "complete": "TaskCompleted", "block": "TaskBlocked", "attention": "TaskNeedsAttention"}
var p map[string]any
if r.Body == nil || json.NewDecoder(r.Body).Decode(&p) != nil || p == nil {
http.Error(w, "invalid lifecycle payload", http.StatusBadRequest)
return
}
if action == "release" && p["reason"] == nil && p["handoff_ref"] == nil {
http.Error(w, "reason or handoff_ref required", http.StatusBadRequest)
return
}
if (action == "block" || action == "attention") && p["blocker"] == nil {
http.Error(w, "blocker required", http.StatusBadRequest)
return
}
if action == "complete" && p["report_ref"] == nil {
http.Error(w, "report_ref required", http.StatusBadRequest)
return
}
ePayload, _ := json.Marshal(p)
e = domain.Event{ID: id(), Type: types[action], TaskID: taskID, Version: t.Version + 1, Payload: ePayload, Surface: string(surface(r))}
err = s.Append(e)
default:
http.Error(w, "unknown action", 404)
return
}
if err != nil {
http.Error(w, err.Error(), 409)
return
}
if rt != nil {
if _, routeErr := rt.HandleEvent(e); routeErr != nil {
log.Printf("route task: %v", routeErr)
}
}
json.NewEncoder(w).Encode(e)
})
if rt != nil {
go func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for range ticker.C {
// When a coordinator is running, its own Monitor loop (every
// 30s) already calls Store.ExpireLeases and, critically,
// kills the herdr session for whatever it expires (S9: two
// independent expiry loops raced on the same reclaim, and
// since this one runs every second vs. the coordinator's
// 30s, it almost always won — meaning the coordinator's
// ExpireLeases call saw nothing left to expire and its
// session-kill path never ran, silently orphaning panes
// past their lease TTL). Calling ExpireLeases here too would
// just resurrect that race, so leave reclaim to the
// coordinator and only keep retrying pending assignment.
if coordinator != nil {
if _, err := rt.AssignPending(); err != nil {
log.Printf("route expired task: %v", err)
}
continue
}
if _, err := s.ExpireLeases(time.Now()); err != nil {
log.Printf("expire leases: %v", err)
} else if _, err := rt.AssignPending(); err != nil {
log.Printf("route expired task: %v", err)
}
}
}()
}
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ok\n")) })
mux.HandleFunc("/readyz", adminServer.Readiness)
mux.HandleFunc("/v1/providers/health", func(w http.ResponseWriter, r *http.Request) {
out := map[string]provider.Health{}
for name, sup := range providerHealth {
out[name] = sup.Health()
}
json.NewEncoder(w).Encode(out)
})
mux.HandleFunc("/v1/orchestrator/health", func(w http.ResponseWriter, r *http.Request) {
if coordinator == nil {
http.Error(w, "orchestrator unavailable", http.StatusServiceUnavailable)
return
}
json.NewEncoder(w).Encode(coordinator.MonitorHealth())
})
mux.HandleFunc("/v1/federation/workers", func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
json.NewEncoder(w).Encode(workers.Snapshot())
return
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
var registration struct {
federation.Worker
Token string `json:"token"`
}
if json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<10)).Decode(&registration) != nil {
http.Error(w, "invalid worker", 400)
return
}
worker := registration.Worker
worker.Token = registration.Token
if worker.Token == "" {
http.Error(w, "token required", 400)
return
}
admitToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if err := workers.Register(worker, admitToken); err != nil {
status := 400
if err == federation.ErrUnauthorized {
status = 401
}
http.Error(w, err.Error(), status)
return
}
if rt != nil {
if _, err := rt.AssignPending(); err != nil {
log.Printf("route after worker registration: %v", err)
}
}
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(worker)
})
workerAuth := func(r *http.Request) (string, error) {
wid := r.Header.Get("X-Orchestra-Worker")
tok := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if err := workers.Authenticate(wid, tok); err != nil {
return "", err
}
return wid, nil
}
mux.HandleFunc("/v1/federation/commands", func(w http.ResponseWriter, r *http.Request) {
wid, err := workerAuth(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", 405)
return
}
out, err := workers.Commands(wid)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
json.NewEncoder(w).Encode(out)
})
mux.HandleFunc("/v1/federation/commands/", func(w http.ResponseWriter, r *http.Request) {
wid, err := workerAuth(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
var body struct {
Status string `json:"status"`
Message string `json:"message"`
}
if json.NewDecoder(r.Body).Decode(&body) != nil || (body.Status != "acknowledged" && body.Status != "stale" && body.Status != "rejected") {
http.Error(w, "invalid command result", 400)
return
}
commandID := strings.TrimPrefix(r.URL.Path, "/v1/federation/commands/")
command, ok := workers.Command(wid, commandID)
if !ok {
http.Error(w, "command not found", 404)
return
}
if err := workers.CompleteCommand(wid, commandID, body.Status, body.Message); err != nil {
http.Error(w, err.Error(), 409)
return
}
// Audit lifecycle evidence only after the worker reports the herdr
// input was acknowledged; a queued browser click is never an approval.
if body.Status == "acknowledged" {
if t, ok := s.Task(command.TaskID); ok {
typ := "ApprovalGranted"
if command.Kind == "deny_approval" {
typ = "ApprovalDenied"
}
payload, _ := json.Marshal(map[string]any{"subject_ref": command.ID, "pane_id": command.PaneID, "capture_revision": command.CaptureRevision})
e := domain.Event{ID: id(), Type: typ, TaskID: command.TaskID, Version: t.Version + 1, Payload: payload, Surface: string(authz.System)}
if err := s.Append(e); err != nil {
log.Printf("record approval %s: %v", command.ID, err)
}
}
}
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("/v1/federation/events", func(w http.ResponseWriter, r *http.Request) {
wid, err := workerAuth(r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", 405)
return
}
cursor, _ := workers.Cursor(wid)
if q := r.URL.Query().Get("since"); q != "" {
cursor, _ = strconv.ParseUint(q, 10, 64)
}
events := s.Events(cursor)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"cursor": cursor, "events": events})
})
mux.HandleFunc("/v1/federation/events/ack", func(w http.ResponseWriter, r *http.Request) {
wid, err := workerAuth(r)
if err != nil {
http.Error(w, err.Error(), 401)
return
}
if r.Method != http.MethodPost {
http.Error(w, "method not allowed", 405)
return
}
var body struct {
Cursor uint64 `json:"cursor"`
}
if json.NewDecoder(r.Body).Decode(&body) != nil {
http.Error(w, "invalid cursor", 400)
return
}
if err := workers.Ack(wid, body.Cursor); err != nil {
http.Error(w, err.Error(), 409)
return
}
w.WriteHeader(http.StatusNoContent)
})
mux.HandleFunc("/v1/federation/workers/", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || (!strings.HasSuffix(r.URL.Path, "/heartbeat") && !strings.HasSuffix(r.URL.Path, "/renew") && !strings.HasSuffix(r.URL.Path, "/handoff") && !strings.HasSuffix(r.URL.Path, "/pickup") && !strings.HasSuffix(r.URL.Path, "/complete") && !strings.HasSuffix(r.URL.Path, "/captures")) {
http.Error(w, "not found", 404)
return
}
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
if len(parts) != 5 {
http.Error(w, "not found", 404)
return
}
wid, err := workerAuth(r)
if err != nil {
http.Error(w, err.Error(), 401)
return
}
if wid != parts[3] {
http.Error(w, "worker identity mismatch", http.StatusForbidden)
return
}
if strings.HasSuffix(r.URL.Path, "/heartbeat") {
var health federation.WorkerHealth
if err := json.NewDecoder(r.Body).Decode(&health); err != nil && !errors.Is(err, io.EOF) {
http.Error(w, "invalid worker health", 400)
return
}
if err := workers.Heartbeat(parts[3], health); err != nil {
http.Error(w, err.Error(), 404)
return
}
w.WriteHeader(http.StatusNoContent)
return
}
if strings.HasSuffix(r.URL.Path, "/captures") {
var c federation.Capture
if json.NewDecoder(r.Body).Decode(&c) != nil {
http.Error(w, "invalid capture", 400)
return
}
if t, ok := s.Task(c.TaskID); !ok || t.Lease == nil || t.Lease.HarnessID != parts[3] {
http.Error(w, "lease not owned", 409)
return
}
out, err := workers.PutCapture(parts[3], c)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
json.NewEncoder(w).Encode(out)
return
}
var b struct {
TaskID string `json:"task_id"`
TTLSeconds int `json:"ttl_seconds"`
ExpectedVersion int `json:"expected_version"`
HandoffRef string `json:"handoff_ref"`
AnchorSHA string `json:"anchor_sha"`
TransactionID string `json:"transaction_id"`
LeaseVersion int `json:"lease_version"`
LeaseEpoch string `json:"lease_epoch"`
ResultSHA string `json:"result_sha"`
Branch string `json:"branch"`
Remote string `json:"remote"`
Receipt map[string]any `json:"receipt"`
SessionEvidence domain.SessionEvidence `json:"session_evidence"`
}
if json.NewDecoder(r.Body).Decode(&b) != nil || b.TaskID == "" {
http.Error(w, "invalid lease body", 400)
return
}
t, ok := s.Task(b.TaskID)
if !ok {
http.Error(w, "task not found", 404)
return
}
ownedLease := (t.State == domain.StateLeased || t.State == domain.StateNeedsAttention) && t.Lease != nil && t.Lease.HarnessID == parts[3] && t.Lease.Epoch == b.LeaseEpoch
// A response can be lost after the append/fsync. Retrying the exact
// release transaction is therefore a successful no-op, never a second
// TaskReleased event and never a reason to discard the predecessor.
if strings.HasSuffix(r.URL.Path, "/handoff") && t.State == domain.StateQueued && b.TransactionID != "" && b.TransactionID == t.ReleaseTransaction && b.HandoffRef == t.HandoffRef && b.AnchorSHA == t.ReleaseAnchor {
w.WriteHeader(http.StatusNoContent)
return
}
if !ownedLease {
http.Error(w, "lease not owned", 409)
return
}
if strings.HasSuffix(r.URL.Path, "/complete") && b.ExpectedVersion != t.Version {
http.Error(w, "lease version conflict", http.StatusConflict)
return
}
if strings.HasSuffix(r.URL.Path, "/renew") {
ttl := b.TTLSeconds
if ttl == 0 {
ttl = int((30 * time.Minute).Seconds())
}
e, err := s.RenewLease(b.TaskID, parts[3], b.LeaseEpoch, b.ExpectedVersion, time.Duration(ttl)*time.Second)
if err != nil {
http.Error(w, err.Error(), http.StatusConflict)
return
}
json.NewEncoder(w).Encode(e)
return
}
if strings.HasSuffix(r.URL.Path, "/pickup") {
if !ownedLease || b.TransactionID == "" || b.TransactionID != t.ReleaseTransaction || b.HandoffRef != t.HandoffRef || b.AnchorSHA != t.ReleaseAnchor {
http.Error(w, "pickup does not match active release", http.StatusConflict)
return
}
if t.PickupTransaction == b.TransactionID && t.PickupLeaseVersion == b.LeaseVersion {
w.WriteHeader(http.StatusNoContent)
return
}
if b.LeaseVersion != t.Version {
http.Error(w, "lease version conflict", http.StatusConflict)
return
}
p, _ := json.Marshal(map[string]any{"transaction_id": b.TransactionID, "handoff_ref": b.HandoffRef, "anchor_sha": b.AnchorSHA, "harness_id": parts[3], "lease_epoch": b.LeaseEpoch, "lease_version": b.LeaseVersion, "expected_version": t.Version, "session_evidence": b.SessionEvidence})
e := domain.Event{ID: id(), Type: "TaskPickupValidated", TaskID: b.TaskID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)}
if err := s.Append(e); err != nil {
http.Error(w, err.Error(), http.StatusConflict)
return
}
json.NewEncoder(w).Encode(e)
return
}
if strings.HasSuffix(r.URL.Path, "/complete") {
if b.HandoffRef == "" {
http.Error(w, "report_ref required", 400)
return
}
if len(b.ResultSHA) != 40 || b.Branch == "" || b.Remote == "" {
http.Error(w, "verified result_sha, branch, and remote required", 400)
return
}
if b.Receipt == nil {
b.Receipt = map[string]any{}
}
b.Receipt["harness_id"] = parts[3]
if _, ok := b.Receipt["consumed"]; !ok {
b.Receipt["consumed"] = 0
}
p, _ := json.Marshal(map[string]any{"report_ref": b.HandoffRef, "receipt": b.Receipt, "result_sha": b.ResultSHA, "branch": b.Branch, "remote": b.Remote, "harness_id": parts[3], "lease_epoch": b.LeaseEpoch, "expected_version": t.Version, "session_evidence": b.SessionEvidence})
e := domain.Event{ID: id(), Type: "TaskCompleted", TaskID: b.TaskID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)}
if err := s.Append(e); err != nil {
http.Error(w, err.Error(), 409)
return
}
if consumed, ok := b.Receipt["consumed"].(float64); ok && consumed > 0 {
qp, _ := json.Marshal(map[string]any{"harness_id": parts[3], "consumed": consumed})
if err := s.Append(domain.Event{ID: id(), Type: "QuotaReported", TaskID: "system", Payload: qp, Surface: string(authz.System)}); err != nil {
log.Printf("federated quota report %s: %v", b.TaskID, err)
}
}
json.NewEncoder(w).Encode(e)
return
}
if b.HandoffRef == "" {
http.Error(w, "handoff_ref required", 400)
return
}
if len(b.AnchorSHA) != 40 {
http.Error(w, "anchor_sha required", 400)
return
}
if b.TransactionID == "" || b.ExpectedVersion != t.Version {
http.Error(w, "release transaction and current lease version required", http.StatusConflict)
return
}
p, _ := json.Marshal(map[string]any{"handoff_ref": b.HandoffRef, "harness_id": parts[3], "lease_epoch": b.LeaseEpoch, "anchor_sha": b.AnchorSHA, "transaction_id": b.TransactionID, "expected_version": t.Version, "session_evidence": b.SessionEvidence})
e := domain.Event{ID: id(), Type: "TaskReleased", TaskID: b.TaskID, Version: t.Version + 1, Payload: p, Surface: string(authz.System)}
if err := s.Append(e); err != nil {
http.Error(w, err.Error(), 409)
return
}
if rt != nil {
_, _ = rt.HandleEvent(e)
}
json.NewEncoder(w).Encode(e)
})
var giteaSources []provider.GiteaSourceConfig
if path := os.Getenv("ORCHESTRA_GITEA_CONFIG"); path != "" {
cfgs, err := provider.LoadGiteaConfigs(path)
if err != nil {
log.Fatalf("load gitea config: %v", err)
}
giteaSources = cfgs
} else if base := os.Getenv("ORCHESTRA_GITEA_URL"); base != "" {
// Legacy single-repo configuration: project defaults to the repo
// name, matching the historical (pre-multi-source) behavior.
giteaSources = []provider.GiteaSourceConfig{{
Project: os.Getenv("ORCHESTRA_GITEA_REPO"), BaseURL: base,
Owner: os.Getenv("ORCHESTRA_GITEA_OWNER"), Repo: os.Getenv("ORCHESTRA_GITEA_REPO"),
Token: os.Getenv("ORCHESTRA_GITEA_TOKEN"), WebhookSecret: os.Getenv("ORCHESTRA_GITEA_WEBHOOK_SECRET"),
}}
}
if len(giteaSources) > 0 {
reflectors := map[string]provider.Gitea{}
for _, c := range giteaSources {
g := provider.Gitea{BaseURL: c.BaseURL, Token: c.Token, WebhookSecret: c.WebhookSecret, Owner: c.Owner, Repo: c.Repo, Project: c.Project}
reflectors[g.SourceName()] = g
}
reflecting := provider.ReflectingSink{Sink: s, Tasks: s, Reflector: provider.MultiGitea{Sources: reflectors}}
for _, c := range giteaSources {
g := provider.Gitea{BaseURL: c.BaseURL, Token: c.Token, WebhookSecret: c.WebhookSecret, Owner: c.Owner, Repo: c.Repo, Project: c.Project}
name := "gitea:" + c.Project
webhookPath := "/v1/providers/gitea/webhook/" + c.Project
if len(giteaSources) == 1 && os.Getenv("ORCHESTRA_GITEA_CONFIG") == "" {
// Preserve the legacy unprefixed webhook path when running
// the single-source (env-var) configuration, so existing
// Gitea webhook configs don't need to be re-pointed.
webhookPath = "/v1/providers/gitea/webhook"
name = "gitea"
}
mux.Handle(webhookPath, g.WebhookHandler(reflecting))
sup := &provider.Supervisor{Name: name, Run: func(ctx context.Context) error {
pollCtx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
_, err := g.Poll(pollCtx, reflecting)
if err == nil {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(time.Minute):
}
}
return err
}}
sup.Start(context.Background())
providerHealth[name] = sup
}
}
if path := os.Getenv("ORCHESTRA_JSONL"); path != "" {
sup := &provider.Supervisor{Name: "jsonl", Run: func(ctx context.Context) error {
return (provider.JSONLWatcher{Path: path, Interval: time.Second, Provider: provider.JSONL{Source: "jsonl"}}).Run(ctx, s)
}}
sup.Start(context.Background())
providerHealth["jsonl"] = sup
}
var senders []delivery.Sender
if token, chat := os.Getenv("ORCHESTRA_TELEGRAM_BOT_TOKEN"), os.Getenv("ORCHESTRA_TELEGRAM_CHAT_ID"); token != "" && chat != "" {
senders = append(senders, delivery.Telegram{Token: token, ChatID: chat})
}
if topic := os.Getenv("ORCHESTRA_NTFY_TOPIC"); topic != "" {
senders = append(senders, delivery.Ntfy{Topic: topic, Token: os.Getenv("ORCHESTRA_NTFY_TOKEN"), URL: os.Getenv("ORCHESTRA_NTFY_URL")})
}
if len(senders) > 0 {
cursorPath := filepath.Join(dir, "delivery-cursor")
var startCursor uint64
if b, err := os.ReadFile(cursorPath); err == nil {
if v, err := strconv.ParseUint(strings.TrimSpace(string(b)), 10, 64); err == nil {
startCursor = v
}
}
go func() {
fanout := &delivery.Fanout{
Senders: senders,
Cursor: startCursor,
SaveCursor: func(cursor uint64) {
if err := os.WriteFile(cursorPath, []byte(strconv.FormatUint(cursor, 10)), 0o644); err != nil {
log.Printf("delivery cursor persist: %v", err)
}
},
}
err := fanout.Run(context.Background(), s.Events)
if err != nil {
log.Printf("delivery fanout: %v", err)
}
}()
}
go func() {
lastDay := ""
t := time.NewTicker(time.Minute)
defer t.Stop()
for now := range t.C {
utc := now.UTC()
day := utc.Format("2006-01-02")
if utc.Hour() == 3 && day != lastDay {
if _, err := standup(); err != nil {
log.Printf("standup advisory: %v", err)
} else {
lastDay = day
}
}
}
}()
if rt != nil {
if _, err := rt.AssignPending(); err != nil {
log.Printf("startup task assignment: %v", err)
}
}
port := os.Getenv("ORCHESTRA_PORT")
if port == "" {
port = "9145"
}
log.Println("orchestra listening on :" + port)
tokens := map[authz.Surface]string{
authz.TUI: os.Getenv("ORCHESTRA_TUI_TOKEN"),
authz.MCP: os.Getenv("ORCHESTRA_MCP_TOKEN"), authz.Maven: os.Getenv("ORCHESTRA_MAVEN_TOKEN"),
// S12: this is the credential callers present *to* Orchestra on the
// ntfy surface. ORCHESTRA_NTFY_TOKEN is a different secret entirely —
// it is handed out to the third-party ntfy server (see the sender
// above) and must never be accepted as an inbound credential.
authz.Telegram: os.Getenv("ORCHESTRA_TELEGRAM_TOKEN"), authz.Ntfy: os.Getenv("ORCHESTRA_NTFY_SURFACE_TOKEN"),
}
log.Fatal(http.ListenAndServe(":"+port, authz.HTTPWithSessions(tokens, sessions, mux)))
}