Delete Design A, the harness-hook completion path, and retired deploy files
Acts on the seven review comments on PR #1. Design A is gone (comment 4). clients/ deleted rather than tracked: with workers carrying cross-machine work the bridge is undeployed, which supersedes the 2026-07-27 "keep through Phase 5" decision. CLAUDE.md, AGENTS.md and AUDIT.md updated from "retained" to "deleted". The harness-hook completion path is gone (comment 10). Investigation of the live OpenCode QA run showed orchestra-worker owns completion end to end: it watches for .orchestra/done, confirms via AgentStatus that the agent is not busy, then posts through /v1/federation/* with both lease epoch and expected version. The hook scripts used a different, older convention (.orchestra-report.md) and posted to /v1/harness/complete, which had already been reduced to a 410 stub - so that path could not have completed a task. Nothing exercised it, because the live run never used it. Deleted: the three deploy/hooks scripts, the 410 route, the unmounted harnessCompletion handler, and its test. That test passed against a handler no mux routed to, which is the exact "looks wired but isn't" pattern CLAUDE.md warns about; the constant-time token compare added to it earlier today goes with it, having never been reachable. /v1/harness/turn is untouched and still live. Retired deployment files (comments 8, 12, 14): deploy/orchestra.service and deploy/redeploy.sh (which sudo-installed to /usr/local/bin and restarted that unit), plus deploy/docker-api-entrypoint.sh. The entrypoint was safe to remove once its premise was checked: env vars reach the container through `env_file: .env` in compose.yaml, not by sourcing /etc/orchestra/orchestra.env - only config.jsonc is bind-mounted there - and Dockerfile.api's line 17 already sets ORCHESTRA_DATA/ORCHESTRA_PORT. Dockerfile.api now execs /app/orchestra directly. orchestra-worker.service is a different, current unit and is kept. deploy/config.example.json deleted as a duplicate (comment 6); the annotated .jsonc is the one registry.go points at, and its header no longer tells the reader to copy the file that just went away. Documentation corrected beyond the deletions: - CLAUDE.md's deployment section claimed the container bind-mounts /etc/orchestra:ro and its entrypoint sources the env file. Both wrong. - AGENTS.md still described a systemd deployment on homesrv as of 2026-07-27. - AUDIT.md's H5 row still described a "retained compatibility handler". - deploy/DEPLOYMENT.md still named redeploy.sh as the deployment path. - deploy/orchestra.env.example still cited EnvironmentFile=. TOKEN_MINIMAL_WORKFLOW_PLAN.md (comment 2) is untouched: it and WEB_UI_PLAN.md were both missed by REVIEW.md's documentation sweep, and reconciling a 534-line forward-looking plan against AUDIT.md is its own task, not a review fixup. Verified: go build ./..., go vet ./..., go test ./... all pass after the deletions, and go list ./... has no node_modules entry. No live herdr or pane was touched; nothing was deployed. The running image still predates this commit until compose is rebuilt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GEugbHVYfAXFpTqDYbByEB
This commit is contained in:
+10
-99
@@ -126,87 +126,6 @@ func validateLocalMachine(rr registry.Registry, localMachine string) error {
|
||||
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 != "" && subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), []byte("Bearer "+h.token)) != 1 {
|
||||
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 == "" {
|
||||
@@ -553,27 +472,19 @@ func main() {
|
||||
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 (task completion is not handled here — it goes through the
|
||||
// authenticated worker completion endpoint, since the retired
|
||||
// /v1/harness/complete had no fencing epoch) and gets back continue /
|
||||
// prepare_handoff / rotate_now / refuse, per spec §5.3. This replaces
|
||||
// what would otherwise be separate ad-hoc marker-file conventions per
|
||||
// 2 items 1-2): a harness-side caller posts here on an ordinary turn
|
||||
// boundary and gets back continue / prepare_handoff / rotate_now / refuse,
|
||||
// per spec §5.3, instead of separate ad-hoc marker-file conventions per
|
||||
// decision.
|
||||
//
|
||||
// Completion does NOT go through this endpoint, and there is no longer a
|
||||
// /v1/harness/complete: an unaffiliated harness hook has no durable worker
|
||||
// identity or fencing epoch, so it cannot safely mutate a leased task.
|
||||
// orchestra-worker owns completion — it watches for the .orchestra/done
|
||||
// marker, confirms the agent is no longer busy, and posts through the
|
||||
// authenticated federation endpoints with both lease epoch and version.
|
||||
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)
|
||||
|
||||
@@ -1,19 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"orchestra/internal/authz"
|
||||
"orchestra/internal/domain"
|
||||
"orchestra/internal/registry"
|
||||
"orchestra/internal/store"
|
||||
)
|
||||
|
||||
type unreachable struct{}
|
||||
@@ -55,75 +48,6 @@ func TestCoordinatorOwnsOnlyLocalHerdrInFederationMode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHarnessCompletionBuildsReceiptAndQuotaFromTranscript(t *testing.T) {
|
||||
s, err := store.Open(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := s.Append(domain.Event{ID: domain.NewID(), Type: "TaskCreated", TaskID: "done", Surface: string(authz.System), Payload: []byte(`{"source":"qa","external_id":"done","project":"p"}`)}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := s.Lease("done", "local-claude", time.Minute); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
transcript := filepath.Join(t.TempDir(), "transcript.jsonl")
|
||||
if err := os.WriteFile(transcript, []byte(`{"message":{"usage":{"input_tokens":100,"cache_read_input_tokens":20,"cache_creation_input_tokens":5,"output_tokens":7}}}`+"\n"), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task, _ := s.Task("done")
|
||||
body, _ := json.Marshal(map[string]string{"task_id": "done", "worker_id": "local-claude", "lease_epoch": task.Lease.Epoch, "transcript_path": transcript, "report": "# done"})
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1/harness/complete", bytes.NewReader(body))
|
||||
res := httptest.NewRecorder()
|
||||
harnessCompletion{store: s, token: "secret"}.ServeHTTP(res, req)
|
||||
if res.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("missing token status = %d, want 401", res.Code)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer secret")
|
||||
req = httptest.NewRequest(http.MethodPost, "/v1/harness/complete", bytes.NewReader(body))
|
||||
req.Header.Set("Authorization", "Bearer secret")
|
||||
res = httptest.NewRecorder()
|
||||
harnessCompletion{store: s, token: "secret"}.ServeHTTP(res, req)
|
||||
if res.Code != http.StatusOK {
|
||||
t.Fatalf("completion status = %d: %s", res.Code, res.Body.String())
|
||||
}
|
||||
task, ok := s.Task("done")
|
||||
if !ok || task.State != domain.StateCompleted {
|
||||
t.Fatalf("task after completion = %#v, present=%v", task, ok)
|
||||
}
|
||||
var completed struct {
|
||||
Receipt struct {
|
||||
Input int `json:"input_tokens"`
|
||||
CacheRead int `json:"cache_read_tokens"`
|
||||
CacheWrite int `json:"cache_write_tokens"`
|
||||
Output int `json:"output_tokens"`
|
||||
} `json:"receipt"`
|
||||
}
|
||||
for _, e := range s.Events(0) {
|
||||
if e.Type == "TaskCompleted" {
|
||||
if err := json.Unmarshal(e.Payload, &completed); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if completed.Receipt.Input != 100 || completed.Receipt.CacheRead != 20 || completed.Receipt.CacheWrite != 5 || completed.Receipt.Output != 7 {
|
||||
t.Fatalf("receipt = %#v", completed.Receipt)
|
||||
}
|
||||
var quota struct {
|
||||
Harness string `json:"harness_id"`
|
||||
Consumed float64 `json:"consumed"`
|
||||
}
|
||||
found := false
|
||||
for _, e := range s.Events(0) {
|
||||
if e.Type == "QuotaReported" {
|
||||
_ = json.Unmarshal(e.Payload, "a)
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found || quota.Harness != "local-claude" || quota.Consumed != 125 {
|
||||
t.Fatalf("quota report = %#v, found=%v", quota, found)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiMachineRegistryRequiresKnownLocalMachine(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "config.json")
|
||||
if err := os.WriteFile(path, []byte(`{
|
||||
|
||||
Reference in New Issue
Block a user