Add drift state, persistence, and override controls to controller
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,7 @@ func TestHappyPathDrivesStates(t *testing.T) {
|
||||
if err := c.EnterPlanning(); err != nil {
|
||||
t.Fatalf("enter planning: %v", err)
|
||||
}
|
||||
if err := c.StartManualCommitment("Port session", "session tests pass", 25*time.Minute); err != nil {
|
||||
if err := c.StartManualCommitment("Port session", "session tests pass", 25*time.Minute, nil); err != nil {
|
||||
t.Fatalf("start commitment: %v", err)
|
||||
}
|
||||
st := c.State()
|
||||
@@ -61,7 +61,7 @@ func TestHappyPathDrivesStates(t *testing.T) {
|
||||
func TestStartCommitmentRejectsInvalidInput(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
_ = c.EnterPlanning()
|
||||
if err := c.StartManualCommitment("", "x", 25*time.Minute); err == nil {
|
||||
if err := c.StartManualCommitment("", "x", 25*time.Minute, nil); err == nil {
|
||||
t.Fatalf("empty next action should error")
|
||||
}
|
||||
if c.State().RuntimeState != domain.RuntimePlanning {
|
||||
@@ -76,7 +76,7 @@ func TestStateRestoresFromSnapshot(t *testing.T) {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
_ = first.EnterPlanning()
|
||||
_ = first.StartManualCommitment("Persisted action", "done", 25*time.Minute)
|
||||
_ = first.StartManualCommitment("Persisted action", "done", 25*time.Minute, nil)
|
||||
|
||||
second, err := New(path)
|
||||
if err != nil {
|
||||
@@ -98,7 +98,7 @@ func TestRestoredCommitmentKeepsDeadline(t *testing.T) {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
_ = first.EnterPlanning()
|
||||
_ = first.StartManualCommitment("Keep deadline", "done", 25*time.Minute)
|
||||
_ = first.StartManualCommitment("Keep deadline", "done", 25*time.Minute, nil)
|
||||
want := first.State().Commitment.DeadlineUnixSecs
|
||||
|
||||
second, err := New(path)
|
||||
@@ -141,7 +141,7 @@ func TestAccumulatesBucketsAndSwitches(t *testing.T) {
|
||||
c.RecordWindow(snap("code", "antidrift")) // latest window before start
|
||||
|
||||
_ = c.EnterPlanning()
|
||||
_ = c.StartManualCommitment("work", "done", 25*time.Minute) // seeds at t=1000 with code/antidrift
|
||||
_ = c.StartManualCommitment("work", "done", 25*time.Minute, nil) // seeds at t=1000 with code/antidrift
|
||||
|
||||
clk.advance(10 * time.Second)
|
||||
c.RecordWindow(snap("firefox", "docs")) // credits 10s to code/antidrift, switch -> firefox
|
||||
@@ -176,7 +176,7 @@ func TestUnavailableAccruesToReservedBucket(t *testing.T) {
|
||||
clk := &fakeClock{now: time.Unix(1000, 0)}
|
||||
c.SetClock(clk.fn())
|
||||
_ = c.EnterPlanning()
|
||||
_ = c.StartManualCommitment("work", "done", 25*time.Minute) // seed: empty unavailable latestWindow
|
||||
_ = c.StartManualCommitment("work", "done", 25*time.Minute, nil) // seed: empty unavailable latestWindow
|
||||
// latestWindow was zero-value (unavailable) at seed time.
|
||||
clk.advance(5 * time.Second)
|
||||
c.RecordWindow(snap("code", "x")) // credits 5s to the reserved bucket
|
||||
@@ -193,7 +193,7 @@ func TestCrashReplayRebuildsStats(t *testing.T) {
|
||||
first.SetClock(clk.fn())
|
||||
first.RecordWindow(snap("code", "antidrift"))
|
||||
_ = first.EnterPlanning()
|
||||
_ = first.StartManualCommitment("work", "done", 25*time.Minute)
|
||||
_ = first.StartManualCommitment("work", "done", 25*time.Minute, nil)
|
||||
clk.advance(15 * time.Second)
|
||||
first.RecordWindow(snap("firefox", "docs")) // 15s -> code, switch
|
||||
|
||||
@@ -222,7 +222,7 @@ func TestEndWritesAuditSummary(t *testing.T) {
|
||||
c.SetClock(clk.fn())
|
||||
c.RecordWindow(snap("code", "antidrift"))
|
||||
_ = c.EnterPlanning()
|
||||
_ = c.StartManualCommitment("write plan", "plan done", 25*time.Minute)
|
||||
_ = c.StartManualCommitment("write plan", "plan done", 25*time.Minute, nil)
|
||||
clk.advance(30 * time.Second)
|
||||
c.RecordWindow(snap("firefox", "docs"))
|
||||
clk.advance(10 * time.Second)
|
||||
@@ -339,13 +339,60 @@ func TestRequestCoachStaleResultDiscarded(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnTaskRequiresActive(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
if err := c.OnTask(); !errors.Is(err, ErrNotActive) {
|
||||
t.Fatalf("OnTask outside Active should error, got %v", err)
|
||||
}
|
||||
if err := c.Refocus(); !errors.Is(err, ErrNotActive) {
|
||||
t.Fatalf("Refocus outside Active should error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStartCommitmentPersistsAllowedClasses(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "state.json")
|
||||
first, err := New(path)
|
||||
if err != nil {
|
||||
t.Fatalf("new: %v", err)
|
||||
}
|
||||
_ = first.EnterPlanning()
|
||||
if err := first.StartManualCommitment("a", "b", 25*time.Minute, []string{"code"}); err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
second, err := New(path)
|
||||
if err != nil {
|
||||
t.Fatalf("reload: %v", err)
|
||||
}
|
||||
if got := second.AllowedClassesForTest(); len(got) != 1 || got[0] != "code" {
|
||||
t.Fatalf("allowed classes not restored: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnTaskAppendsCurrentClass(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
_ = c.EnterPlanning()
|
||||
_ = c.StartManualCommitment("a", "b", 25*time.Minute, nil)
|
||||
c.RecordWindow(evidence.WindowSnapshot{Class: "slack", Title: "chat", Health: evidence.EvidenceHealth{Available: true}})
|
||||
if err := c.OnTask(); err != nil {
|
||||
t.Fatalf("ontask: %v", err)
|
||||
}
|
||||
got := c.AllowedClassesForTest()
|
||||
if len(got) != 1 || got[0] != "slack" {
|
||||
t.Fatalf("OnTask should append current class, got %#v", got)
|
||||
}
|
||||
st := c.State()
|
||||
if st.Drift == nil || st.Drift.Status != "ontask" {
|
||||
t.Fatalf("OnTask should set drift ontask, got %+v", st.Drift)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeavingPlanningClearsCoach(t *testing.T) {
|
||||
c, _ := newTestController(t)
|
||||
_ = c.EnterPlanning()
|
||||
c.SetCoach(&fakeCoach{prop: ai.Proposal{NextAction: "x", SuccessCondition: "y", TimeboxSecs: 60}})
|
||||
_ = c.RequestCoach("x")
|
||||
waitCoachStatus(t, c, "ready")
|
||||
if err := c.StartManualCommitment("a", "b", 25*time.Minute); err != nil {
|
||||
if err := c.StartManualCommitment("a", "b", 25*time.Minute, nil); err != nil {
|
||||
t.Fatalf("start: %v", err)
|
||||
}
|
||||
if c.State().Coach != nil {
|
||||
|
||||
Reference in New Issue
Block a user