Add inbox and orch command contract tests

This commit is contained in:
2026-03-24 09:06:30 +08:00
parent fd2b57feaf
commit ea3b617ad1
12 changed files with 2088 additions and 9 deletions
@@ -0,0 +1,233 @@
package orch
import (
"os"
"path/filepath"
"testing"
)
// TestOrchCleanupAllCompletedRemovesMultipleWorktrees verifies cleanup removes every completed worktree when --all-completed is used.
func TestOrchCleanupAllCompletedRemovesMultipleWorktrees(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
repoPath := initGitRepo(t)
runID := "run_blog_cleanup_all_completed_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Validate cleanup across multiple completed worktrees",
)
worktreeOne := seedCompletedCodeTaskForCleanupCouncilTests(t, dbPath, repoPath, runID, "T1", "worker-a")
worktreeTwo := seedCompletedCodeTaskForCleanupCouncilTests(t, dbPath, repoPath, runID, "T2", "worker-b")
cleanupOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"cleanup",
"--run", runID,
"--all-completed",
)
var cleanupResp map[string]any
mustDecodeJSON(t, cleanupOut, &cleanupResp)
cleaned := nestedArray(t, cleanupResp, "data", "cleaned")
if len(cleaned) != 2 {
t.Fatalf("expected two cleaned attempts, got %#v", cleaned)
}
for _, worktreePath := range []string{worktreeOne, worktreeTwo} {
if _, err := os.Stat(worktreePath); !os.IsNotExist(err) {
t.Fatalf("expected cleaned worktree path %q to be removed, err=%v", worktreePath, err)
}
}
}
// TestOrchCleanupForceRemovesCompletedWorktrees verifies cleanup accepts --force while removing completed worktrees selected by --all-completed.
func TestOrchCleanupForceRemovesCompletedWorktrees(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
repoPath := initGitRepo(t)
runID := "run_blog_cleanup_force_001"
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Validate cleanup force flag acceptance",
)
worktreePath := seedCompletedCodeTaskForCleanupCouncilTests(t, dbPath, repoPath, runID, "T1", "worker-a")
cleanupOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"cleanup",
"--run", runID,
"--all-completed",
"--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 attempt with --force, got %#v", cleaned)
}
if _, err := os.Stat(worktreePath); !os.IsNotExist(err) {
t.Fatalf("expected forced cleanup worktree path %q to be removed, err=%v", worktreePath, err)
}
}
// TestOrchCouncilStartPersistsCustomInputs verifies council start stores explicit target-file, repo-path, task-id, review mode, and only-unanimous settings.
func TestOrchCouncilStartPersistsCustomInputs(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
repoPath := initGitRepo(t)
targetFile := filepath.Join(tempDir, "target.md")
if err := os.WriteFile(targetFile, []byte("# Review target\n\nInspect the API changes.\n"), 0o644); err != nil {
t.Fatalf("write council target file: %v", err)
}
startOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"council", "start",
"--run", "council_blog_custom_inputs_001",
"--target-file", targetFile,
"--repo-path", repoPath,
"--task-id", "TASK-42",
"--target-type", "repo",
"--mode", "review",
"--output", "json",
"--only-unanimous",
)
var startResp map[string]any
mustDecodeJSON(t, startOut, &startResp)
if got := nestedString(t, startResp, "data", "run_id"); got != "council_blog_custom_inputs_001" {
t.Fatalf("expected council run id, got %q", got)
}
if got := nestedString(t, startResp, "data", "mode"); got != "review" {
t.Fatalf("expected council mode review, got %q", got)
}
reviewers := nestedArray(t, startResp, "data", "reviewers")
if len(reviewers) != 3 {
t.Fatalf("expected three reviewers, got %#v", reviewers)
}
sqlDB, err := openOrchDB(t.Context(), dbPath)
if err != nil {
t.Fatalf("open orch db: %v", err)
}
defer sqlDB.Close()
var (
mode string
targetType string
outputMode string
onlyUnanimous int
)
if err := sqlDB.QueryRowContext(
t.Context(),
`SELECT mode, target_type, output_mode, only_unanimous
FROM council_runs
WHERE run_id = ?`,
"council_blog_custom_inputs_001",
).Scan(&mode, &targetType, &outputMode, &onlyUnanimous); err != nil {
t.Fatalf("query council_runs: %v", err)
}
if mode != "review" || targetType != "repo" || outputMode != "json" || onlyUnanimous != 1 {
t.Fatalf("unexpected council run metadata: mode=%q targetType=%q outputMode=%q onlyUnanimous=%d", mode, targetType, outputMode, onlyUnanimous)
}
var (
storedTargetFile string
storedRepoPath string
storedTaskID string
)
if err := sqlDB.QueryRowContext(
t.Context(),
`SELECT target_file, repo_path, target_task_id
FROM council_inputs
WHERE run_id = ?`,
"council_blog_custom_inputs_001",
).Scan(&storedTargetFile, &storedRepoPath, &storedTaskID); err != nil {
t.Fatalf("query council_inputs: %v", err)
}
if storedTargetFile != targetFile || storedRepoPath != repoPath || storedTaskID != "TASK-42" {
t.Fatalf("unexpected council inputs: targetFile=%q repoPath=%q taskID=%q", storedTargetFile, storedRepoPath, storedTaskID)
}
}
func seedCompletedCodeTaskForCleanupCouncilTests(t *testing.T, dbPath, repoPath, runID, taskID, agent string) string {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", "Complete cleanup target "+taskID,
"--default-to", agent,
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", taskID,
"--execution-mode", "code",
"--repo-path", repoPath,
"--workspace-root", ".orch/worktrees",
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
worktreePath := nestedString(t, dispatchResp, "data", "attempt", "worktree_path")
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", agent,
"--thread", threadID,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"done",
"--agent", agent,
"--thread", threadID,
"--summary", "Completed cleanup target "+taskID,
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
return worktreePath
}
@@ -0,0 +1,330 @@
package orch
import (
"os"
"path/filepath"
"testing"
)
// TestOrchTaskAddRejectsInvalidMetadataJSON verifies orch task add rejects invalid metadata JSON.
func TestOrchTaskAddRejectsInvalidMetadataJSON(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_008",
"--goal", "Validate task metadata input",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_008",
"--task", "T1",
"--title", "Implement retry policy",
"--metadata-json", `{"repo":`,
)
if exitCode != 30 {
t.Fatalf("expected invalid_input exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
}
// TestOrchDepAddCreatesDependencyAndAffectsReadyState verifies orch dep add returns the dependency and blocks the dependent task from ready output.
func TestOrchDepAddCreatesDependencyAndAffectsReadyState(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_dep_001",
"--goal", "Validate dependency contracts",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_dep_001",
"--task", "T1",
"--title", "Prerequisite task",
"--default-to", "worker-a",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_dep_001",
"--task", "T2",
"--title", "Dependent task",
"--default-to", "worker-b",
)
depOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dep", "add",
"--run", "run_blog_dep_001",
"--task", "T2",
"--depends-on", "T1",
)
var depResp map[string]any
mustDecodeJSON(t, depOut, &depResp)
if got := nestedString(t, depResp, "data", "dependency", "run_id"); got != "run_blog_dep_001" {
t.Fatalf("expected dependency run_id run_blog_dep_001, got %q", got)
}
if got := nestedString(t, depResp, "data", "dependency", "task_id"); got != "T2" {
t.Fatalf("expected dependency task_id T2, got %q", got)
}
if got := nestedString(t, depResp, "data", "dependency", "depends_on_task_id"); got != "T1" {
t.Fatalf("expected dependency depends_on_task_id T1, got %q", got)
}
readyOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"ready",
"--run", "run_blog_dep_001",
)
var readyResp map[string]any
mustDecodeJSON(t, readyOut, &readyResp)
readyTasks := nestedArray(t, readyResp, "data", "tasks")
if len(readyTasks) != 1 {
t.Fatalf("expected one ready task after dependency add, got %#v", readyTasks)
}
readyTask, ok := readyTasks[0].(map[string]any)
if !ok {
t.Fatalf("expected ready task object, got %#v", readyTasks[0])
}
if got, _ := readyTask["task_id"].(string); got != "T1" {
t.Fatalf("expected only prerequisite task T1 to remain ready, got %#v", readyTask["task_id"])
}
}
// TestOrchDepAddRejectsMissingDependencyTask verifies orch dep add rejects a dependency on a missing task.
func TestOrchDepAddRejectsMissingDependencyTask(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_dep_002",
"--goal", "Validate dependency not found",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_dep_002",
"--task", "T2",
"--title", "Dependent task",
"--default-to", "worker-b",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dep", "add",
"--run", "run_blog_dep_002",
"--task", "T2",
"--depends-on", "T9",
)
if exitCode != 40 {
t.Fatalf("expected not_found exit code 40, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "not_found")
}
// TestOrchDepAddRejectsMissingTask verifies orch dep add rejects a missing source task.
func TestOrchDepAddRejectsMissingTask(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_blog_dep_003",
"--goal", "Validate source task not found",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_blog_dep_003",
"--task", "T1",
"--title", "Prerequisite task",
"--default-to", "worker-a",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"dep", "add",
"--run", "run_blog_dep_003",
"--task", "T9",
"--depends-on", "T1",
)
if exitCode != 40 {
t.Fatalf("expected not_found exit code 40, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "not_found")
}
// TestOrchVerifyStatusReturnsTaskAttemptSpecAndGate verifies orch verify status returns task, attempt, spec, and gate details.
func TestOrchVerifyStatusReturnsTaskAttemptSpecAndGate(t *testing.T) {
t.Parallel()
dbPath, specFile := seedVerifyStatusTaskForCoreContracts(t, "run_verify_core_001", true)
verifyStatusOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "status",
"--run", "run_verify_core_001",
"--task", "T1",
)
var verifyStatusResp map[string]any
mustDecodeJSON(t, verifyStatusOut, &verifyStatusResp)
if got := nestedString(t, verifyStatusResp, "data", "task", "task_id"); got != "T1" {
t.Fatalf("expected task_id T1, got %q", got)
}
if got := nestedString(t, verifyStatusResp, "data", "task", "status"); got != "verifying" {
t.Fatalf("expected task status verifying, got %q", got)
}
if got := nestedString(t, verifyStatusResp, "data", "attempt", "assigned_to"); got != "worker-a" {
t.Fatalf("expected attempt assigned_to worker-a, got %q", got)
}
if got := nestedString(t, verifyStatusResp, "data", "spec", "spec_file"); got != specFile {
t.Fatalf("expected spec_file %q, got %q", specFile, got)
}
requiredChecks := nestedArray(t, verifyStatusResp, "data", "gate", "required_checks")
if len(requiredChecks) != 1 || requiredChecks[0] != "lint" {
t.Fatalf("expected gate required_checks [lint], got %#v", requiredChecks)
}
pendingChecks := nestedArray(t, verifyStatusResp, "data", "gate", "pending_checks")
if len(pendingChecks) != 1 || pendingChecks[0] != "lint" {
t.Fatalf("expected gate pending_checks [lint], got %#v", pendingChecks)
}
}
// TestOrchVerifyStatusRejectsMissingExplicitAttempt verifies orch verify status rejects an explicit attempt that does not exist.
func TestOrchVerifyStatusRejectsMissingExplicitAttempt(t *testing.T) {
t.Parallel()
dbPath, _ := seedVerifyStatusTaskForCoreContracts(t, "run_verify_core_002", false)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"verify", "status",
"--run", "run_verify_core_002",
"--task", "T1",
"--attempt", "2",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
}
func seedVerifyStatusTaskForCoreContracts(t *testing.T, runID string, moveToVerifying bool) (string, string) {
t.Helper()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
specFile := filepath.Join(tempDir, "task.md")
if err := os.WriteFile(specFile, []byte("# Task\n\nShip the verifier-backed component change.\n"), 0o644); err != nil {
t.Fatalf("write spec file: %v", err)
}
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Seed verify status contract task",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", "T1",
"--title", "Implement verifier-backed task",
"--default-to", "worker-a",
"--spec-file", specFile,
"--required-check", "lint",
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", "T1",
"--execution-mode", "analysis",
"--body", "Implement the gated task.",
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
threadID := nestedString(t, dispatchResp, "data", "attempt", "thread_id")
if moveToVerifying {
runInboxCommand(
t,
"--db", dbPath,
"--json",
"claim",
"--agent", "worker-a",
"--thread", threadID,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"done",
"--agent", "worker-a",
"--thread", threadID,
"--summary", "Implementation finished",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
}
return dbPath, specFile
}
@@ -0,0 +1,136 @@
package orch
import (
"os"
"path/filepath"
"strings"
"testing"
)
// TestOrchAnswerPropagatesBodyFileContent verifies orch answer reads the leader response body from a file.
func TestOrchAnswerPropagatesBodyFileContent(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
bodyPath := filepath.Join(tempDir, "answer.md")
body := "Use stdout for the MVP logs.\nKeep stderr for unexpected failures.\n"
if err := os.WriteFile(bodyPath, []byte(body), 0o644); err != nil {
t.Fatalf("write answer body file: %v", err)
}
threadID := seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_blog_answer_file_001", "T2", "worker-b")
answerOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"answer",
"--run", "run_blog_answer_file_001",
"--task", "T2",
"--body-file", bodyPath,
)
var answerResp map[string]any
mustDecodeJSON(t, answerOut, &answerResp)
if got := nestedString(t, answerResp, "data", "message", "kind"); got != "answer" {
t.Fatalf("expected answer message kind, got %q", got)
}
if got := nestedString(t, answerResp, "data", "message", "body"); got != body {
t.Fatalf("expected answer body %q, got %q", body, got)
}
showOut := runInboxCommand(
t,
"--db", dbPath,
"--json",
"show",
"--thread", threadID,
)
var showResp map[string]any
mustDecodeJSON(t, showOut, &showResp)
messages := nestedArray(t, showResp, "data", "messages")
lastMessage, ok := messages[len(messages)-1].(map[string]any)
if !ok {
t.Fatalf("expected last message object, got %#v", messages[len(messages)-1])
}
if got, _ := lastMessage["body"].(string); got != body {
t.Fatalf("expected latest inbox message body %q, got %#v", body, lastMessage["body"])
}
}
// TestOrchAnswerRejectsWithoutActiveBlockedQuestion verifies orch answer rejects tasks without an active blocked question.
func TestOrchAnswerRejectsWithoutActiveBlockedQuestion(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedDispatchedTaskForAnswerEdgeTests(t, dbPath, "run_blog_answer_ready_001", "T2", "worker-b")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"answer",
"--run", "run_blog_answer_ready_001",
"--task", "T2",
"--body", "Use stdout for the MVP logs.",
)
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, "blocked")
}
// TestOrchBlockedHelpMentionsCompactQueue verifies blocked help explains the compact queue leaders inspect before answering.
func TestOrchBlockedHelpMentionsCompactQueue(t *testing.T) {
t.Parallel()
stdout, stderr, exitCode := executeOrchCommand("blocked", "--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, "latest question the leader needs to answer") {
t.Fatalf("expected blocked help to explain latest question role, got:\n%s", combined)
}
if !strings.Contains(combined, "Use blocked before answer") {
t.Fatalf("expected blocked help to explain inspect-before-answer flow, got:\n%s", combined)
}
if !strings.Contains(combined, "compact queue of unresolved worker questions") {
t.Fatalf("expected blocked help to explain compact queue role, got:\n%s", combined)
}
}
func seedDispatchedTaskForAnswerEdgeTests(t *testing.T, dbPath, runID, taskID, agent string) {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Prepare dispatched task for answer edge tests",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", "Build frontend",
"--default-to", agent,
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", taskID,
"--execution-mode", "analysis",
)
}
@@ -0,0 +1,547 @@
package orch
import (
"os"
"path/filepath"
"testing"
)
// TestOrchDispatchReadsBodyFromBodyFileAndCarriesSpecPayload verifies dispatch loads body-file input and includes spec and verification policy in the task payload.
func TestOrchDispatchReadsBodyFromBodyFileAndCarriesSpecPayload(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
specFile := filepath.Join(tempDir, "task.md")
bodyFile := filepath.Join(tempDir, "dispatch-body.txt")
specBody := "# Task\n\nImplement the spec-aware worker handoff.\n"
dispatchBody := "Worker brief loaded from file.\n"
if err := os.WriteFile(specFile, []byte(specBody), 0o644); err != nil {
t.Fatalf("write spec file: %v", err)
}
if err := os.WriteFile(bodyFile, []byte(dispatchBody), 0o644); err != nil {
t.Fatalf("write body file: %v", err)
}
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_dispatch_contracts_001",
"--goal", "Validate dispatch payload contracts",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_dispatch_contracts_001",
"--task", "T1",
"--title", "Implement worker handoff",
"--summary", "Ship the spec-aware task payload",
"--default-to", "worker-a",
"--spec-file", specFile,
"--check-profile", "cadence_component",
"--required-check", "lint",
"--allowed-path", "packages/orch-runtime",
"--blocked-path", "scripts/release.sh",
"--metadata-json", `{"team":"core"}`,
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_dispatch_contracts_001",
"--task", "T1",
"--execution-mode", "analysis",
"--to", "worker-a",
"--body-file", bodyFile,
)
var dispatchResp map[string]any
mustDecodeJSON(t, dispatchOut, &dispatchResp)
if got := nestedString(t, dispatchResp, "data", "message", "body"); got != dispatchBody {
t.Fatalf("expected dispatch body %q, got %q", dispatchBody, got)
}
if got := nestedString(t, dispatchResp, "data", "task", "status"); got != "dispatched" {
t.Fatalf("expected task status dispatched, got %q", got)
}
payload, ok := nestedValue(t, dispatchResp, "data", "message", "payload_json").(map[string]any)
if !ok {
t.Fatalf("expected payload_json object, got %#v", nestedValue(t, dispatchResp, "data", "message", "payload_json"))
}
if got, _ := payload["execution_mode"].(string); got != "analysis" {
t.Fatalf("expected execution_mode analysis, got %#v", payload["execution_mode"])
}
spec, ok := payload["spec"].(map[string]any)
if !ok {
t.Fatalf("expected payload spec object, got %#v", payload["spec"])
}
if got, _ := spec["file"].(string); got != specFile {
t.Fatalf("expected spec file %q, got %#v", specFile, spec["file"])
}
if got, _ := spec["body"].(string); got != specBody {
t.Fatalf("expected spec body %q, got %#v", specBody, spec["body"])
}
if got, _ := spec["check_profile"].(string); got != "cadence_component" {
t.Fatalf("expected check_profile cadence_component, got %#v", spec["check_profile"])
}
requiredChecks, ok := spec["required_checks"].([]any)
if !ok || len(requiredChecks) != 1 || requiredChecks[0] != "lint" {
t.Fatalf("expected required_checks [lint], got %#v", spec["required_checks"])
}
allowedPaths, ok := spec["allowed_paths"].([]any)
if !ok || len(allowedPaths) != 1 || allowedPaths[0] != "packages/orch-runtime" {
t.Fatalf("expected allowed_paths [packages/orch-runtime], got %#v", spec["allowed_paths"])
}
blockedPaths, ok := spec["blocked_paths"].([]any)
if !ok || len(blockedPaths) != 1 || blockedPaths[0] != "scripts/release.sh" {
t.Fatalf("expected blocked_paths [scripts/release.sh], got %#v", spec["blocked_paths"])
}
metadata, ok := spec["metadata"].(map[string]any)
if !ok {
t.Fatalf("expected payload spec metadata object, got %#v", spec["metadata"])
}
if got, _ := metadata["team"].(string); got != "core" {
t.Fatalf("expected payload spec metadata.team core, got %#v", metadata["team"])
}
}
// TestOrchVerifyRecordMovesTaskToFailedOnFailedCheck verifies verify record moves a verifying task to failed when a required check fails.
func TestOrchVerifyRecordMovesTaskToFailedOnFailedCheck(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, "run_verify_contracts_001", "T1", []string{"lint"})
verifyOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_contracts_001",
"--task", "T1",
"--check", "lint",
"--status", "failed",
"--summary", "lint failed",
)
var verifyResp map[string]any
mustDecodeJSON(t, verifyOut, &verifyResp)
if got := nestedString(t, verifyResp, "data", "task", "status"); got != "failed" {
t.Fatalf("expected task status failed, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "gate", "status"); got != "failed" {
t.Fatalf("expected gate status failed, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "check", "status"); got != "failed" {
t.Fatalf("expected recorded check status failed, got %q", got)
}
}
// TestOrchVerifyRecordUpdatesExistingNamedCheck verifies verify record updates an existing named check result and recomputes the gate.
func TestOrchVerifyRecordUpdatesExistingNamedCheck(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, "run_verify_contracts_002", "T1", []string{"lint", "test"})
runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_contracts_002",
"--task", "T1",
"--check", "lint",
"--status", "failed",
"--summary", "first lint failure",
"--recorded-by", "qa-1",
)
verifyOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_contracts_002",
"--task", "T1",
"--check", "lint",
"--status", "passed",
"--summary", "lint fixed",
"--recorded-by", "qa-2",
)
var verifyResp map[string]any
mustDecodeJSON(t, verifyOut, &verifyResp)
if got := nestedString(t, verifyResp, "data", "task", "status"); got != "verifying" {
t.Fatalf("expected task status verifying after updating lint result, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "gate", "status"); got != "pending" {
t.Fatalf("expected gate status pending after updating lint result, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "check", "status"); got != "passed" {
t.Fatalf("expected updated check status passed, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "check", "summary"); got != "lint fixed" {
t.Fatalf("expected updated check summary lint fixed, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "check", "recorded_by"); got != "qa-2" {
t.Fatalf("expected updated check recorded_by qa-2, got %q", got)
}
verifyStatusOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "status",
"--run", "run_verify_contracts_002",
"--task", "T1",
)
var verifyStatusResp map[string]any
mustDecodeJSON(t, verifyStatusOut, &verifyStatusResp)
pendingChecks := nestedArray(t, verifyStatusResp, "data", "gate", "pending_checks")
if len(pendingChecks) != 1 || pendingChecks[0] != "test" {
t.Fatalf("expected only test to remain pending after lint update, got %#v", pendingChecks)
}
}
// TestOrchVerifyRecordRoundTripsBodyMetadataAndRecorder verifies verify record preserves body-file content, metadata JSON, and recorder identity in the returned check result.
func TestOrchVerifyRecordRoundTripsBodyMetadataAndRecorder(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
bodyFile := filepath.Join(tempDir, "verify.txt")
body := "verification details from file\n"
if err := os.WriteFile(bodyFile, []byte(body), 0o644); err != nil {
t.Fatalf("write verify body file: %v", err)
}
seedVerifyingTaskForVerifyAndRecoveryTests(t, dbPath, "run_verify_contracts_003", "T1", []string{"lint"})
verifyOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", "run_verify_contracts_003",
"--task", "T1",
"--check", "lint",
"--status", "passed",
"--summary", "lint clean",
"--body-file", bodyFile,
"--metadata-json", `{"ticket":"QA-1"}`,
"--recorded-by", "qa-bot",
)
var verifyResp map[string]any
mustDecodeJSON(t, verifyOut, &verifyResp)
if got := nestedString(t, verifyResp, "data", "task", "status"); got != "done" {
t.Fatalf("expected task status done after the only required check passes, got %q", got)
}
if got := nestedString(t, verifyResp, "data", "check", "body"); got != body {
t.Fatalf("expected check body %q, got %q", body, got)
}
if got := nestedString(t, verifyResp, "data", "check", "recorded_by"); got != "qa-bot" {
t.Fatalf("expected check recorded_by qa-bot, got %q", got)
}
metadata, ok := nestedValue(t, verifyResp, "data", "check", "metadata_json").(map[string]any)
if !ok {
t.Fatalf("expected check metadata_json object, got %#v", nestedValue(t, verifyResp, "data", "check", "metadata_json"))
}
if got, _ := metadata["ticket"].(string); got != "QA-1" {
t.Fatalf("expected metadata_json.ticket QA-1, got %#v", metadata["ticket"])
}
}
// TestOrchRetryRejectsNonFailedTask verifies retry rejects tasks that are not currently failed.
func TestOrchRetryRejectsNonFailedTask(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", "run_retry_contracts_001",
"--goal", "Validate retry state guards",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", "run_retry_contracts_001",
"--task", "T1",
"--title", "Implement retry guard",
"--default-to", "worker-a",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", "run_retry_contracts_001",
"--task", "T1",
"--execution-mode", "analysis",
"--to", "worker-a",
)
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"retry",
"--run", "run_retry_contracts_001",
"--task", "T1",
)
if exitCode != 30 {
t.Fatalf("expected invalid_state exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_state")
}
// TestOrchRetryUsesBodyFileForReplacementAttempt verifies retry loads the replacement worker brief from body-file.
func TestOrchRetryUsesBodyFileForReplacementAttempt(t *testing.T) {
t.Parallel()
tempDir := t.TempDir()
dbPath := filepath.Join(tempDir, "coord.db")
bodyFile := filepath.Join(tempDir, "retry-body.txt")
body := "Retry after the failure with the updated brief.\n"
if err := os.WriteFile(bodyFile, []byte(body), 0o644); err != nil {
t.Fatalf("write retry body file: %v", err)
}
originalThreadID := seedFailedTaskForVerifyAndRecoveryTests(t, dbPath, "run_retry_contracts_002", "T1")
retryOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"retry",
"--run", "run_retry_contracts_002",
"--task", "T1",
"--body-file", bodyFile,
)
var retryResp map[string]any
mustDecodeJSON(t, retryOut, &retryResp)
if got := nestedString(t, retryResp, "data", "task", "status"); got != "dispatched" {
t.Fatalf("expected task status dispatched after retry, got %q", got)
}
newThreadID := nestedString(t, retryResp, "data", "attempt", "thread_id")
if newThreadID == originalThreadID {
t.Fatalf("expected retry to create a new thread, got %q", newThreadID)
}
showOut := runInboxCommand(
t,
"--db", dbPath,
"--json",
"show",
"--thread", newThreadID,
)
var showResp map[string]any
mustDecodeJSON(t, showOut, &showResp)
lastMessage := lastThreadMessageForVerifyAndRecoveryTests(t, showResp)
if got, _ := lastMessage["body"].(string); got != body {
t.Fatalf("expected retry message body %q, got %#v", body, lastMessage["body"])
}
}
// TestOrchReassignWorksFromFailedTask verifies reassign can move a failed task to a new worker by creating a replacement attempt.
func TestOrchReassignWorksFromFailedTask(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
originalThreadID := seedFailedTaskForVerifyAndRecoveryTests(t, dbPath, "run_reassign_contracts_001", "T1")
reassignOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"reassign",
"--run", "run_reassign_contracts_001",
"--task", "T1",
"--to", "worker-b",
"--reason", "Route the failed task to a different worker.",
)
var reassignResp map[string]any
mustDecodeJSON(t, reassignOut, &reassignResp)
if got := nestedString(t, reassignResp, "data", "task", "status"); got != "dispatched" {
t.Fatalf("expected task status dispatched after reassign, got %q", got)
}
if got := nestedString(t, reassignResp, "data", "attempt", "assigned_to"); got != "worker-b" {
t.Fatalf("expected reassigned attempt to target worker-b, got %q", got)
}
if got := nestedValue(t, reassignResp, "data", "attempt", "attempt_no").(float64); got != 2 {
t.Fatalf("expected reassign attempt 2, got %#v", got)
}
newThreadID := nestedString(t, reassignResp, "data", "attempt", "thread_id")
if newThreadID == originalThreadID {
t.Fatalf("expected reassign to create a new thread, got %q", newThreadID)
}
}
func seedVerifyingTaskForVerifyAndRecoveryTests(t *testing.T, dbPath, runID, taskID string, requiredChecks []string) string {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Prepare verifying task for contract tests",
)
args := []string{
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", "Prepare verifying task",
"--default-to", "worker-a",
}
for _, check := range requiredChecks {
args = append(args, "--required-check", check)
}
runOrchCommand(t, args...)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", taskID,
"--execution-mode", "analysis",
"--to", "worker-a",
"--body", "Prepare the task for verification.",
)
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,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"done",
"--agent", "worker-a",
"--thread", threadID,
"--summary", "Implementation complete",
"--body", "Ready for verification.",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
return threadID
}
func seedFailedTaskForVerifyAndRecoveryTests(t *testing.T, dbPath, runID, taskID string) string {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Prepare failed task for recovery tests",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", "Prepare failed task",
"--default-to", "worker-a",
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", taskID,
"--execution-mode", "analysis",
"--to", "worker-a",
"--body", "Initial worker brief.",
)
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,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"fail",
"--agent", "worker-a",
"--thread", threadID,
"--summary", "Worker reported a failure",
"--body", "The task failed before completion.",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
return threadID
}
func lastThreadMessageForVerifyAndRecoveryTests(t *testing.T, showResp map[string]any) map[string]any {
t.Helper()
messages := nestedArray(t, showResp, "data", "messages")
if len(messages) == 0 {
t.Fatalf("expected at least one message, got %#v", messages)
}
lastMessage, ok := messages[len(messages)-1].(map[string]any)
if !ok {
t.Fatalf("expected message object, got %#v", messages[len(messages)-1])
}
return lastMessage
}
@@ -0,0 +1,143 @@
package orch
import (
"path/filepath"
"testing"
)
// TestOrchAnswerRejectsInvalidPayloadJSON verifies answer rejects malformed payload JSON.
func TestOrchAnswerRejectsInvalidPayloadJSON(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
_ = seedBlockedTaskForAnswerCleanupEdgeTests(t, dbPath, "run_blog_answer_extra_002", "T2", "worker-b")
stdout, _, exitCode := executeOrchCommand(
"--db", dbPath,
"--json",
"answer",
"--run", "run_blog_answer_extra_002",
"--task", "T2",
"--payload-json", "not-json",
)
if exitCode != 30 {
t.Fatalf("expected exit code 30, got %d\nstdout:\n%s", exitCode, stdout)
}
assertErrorJSON(t, stdout, "invalid_input")
}
// TestOrchVerifyStatusSelectsExplicitAttempt verifies verify status can inspect a non-latest attempt by number.
func TestOrchVerifyStatusSelectsExplicitAttempt(t *testing.T) {
t.Parallel()
dbPath := filepath.Join(t.TempDir(), "coord.db")
runID := "run_blog_verify_extra_002"
seedVerifyingTaskForVerifyContractTests(t, dbPath, runID, "T1")
runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "record",
"--run", runID,
"--task", "T1",
"--check", "lint",
"--status", "failed",
"--summary", "Lint failed",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"retry",
"--run", runID,
"--task", "T1",
"--body", "Retry after fixing lint.",
)
verifyStatusOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"verify", "status",
"--run", runID,
"--task", "T1",
"--attempt", "1",
)
var verifyStatusResp map[string]any
mustDecodeJSON(t, verifyStatusOut, &verifyStatusResp)
if got := nestedValue(t, verifyStatusResp, "data", "attempt", "attempt_no").(float64); got != 1 {
t.Fatalf("expected verify status to select attempt 1, got %#v", got)
}
if got := nestedString(t, verifyStatusResp, "data", "attempt", "task_id"); got != "T1" {
t.Fatalf("expected verify status attempt task T1, got %q", got)
}
if got := nestedValue(t, verifyStatusResp, "data", "gate", "attempt_no").(float64); got != 1 {
t.Fatalf("expected verify gate attempt 1, got %#v", got)
}
}
func seedVerifyingTaskForVerifyContractTests(t *testing.T, dbPath, runID, taskID string) {
t.Helper()
runOrchCommand(
t,
"--db", dbPath,
"--json",
"run", "init",
"--run", runID,
"--goal", "Prepare verify contract task",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"task", "add",
"--run", runID,
"--task", taskID,
"--title", "Implement gated task",
"--default-to", "worker-a",
"--required-check", "lint",
)
dispatchOut := runOrchCommand(
t,
"--db", dbPath,
"--json",
"dispatch",
"--run", runID,
"--task", taskID,
"--execution-mode", "analysis",
"--body", "Implement the gated task.",
)
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,
)
runInboxCommand(
t,
"--db", dbPath,
"--json",
"done",
"--agent", "worker-a",
"--thread", threadID,
"--summary", "Task complete",
)
runOrchCommand(
t,
"--db", dbPath,
"--json",
"reconcile",
"--run", runID,
)
}