feat(infra): modelo de datos del meta-orquestador de flota (flow 0012)

Fase 1, piezas 1+2:
- ClaudeFleet + list_claude_fleet ganan DodContract/DodStatus/Role,
  leidos de goals/<sessionId>.json (.dod_contract/.dod_status/.role).
  Aditivo: fleetview sigue compilando.
- classify_fleet_termination (pura): clasifica el estado de terminacion
  de un agente (RECLAMA/MAL_LANZADO/DICE_TERMINADO/ESTANCADO/TRABAJANDO)
  con precedencia fija, para que un watcher sin LLM decida. 34 tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
agent
2026-06-20 19:51:11 +02:00
parent b569561115
commit 28a53ee357
7 changed files with 315 additions and 40 deletions
@@ -0,0 +1,74 @@
package infra
import "testing"
func TestClassifyFleetTermination(t *testing.T) {
const stall = 600
cases := []struct {
name string
status string
phase string
dodC string
dodS string
idle int
thresh int
want string
}{
// --- RECLAMA (precedencia 1) ---
{"waiting reclama input", "waiting", "haciendo", "criterio", "pending", 10, stall, TerminationReclama},
{"phase preguntando reclama", "busy", "preguntando", "criterio", "pending", 10, stall, TerminationReclama},
{"phase bloqueado reclama", "idle", "bloqueado", "criterio", "pending", 9999, stall, TerminationReclama},
{"waiting manda aunque sin dodContract", "waiting", "haciendo", "", "", 10, stall, TerminationReclama},
{"preguntando manda aunque sin dodContract", "idle", "preguntando", "", "", 10, stall, TerminationReclama},
{"bloqueado manda aunque idle estancado", "idle", "bloqueado", "criterio", "pending", 10000, stall, TerminationReclama},
// --- MAL_LANZADO (precedencia 2) ---
{"sin dodContract busy", "busy", "haciendo", "", "pending", 10, stall, TerminationMalLanzado},
{"sin dodContract idle reciente", "idle", "haciendo", "", "", 5, stall, TerminationMalLanzado},
{"sin dodContract idle estancado", "idle", "haciendo", "", "pending", 10000, stall, TerminationMalLanzado},
{"sin dodContract phase hecho", "idle", "hecho", "", "met", 10, stall, TerminationMalLanzado},
{"sin dodContract dodStatus met", "idle", "testeando", "", "met", 10, stall, TerminationMalLanzado},
// --- DICE_TERMINADO (precedencia 3) ---
{"idle phase hecho", "idle", "hecho", "criterio", "pending", 10, stall, TerminationDiceTerminado},
{"idle dodStatus met", "idle", "testeando", "criterio", "met", 10, stall, TerminationDiceTerminado},
{"idle hecho y met", "idle", "hecho", "criterio", "met", 10, stall, TerminationDiceTerminado},
{"idle met aunque estancado por tiempo", "idle", "puliendo", "criterio", "met", 10000, stall, TerminationDiceTerminado},
{"idle hecho aunque estancado por tiempo", "idle", "hecho", "criterio", "pending", 10000, stall, TerminationDiceTerminado},
// --- ESTANCADO (precedencia 4) ---
{"idle no hecho en umbral exacto", "idle", "haciendo", "criterio", "pending", stall, stall, TerminationEstancado},
{"idle no hecho por encima del umbral", "idle", "investigando", "criterio", "pending", stall + 1, stall, TerminationEstancado},
{"idle iterando estancado", "idle", "iterando", "criterio", "failed", 5000, stall, TerminationEstancado},
{"idle dodStatus failed estancado", "idle", "testeando", "criterio", "failed", 700, stall, TerminationEstancado},
{"idle en_pausa estancado", "idle", "en_pausa", "criterio", "pending", 601, stall, TerminationEstancado},
// --- TRABAJANDO (precedencia 5, todo lo demas) ---
{"busy trabajando", "busy", "haciendo", "criterio", "pending", 0, stall, TerminationTrabajando},
{"busy aunque idleSeconds alto", "busy", "investigando", "criterio", "pending", 99999, stall, TerminationTrabajando},
{"idle reciente bajo umbral", "idle", "haciendo", "criterio", "pending", stall - 1, stall, TerminationTrabajando},
{"idle reciente cero segundos", "idle", "planificando", "criterio", "pending", 0, stall, TerminationTrabajando},
{"idle no hecho justo bajo umbral", "idle", "testeando", "criterio", "failed", 599, stall, TerminationTrabajando},
{"busy phase vacia con dodContract", "busy", "", "criterio", "", 10, stall, TerminationTrabajando},
// --- Bordes y combinaciones ---
{"umbral cero idle no hecho => estancado", "idle", "haciendo", "criterio", "pending", 0, 0, TerminationEstancado},
{"idle hecho con umbral cero => dice terminado", "idle", "hecho", "criterio", "pending", 0, 0, TerminationDiceTerminado},
{"dodStatus met con status busy NO termina", "busy", "haciendo", "criterio", "met", 10, stall, TerminationTrabajando},
{"phase hecho con status busy NO termina", "busy", "hecho", "criterio", "pending", 10, stall, TerminationTrabajando},
{"idle pendiente_revision bajo umbral trabajando", "idle", "pendiente_revision", "criterio", "pending", 100, stall, TerminationTrabajando},
{"idle pendiente_revision sobre umbral estancado", "idle", "pendiente_revision", "criterio", "pending", 1000, stall, TerminationEstancado},
{"waiting con dodStatus met sigue reclamando", "waiting", "hecho", "criterio", "met", 10, stall, TerminationReclama},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := ClassifyFleetTermination(c.status, c.phase, c.dodC, c.dodS, c.idle, c.thresh)
if got != c.want {
t.Errorf("ClassifyFleetTermination(%q,%q,%q,%q,%d,%d) = %q, want %q",
c.status, c.phase, c.dodC, c.dodS, c.idle, c.thresh, got, c.want)
}
})
}
}