corago/codex-keep/reconcile_test.go

477 lines
11 KiB
Go
Raw Permalink Normal View History

package main
import (
"encoding/json"
"io"
"testing"
"time"
)
func TestMissedStart(t *testing.T) {
w := activeWatch()
w.observe(testTurn("completed"))
w.update(testGoal(blocked))
failure := testTurn("failed")
failure.ID = "turn-2"
w.observe(failure)
if !w.eligible() {
t.Fatal("API failure after a missed turn start was not recovered")
}
}
func TestStopInFlight(t *testing.T) {
for _, status := range []goalStatus{paused, "complete", "budgetLimited", "usageLimited"} {
t.Run(string(status), func(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
f.before = nil
f.goal = testGoal(status)
2026-10-22 22:05:33 +09:00
f.human(f.goal)
}
for range 3 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal.Status != status || w.Armed || w.eligible() || !w.Due.IsZero() {
t.Fatalf("human stop did not win reconciliation: target=%+v watch=%+v", f.goal, w)
}
})
}
}
func TestStopLostReply(t *testing.T) {
f := newFake(t, nil)
f.fail()
f.op.attached["thread-1"] = true
f.lostReply = true
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
f.before = nil
f.goal = testGoal(paused)
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
}
if err := f.reconcile(); err != io.EOF {
t.Fatalf("error = %v", err)
}
path := f.op.store.path
closeStore(t, f.op.store)
s, err := openStore(path)
if err != nil {
t.Fatal(err)
}
defer closeStore(t, s)
next := newFake(t, s)
next.goal, next.turn = f.goal, f.turn
for range 2 {
if err := next.reconcile(); err != nil {
t.Fatal(err)
}
}
if next.goal.Status != paused || s.watch("thread-1").Armed {
t.Fatalf("lost human cancellation across restart: target=%+v watch=%+v", next.goal, s.watch("thread-1"))
}
}
func TestBlockStamp(t *testing.T) {
w := failedWatch()
w.Due = time.Now().Add(time.Second)
due := w.Due
g := testGoal(blocked)
g.UpdatedAt++
w.update(g)
if !w.eligible() || w.Due != due {
t.Fatal("accounting timestamp cancelled recovery")
}
}
func TestStaleActiveEvent(t *testing.T) {
f := newFake(t, nil)
f.goal = testGoal(paused)
w := activeWatch()
w.update(f.goal)
f.op.store.Watches["thread-1"] = w
raw, err := json.Marshal(params{ThreadID: "thread-1", Goal: testGoal(active)})
if err != nil {
t.Fatal(err)
}
if err := f.op.event(message{Method: "thread/goal/updated", Params: raw}); err != nil {
t.Fatal(err)
}
if w.Armed || w.Goal.Status != paused {
t.Fatal("stale active echo revoked cancellation")
}
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
if f.goal.Status != paused || w.Armed {
t.Fatal("stale active echo changed desired state")
}
}
func TestResumeMissed(t *testing.T) {
w := activeWatch()
w.observe(testTurn("interrupted"))
w.update(testGoal(blocked))
failure := testTurn("failed")
failure.ID = "turn-2"
w.observe(failure)
if !w.eligible() {
t.Fatal("new API-failed turn did not restore known goal authorization")
}
}
func TestRepairIntent(t *testing.T) {
for _, final := range []goalStatus{"complete", active} {
t.Run(string(final), func(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
phase := 0
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
var p setParams
if err := json.Unmarshal(m.Params, &p); err != nil {
t.Fatal(err)
}
switch phase {
case 0:
if p.Status != active {
t.Fatal("missing initial resume")
}
phase++
f.goal = testGoal(paused)
case 1:
if p.Status != paused {
return
}
phase++
f.goal = testGoal(final)
default:
return
}
if phase == 2 && final == active {
f.human(f.goal)
} else {
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
}
}
for range 6 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if phase != 2 || f.goal.Status != final {
t.Fatalf("latest observed intent lost: phase=%d target=%+v watch=%+v", phase, f.goal, w)
}
})
}
}
func TestClearInFlight(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
f.before = nil
f.goal = nil
f.notify("thread/goal/cleared", map[string]any{"threadId": "thread-1"})
}
for range 3 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal != nil || w.Goal != nil || w.eligible() {
t.Fatal("cleared goal was recreated")
}
}
func TestSwapInFlight(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
f.before = nil
f.goal = &goal{Objective: "Replacement", CreatedAt: 2, UpdatedAt: 2, Status: blocked}
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
}
for range 4 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal.Objective != "Replacement" || f.goal.Status != blocked || w.eligible() {
t.Fatalf("replacement was changed: target=%+v watch=%+v", f.goal, w)
}
}
func TestBusyRecovery(t *testing.T) {
f := newFake(t, nil)
f.fail()
f.status = "active"
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
f.status = "systemError"
f.turn.ID = "turn-2"
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
if !w.eligible() || w.Due.IsZero() {
t.Fatal("busy observation lost goal authorization")
}
}
func TestLateBlock(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
f.before = nil
f.goal = testGoal(paused)
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": testGoal(blocked)})
}
for range 4 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal.Status != paused || w.Armed {
t.Fatalf("server block replaced human stop: %+v", w)
}
}
func TestNewGoalNotice(t *testing.T) {
f := newFake(t, nil)
f.goal = &goal{Objective: "Replacement", CreatedAt: 2, UpdatedAt: 2, Status: active}
raw, err := json.Marshal(params{ThreadID: "thread-1", Goal: f.goal})
if err != nil {
t.Fatal(err)
}
if err := f.op.event(message{Method: "thread/goal/updated", Params: raw}); err != nil {
t.Fatal(err)
}
f.goal.Status = blocked
f.turn = testTurn("failed")
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
if !f.op.store.watch("thread-1").eligible() {
t.Fatal("observed active replacement lost authorization before polling")
}
}
func TestSameTurnResume(t *testing.T) {
for _, failedFirst := range []bool{false, true} {
w := activeWatch()
f := newFake(t, nil)
f.op.store.Watches["thread-1"] = w
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
w.update(testGoal("budgetLimited"))
f.human(testGoal(active))
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
if failedFirst {
w.observe(testTurn("failed"))
}
w.update(testGoal(blocked))
if !failedFirst {
w.observe(testTurn("failed"))
}
if !w.eligible() {
t.Fatalf("resumed turn lost recovery, failedFirst=%v", failedFirst)
}
}
}
func TestReconnectEcho(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
next := newFake(t, f.op.store)
next.fail()
next.turn.ID = "turn-2"
if err := next.reconcile(); err != nil {
t.Fatal(err)
}
w.Due = time.Now().Add(-time.Second)
if err := next.reconcile(); err != nil {
t.Fatal(err)
}
if next.count("thread/goal/set") != 1 {
t.Fatal("old connection echo blocked recovery after reconnection")
}
}
func TestAccountStop(t *testing.T) {
f := newFake(t, nil)
f.fail()
w := failedWatch()
w.Due = time.Now().Add(-time.Second)
f.op.store.Watches["thread-1"] = w
f.before = func(m message) {
if m.Method != "thread/goal/set" {
return
}
f.before = nil
f.goal = testGoal(paused)
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
}
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
f.before = func(m message) {
if m.Method != "thread/loaded/list" {
return
}
f.before = nil
g := *f.goal
g.UpdatedAt++
f.goal = &g
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
}
for range 4 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal.Status != paused || w.eligible() {
t.Fatal("accounting update revoked human cancellation")
}
}
func TestDelayedWrite(t *testing.T) {
f := newFake(t, nil)
w := failedWatch()
w.Pending = true
f.op.store.Watches["thread-1"] = w
var err error
w.Source, err = openIntent(f.path)
if err != nil {
t.Fatal(err)
}
f.goal = testGoal(paused)
f.human(f.goal)
for range 2 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
f.goal = testGoal(active)
f.notify("thread/goal/updated", map[string]any{"threadId": "thread-1", "goal": f.goal})
for range 3 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal.Status != paused || w.Armed {
t.Fatal("delayed activation revoked settled cancellation")
}
}
func TestQueuedResume(t *testing.T) {
for _, bound := range []bool{false, true} {
t.Run(map[bool]string{false: "startup", true: "bound"}[bound], func(t *testing.T) {
f := newFake(t, nil)
f.op.store.Watches["thread-1"] = activeWatch()
if bound {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
f.human(testGoal(paused))
f.human(testGoal(active))
for range 3 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.goal.Status != active {
t.Fatal("queued resumption was discarded by an older pause notification")
}
})
}
}
func TestBarrierChange(t *testing.T) {
f := newFake(t, nil)
f.op.store.Watches["thread-1"] = activeWatch()
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
f.before = func(m message) {
if m.Method != "thread/resume" {
return
}
f.before = nil
f.goal = testGoal("complete")
f.human(f.goal)
}
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
for range 2 {
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
}
if f.count("thread/goal/set") != 0 {
t.Fatal("barrier notification left an invalid goal snapshot in use")
}
}
func TestNoGoalPath(t *testing.T) {
f := newFake(t, nil)
f.goal = nil
f.path = ""
if err := f.reconcile(); err != nil {
t.Fatal(err)
}
if f.count("thread/goal/set") != 0 {
t.Fatal("mutated a thread without a goal")
}
}