package operations import ( "fmt" "orchestra/internal/domain" ) // DebtCheck is why a debt item may or may not become work. Reasons are listed // rather than summarised, for the same reason SubmissionCheck lists them: "not // eligible" alone sends an operator reading code. type DebtCheck struct { Eligible bool `json:"eligible"` Reasons []string `json:"reasons,omitempty"` } // CheckDebtEligibility is the whole promotion rule, as one pure function of a // projected item. It decides only whether a maintenance task may be created. // It never creates one, and it never changes what that task must then pass: // classification as debt changes what gets scheduled, never what gets checked. // // The thresholds differ by class on purpose. Correctness and operational debt // have already cost something measurable. Structural debt needs evidence that // it is causing repeated work rather than merely offending taste. Polish never // promotes itself, or the ledger becomes a permanent cleanup generator. func CheckDebtEligibility(item domain.DebtItem) DebtCheck { recurrence, tasks := item.Recurrence(), item.AffectedTasks() blocked, manual := item.BlockedTasks(), item.ManualInterventions() switch item.Class { case domain.DebtCorrectness: if recurrence >= 1 { return DebtCheck{true, []string{fmt.Sprintf("correctness debt is eligible on first confirmed observation, and has %d", recurrence)}} } return DebtCheck{false, []string{"no confirmed observation"}} case domain.DebtOperational: var why []string if manual >= 1 { why = append(why, fmt.Sprintf("%d manual intervention(s) recorded", manual)) } if recurrence >= 3 && tasks >= 2 { why = append(why, fmt.Sprintf("recurred %d times across %d tasks", recurrence, tasks)) } if len(why) > 0 { return DebtCheck{true, why} } // Reasons state facts about this item and nothing about the // architecture around it. The second line here used to say manual // interventions were recorded by no event type, which was true when it // was written and false the moment OperatorInterventionRecorded // landed. A decision that is right for a reason that has become a lie // cannot be audited, and why a count is zero is not this function's // business: no intervention happened, none was recorded, or none was // migrated all read the same from here. return DebtCheck{false, []string{ fmt.Sprintf("breadth threshold not met: %d occurrences across %d tasks, needs 3 across 2", recurrence, tasks), fmt.Sprintf("manual intervention threshold not met: %d recorded, needs 1", manual), }} case domain.DebtStructural: if recurrence >= 3 { return DebtCheck{true, []string{fmt.Sprintf("%d review findings in this component", recurrence)}} } if blocked >= 2 { return DebtCheck{true, []string{fmt.Sprintf("blocked %d distinct tasks in this component", blocked)}} } return DebtCheck{false, []string{ fmt.Sprintf("needs 3 review findings or 2 blocked tasks in one component; has %d findings and %d blocked", recurrence, blocked), }} case domain.DebtPolish: return DebtCheck{false, []string{"polish never promotes itself, an operator promotes it explicitly"}} } return DebtCheck{false, []string{"unknown debt class " + string(item.Class)}} } // EligibleDebt filters a projected ledger to what policy would allow to become // work. It returns the check alongside each item so the reasons stay visible. func EligibleDebt(ledger domain.DebtLedger) []DebtCandidate { out := []DebtCandidate{} for _, item := range ledger.Items { if check := CheckDebtEligibility(item); check.Eligible { out = append(out, DebtCandidate{Item: item, Check: check}) } } return out } type DebtCandidate struct { Item domain.DebtItem `json:"item"` Check DebtCheck `json:"check"` }