Reconcile docs with reality; fix module graph, token compare, health #1

Open
kami wants to merge 216 commits from webui-and-audit-reconciliation into master
4 changed files with 107 additions and 2 deletions
Showing only changes of commit 92f32d6fea - Show all commits
+26
View File
@@ -475,6 +475,19 @@ func (w *worker) start(ctx context.Context, t domain.Task, ref string) error {
}
log.Printf("launch %s confirmed: %s", t.ID, evidence)
}
// Baseline the progress check at launch, not at the first renewal. The
// renewal gate exempts a lease with no baseline, which handed a pane that
// opened and never started a full free renewal period (F34) — the exact
// case the gate exists to catch. Capturing here costs one pane read and
// makes the first renewal a real comparison.
if l, ok := w.leases[t.ID]; ok && l.ProgressSHA == "" {
if text, progressErr := w.paneProgress(ctx, herdr.CLIAdapter{Backend: backend, Harness: w.harness}, s); progressErr == nil {
l.ProgressSHA = domain.Hash([]byte(text))
w.leases[t.ID] = l
} else {
w.recordError(fmt.Errorf("baseline launch progress %s: %w", t.ID, progressErr))
}
}
if l, ok := w.leases[t.ID]; ok {
if err := w.api.Start(ctx, t.ID, l.Epoch, l.Version, w.sessionEvidence(ctx, t.ID, s)); err != nil {
return fmt.Errorf("ack start: %w", err)
@@ -1093,6 +1106,19 @@ func (w *worker) runCommands(ctx context.Context) {
_ = w.api.ResolveCommand(ctx, command.ID, "stale", "capture revision changed")
continue
}
if command.Kind == "resubmit" {
// Not an approval: the pane is holding input Orchestra already
// submitted and believes it delivered. Press Enter and say so.
if err := w.executionBackend().SendKeys(ctx, session, []string{"Enter"}); err != nil {
_ = w.api.ResolveCommand(ctx, command.ID, "rejected", "resubmit not delivered: "+err.Error())
continue
}
log.Printf("resubmit %s: Enter resent to %s at capture revision %d", command.TaskID, session.PaneID, command.CaptureRevision)
if err := w.api.ResolveCommand(ctx, command.ID, "acknowledged", ""); err != nil {
log.Printf("ack command %s: %v", command.ID, err)
}
continue
}
input, ok := approvalResponse(text, command.Kind)
if !ok {
_ = w.api.ResolveCommand(ctx, command.ID, "rejected", "prompt does not expose an executable approval control")
+5 -1
View File
@@ -237,7 +237,11 @@ func (r *Registry) Queue(worker string, c Command) (Command, error) {
if _, ok := r.workers[worker]; !ok {
return Command{}, ErrUnknownWorker
}
if c.TaskID == "" || c.PaneID == "" || c.CaptureRevision == 0 || (c.Kind != "grant_approval" && c.Kind != "deny_approval") {
// "resubmit" presses Enter on input Orchestra already submitted and the
// harness never took (F33). It carries no decision, so it is not an
// approval, but it is fenced the same way: a live pane and the capture
// revision the operator was looking at.
if c.TaskID == "" || c.PaneID == "" || c.CaptureRevision == 0 || (c.Kind != "grant_approval" && c.Kind != "deny_approval" && c.Kind != "resubmit") {
return Command{}, errors.New("invalid control command")
}
c.ID = fmt.Sprintf("cmd-%x", sha256.Sum256([]byte(fmt.Sprintf("%s/%s/%s/%d/%d", worker, c.TaskID, c.Kind, c.CaptureRevision, time.Now().UnixNano()))))[:20]
+28 -1
View File
@@ -210,7 +210,7 @@ func capturePane(s Server, t domain.Task, c *Capture) string {
}
func actions(t domain.Task) []Action {
active := t.State == domain.StateLeased
return []Action{{ID: "handoff", Enabled: active, Reason: "requires a live leased session"}, {ID: "release", Enabled: active, Needs: []string{"reason or handoff_ref"}}, {ID: "block", Enabled: active, Needs: []string{"blocker"}}, {ID: "complete", Enabled: active, Needs: []string{"report_ref", "receipt"}}}
return []Action{{ID: "handoff", Enabled: active, Reason: "requires a live leased session"}, {ID: "resubmit", Enabled: active, Reason: "resends Enter to a pane still holding unsent input"}, {ID: "release", Enabled: active, Needs: []string{"reason or handoff_ref"}}, {ID: "block", Enabled: active, Needs: []string{"blocker"}}, {ID: "complete", Enabled: active, Needs: []string{"report_ref", "receipt"}}}
}
func (s Server) Overview(ctx context.Context) Overview {
// JSON null is not an empty collection to browser clients. In particular,
@@ -400,6 +400,33 @@ func (s Server) action(w http.ResponseWriter, r *http.Request, id, action string
d, _ := s.detail(r.Context(), id)
writeJSON(w, d)
return
case "resubmit":
// Orchestra can put text in an editor and be wrong about whether it
// landed (F33). Until this existed the only way to recover a pane
// holding unsent input was to destroy the lease and wait for expiry,
// which cost the better part of an hour and threw away the session.
//
// This presses Enter on text Orchestra itself submitted. It changes no
// lifecycle state, so it appends no event; the worker logs the
// keystroke and the capture revision fences it against a pane that has
// moved on since the operator looked.
c, err := s.capture(r.Context(), t)
if err != nil || c == nil {
http.Error(w, "current capture unavailable", 503)
return
}
pane := capturePane(s, t, c)
if pane == "" || c.Source != "worker" {
http.Error(w, "resubmit needs a live worker-owned pane", 409)
return
}
command, err := s.Workers.Queue(t.Lease.HarnessID, federation.Command{TaskID: id, Kind: "resubmit", PaneID: pane, CaptureRevision: c.Revision})
if err != nil {
http.Error(w, err.Error(), 409)
return
}
writeJSON(w, map[string]any{"command": command, "task": t})
return
case "handoff":
if s.Coordinator == nil {
http.Error(w, "live coordinator unavailable", 503)
+48
View File
@@ -187,3 +187,51 @@ func TestActionWithoutABodyDoesNotPanic(t *testing.T) {
t.Fatalf("block status=%d body=%s, want 409 blocker required", r.Code, r.Body.String())
}
}
// TestResubmitQueuesAKeystrokeWithoutTouchingLifecycle covers the gap F33 left
// behind: a pane holding unsent input had no recovery short of destroying the
// lease and waiting out expiry. Resubmit presses Enter on text Orchestra
// already submitted, so it queues a worker command and appends no event.
func TestResubmitQueuesAKeystrokeWithoutTouchingLifecycle(t *testing.T) {
s, err := store.Open(t.TempDir())
if err != nil {
t.Fatal(err)
}
workers := &federation.Registry{}
if err := workers.Register(federation.Worker{ID: "wpc-claude", Token: "tok"}, ""); err != nil {
t.Fatal(err)
}
h := Server{Store: s, Workers: workers}.Handler()
req := httptest.NewRequest(http.MethodPost, "/v1/ui/tasks", bytes.NewBufferString(`{"source":"web","external_id":"stuck","project":"demo","title":"Stuck launch"}`))
r := httptest.NewRecorder()
h.ServeHTTP(r, req)
id := s.Tasks()[0].ID
if _, err := s.Lease(id, "wpc-claude", time.Hour); err != nil {
t.Fatal(err)
}
before := len(s.Events(0))
capture, err := workers.PutCapture("wpc-claude", federation.Capture{TaskID: id, PaneID: "wA:p1", Text: "> read .orchestra/launch.md now"})
if err != nil {
t.Fatal(err)
}
req = httptest.NewRequest(http.MethodPost, "/v1/ui/tasks/"+id+"/actions/resubmit", bytes.NewBufferString(`{}`))
r = httptest.NewRecorder()
h.ServeHTTP(r, req)
if r.Code != http.StatusOK {
t.Fatalf("resubmit status=%d body=%s", r.Code, r.Body.String())
}
pending, err := workers.Commands("wpc-claude")
if err != nil || len(pending) != 1 {
t.Fatalf("commands=%#v err=%v", pending, err)
}
if pending[0].Kind != "resubmit" || pending[0].PaneID != "wA:p1" || pending[0].CaptureRevision != capture.Revision {
t.Fatalf("queued %#v, want a resubmit bound to wA:p1 at revision %d", pending[0], capture.Revision)
}
if got := len(s.Events(0)); got != before {
t.Fatalf("events %d -> %d: a keystroke must not append a lifecycle event", before, got)
}
task, _ := s.Task(id)
if task.State != domain.StateLeased {
t.Fatalf("state=%q, want the lease untouched", task.State)
}
}