package store import ( "encoding/json" "testing" "time" "orchestra/internal/domain" ) func debtEvent(id, typ, task string, payload map[string]any) domain.Event { b, _ := json.Marshal(payload) return domain.Event{ID: id, Type: typ, TaskID: task, Payload: b, At: time.Now().UTC()} } // The opencode shape from run 14: one harness failing the same way across // several tasks. That is the recurrence the ledger exists to notice. func TestProjectDebtGroupsOneFailureShapeAcrossTasks(t *testing.T) { events := []domain.Event{ debtEvent("e1", "TaskFailed", "t1", map[string]any{"failure_class": "retry_limit", "harness_id": "workpc-opencode"}), debtEvent("e2", "TaskFailed", "t2", map[string]any{"failure_class": "retry_limit", "harness_id": "workpc-opencode"}), debtEvent("e3", "TaskFailed", "t3", map[string]any{"failure_class": "retry_limit", "harness_id": "workpc-opencode"}), debtEvent("e4", "TaskFailed", "t4", map[string]any{"failure_class": "retry_limit", "harness_id": "workpc-claude"}), // A handoff release is ordinary progress and must not become debt. debtEvent("e5", "TaskReleased", "t5", map[string]any{"handoff_ref": "sha256:abc", "harness_id": "workpc-claude"}), } ledger := ProjectDebt(events) if len(ledger.Items) != 2 { t.Fatalf("want one item per harness, got %d: %+v", len(ledger.Items), ledger.Items) } top := ledger.Items[0] if top.Recurrence() != 3 || top.AffectedTasks() != 3 { t.Fatalf("recurrence %d across %d tasks, want 3 and 3", top.Recurrence(), top.AffectedTasks()) } if top.Class != domain.DebtOperational { t.Fatalf("class %q, want operational", top.Class) } if got := top.ID; got != "v1:operational:retry_limit:workpc-opencode:lease" { t.Fatalf("signature %q", got) } for _, o := range top.Observations { if o.EventID == "" || o.LegacyRef != "" { t.Fatalf("observation lost its event provenance: %+v", o) } } } // A lifecycle stop is the system working. Waiting for a human is not debt, and // counting it would drown the real signal. func TestProjectDebtIgnoresOrdinaryLifecycleStops(t *testing.T) { events := []domain.Event{ debtEvent("e1", "TaskBlocked", "t1", map[string]any{"block_reason": "human_decision", "blocker": "which route"}), debtEvent("e2", "TaskBlocked", "t2", map[string]any{"block_reason": "trajectory_gate", "blocker": "confirm plan"}), debtEvent("e3", "TaskBlocked", "t3", map[string]any{"block_reason": "lease_expired", "blocker": "lease expired"}), } ledger := ProjectDebt(events) if len(ledger.Items) != 1 || ledger.Items[0].Class != domain.DebtOperational { t.Fatalf("want only the lease_expired item, got %+v", ledger.Items) } } // Incompleteness is part of the result. A ledger that stays silent about what // it cannot record reads as "no operator cost" when it means "operator cost is // not recorded anywhere". func TestProjectDebtReportsWhatItCannotSee(t *testing.T) { ledger := ProjectDebt(nil) var manual, worker bool for _, g := range ledger.Gaps { if g.Durable { continue } switch g.Kind { case domain.ObservationManualIntervention: manual = true case domain.ObservationWorkerFailure: worker = true } } if !manual || !worker { t.Fatalf("the two known holes must always be reported: %+v", ledger.Gaps) } for _, g := range ledger.Gaps { if g.Reason == "" { t.Fatalf("gap %q has no reason", g.Kind) } } }