Add orch and inbox CLI gap-fill tests

This commit is contained in:
2026-03-24 09:40:39 +08:00
parent ea3b617ad1
commit 1bfe00b1a8
9 changed files with 4279 additions and 0 deletions
@@ -0,0 +1,342 @@
package orch
import (
"fmt"
"path/filepath"
"strings"
"testing"
)
// TestOrchRunInitRejectsDuplicateRun verifies orch run init rejects a duplicate run ID.
func TestOrchRunInitRejectsDuplicateRun(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_dup_001",
"--goal", "Create the initial run",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_dup_001",
"--goal", "Try creating the same run again",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "already exists")
}
// TestOrchRunShowPlainTextOutput verifies orch run show prints the human-readable summary.
func TestOrchRunShowPlainTextOutput(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_show_plain_001",
"--goal", "Inspect plain-text status output",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_show_plain_001",
"--task", "T1",
"--title", "Make the run ready",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"run", "show",
"--run", "run_blog_show_plain_001",
)
if exitCode != 0 {
t.Fatalf("expected success exit code, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if stderr != "" {
t.Fatalf("expected empty stderr, got:\n%s", stderr)
}
if got := strings.TrimSpace(stdout); got != "run run_blog_show_plain_001 status ready" {
t.Fatalf("expected plain-text run summary, got %q", got)
}
}
// TestOrchTaskAddRejectsDuplicateTask verifies orch task add rejects a duplicate task ID in one run.
func TestOrchTaskAddRejectsDuplicateTask(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_task_dup_001", "Validate duplicate task rejection")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_task_dup_001",
"--task", "T1",
"--title", "Create the original task",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_task_dup_001",
"--task", "T1",
"--title", "Create the duplicate task",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "already exists")
}
// TestOrchTaskAddRejectsSpecSHAWithoutSpecFile verifies orch task add rejects spec-sha when spec-file is missing.
func TestOrchTaskAddRejectsSpecSHAWithoutSpecFile(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_task_sha_001", "Validate spec-sha guard")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_task_sha_001",
"--task", "T1",
"--title", "Reject missing spec file",
"--spec-sha", "deadbeef",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "spec-sha requires spec-file")
}
// TestOrchTaskAddRejectsUnreadableSpecFile verifies orch task add rejects an unreadable spec file.
func TestOrchTaskAddRejectsUnreadableSpecFile(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_task_spec_001", "Validate unreadable spec file")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_task_spec_001",
"--task", "T1",
"--title", "Reject unreadable spec file",
"--spec-file", filepath.Join(tempDir, "missing-task.md"),
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "failed to read spec-file")
}
// TestOrchDepAddRejectsSelfDependency verifies orch dep add rejects a task depending on itself.
func TestOrchDepAddRejectsSelfDependency(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_dep_self_001", "Validate self-dependency")
seedCoreAdditionalTask(t, dbPath, "run_blog_dep_self_001", "T1", "One task")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dep", "add",
"--run", "run_blog_dep_self_001",
"--task", "T1",
"--depends-on", "T1",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "task cannot depend on itself")
}
// TestOrchDepAddRejectsDuplicateDependency verifies orch dep add rejects a duplicate edge.
func TestOrchDepAddRejectsDuplicateDependency(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_dep_dup_001", "Validate duplicate dependency")
seedCoreAdditionalTask(t, dbPath, "run_blog_dep_dup_001", "T1", "Prerequisite task")
seedCoreAdditionalTask(t, dbPath, "run_blog_dep_dup_001", "T2", "Dependent task")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"dep", "add",
"--run", "run_blog_dep_dup_001",
"--task", "T2",
"--depends-on", "T1",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dep", "add",
"--run", "run_blog_dep_dup_001",
"--task", "T2",
"--depends-on", "T1",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "already exists")
}
// TestOrchReadyRejectsMissingRun verifies orch ready rejects a missing run.
func TestOrchReadyRejectsMissingRun(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"ready",
"--run", "run_blog_ready_missing_001",
)
if exitCode != 40 {
t.Fatalf("expected not_found exit code 40, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "not_found")
}
// TestOrchReadyPrintsNoReadyTasks verifies orch ready prints the no-work message for an empty run.
func TestOrchReadyPrintsNoReadyTasks(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_ready_empty_001", "Validate empty ready queue")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"ready",
"--run", "run_blog_ready_empty_001",
)
if exitCode != 0 {
t.Fatalf("expected success exit code, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if stderr != "" {
t.Fatalf("expected empty stderr, got:\n%s", stderr)
}
if got := strings.TrimSpace(stdout); got != "no ready tasks" {
t.Fatalf("expected no ready tasks message, got %q", got)
}
}
// TestOrchReadyPrintsPlainTextTasks verifies orch ready prints tabular plain-text results.
func TestOrchReadyPrintsPlainTextTasks(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_ready_plain_001", "Validate plain-text ready output")
seedCoreAdditionalTask(t, dbPath, "run_blog_ready_plain_001", "T1", "First task")
seedCoreAdditionalTask(t, dbPath, "run_blog_ready_plain_001", "T2", "Second task")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"ready",
"--run", "run_blog_ready_plain_001",
)
if exitCode != 0 {
t.Fatalf("expected success exit code, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if stderr != "" {
t.Fatalf("expected empty stderr, got:\n%s", stderr)
}
if !strings.Contains(stdout, "T1\tnormal\tFirst task") {
t.Fatalf("expected plain-text output to contain T1 row, got:\n%s", stdout)
}
if !strings.Contains(stdout, "T2\tnormal\tSecond task") {
t.Fatalf("expected plain-text output to contain T2 row, got:\n%s", stdout)
}
}
// TestOrchReadyDefaultsLimitWhenNonPositive verifies orch ready falls back to the default limit when limit is non-positive.
func TestOrchReadyDefaultsLimitWhenNonPositive(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedCoreAdditionalRun(t, dbPath, "run_blog_ready_limit_001", "Validate default ready limit")
for i := 1; i <= 25; i++ {
seedCoreAdditionalTask(
t,
dbPath,
"run_blog_ready_limit_001",
fmt.Sprintf("T%02d", i),
fmt.Sprintf("Task %02d", i),
)
}
readyOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"ready",
"--run", "run_blog_ready_limit_001",
"--limit", "0",
)
var readyResp map[string]any
mustDecodeJSON(t, readyOut, &readyResp)
readyTasks := nestedArray(t, readyResp, "data", "tasks")
if len(readyTasks) != 20 {
t.Fatalf("expected default ready limit of 20 tasks, got %#v", len(readyTasks))
}
}
func seedCoreAdditionalRun(t *testing.T, dbPath, runID, goal string) {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", goal,
)
}
func seedCoreAdditionalTask(t *testing.T, dbPath, runID, taskID, title string) {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", title,
)
}
@@ -0,0 +1,640 @@
package orch
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestOrchDispatchRejectsInvalidExecutionMode verifies dispatch rejects unknown execution modes.
func TestOrchDispatchRejectsInvalidExecutionMode(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_dispatch_invalid_mode_001",
"--goal", "Validate dispatch mode guards",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_dispatch_invalid_mode_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_dispatch_invalid_mode_001",
"--task", "T1",
"--execution-mode", "review",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "execution-mode must be one of: analysis, code")
}
// TestOrchDispatchRejectsCodeOnlyFlagsInAnalysisMode verifies analysis mode rejects code-only flags.
func TestOrchDispatchRejectsCodeOnlyFlagsInAnalysisMode(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
args []string
message string
}{
{
name: "repo-path",
args: []string{"--repo-path", "/tmp/repo"},
message: "repo-path is only valid with --execution-mode code",
},
{
name: "workspace-root",
args: []string{"--workspace-root", ".orch/worktrees"},
message: "workspace-root is only valid with --execution-mode code",
},
{
name: "base-ref",
args: []string{"--base-ref", "HEAD"},
message: "base-ref is only valid with --execution-mode code",
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
args := append([]string{
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_unused",
"--task", "T1",
"--execution-mode", "analysis",
}, tc.args...)
stdout, _, exitCode := executeOrchCommand(args...)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, tc.message)
})
}
}
// TestOrchDispatchRejectsMissingTargetAgent verifies dispatch requires either --to or task default_to.
func TestOrchDispatchRejectsMissingTargetAgent(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_dispatch_target_001",
"--goal", "Validate dispatch target guards",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_dispatch_target_001",
"--task", "T1",
"--title", "Investigate logs",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_dispatch_target_001",
"--task", "T1",
"--execution-mode", "analysis",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "dispatch target agent is required")
}
// TestOrchDispatchCodeModeUsesCurrentWorkingDirectoryAndDefaultWorkspaceRoot verifies code-mode dispatch can infer repo and workspace root.
func TestOrchDispatchCodeModeUsesCurrentWorkingDirectoryAndDefaultWorkspaceRoot(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
repoPath := initGitRepo(t)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_dispatch_cwd_001",
"--goal", "Validate cwd repo inference",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_dispatch_cwd_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
stdout, stderr, exitCode := executeOrchCommandInDir(
repoPath,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_dispatch_cwd_001",
"--task", "T1",
"--execution-mode", "code",
"--body", "Work from the current repository.",
)
if exitCode != 0 {
t.Fatalf("expected successful dispatch from cwd, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
var dispatchResp map[string]any
mustDecodeJSON(t, stdout, &dispatchResp)
worktreePath := nestedString(t, dispatchResp, "data", "attempt", "worktree_path")
expectedPath := buildAttemptWorktreePath(filepath.Join(repoPath, ".orch", "worktrees"), "run_dispatch_cwd_001", "T1", 1)
if worktreePath != expectedPath {
t.Fatalf("expected inferred worktree path %q, got %q", expectedPath, worktreePath)
}
if _, err := os.Stat(worktreePath); err != nil {
t.Fatalf("stat inferred worktree path %s: %v", worktreePath, err)
}
}
// TestOrchDispatchCodeModeRejectsNonGitRepoPath verifies code-mode dispatch rejects non-Git paths.
func TestOrchDispatchCodeModeRejectsNonGitRepoPath(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
nonGitPath := filepath.Join(t.TempDir(), "not-a-repo")
if err := os.MkdirAll(nonGitPath, 0o755); err != nil {
t.Fatalf("mkdir non-git path: %v", err)
}
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_dispatch_repo_guard_001",
"--goal", "Validate repo path guard",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_dispatch_repo_guard_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_dispatch_repo_guard_001",
"--task", "T1",
"--execution-mode", "code",
"--repo-path", nonGitPath,
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "repo-path must point to a Git worktree")
}
// TestOrchDispatchCodeModeRejectsBadBaseRef verifies code-mode dispatch rejects unresolved base refs.
func TestOrchDispatchCodeModeRejectsBadBaseRef(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
repoPath := initGitRepo(t)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_dispatch_base_ref_001",
"--goal", "Validate base ref guard",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_dispatch_base_ref_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_dispatch_base_ref_001",
"--task", "T1",
"--execution-mode", "code",
"--repo-path", repoPath,
"--base-ref", "does-not-exist",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "base-ref must resolve to a commit")
}
// TestOrchReconcileMapsCancelledThreadsAndNoOpsWhenRepeated verifies reconcile maps cancelled threads once and then becomes a no-op.
func TestOrchReconcileMapsCancelledThreadsAndNoOpsWhenRepeated(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_reconcile_cancelled_001",
"--goal", "Validate cancelled reconcile mapping",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_reconcile_cancelled_001",
"--task", "T1",
"--title", "Investigate logs",
"--default-to", "worker-a",
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_reconcile_cancelled_001",
"--task", "T1",
"--execution-mode", "analysis",
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
runInboxCommand(
t,
"--db", dbPath,
"--json",
"cancel",
"--agent", "leader",
"--thread", threadID,
"--reason", "Task cancelled by operator.",
)
reconcileOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", "run_reconcile_cancelled_001",
)
var reconcileResp map[string]any
mustDecodeJSON(t, reconcileOut, &reconcileResp)
updatedTasks := nestedArray(t, reconcileResp, "data", "updated_tasks")
if len(updatedTasks) != 1 {
t.Fatalf("expected one updated task after cancelled reconcile, got %#v", updatedTasks)
}
task := updatedTasks[0].(map[string]any)
if got, _ := task["status"].(string); got != "cancelled" {
t.Fatalf("expected cancelled task status, got %#v", task["status"])
}
taskCounts := nestedValue(t, reconcileResp, "data", "task_counts").(map[string]any)
if got, _ := taskCounts["cancelled"].(float64); got != 1 {
t.Fatalf("expected cancelled task count 1, got %#v", taskCounts["cancelled"])
}
secondReconcileOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", "run_reconcile_cancelled_001",
)
var secondResp map[string]any
mustDecodeJSON(t, secondReconcileOut, &secondResp)
if updated := nestedArray(t, secondResp, "data", "updated_tasks"); len(updated) != 0 {
t.Fatalf("expected repeated reconcile to be a no-op, got %#v", updated)
}
}
// TestOrchStatusRejectsMissingRun verifies status reports not_found for unknown runs.
func TestOrchStatusRejectsMissingRun(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"status",
"--run", "run_status_missing_001",
)
if exitCode != 40 {
t.Fatalf("expected not_found exit code 40, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "not_found")
}
// TestOrchStatusPlainTextOutput verifies status renders the human-readable dashboard view.
func TestOrchStatusPlainTextOutput(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_status_text_001",
"--goal", "Validate status plain-text output",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_status_text_001",
"--task", "T1",
"--title", "Investigate flaky tests",
"--default-to", "worker-a",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"status",
"--run", "run_status_text_001",
)
if exitCode != 0 {
t.Fatalf("expected status exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if !strings.Contains(stdout, "run run_status_text_001 status ready") {
t.Fatalf("expected run summary in plain-text status output, got:\n%s", stdout)
}
if !strings.Contains(stdout, "T1\tready\tInvestigate flaky tests") {
t.Fatalf("expected task row in plain-text status output, got:\n%s", stdout)
}
}
// TestOrchWaitNormalizesForFlagAndReturnsExistingEventsImmediately verifies wait normalizes event filters and returns existing events.
func TestOrchWaitNormalizesForFlagAndReturnsExistingEventsImmediately(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_wait_existing_001",
"--goal", "Validate wait filter normalization",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_wait_existing_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_wait_existing_001",
"--task", "T1",
"--execution-mode", "analysis",
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", "worker-a",
"--thread", threadID,
)
runInboxCommandEventually(
t,
"--db", dbPath,
"--json",
"update",
"--agent", "worker-a",
"--thread", threadID,
"--status", "blocked",
"--summary", "Need API decision",
"--payload-json", `{"question":"Use v1 or v2?"}`,
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", "run_wait_existing_001",
)
waitOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"wait",
"--run", "run_wait_existing_001",
"--for", " task_blocked , task_blocked , ",
"--after-event", "0",
)
var waitResp map[string]any
mustDecodeJSON(t, waitOut, &waitResp)
if woke, _ := nestedValue(t, waitResp, "data", "woke").(bool); !woke {
t.Fatalf("expected wait to wake on an existing blocked event, got %#v", waitResp)
}
events := nestedArray(t, waitResp, "data", "events")
if len(events) != 1 {
t.Fatalf("expected one matching blocked event, got %#v", events)
}
event := events[0].(map[string]any)
if got, _ := event["type"].(string); got != "task_blocked" {
t.Fatalf("expected task_blocked event, got %#v", event["type"])
}
if got, _ := event["summary"].(string); got != "Need API decision" {
t.Fatalf("expected blocked summary in event, got %#v", event["summary"])
}
eventID, ok := event["event_id"].(float64)
if !ok {
t.Fatalf("expected numeric event_id, got %#v", event["event_id"])
}
if nextEventID, _ := nestedValue(t, waitResp, "data", "next_event_id").(float64); nextEventID != eventID {
t.Fatalf("expected next_event_id %.0f, got %#v", eventID, nestedValue(t, waitResp, "data", "next_event_id"))
}
}
// TestOrchWaitRejectsMissingRun verifies wait reports not_found for unknown runs.
func TestOrchWaitRejectsMissingRun(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"wait",
"--run", "run_wait_missing_001",
)
if exitCode != 40 {
t.Fatalf("expected not_found exit code 40, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "not_found")
}
// TestOrchWaitHelpExplainsBlockingPrimitive verifies wait help explains its blocking role and resume flag.
func TestOrchWaitHelpExplainsBlockingPrimitive(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeOrchCommand("wait", "--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "leader-side blocking primitive") {
t.Fatalf("expected wait help to explain its role, got:\n%s", combined)
}
if !strings.Contains(combined, "--after-event 0 --timeout-seconds 900") {
t.Fatalf("expected wait help to include resume example, got:\n%s", combined)
}
}
// TestOrchWaitPlainTextOutput verifies wait renders matching events in plain-text mode.
func TestOrchWaitPlainTextOutput(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_wait_text_001",
"--goal", "Validate wait plain-text output",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_wait_text_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_wait_text_001",
"--task", "T1",
"--execution-mode", "analysis",
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", "worker-a",
"--thread", threadID,
)
runInboxCommandEventually(
t,
"--db", dbPath,
"--json",
"update",
"--agent", "worker-a",
"--thread", threadID,
"--status", "blocked",
"--summary", "Need deployment window",
"--payload-json", `{"question":"Can we ship today?"}`,
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", "run_wait_text_001",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"wait",
"--run", "run_wait_text_001",
"--for", "task_blocked",
"--after-event", "0",
)
if exitCode != 0 {
t.Fatalf("expected wait exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if !strings.Contains(stdout, "\ttask_blocked\tT1\tNeed deployment window") {
t.Fatalf("expected plain-text wait event row, got:\n%s", stdout)
}
}
@@ -0,0 +1,552 @@
package orch
import (
"fmt"
"path/filepath"
"strings"
"testing"
)
// TestOrchReconcileAdditionalContracts verifies reconcile missing-run, plain-text, and help behavior.
func TestOrchReconcileAdditionalContracts(t *testing.T) {
t.Parallel()
t.Run("missing run", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"reconcile",
"--run", "run_reconcile_missing_001",
)
if exitCode != 40 {
t.Fatalf("expected not_found exit code 40, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "not_found")
})
t.Run("plain text", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_reconcile_plain_001",
"--goal", "Validate reconcile plain-text output",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_reconcile_plain_001",
"--task", "T1",
"--title", "Investigate current state",
"--default-to", "worker-a",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_reconcile_plain_001",
"--task", "T1",
"--execution-mode", "analysis",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"reconcile",
"--run", "run_reconcile_plain_001",
)
if exitCode != 0 {
t.Fatalf("expected reconcile success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "reconciled run run_reconcile_plain_001 (0 updated tasks)" {
t.Fatalf("expected reconcile plain-text summary, got %q", got)
}
})
t.Run("help", func(t *testing.T) {
stdout, stderr, exitCode := executeOrchCommand("reconcile", "--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "bridge from worker-side inbox activity") {
t.Fatalf("expected reconcile help to explain the inbox bridge, got:\n%s", combined)
}
if !strings.Contains(combined, "fresh state") {
t.Fatalf("expected reconcile help to mention fresh state, got:\n%s", combined)
}
})
}
// TestOrchStatusShowsEmptyRunWithoutAttempts verifies status returns an empty task view for untouched runs.
func TestOrchStatusShowsEmptyRunWithoutAttempts(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_status_empty_001",
"--goal", "Inspect untouched run status",
)
statusOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"status",
"--run", "run_status_empty_001",
)
var statusResp map[string]any
mustDecodeJSON(t, statusOut, &statusResp)
if got := nestedString(t, statusResp, "data", "run", "status"); got != "active" {
t.Fatalf("expected active run status, got %q", got)
}
tasks := nestedArray(t, statusResp, "data", "tasks")
if len(tasks) != 0 {
t.Fatalf("expected no tasks for untouched run, got %#v", tasks)
}
}
// TestOrchWaitBlankFilterFallsBackToDefaultEvents verifies blank wait filters normalize back to the default event set.
func TestOrchWaitBlankFilterFallsBackToDefaultEvents(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_wait_blank_001",
"--goal", "Validate wait filter fallback",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_wait_blank_001",
"--task", "T1",
"--title", "Ready task",
)
stdout := runOrchCommand(
t,
"--db", dbPath,
"--json",
"wait",
"--run", "run_wait_blank_001",
"--for", " , , ",
"--after-event", "0",
"--timeout-seconds", "1",
)
var waitResp map[string]any
mustDecodeJSON(t, stdout, &waitResp)
events := nestedArray(t, waitResp, "data", "events")
if len(events) != 1 {
t.Fatalf("expected one default wait event, got %#v", events)
}
event, ok := events[0].(map[string]any)
if !ok {
t.Fatalf("expected wait event object, got %#v", events[0])
}
if got, _ := event["type"].(string); got != "task_ready" {
t.Fatalf("expected default task_ready event, got %#v", event["type"])
}
}
// TestOrchRecoveryCommandPlainTextOutputs verifies recovery and cleanup commands render human-readable success output.
func TestOrchRecoveryCommandPlainTextOutputs(t *testing.T) {
t.Run("answer", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
threadID := seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_answer_plain_001", "T2", "worker-b")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"answer",
"--run", "run_answer_plain_001",
"--task", "T2",
"--body", "Use stdout for MVP.",
)
if exitCode != 0 {
t.Fatalf("expected answer success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "answered task T2 on thread "+threadID {
t.Fatalf("expected answer plain-text output, got %q", got)
}
})
t.Run("retry", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedFailedTaskForVerifyAndRecoveryTests(t, dbPath, "run_retry_plain_001", "T1")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"retry",
"--run", "run_retry_plain_001",
"--task", "T1",
"--body", "Retry with the same execution contract.",
)
if exitCode != 0 {
t.Fatalf("expected retry success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "retried task T1 as attempt 2" {
t.Fatalf("expected retry plain-text output, got %q", got)
}
})
t.Run("reassign", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedFailedTaskForVerifyAndRecoveryTests(t, dbPath, "run_reassign_plain_001", "T1")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"reassign",
"--run", "run_reassign_plain_001",
"--task", "T1",
"--to", "worker-b",
"--reason", "Move the retry to another worker.",
)
if exitCode != 0 {
t.Fatalf("expected reassign success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "reassigned task T1 to worker-b as attempt 2" {
t.Fatalf("expected reassign plain-text output, got %q", got)
}
})
t.Run("cancel", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_cancel_plain_001",
"--goal", "Validate cancel plain-text output",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_cancel_plain_001",
"--task", "T1",
"--title", "Superseded task",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"cancel",
"--run", "run_cancel_plain_001",
"--task", "T1",
"--reason", "Superseded by a new plan.",
)
if exitCode != 0 {
t.Fatalf("expected cancel success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "cancelled task T1 in run run_cancel_plain_001" {
t.Fatalf("expected cancel plain-text output, got %q", got)
}
})
t.Run("cleanup", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
repoPath := initGitRepo(t)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_cleanup_plain_001",
"--goal", "Validate cleanup plain-text output",
)
seedCompletedCodeTaskForCleanupCouncilTests(t, dbPath, repoPath, "run_cleanup_plain_001", "T1", "worker-a")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"cleanup",
"--run", "run_cleanup_plain_001",
"--task", "T1",
)
if exitCode != 0 {
t.Fatalf("expected cleanup success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "cleaned 1 worktrees" {
t.Fatalf("expected cleanup plain-text output, got %q", got)
}
})
}
// TestOrchRecoveryCommandHelpContracts verifies recovery commands explain their intended operator contract.
func TestOrchRecoveryCommandHelpContracts(t *testing.T) {
t.Parallel()
cases := []struct {
name string
args []string
want []string
}{
{
name: "answer",
args: []string{"answer", "--help"},
want: []string{"active blocked attempt thread", "payload-json", "Constraints:"},
},
{
name: "retry",
args: []string{"retry", "--help"},
want: []string{"fresh attempt and inbox thread", "analysis-only", "Constraints:"},
},
{
name: "reassign",
args: []string{"reassign", "--help"},
want: []string{"create a new attempt for another worker", "fresh worktree", "Constraints:"},
},
{
name: "cancel",
args: []string{"cancel", "--help"},
want: []string{"cancel one task inside the run", "entire request", "Constraints:"},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
stdout, stderr, exitCode := executeOrchCommand(tc.args...)
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
for _, want := range tc.want {
if !strings.Contains(combined, want) {
t.Fatalf("expected %s help to contain %q, got:\n%s", tc.name, want, combined)
}
}
})
}
}
// TestOrchVerifyPlainTextOutputs verifies verify record and verify status render human-readable success output.
func TestOrchVerifyPlainTextOutputs(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "run_verify_plain_001"
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, runID, "T1", []string{"lint"})
recordStdout, recordStderr, exitCode := executeOrchCommand(
"--db", dbPath,
"verify", "record",
"--run", runID,
"--task", "T1",
"--check", "lint",
"--status", "passed",
"--summary", "lint clean",
)
if exitCode != 0 {
t.Fatalf("expected verify record success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, recordStderr, recordStdout)
}
if got := strings.TrimSpace(recordStdout); got != "recorded check lint=passed for "+runID+"/T1 attempt 1" {
t.Fatalf("expected verify record plain-text output, got %q", got)
}
statusStdout, statusStderr, exitCode := executeOrchCommand(
"--db", dbPath,
"verify", "status",
"--run", runID,
"--task", "T1",
)
if exitCode != 0 {
t.Fatalf("expected verify status success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, statusStderr, statusStdout)
}
if got := strings.TrimSpace(statusStdout); got != runID+"/T1 verification status passed" {
t.Fatalf("expected verify status plain-text output, got %q", got)
}
}
// TestOrchCouncilAdditionalContracts verifies remaining council help, plain-text, and cancelled-reviewer failure paths.
func TestOrchCouncilAdditionalContracts(t *testing.T) {
t.Run("wait and tally help", func(t *testing.T) {
cases := []struct {
name string
args []string
want []string
}{
{
name: "council wait",
args: []string{"council", "wait", "--help"},
want: []string{"instead of polling reviewer task state manually", "--timeout-seconds 900", "Constraints:"},
},
{
name: "council tally",
args: []string{"council", "tally", "--help"},
want: []string{"group similar proposals", "--similarity normal", "Constraints:"},
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
stdout, stderr, exitCode := executeOrchCommand(tc.args...)
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
for _, want := range tc.want {
if !strings.Contains(combined, want) {
t.Fatalf("expected %s help to contain %q, got:\n%s", tc.name, want, combined)
}
}
})
}
})
t.Run("wait plain text", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_wait_plain_001"
seedCouncilRunReadyForTally(t, dbPath, runID)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"council", "wait",
"--run", runID,
"--timeout-seconds", "1",
)
if exitCode != 0 {
t.Fatalf("expected council wait success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if got := strings.TrimSpace(stdout); got != "all council reviewers completed for run "+runID {
t.Fatalf("expected council wait plain-text output, got %q", got)
}
})
t.Run("tally plain text", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_tally_plain_001"
seedCouncilRunReadyForTally(t, dbPath, runID)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"council", "tally",
"--run", runID,
"--similarity", "normal",
)
if exitCode != 0 {
t.Fatalf("expected council tally success, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if !strings.Contains(strings.TrimSpace(stdout), "tallied council run "+runID+" into ") {
t.Fatalf("expected council tally plain-text output, got %q", stdout)
}
})
t.Run("cancelled reviewer rejected by tally", func(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_tally_cancelled_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", runID,
"--target", "Review cancelled reviewer handling.",
)
completeCouncilReviewer(t, dbPath, runID, "architecture-reviewer", councilReviewerBody("architecture-reviewer", "Keep terminal states explicit", "Terminal states should stay visible.", "Preserve reviewer terminal states in council workflows."))
completeCouncilReviewer(t, dbPath, runID, "implementation-reviewer", councilReviewerBody("implementation-reviewer", "Reject incomplete tally inputs", "Tally should require successful reviewers.", "Reject reviewer sets that are not all done."))
cancelCouncilReviewerForAdditionalTests(t, dbPath, runID, "risk-reviewer", "Risk review superseded")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "tally",
"--run", runID,
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "did not finish successfully")
})
}
// TestOrchCouncilReportArtifactPathUsesExpectedBaseDir verifies council report artifacts derive from the intended base directory.
func TestOrchCouncilReportArtifactPathUsesExpectedBaseDir(t *testing.T) {
t.Parallel()
cases := []struct {
name string
dbPath string
runID string
want string
}{
{
name: "current directory db",
dbPath: ".agents/coord.db",
runID: "council_blog_001",
want: filepath.Join(".orch", "reports", "council_blog_001.md"),
},
{
name: "db outside .agents",
dbPath: filepath.Join("/tmp", "coord", "coord.db"),
runID: "council_blog_002",
want: filepath.Join("/tmp", "coord", ".orch", "reports", "council_blog_002.md"),
},
{
name: "sanitize run id separators",
dbPath: filepath.Join("/tmp", "project", ".agents", "coord.db"),
runID: filepath.Join("council", "blog", "003"),
want: filepath.Join("/tmp", "project", ".orch", "reports", "council_blog_003.md"),
},
}
for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if got := councilReportArtifactPath(tc.dbPath, tc.runID); got != tc.want {
t.Fatalf("expected council report path %q, got %q", tc.want, got)
}
})
}
}
func seedCouncilRunReadyForTally(t *testing.T, dbPath, runID string) {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", runID,
"--target", "Review the council plain-text flow.",
)
completeCouncilReviewer(t, dbPath, runID, "architecture-reviewer", councilReviewerBody("architecture-reviewer", "Split contracts", "Transport contracts are mixed into UI code.", "Move API contract definitions into a dedicated module."))
completeCouncilReviewer(t, dbPath, runID, "implementation-reviewer", councilReviewerBody("implementation-reviewer", "Extract contracts", "Shared transport shapes are duplicated.", "Move API contract definitions into a dedicated module."))
completeCouncilReviewer(t, dbPath, runID, "risk-reviewer", councilReviewerBody("risk-reviewer", "Cover regressions", "Contract drift becomes risky over time.", "Add integration tests for the council report flow."))
}
func councilReviewerBody(role, title, summary, proposal string) string {
return fmt.Sprintf(
`{"reviewer_role":%q,"findings":[{"title":%q,"summary":%q,"proposal":%q,"rationale":"Keep the council workflow deterministic.","confidence":"high","tags":["contracts"],"target_refs":{"repo_path":"."}}]}`,
role,
title,
summary,
proposal,
)
}
@@ -0,0 +1,646 @@
package orch
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)
// TestOrchBlockedPlainTextOutput verifies blocked renders tabular output in plain-text mode.
func TestOrchBlockedPlainTextOutput(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
_ = seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_blog_blocked_plain_001", "T2", "worker-b")
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"blocked",
"--run", "run_blog_blocked_plain_001",
)
if exitCode != 0 {
t.Fatalf("expected plain blocked exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if !strings.Contains(stdout, "T2\tNeed logging decision\n") {
t.Fatalf("expected blocked plain output to include task summary, got:\n%s", stdout)
}
}
// TestOrchBlockedPlainTextNoBlockedTasks verifies blocked reports the empty queue in plain-text mode.
func TestOrchBlockedPlainTextNoBlockedTasks(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_blocked_plain_002",
"--goal", "Validate empty blocked queue",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"blocked",
"--run", "run_blog_blocked_plain_002",
)
if exitCode != 0 {
t.Fatalf("expected empty blocked exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if stdout != "no blocked tasks\n" {
t.Fatalf("expected no blocked tasks line, got %#v", stdout)
}
}
// TestOrchAnswerRejectsTerminalThread verifies answer rejects blocked tasks whose active thread is already terminal.
func TestOrchAnswerRejectsTerminalThread(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
threadID := seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_blog_answer_terminal_001", "T2", "worker-b")
setThreadStatusForRecoveryCleanupTests(t, dbPath, threadID, "cancelled")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"answer",
"--run", "run_blog_answer_terminal_001",
"--task", "T2",
"--body", "Use stdout.",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "already terminal")
}
// TestOrchAnswerRejectsBodyAndBodyFileTogether verifies answer enforces body/body-file mutual exclusion.
func TestOrchAnswerRejectsBodyAndBodyFileTogether(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
bodyFile := filepath.Join(tempDir, "answer.txt")
if err := os.WriteFile(bodyFile, []byte("Use stdout.\n"), 0o644); err != nil {
t.Fatalf("write answer body file: %v", err)
}
_ = seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_blog_answer_body_guard_001", "T2", "worker-b")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"answer",
"--run", "run_blog_answer_body_guard_001",
"--task", "T2",
"--body", "Use stdout.",
"--body-file", bodyFile,
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "mutually exclusive")
}
// TestOrchAnswerRejectsUnreadableBodyFile verifies answer surfaces unreadable body-file errors.
func TestOrchAnswerRejectsUnreadableBodyFile(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
_ = seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_blog_answer_body_guard_002", "T2", "worker-b")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"answer",
"--run", "run_blog_answer_body_guard_002",
"--task", "T2",
"--body-file", filepath.Join(tempDir, "missing-answer.txt"),
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "failed to read body-file")
}
// TestOrchRetryOverridesAssignedWorkerAndAbandonsPreviousWorktree verifies retry can retarget a code attempt and mark the old worktree abandoned.
func TestOrchRetryOverridesAssignedWorkerAndAbandonsPreviousWorktree(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
_, originalWorktreePath := seedFailedCodeModeTaskForRecoveryCleanupTests(t, dbPath, "run_blog_retry_extra_001", "T1", "worker-a")
retryOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"retry",
"--run", "run_blog_retry_extra_001",
"--task", "T1",
"--to", "worker-b",
"--body", "Retry with a different worker.",
)
var retryResp map[string]any
mustDecodeJSON(t, retryOut, &retryResp)
if got := nestedString(t, retryResp, "data", "attempt", "assigned_to"); got != "worker-b" {
t.Fatalf("expected retry target worker-b, got %q", got)
}
if got := nestedString(t, retryResp, "data", "previous_attempt", "worktree_path"); got != originalWorktreePath {
t.Fatalf("expected previous attempt worktree %q, got %q", originalWorktreePath, got)
}
newWorktreePath := nestedString(t, retryResp, "data", "attempt", "worktree_path")
if newWorktreePath == "" || newWorktreePath == originalWorktreePath {
t.Fatalf("expected retry to create a fresh worktree, got %q", newWorktreePath)
}
if _, err := os.Stat(newWorktreePath); err != nil {
t.Fatalf("stat retry worktree %s: %v", newWorktreePath, err)
}
status, workspaceStatus, worktreePath := queryAttemptStateForRecoveryCleanupTests(t, dbPath, "run_blog_retry_extra_001", "T1", 1)
if status != "failed" {
t.Fatalf("expected previous attempt status failed, got %q", status)
}
if workspaceStatus != "abandoned" {
t.Fatalf("expected previous workspace_status abandoned, got %q", workspaceStatus)
}
if worktreePath != originalWorktreePath {
t.Fatalf("expected original worktree path %q, got %q", originalWorktreePath, worktreePath)
}
}
// TestOrchRetryAnalysisModeRemainsWithoutWorktree verifies retry preserves analysis-mode attempts without allocating a worktree.
func TestOrchRetryAnalysisModeRemainsWithoutWorktree(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
originalThreadID := seedFailedTaskForVerifyAndRecoveryTests(t, dbPath, "run_blog_retry_extra_002", "T1")
retryOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"retry",
"--run", "run_blog_retry_extra_002",
"--task", "T1",
"--body", "Retry analysis-only work.",
)
var retryResp map[string]any
mustDecodeJSON(t, retryOut, &retryResp)
attempt, ok := nestedValue(t, retryResp, "data", "attempt").(map[string]any)
if !ok {
t.Fatalf("expected retry attempt object, got %#v", nestedValue(t, retryResp, "data", "attempt"))
}
if got, _ := attempt["thread_id"].(string); got == originalThreadID {
t.Fatalf("expected retry to create a new thread, got %q", got)
}
if got, _ := attempt["worktree_path"].(string); got != "" {
t.Fatalf("expected no worktree_path for analysis retry, got %q", got)
}
if got, _ := attempt["workspace_status"].(string); got != "" {
t.Fatalf("expected no workspace_status for analysis retry, got %q", got)
}
status, workspaceStatus, worktreePath := queryAttemptStateForRecoveryCleanupTests(t, dbPath, "run_blog_retry_extra_002", "T1", 2)
if status != "dispatched" {
t.Fatalf("expected retry attempt status dispatched, got %q", status)
}
if workspaceStatus != "" {
t.Fatalf("expected empty workspace_status for analysis retry, got %q", workspaceStatus)
}
if worktreePath != "" {
t.Fatalf("expected empty worktree_path for analysis retry, got %q", worktreePath)
}
}
// TestOrchReassignCodeModeCreatesFreshWorktree verifies reassign creates a new code worktree and abandons the previous one.
func TestOrchReassignCodeModeCreatesFreshWorktree(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
_, originalWorktreePath := seedBlockedCodeModeTaskForRecoveryCleanupTests(t, dbPath, "run_blog_reassign_extra_001", "T1", "worker-a")
reassignOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"reassign",
"--run", "run_blog_reassign_extra_001",
"--task", "T1",
"--to", "worker-b",
"--reason", "Move to another worker.",
)
var reassignResp map[string]any
mustDecodeJSON(t, reassignOut, &reassignResp)
if got := nestedString(t, reassignResp, "data", "previous_attempt", "worktree_path"); got != originalWorktreePath {
t.Fatalf("expected previous attempt worktree %q, got %q", originalWorktreePath, got)
}
newWorktreePath := nestedString(t, reassignResp, "data", "attempt", "worktree_path")
if newWorktreePath == "" || newWorktreePath == originalWorktreePath {
t.Fatalf("expected reassignment to create a fresh worktree, got %q", newWorktreePath)
}
if _, err := os.Stat(newWorktreePath); err != nil {
t.Fatalf("stat reassigned worktree %s: %v", newWorktreePath, err)
}
status, workspaceStatus, _ := queryAttemptStateForRecoveryCleanupTests(t, dbPath, "run_blog_reassign_extra_001", "T1", 1)
if status != "cancelled" {
t.Fatalf("expected previous attempt status cancelled, got %q", status)
}
if workspaceStatus != "abandoned" {
t.Fatalf("expected previous workspace_status abandoned, got %q", workspaceStatus)
}
}
// TestOrchCancelRejectsAlreadyCancelledTask verifies cancel rejects a task that was already cancelled explicitly.
func TestOrchCancelRejectsAlreadyCancelledTask(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_cancel_extra_001",
"--goal", "Validate repeated task cancellation",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_cancel_extra_001",
"--task", "T1",
"--title", "Implement backend",
"--default-to", "worker-a",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"cancel",
"--run", "run_blog_cancel_extra_001",
"--task", "T1",
"--reason", "Task no longer needed.",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"cancel",
"--run", "run_blog_cancel_extra_001",
"--task", "T1",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "already cancelled")
}
// TestOrchCancelCodeModeMarksWorkspaceAbandoned verifies cancel leaves code-mode attempts as abandoned until cleanup removes them.
func TestOrchCancelCodeModeMarksWorkspaceAbandoned(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
threadID, worktreePath := seedCodeModeDispatchedTaskForRecoveryCleanupTests(t, dbPath, "run_blog_cancel_extra_002", "T1", "worker-a")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"cancel",
"--run", "run_blog_cancel_extra_002",
"--task", "T1",
"--reason", "Stop this worktree attempt.",
)
status, workspaceStatus, gotWorktreePath := queryAttemptStateForRecoveryCleanupTests(t, dbPath, "run_blog_cancel_extra_002", "T1", 1)
if status != "cancelled" {
t.Fatalf("expected cancelled attempt status, got %q", status)
}
if workspaceStatus != "abandoned" {
t.Fatalf("expected abandoned workspace_status, got %q", workspaceStatus)
}
if gotWorktreePath != worktreePath {
t.Fatalf("expected worktree_path %q, got %q", worktreePath, gotWorktreePath)
}
if _, err := os.Stat(worktreePath); err != nil {
t.Fatalf("expected cancelled worktree to remain for later cleanup, err=%v", err)
}
showOut := runInboxCommand(
t,
"--db", dbPath,
"--json",
"show",
"--thread", threadID,
)
var showResp map[string]any
mustDecodeJSON(t, showOut, &showResp)
if got := nestedString(t, showResp, "data", "thread", "status"); got != "cancelled" {
t.Fatalf("expected cancelled inbox thread, got %q", got)
}
}
// TestOrchCleanupRejectsWithoutSelector verifies cleanup requires an explicit selector.
func TestOrchCleanupRejectsWithoutSelector(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_cleanup_extra_001",
"--goal", "Validate cleanup selector requirements",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"cleanup",
"--run", "run_blog_cleanup_extra_001",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "specify --task, --attempt, or --all-completed")
}
// TestOrchCleanupForceRemovesActiveWorktree verifies cleanup can force-remove a created worktree that is not yet terminal.
func TestOrchCleanupForceRemovesActiveWorktree(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
_, worktreePath := seedCodeModeDispatchedTaskForRecoveryCleanupTests(t, dbPath, "run_blog_cleanup_extra_002", "T1", "worker-a")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"cleanup",
"--run", "run_blog_cleanup_extra_002",
"--task", "T1",
"--attempt", "1",
)
if exitCode != 10 {
t.Fatalf("expected no_matching_work exit 10 without force, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "no_matching_work")
cleanupOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"cleanup",
"--run", "run_blog_cleanup_extra_002",
"--task", "T1",
"--attempt", "1",
"--force",
)
var cleanupResp map[string]any
mustDecodeJSON(t, cleanupOut, &cleanupResp)
cleaned := nestedArray(t, cleanupResp, "data", "cleaned")
if len(cleaned) != 1 {
t.Fatalf("expected one cleaned forced attempt, got %#v", cleaned)
}
if _, err := os.Stat(worktreePath); !os.IsNotExist(err) {
t.Fatalf("expected forced cleanup worktree path to be removed, err=%v", err)
}
_, workspaceStatus, _ := queryAttemptStateForRecoveryCleanupTests(t, dbPath, "run_blog_cleanup_extra_002", "T1", 1)
if workspaceStatus != "cleaned" {
t.Fatalf("expected cleaned workspace_status after force cleanup, got %q", workspaceStatus)
}
}
// TestOrchCleanupRemovesAbandonedWorktreeByExactAttempt verifies cleanup can target one abandoned attempt without touching the replacement worktree.
func TestOrchCleanupRemovesAbandonedWorktreeByExactAttempt(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
_, originalWorktreePath := seedFailedCodeModeTaskForRecoveryCleanupTests(t, dbPath, "run_blog_cleanup_extra_003", "T1", "worker-a")
retryOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"retry",
"--run", "run_blog_cleanup_extra_003",
"--task", "T1",
"--body", "Retry in a fresh worktree.",
)
var retryResp map[string]any
mustDecodeJSON(t, retryOut, &retryResp)
replacementWorktreePath := nestedString(t, retryResp, "data", "attempt", "worktree_path")
if replacementWorktreePath == "" || replacementWorktreePath == originalWorktreePath {
t.Fatalf("expected retry to create a replacement worktree, got %q", replacementWorktreePath)
}
cleanupOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"cleanup",
"--run", "run_blog_cleanup_extra_003",
"--task", "T1",
"--attempt", "1",
)
var cleanupResp map[string]any
mustDecodeJSON(t, cleanupOut, &cleanupResp)
cleaned := nestedArray(t, cleanupResp, "data", "cleaned")
if len(cleaned) != 1 {
t.Fatalf("expected one cleaned abandoned attempt, got %#v", cleaned)
}
cleanedAttempt, ok := cleaned[0].(map[string]any)
if !ok {
t.Fatalf("expected cleaned attempt object, got %#v", cleaned[0])
}
if got, _ := cleanedAttempt["attempt_no"].(float64); got != 1 {
t.Fatalf("expected cleaned attempt 1, got %#v", cleanedAttempt["attempt_no"])
}
if _, err := os.Stat(originalWorktreePath); !os.IsNotExist(err) {
t.Fatalf("expected original abandoned worktree to be removed, err=%v", err)
}
if _, err := os.Stat(replacementWorktreePath); err != nil {
t.Fatalf("expected replacement worktree to remain, err=%v", err)
}
_, workspaceStatus, _ := queryAttemptStateForRecoveryCleanupTests(t, dbPath, "run_blog_cleanup_extra_003", "T1", 1)
if workspaceStatus != "cleaned" {
t.Fatalf("expected cleaned workspace_status for attempt 1, got %q", workspaceStatus)
}
}
func seedCodeModeDispatchedTaskForRecoveryCleanupTests(t *testing.T, dbPath, runID, taskID, agent string) (string, string) {
t.Helper()
repoPath := initGitRepo(t)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Prepare code-mode task for recovery and cleanup tests",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", "Prepare code-mode task",
"--default-to", agent,
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", taskID,
"--execution-mode", "code",
"--repo-path", repoPath,
"--workspace-root", ".orch/worktrees",
"--to", agent,
"--body", "Prepare isolated worktree.",
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
return nestedString(t, dispatchResp, "data", "attempt", "thread_id"), nestedString(t, dispatchResp, "data", "attempt", "worktree_path")
}
func seedFailedCodeModeTaskForRecoveryCleanupTests(t *testing.T, dbPath, runID, taskID, agent string) (string, string) {
t.Helper()
threadID, worktreePath := seedCodeModeDispatchedTaskForRecoveryCleanupTests(t, dbPath, runID, taskID, agent)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", agent,
"--thread", threadID,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"fail",
"--agent", agent,
"--thread", threadID,
"--summary", "Code attempt failed",
"--body", "The code attempt failed before completion.",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
return threadID, worktreePath
}
func seedBlockedCodeModeTaskForRecoveryCleanupTests(t *testing.T, dbPath, runID, taskID, agent string) (string, string) {
t.Helper()
threadID, worktreePath := seedCodeModeDispatchedTaskForRecoveryCleanupTests(t, dbPath, runID, taskID, agent)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", agent,
"--thread", threadID,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"update",
"--agent", agent,
"--thread", threadID,
"--status", "blocked",
"--summary", "Need code review decision",
"--payload-json", `{"question":"Proceed with the current worktree?"}`,
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
return threadID, worktreePath
}
func queryAttemptStateForRecoveryCleanupTests(t *testing.T, dbPath, runID, taskID string, attemptNo int) (string, string, string) {
t.Helper()
sqlDB, err := openOrchDB(context.Background(), dbPath)
if err != nil {
t.Fatalf("open orch db: %v", err)
}
defer sqlDB.Close()
row := sqlDB.QueryRowContext(
context.Background(),
`SELECT status, COALESCE(workspace_status, ''), COALESCE(worktree_path, '')
FROM task_attempts
WHERE run_id = ? AND task_id = ? AND attempt_no = ?`,
runID,
taskID,
attemptNo,
)
var status string
var workspaceStatus string
var worktreePath string
if err := row.Scan(&status, &workspaceStatus, &worktreePath); err != nil {
t.Fatalf("scan attempt state for %s/%s/%d: %v", runID, taskID, attemptNo, err)
}
return status, workspaceStatus, worktreePath
}
func setThreadStatusForRecoveryCleanupTests(t *testing.T, dbPath, threadID, status string) {
t.Helper()
sqlDB, err := openOrchDB(context.Background(), dbPath)
if err != nil {
t.Fatalf("open orch db: %v", err)
}
defer sqlDB.Close()
if _, err := sqlDB.ExecContext(context.Background(), `UPDATE threads SET status = ? WHERE thread_id = ?`, status, threadID); err != nil {
t.Fatalf("update thread %s status to %s: %v", threadID, status, err)
}
}
@@ -3,12 +3,16 @@ package orch
import (
"bytes"
"encoding/json"
"os"
"os/exec"
"path/filepath"
"runtime"
"sync"
"testing"
)
var orchCommandDirMu sync.Mutex
func runOrchCommand(t *testing.T, args ...string) string {
t.Helper()
@@ -27,6 +31,35 @@ func executeOrchCommand(args ...string) (string, string, int) {
return stdout.String(), stderr.String(), exitCode
}
func executeOrchCommandInDir(dir string, args ...string) (string, string, int) {
orchCommandDirMu.Lock()
defer orchCommandDirMu.Unlock()
cwd, err := os.Getwd()
if err != nil {
return "", err.Error(), 1
}
if err := os.Chdir(dir); err != nil {
return "", err.Error(), 1
}
defer func() {
if err := os.Chdir(cwd); err != nil {
panic(err)
}
}()
return executeOrchCommand(args...)
}
func orchCommandPath() string {
_, file, _, ok := runtime.Caller(0)
if !ok {
panic("unable to determine orch test helper path")
}
return filepath.Join(filepath.Dir(file), "..", "..", "..", "cmd", "orch")
}
func runInboxCommand(t *testing.T, args ...string) string {
t.Helper()
@@ -0,0 +1,632 @@
package orch
import (
"path/filepath"
"strings"
"testing"
)
// TestOrchVerifyRecordRejectsInvalidStatus verifies verify record rejects unsupported check statuses.
func TestOrchVerifyRecordRejectsInvalidStatus(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, "run_verify_additional_001", "T1", []string{"lint"})
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_additional_001",
"--task", "T1",
"--check", "lint",
"--status", "maybe",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "check status must be one of passed, failed, skipped")
}
// TestOrchVerifyRecordRejectsInvalidMetadataJSON verifies verify record rejects malformed metadata JSON.
func TestOrchVerifyRecordRejectsInvalidMetadataJSON(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, "run_verify_additional_002", "T1", []string{"lint"})
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_additional_002",
"--task", "T1",
"--check", "lint",
"--status", "passed",
"--metadata-json", `{"trace":`,
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "metadata-json must be valid JSON")
}
// TestOrchVerifyRecordRejectsNonLatestAttempt verifies verify record only accepts the latest attempt.
func TestOrchVerifyRecordRejectsNonLatestAttempt(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "run_verify_additional_003"
seedVerifyingTaskForVerifyContractTests(t, dbPath, runID, "T1")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", runID,
"--task", "T1",
"--check", "lint",
"--status", "failed",
"--summary", "lint failed on attempt 1",
)
retryOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"retry",
"--run", runID,
"--task", "T1",
"--body", "Retry after fixing lint failures.",
)
var retryResp map[string]any
mustDecodeJSON(t, retryOut, &retryResp)
threadID := nestedString(t, retryResp, "data", "attempt", "thread_id")
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", "worker-a",
"--thread", threadID,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"done",
"--agent", "worker-a",
"--thread", threadID,
"--summary", "Attempt 2 complete",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"verify", "record",
"--run", runID,
"--task", "T1",
"--attempt", "1",
"--check", "lint",
"--status", "passed",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "latest attempt")
}
// TestOrchVerifyRecordRejectsTaskWithoutAttempt verifies verify record rejects tasks that were never dispatched.
func TestOrchVerifyRecordRejectsTaskWithoutAttempt(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_verify_additional_004",
"--goal", "Seed task without attempts",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_verify_additional_004",
"--task", "T1",
"--title", "Undispatched task",
"--required-check", "lint",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_additional_004",
"--task", "T1",
"--check", "lint",
"--status", "passed",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "has no attempt to verify")
}
// TestOrchVerifyRecordSkippedCheckKeepsGatePending verifies skipped checks remain pending instead of completing the gate.
func TestOrchVerifyRecordSkippedCheckKeepsGatePending(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, "run_verify_additional_005", "T1", []string{"lint"})
recordOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_additional_005",
"--task", "T1",
"--check", "lint",
"--status", "skipped",
"--summary", "lint intentionally skipped",
)
var recordResp map[string]any
mustDecodeJSON(t, recordOut, &recordResp)
if got := nestedString(t, recordResp, "data", "check", "status"); got != "skipped" {
t.Fatalf("expected skipped check status, got %q", got)
}
if got := nestedString(t, recordResp, "data", "task", "status"); got != "verifying" {
t.Fatalf("expected task to stay verifying, got %q", got)
}
if got := nestedString(t, recordResp, "data", "gate", "status"); got != "pending" {
t.Fatalf("expected pending gate after skipped check, got %q", got)
}
pendingChecks := nestedArray(t, recordResp, "data", "gate", "pending_checks")
if len(pendingChecks) != 1 || pendingChecks[0] != "lint" {
t.Fatalf("expected lint to remain pending, got %#v", pendingChecks)
}
}
// TestOrchVerifyStatusReportsNoGateInPlainText verifies verify status prints a no-gate message for ungated tasks.
func TestOrchVerifyStatusReportsNoGateInPlainText(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_verify_additional_006",
"--goal", "Seed ungated verify status task",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_verify_additional_006",
"--task", "T1",
"--title", "Ungated task",
)
stdout, stderr, exitCode := executeOrchCommand(
"--db", dbPath,
"verify", "status",
"--run", "run_verify_additional_006",
"--task", "T1",
)
if exitCode != 0 {
t.Fatalf("expected verify status exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
if !strings.Contains(stdout, "run_verify_additional_006/T1 has no verification gate") {
t.Fatalf("expected no-gate plain-text output, got:\n%s", stdout)
}
}
// TestOrchVerifyStatusHelpExplainsGateInspection verifies verify status help explains gate inspection scope.
func TestOrchVerifyStatusHelpExplainsGateInspection(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeOrchCommand("verify", "status", "--help")
if exitCode != 0 {
t.Fatalf("expected help exit 0, got %d\nstderr:\n%s\nstdout:\n%s", exitCode, stderr, stdout)
}
combined := stdout + stderr
if !strings.Contains(combined, "task spec snapshot") {
t.Fatalf("expected verify status help to mention the task spec snapshot, got:\n%s", combined)
}
if !strings.Contains(combined, "stuck in verifying or failed") {
t.Fatalf("expected verify status help to explain verifying/failed inspection, got:\n%s", combined)
}
}
// TestOrchCouncilStartRejectsInvalidInputs verifies council start rejects missing targets and invalid enum values.
func TestOrchCouncilStartRejectsInvalidInputs(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
cases := []struct {
name string
args []string
message string
}{
{
name: "missing target",
args: []string{
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_invalid_missing_target_001",
},
message: "at least one of target, target-file, repo-path, or task-id is required",
},
{
name: "invalid mode",
args: []string{
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_invalid_mode_001",
"--target", "Review the current design.",
"--mode", "plan",
},
message: "mode must be brainstorm or review",
},
{
name: "invalid target type",
args: []string{
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_invalid_target_type_001",
"--target", "Review the current design.",
"--target-type", "doc",
},
message: "target-type must be text, repo, or mixed",
},
{
name: "invalid output",
args: []string{
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_invalid_output_001",
"--target", "Review the current design.",
"--output", "html",
},
message: "output must be markdown, json, or both",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
stdout, _, exitCode := executeOrchCommand(tc.args...)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, tc.message)
})
}
}
// TestOrchCouncilStartRejectsDuplicateRun verifies council start refuses to reuse an existing run ID.
func TestOrchCouncilStartRejectsDuplicateRun(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_duplicate_run_001",
"--target", "Review the current design.",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_duplicate_run_001",
"--target", "Review the current design again.",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "already exists")
}
// TestOrchCouncilWaitTreatsFailedAndCancelledReviewersAsComplete verifies council wait wakes once reviewers are terminal, even when some failed or were cancelled.
func TestOrchCouncilWaitTreatsFailedAndCancelledReviewersAsComplete(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_wait_terminals_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", runID,
"--target", "Review the council workflow terminals.",
)
completeCouncilReviewer(t, dbPath, runID, "architecture-reviewer", `{"reviewer_role":"architecture-reviewer","findings":[{"title":"Keep queue small","summary":"One reviewer completed successfully.","proposal":"Keep the council wait contract explicit.","rationale":"This protects terminal-state handling.","confidence":"high","tags":["workflow"],"target_refs":{"repo_path":"."}}]}`)
failCouncilReviewerForAdditionalTests(t, dbPath, runID, "implementation-reviewer", "Implementation review failed")
cancelCouncilReviewerForAdditionalTests(t, dbPath, runID, "risk-reviewer", "Risk review superseded")
waitOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "wait",
"--run", runID,
"--timeout-seconds", "2",
)
var waitResp map[string]any
mustDecodeJSON(t, waitOut, &waitResp)
if woke, _ := nestedValue(t, waitResp, "data", "woke").(bool); !woke {
t.Fatalf("expected council wait to wake, got %#v", waitResp)
}
if allComplete, _ := nestedValue(t, waitResp, "data", "all_complete").(bool); !allComplete {
t.Fatalf("expected all_complete true, got %#v", waitResp)
}
statuses := reviewerStatusesByRole(t, waitResp)
if statuses["architecture-reviewer"] != "done" || statuses["implementation-reviewer"] != "failed" || statuses["risk-reviewer"] != "cancelled" {
t.Fatalf("unexpected reviewer statuses: %#v", statuses)
}
}
// TestOrchCouncilTallyRejectsInvalidSimilarity verifies council tally rejects unsupported similarity modes.
func TestOrchCouncilTallyRejectsInvalidSimilarity(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "tally",
"--run", "council_invalid_similarity_001",
"--similarity", "loose",
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "similarity must be strict or normal")
}
// TestOrchCouncilTallyRejectsUnfinishedReviewers verifies council tally refuses runs with incomplete reviewer work.
func TestOrchCouncilTallyRejectsUnfinishedReviewers(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_tally_unfinished_001",
"--target", "Review unfinished council behavior.",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "tally",
"--run", "council_tally_unfinished_001",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "not complete yet")
}
// TestOrchCouncilTallyRejectsFailedReviewer verifies council tally refuses reviewer sets that did not all finish successfully.
func TestOrchCouncilTallyRejectsFailedReviewer(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_tally_failed_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", runID,
"--target", "Review failed reviewer handling.",
)
completeCouncilReviewer(t, dbPath, runID, "architecture-reviewer", `{"reviewer_role":"architecture-reviewer","findings":[{"title":"Use explicit gates","summary":"Architecture reviewer completed.","proposal":"Keep council tally strict about success.","rationale":"This avoids mixed-quality reports.","confidence":"high","tags":["architecture"],"target_refs":{"repo_path":"."}}]}`)
completeCouncilReviewer(t, dbPath, runID, "risk-reviewer", `{"reviewer_role":"risk-reviewer","findings":[{"title":"Keep failure visible","summary":"Risk reviewer completed.","proposal":"Preserve terminal reviewer status.","rationale":"Operators need the final state.","confidence":"medium","tags":["risk"],"target_refs":{"repo_path":"."}}]}`)
failCouncilReviewerForAdditionalTests(t, dbPath, runID, "implementation-reviewer", "Implementation reviewer could not finish")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "tally",
"--run", runID,
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
assertErrorMessageContains(t, stdout, "did not finish successfully")
}
// TestOrchCouncilTallyRejectsInvalidReviewerJSON verifies council tally rejects reviewer result bodies that are not JSON.
func TestOrchCouncilTallyRejectsInvalidReviewerJSON(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_tally_invalid_json_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", runID,
"--target", "Review invalid council reviewer JSON.",
)
completeCouncilReviewer(t, dbPath, runID, "architecture-reviewer", `{"reviewer_role":"architecture-reviewer","findings":[{"title":"Keep it valid","summary":"Architecture reviewer completed.","proposal":"Use valid JSON payloads.","rationale":"The tally step requires structured input.","confidence":"high","tags":["contracts"],"target_refs":{"repo_path":"."}}]}`)
completeCouncilReviewer(t, dbPath, runID, "risk-reviewer", `{"reviewer_role":"risk-reviewer","findings":[{"title":"Validate inputs","summary":"Risk reviewer completed.","proposal":"Reject malformed reviewer payloads.","rationale":"This keeps the council run deterministic.","confidence":"high","tags":["risk"],"target_refs":{"repo_path":"."}}]}`)
completeCouncilReviewer(t, dbPath, runID, "implementation-reviewer", "not-json")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "tally",
"--run", runID,
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "reviewer output must be valid JSON")
}
// TestOrchCouncilTallyRejectsReviewerRoleMismatch verifies council tally rejects reviewer outputs with mismatched reviewer_role values.
func TestOrchCouncilTallyRejectsReviewerRoleMismatch(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "council_tally_role_mismatch_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", runID,
"--target", "Review council reviewer role mismatches.",
)
completeCouncilReviewer(t, dbPath, runID, "architecture-reviewer", `{"reviewer_role":"architecture-reviewer","findings":[{"title":"Match reviewer roles","summary":"Architecture reviewer completed.","proposal":"Keep reviewer metadata aligned.","rationale":"The tally step groups by reviewer role.","confidence":"medium","tags":["contracts"],"target_refs":{"repo_path":"."}}]}`)
completeCouncilReviewer(t, dbPath, runID, "risk-reviewer", `{"reviewer_role":"risk-reviewer","findings":[{"title":"Keep roles explicit","summary":"Risk reviewer completed.","proposal":"Validate reviewer_role during tally.","rationale":"This blocks malformed council output.","confidence":"high","tags":["risk"],"target_refs":{"repo_path":"."}}]}`)
completeCouncilReviewer(t, dbPath, runID, "implementation-reviewer", `{"reviewer_role":"risk-reviewer","findings":[{"title":"Wrong role","summary":"Role intentionally mismatched.","proposal":"Reject this output.","rationale":"The implementation reviewer should not impersonate another reviewer.","confidence":"high","tags":["contracts"],"target_refs":{"repo_path":"."}}]}`)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"council", "tally",
"--run", runID,
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
assertErrorMessageContains(t, stdout, "does not match expected implementation-reviewer")
}
func failCouncilReviewerForAdditionalTests(t *testing.T, dbPath, runID, reviewerRole, summary string) {
t.Helper()
threadID := councilReviewerThreadIDForAdditionalTests(t, dbPath, runID, reviewerRole)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", reviewerRole,
"--thread", threadID,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"fail",
"--agent", reviewerRole,
"--thread", threadID,
"--summary", summary,
)
}
func cancelCouncilReviewerForAdditionalTests(t *testing.T, dbPath, runID, reviewerRole, reason string) {
t.Helper()
threadID := councilReviewerThreadIDForAdditionalTests(t, dbPath, runID, reviewerRole)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"cancel",
"--agent", "leader",
"--thread", threadID,
"--reason", reason,
)
}
func councilReviewerThreadIDForAdditionalTests(t *testing.T, dbPath, runID, reviewerRole string) string {
t.Helper()
sqlDB, err := openOrchDB(t.Context(), dbPath)
if err != nil {
t.Fatalf("open orch db: %v", err)
}
defer sqlDB.Close()
var threadID string
if err := sqlDB.QueryRowContext(
t.Context(),
`SELECT a.thread_id
FROM council_reviewers cr
JOIN task_attempts a
ON a.run_id = cr.run_id
AND a.task_id = cr.task_id
AND a.attempt_no = 1
WHERE cr.run_id = ? AND cr.reviewer_role = ?`,
runID,
reviewerRole,
).Scan(&threadID); err != nil {
t.Fatalf("query council reviewer thread for %s: %v", reviewerRole, err)
}
return threadID
}
func reviewerStatusesByRole(t *testing.T, payload map[string]any) map[string]string {
t.Helper()
reviewers := nestedArray(t, payload, "data", "reviewers")
statuses := make(map[string]string, len(reviewers))
for _, item := range reviewers {
reviewer, ok := item.(map[string]any)
if !ok {
t.Fatalf("expected reviewer object, got %#v", item)
}
role, _ := reviewer["reviewer_role"].(string)
status, _ := reviewer["status"].(string)
statuses[role] = status
}
return statuses
}