feat(orchestrator): wire turn-decision endpoint and QuotaReported producer
Closes Phase 2 items 1-2 (AUDIT.md): Coordinator.TurnDecision evaluates occupancy/turn-boundary/handoff state synchronously per turn and returns continue/prepare_handoff/rotate_now/refuse, exposed via POST /v1/harness/turn. The Claude Stop hook now calls it on ordinary turn boundaries instead of no-op'ing, and exits 2 on refuse. Also closes B7's post-hoc producer: /v1/harness/complete now appends a QuotaReported event from the completing lease's harness usage, so the router's quota-availability filter and the brief's quota_consumed stop evaluating against a permanent zero. Live per-harness push producers (Claude statusline, Codex rollout tail) remain unbuilt — investigation recorded in AUDIT.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01W1rkJ2hBMybnJctPbcy4tT
This commit is contained in:
@@ -302,8 +302,58 @@ func main() {
|
||||
log.Printf("route task: %v", routeErr)
|
||||
}
|
||||
}
|
||||
// B7 (AUDIT.md): QuotaReported has no other producer, so router's
|
||||
// 5h/weekly availability filter and the brief's quota_consumed are
|
||||
// permanently zero without this. Fed by the same per-harness usage
|
||||
// read as the receipt above (spec §7.2 — "same per-harness session
|
||||
// state as §5.2.1"); harness_id comes from the lease this completion
|
||||
// closes out, before it's released.
|
||||
if t.Lease != nil && t.Lease.HarnessID != "" {
|
||||
qp, _ := json.Marshal(map[string]any{
|
||||
"harness_id": t.Lease.HarnessID,
|
||||
"consumed": float64(usage.Numerator()),
|
||||
})
|
||||
qe := domain.Event{ID: id(), Type: "QuotaReported", TaskID: "system", Payload: qp, Surface: string(authz.System)}
|
||||
if err := s.Append(qe); err != nil {
|
||||
log.Printf("quota report: %v", err)
|
||||
}
|
||||
}
|
||||
json.NewEncoder(w).Encode(e)
|
||||
})
|
||||
// /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)
|
||||
|
||||
Reference in New Issue
Block a user