From 6511d6e0ff139495413c7848a9b4aabb9d9ee4e2 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 4 Mar 2026 21:25:34 +0000 Subject: Add READY state for human-in-the-loop verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Top-level tasks now land in READY after successful execution instead of going directly to COMPLETED. Subtasks (with parent_task_id) skip the gate and remain COMPLETED. Users accept or reject via new API endpoints: POST /api/tasks/{id}/accept → READY → COMPLETED POST /api/tasks/{id}/reject → READY → PENDING (with rejection_comment) - task: add StateReady, RejectionComment field, update ValidTransition - storage: migrate rejection_comment column, add RejectTask method - executor: route top-level vs subtask to READY vs COMPLETED - api: /accept and /reject handlers with 409 on invalid state Co-Authored-By: Claude Sonnet 4.6 --- internal/executor/executor_test.go | 39 ++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) (limited to 'internal/executor/executor_test.go') diff --git a/internal/executor/executor_test.go b/internal/executor/executor_test.go index 5d6a55a..18a79bb 100644 --- a/internal/executor/executor_test.go +++ b/internal/executor/executor_test.go @@ -73,13 +73,41 @@ func makeTask(id string) *task.Task { } } -func TestPool_Submit_Success(t *testing.T) { +func TestPool_Submit_TopLevel_GoesToReady(t *testing.T) { store := testStore(t) runner := &mockRunner{} logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) pool := NewPool(2, runner, store, logger) - tk := makeTask("ps-1") + tk := makeTask("ps-1") // no ParentTaskID → top-level + store.CreateTask(tk) + + if err := pool.Submit(context.Background(), tk); err != nil { + t.Fatalf("submit: %v", err) + } + + result := <-pool.Results() + if result.Err != nil { + t.Errorf("expected no error, got: %v", result.Err) + } + if result.Execution.Status != "READY" { + t.Errorf("status: want READY, got %q", result.Execution.Status) + } + + got, _ := store.GetTask("ps-1") + if got.State != task.StateReady { + t.Errorf("task state: want READY, got %v", got.State) + } +} + +func TestPool_Submit_Subtask_GoesToCompleted(t *testing.T) { + store := testStore(t) + runner := &mockRunner{} + logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelError})) + pool := NewPool(2, runner, store, logger) + + tk := makeTask("sub-1") + tk.ParentTaskID = "parent-99" // subtask store.CreateTask(tk) if err := pool.Submit(context.Background(), tk); err != nil { @@ -94,8 +122,7 @@ func TestPool_Submit_Success(t *testing.T) { t.Errorf("status: want COMPLETED, got %q", result.Execution.Status) } - // Verify task state in DB. - got, _ := store.GetTask("ps-1") + got, _ := store.GetTask("sub-1") if got.State != task.StateCompleted { t.Errorf("task state: want COMPLETED, got %v", got.State) } @@ -195,8 +222,8 @@ func TestPool_ConcurrentExecution(t *testing.T) { for i := 0; i < 3; i++ { result := <-pool.Results() - if result.Execution.Status != "COMPLETED" { - t.Errorf("task %s: want COMPLETED, got %q", result.TaskID, result.Execution.Status) + if result.Execution.Status != "READY" { + t.Errorf("task %s: want READY, got %q", result.TaskID, result.Execution.Status) } } -- cgit v1.2.3