a757cffc78
Slice one of DEBT-DESIGN.md, with the four amendments applied. It writes nothing: no new event types, no scheduling, no clustering, no maintenance tasks. The point is to find out whether the model can represent debt this project already knows about, before committing to a durable schema. The projected type lives in domain and the fold lives in store, so the first implementation does not bake a read model into the command layer. operations owns the one action that exists, CheckDebtEligibility, which is a pure function returning explicit reasons like CheckSubmission. Signatures carry their version in the string. Normalization rules will change, and without a version that silently regroups history and moves the recurrence counts eligibility was already decided on. Observations require exactly one of event_id and legacy_ref. Imported Fxx history predates the events that would justify it, and a fabricated event id would break the provenance rule the ledger exists to enforce. Incompleteness is reported, not hidden. Manual interventions and worker observations are carried by no event type, so the ledger names both as non-durable gaps rather than reading as "no operational cost". The operational refusal reason says the intervention count is structurally zero on every current log. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVbaKucEYBjMqVeUgJUsc1
88 lines
3.3 KiB
Go
88 lines
3.3 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|