Preserve leases needing recovery
This commit is contained in:
@@ -445,7 +445,7 @@ func (c *Coordinator) Reconcile(ctx context.Context) error {
|
||||
c.mu.Lock()
|
||||
for taskID, session := range c.sessions {
|
||||
t, ok := c.Store.Task(taskID)
|
||||
if ok && (t.State == domain.StateLeased || t.State == domain.StateBlocked) {
|
||||
if ok && (t.State == domain.StateLeased || t.State == domain.StateNeedsAttention) {
|
||||
continue
|
||||
}
|
||||
if a, err := c.adapterFor(taskID, session); err == nil {
|
||||
@@ -589,7 +589,7 @@ func (c *Coordinator) expire(ctx context.Context) ([]domain.Event, error) {
|
||||
c.loadSessions()
|
||||
c.mu.Lock()
|
||||
for taskID, s := range c.sessions {
|
||||
if t, ok := c.Store.Task(taskID); ok && t.State == domain.StateLeased {
|
||||
if t, ok := c.Store.Task(taskID); ok && (t.State == domain.StateLeased || t.State == domain.StateNeedsAttention) {
|
||||
if a, ae := c.adapterFor(taskID, s); ae == nil {
|
||||
if p, ok := a.(herdr.PaneExit); ok {
|
||||
if exited, ee := p.PaneExited(ctx, s); ee == nil && exited {
|
||||
@@ -604,7 +604,7 @@ func (c *Coordinator) expire(ctx context.Context) ([]domain.Event, error) {
|
||||
var events []domain.Event
|
||||
var firstErr error
|
||||
for _, task := range c.Store.Tasks() {
|
||||
if task.State != domain.StateLeased || task.Lease == nil || task.Lease.Until.After(time.Now()) {
|
||||
if (task.State != domain.StateLeased && task.State != domain.StateNeedsAttention) || task.Lease == nil || task.Lease.Until.After(time.Now()) {
|
||||
continue
|
||||
}
|
||||
// Stop a local predecessor before making its lease eligible for a
|
||||
@@ -947,7 +947,7 @@ func (c *Coordinator) Start(ctx context.Context, e domain.Event) error {
|
||||
}
|
||||
if err != nil {
|
||||
// A UI-changing prompt can time out after herdr accepted it. Keep the
|
||||
// live pane mapped before recording TaskBlocked so a later completion
|
||||
// live pane mapped before recording TaskNeedsAttention so a later completion
|
||||
// can reconcile the lifecycle instead of becoming an orphan (B15).
|
||||
if s.PaneID != "" {
|
||||
s.HerdrID = p.HarnessID
|
||||
@@ -1034,7 +1034,7 @@ func (c *Coordinator) block(t domain.Task, reason string) error {
|
||||
p["expected_version"] = t.Version
|
||||
b, _ = json.Marshal(p)
|
||||
}
|
||||
return c.Store.Append(domain.Event{ID: domain.NewID(), Type: "TaskBlocked", TaskID: t.ID, Version: t.Version + 1, Payload: b, Surface: string(authz.System)})
|
||||
return c.Store.Append(domain.Event{ID: domain.NewID(), Type: "TaskNeedsAttention", TaskID: t.ID, Version: t.Version + 1, Payload: b, Surface: string(authz.System)})
|
||||
}
|
||||
|
||||
func (c *Coordinator) Session(taskID string) (herdr.Session, bool) {
|
||||
|
||||
@@ -98,8 +98,8 @@ func TestPromptFailureRetainsLivePaneForBlockedTaskAcrossRestart(t *testing.T) {
|
||||
if err := c.Start(context.Background(), lease); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, ok := s.Task(task.ID); !ok || got.State != domain.StateBlocked {
|
||||
t.Fatalf("task state = %+v, want blocked", got)
|
||||
if got, ok := s.Task(task.ID); !ok || got.State != domain.StateNeedsAttention || got.Lease == nil {
|
||||
t.Fatalf("task state = %+v, want needs_attention with retained lease", got)
|
||||
}
|
||||
if session, ok := c.Session(task.ID); !ok || session.PaneID != "pane-created-before-timeout" || session.HerdrID != "h1" {
|
||||
t.Fatalf("retained session = %+v, present=%v", session, ok)
|
||||
@@ -168,8 +168,8 @@ func TestCoordinatorRefusesRemoteHerdrOperations(t *testing.T) {
|
||||
if a.leases != 0 {
|
||||
t.Fatal("remote adapter was started by coordinator")
|
||||
}
|
||||
if task, ok := s.Task("remote"); !ok || task.State != domain.StateBlocked {
|
||||
t.Fatalf("remote task state = %#v, present=%v; want blocked", task, ok)
|
||||
if task, ok := s.Task("remote"); !ok || task.State != domain.StateNeedsAttention || task.Lease == nil {
|
||||
t.Fatalf("remote task state = %#v, present=%v; want needs_attention with retained lease", task, ok)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ func TestTurnDecision(t *testing.T) {
|
||||
// TestStartBlocksOnInvalidPickup guards AUDIT.md's B6/Phase 4 item 4:
|
||||
// Coordinator.Start must run §6.2 pickup validation against the real
|
||||
// worktree before bootstrapping a successor onto a handoff_ref, and refuse
|
||||
// (TaskBlocked) rather than bootstrap on a mismatched anchor.
|
||||
// (TaskNeedsAttention) rather than bootstrap on a mismatched anchor.
|
||||
func TestStartBlocksOnInvalidPickup(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
run(t, repo, "init")
|
||||
@@ -476,8 +476,8 @@ func TestStartBlocksOnInvalidPickup(t *testing.T) {
|
||||
}
|
||||
|
||||
got, ok := s.Task(task.ID)
|
||||
if !ok || got.State != domain.StateBlocked {
|
||||
t.Fatalf("expected TaskBlocked on invalid pickup, got state=%v ok=%v", got.State, ok)
|
||||
if !ok || got.State != domain.StateNeedsAttention || got.Lease == nil {
|
||||
t.Fatalf("expected recoverable needs_attention on invalid pickup, got state=%v lease=%v ok=%v", got.State, got.Lease, ok)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user