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
69 lines
2.6 KiB
Go
69 lines
2.6 KiB
Go
package operations
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"orchestra/internal/domain"
|
|
)
|
|
|
|
func debtItem(class domain.DebtClass, kinds ...struct {
|
|
kind domain.ObservationKind
|
|
task string
|
|
}) domain.DebtItem {
|
|
item := domain.DebtItem{Class: class}
|
|
for _, k := range kinds {
|
|
item.Observations = append(item.Observations, domain.DebtObservation{Kind: k.kind, TaskID: k.task})
|
|
}
|
|
return item
|
|
}
|
|
|
|
type obs = struct {
|
|
kind domain.ObservationKind
|
|
task string
|
|
}
|
|
|
|
// The thresholds are the whole policy, so each class gets its own case. Polish
|
|
// is the one that must never pass, or the ledger becomes a cleanup generator.
|
|
func TestDebtEligibilityPerClass(t *testing.T) {
|
|
fail := obs{domain.ObservationFailureClass, "t1"}
|
|
if !CheckDebtEligibility(debtItem(domain.DebtCorrectness, fail)).Eligible {
|
|
t.Fatal("correctness debt is eligible on first observation")
|
|
}
|
|
twice := debtItem(domain.DebtOperational, fail, obs{domain.ObservationFailureClass, "t1"})
|
|
if CheckDebtEligibility(twice).Eligible {
|
|
t.Fatal("two occurrences on one task must not qualify as operational debt")
|
|
}
|
|
spread := debtItem(domain.DebtOperational, fail,
|
|
obs{domain.ObservationFailureClass, "t2"}, obs{domain.ObservationFailureClass, "t3"})
|
|
if !CheckDebtEligibility(spread).Eligible {
|
|
t.Fatal("three occurrences across three tasks must qualify")
|
|
}
|
|
manual := debtItem(domain.DebtOperational, obs{domain.ObservationManualIntervention, "t1"})
|
|
if !CheckDebtEligibility(manual).Eligible {
|
|
t.Fatal("one manual intervention must qualify on its own")
|
|
}
|
|
blocked := debtItem(domain.DebtStructural,
|
|
obs{domain.ObservationBlockReason, "t1"}, obs{domain.ObservationBlockReason, "t2"})
|
|
if !CheckDebtEligibility(blocked).Eligible {
|
|
t.Fatal("two blocked tasks in one component must qualify as structural")
|
|
}
|
|
polish := debtItem(domain.DebtPolish, fail, fail, fail, fail, fail)
|
|
if CheckDebtEligibility(polish).Eligible {
|
|
t.Fatal("polish must never promote itself, at any recurrence")
|
|
}
|
|
}
|
|
|
|
// A refusal has to say what is missing. "Not eligible" alone sends an operator
|
|
// reading code, which is the mistake SubmissionCheck already documents.
|
|
func TestDebtRefusalNamesTheMissingEvidence(t *testing.T) {
|
|
check := CheckDebtEligibility(debtItem(domain.DebtOperational, obs{domain.ObservationFailureClass, "t1"}))
|
|
if check.Eligible || len(check.Reasons) == 0 {
|
|
t.Fatalf("want a refusal with reasons, got %+v", check)
|
|
}
|
|
joined := strings.Join(check.Reasons, " ")
|
|
if !strings.Contains(joined, "manual interventions are not recorded") {
|
|
t.Fatalf("the refusal must say the intervention count is structurally zero: %v", check.Reasons)
|
|
}
|
|
}
|