Report queued tasks the scheduling pass never considers

A task in retry backoff was filtered out before the candidate loop, so it
recorded no rejection at all: queued, apparently assignable, and silent. That is
the exact shape that made F5 take a live session to diagnose. It now reports
"retry backoff until <time>".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 23:42:26 +04:00
parent 0ead6d2d02
commit 4fbf3ac966
18 changed files with 1163 additions and 165 deletions
+38 -46
View File
@@ -11,6 +11,7 @@ import (
"net"
"net/http"
"orchestra/internal/admin"
"orchestra/internal/authn"
"orchestra/internal/authz"
"orchestra/internal/buildinfo"
"orchestra/internal/delivery"
@@ -150,6 +151,37 @@ func main() {
if err != nil {
log.Fatal(err)
}
users, err := authn.Open(authn.Path(dir))
if err != nil {
log.Fatal(err)
}
defer users.Close()
userCount, err := users.Count()
if err != nil {
log.Fatalf("read operator database: %v", err)
}
legacyUsername := os.Getenv("ORCHESTRA_WEB_USERNAME")
legacyPasswordHash := os.Getenv("ORCHESTRA_WEB_PASSWORD_HASH")
if userCount == 0 {
if legacyUsername != "" || legacyPasswordHash != "" {
if legacyUsername == "" || legacyPasswordHash == "" {
log.Fatal("operator database is empty and the legacy web credential is incomplete: both ORCHESTRA_WEB_USERNAME and ORCHESTRA_WEB_PASSWORD_HASH are required for one-time migration")
}
imported, importErr := users.ImportBcrypt(legacyUsername, legacyPasswordHash)
if importErr != nil {
log.Fatalf("migrate legacy web credential: %v", importErr)
}
if imported {
userCount = 1
log.Printf("migrated web operator %q into %s; remove ORCHESTRA_WEB_USERNAME and ORCHESTRA_WEB_PASSWORD_HASH from the deployment environment", legacyUsername, authn.Path(dir))
}
}
if userCount == 0 {
log.Fatalf("operator database is empty: stop Orchestra and run orchestra-user set -data %s -username NAME", dir)
}
} else if legacyUsername != "" || legacyPasswordHash != "" {
log.Printf("operator database already contains %d account(s); legacy ORCHESTRA_WEB_USERNAME and ORCHESTRA_WEB_PASSWORD_HASH are ignored and should be removed", userCount)
}
var rr registry.Registry
var rt *router.Router
var coordinator *orchestrator.Coordinator
@@ -275,53 +307,13 @@ func main() {
}
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)
}
// complete them, and inject approval keystrokes into live panes. Browser
// sessions are backed by operator accounts in the embedded auth database;
// no missing environment variable can open this surface.
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)
})
browserAuth := authn.HTTP{Users: users, Sessions: sessions, SecureCookie: os.Getenv("ORCHESTRA_UI_INSECURE_COOKIE") == ""}
mux.HandleFunc(authz.SessionPath, browserAuth.Session)
mux.HandleFunc("/v1/ui/account", browserAuth.Account)
// 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 {