Thread per-commitment enforcement level through to the snapshot

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 12:38:35 -04:00
parent 7fdcae5d14
commit 41c0a5fbbc
4 changed files with 76 additions and 26 deletions
+45 -16
View File
@@ -37,7 +37,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, nil); err != nil {
if err := c.StartManualCommitment("Port session", "session tests pass", 25*time.Minute, nil, domain.EnforcementWarn); err != nil {
t.Fatalf("start commitment: %v", err)
}
st := c.State()
@@ -67,7 +67,7 @@ func TestHappyPathDrivesStates(t *testing.T) {
func TestStartCommitmentRejectsInvalidInput(t *testing.T) {
c, _ := newTestController(t)
_ = c.EnterPlanning()
if err := c.StartManualCommitment("", "x", 25*time.Minute, nil); err == nil {
if err := c.StartManualCommitment("", "x", 25*time.Minute, nil, domain.EnforcementWarn); err == nil {
t.Fatalf("empty next action should error")
}
if c.State().RuntimeState != domain.RuntimePlanning {
@@ -82,7 +82,7 @@ func TestStateRestoresFromSnapshot(t *testing.T) {
t.Fatalf("new: %v", err)
}
_ = first.EnterPlanning()
_ = first.StartManualCommitment("Persisted action", "done", 25*time.Minute, nil)
_ = first.StartManualCommitment("Persisted action", "done", 25*time.Minute, nil, domain.EnforcementWarn)
second, err := New(path)
if err != nil {
@@ -104,7 +104,7 @@ func TestRestoredCommitmentKeepsDeadline(t *testing.T) {
t.Fatalf("new: %v", err)
}
_ = first.EnterPlanning()
_ = first.StartManualCommitment("Keep deadline", "done", 25*time.Minute, nil)
_ = first.StartManualCommitment("Keep deadline", "done", 25*time.Minute, nil, domain.EnforcementWarn)
want := first.State().Commitment.DeadlineUnixSecs
second, err := New(path)
@@ -147,7 +147,7 @@ func TestAccumulatesBucketsAndSwitches(t *testing.T) {
c.RecordWindow(snap("code", "antidrift")) // latest window before start
_ = c.EnterPlanning()
_ = c.StartManualCommitment("work", "done", 25*time.Minute, nil) // seeds at t=1000 with code/antidrift
_ = c.StartManualCommitment("work", "done", 25*time.Minute, nil, domain.EnforcementWarn) // seeds at t=1000 with code/antidrift
clk.advance(10 * time.Second)
c.RecordWindow(snap("firefox", "docs")) // credits 10s to code/antidrift, switch -> firefox
@@ -182,7 +182,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, nil) // seed: empty unavailable latestWindow
_ = c.StartManualCommitment("work", "done", 25*time.Minute, nil, domain.EnforcementWarn) // 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
@@ -199,7 +199,7 @@ func TestCrashReplayRebuildsStats(t *testing.T) {
first.SetClock(clk.fn())
first.RecordWindow(snap("code", "antidrift"))
_ = first.EnterPlanning()
_ = first.StartManualCommitment("work", "done", 25*time.Minute, nil)
_ = first.StartManualCommitment("work", "done", 25*time.Minute, nil, domain.EnforcementWarn)
clk.advance(15 * time.Second)
first.RecordWindow(snap("firefox", "docs")) // 15s -> code, switch
@@ -228,7 +228,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, nil)
_ = c.StartManualCommitment("write plan", "plan done", 25*time.Minute, nil, domain.EnforcementWarn)
clk.advance(30 * time.Second)
c.RecordWindow(snap("firefox", "docs"))
clk.advance(10 * time.Second)
@@ -374,7 +374,7 @@ func TestStartCommitmentPersistsAllowedClasses(t *testing.T) {
t.Fatalf("new: %v", err)
}
_ = first.EnterPlanning()
if err := first.StartManualCommitment("a", "b", 25*time.Minute, []string{"code"}); err != nil {
if err := first.StartManualCommitment("a", "b", 25*time.Minute, []string{"code"}, domain.EnforcementWarn); err != nil {
t.Fatalf("start: %v", err)
}
second, err := New(path)
@@ -389,7 +389,7 @@ func TestStartCommitmentPersistsAllowedClasses(t *testing.T) {
func TestOnTaskAppendsCurrentClass(t *testing.T) {
c, _ := newTestController(t)
_ = c.EnterPlanning()
_ = c.StartManualCommitment("a", "b", 25*time.Minute, nil)
_ = c.StartManualCommitment("a", "b", 25*time.Minute, nil, domain.EnforcementWarn)
c.RecordWindow(evidence.WindowSnapshot{Class: "slack", Title: "chat", Health: evidence.EvidenceHealth{Available: true}})
if err := c.OnTask(); err != nil {
t.Fatalf("ontask: %v", err)
@@ -434,11 +434,16 @@ func waitDriftStatus(t *testing.T, c *Controller, want string) State {
}
func startActive(t *testing.T, c *Controller, allowed []string) {
t.Helper()
startActiveLevel(t, c, allowed, domain.EnforcementWarn)
}
func startActiveLevel(t *testing.T, c *Controller, allowed []string, level domain.EnforcementLevel) {
t.Helper()
if err := c.EnterPlanning(); err != nil {
t.Fatalf("planning: %v", err)
}
if err := c.StartManualCommitment("write report", "report drafted", 25*time.Minute, allowed); err != nil {
if err := c.StartManualCommitment("write report", "report drafted", 25*time.Minute, allowed, level); err != nil {
t.Fatalf("start: %v", err)
}
}
@@ -541,7 +546,7 @@ func TestRestoredActiveSessionCanBeJudged(t *testing.T) {
t.Fatalf("new: %v", err)
}
_ = first.EnterPlanning()
if err := first.StartManualCommitment("write report", "drafted", 25*time.Minute, []string{"code"}); err != nil {
if err := first.StartManualCommitment("write report", "drafted", 25*time.Minute, []string{"code"}, domain.EnforcementWarn); err != nil {
t.Fatalf("start: %v", err)
}
second, err := New(path)
@@ -559,7 +564,7 @@ func TestLeavingPlanningClearsCoach(t *testing.T) {
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, nil); err != nil {
if err := c.StartManualCommitment("a", "b", 25*time.Minute, nil, domain.EnforcementWarn); err != nil {
t.Fatalf("start: %v", err)
}
if c.State().Coach != nil {
@@ -888,7 +893,7 @@ func TestStaleTasksFetchDiscardedAfterLeavingPlanning(t *testing.T) {
waitTasksStatus(t, c, "pending")
// Step 2: leave planning via a commitment — the stale fetch is still blocked.
if err := c.StartManualCommitment("task", "done", 25*time.Minute, nil); err != nil {
if err := c.StartManualCommitment("task", "done", 25*time.Minute, nil, domain.EnforcementWarn); err != nil {
t.Fatalf("start commitment: %v", err)
}
if c.State().RuntimeState != domain.RuntimeActive {
@@ -1066,7 +1071,7 @@ func driveToReview(t *testing.T, c *Controller) {
if err := c.EnterPlanning(); err != nil {
t.Fatalf("planning: %v", err)
}
if err := c.StartManualCommitment("write plan", "plan done", 25*time.Minute, nil); err != nil {
if err := c.StartManualCommitment("write plan", "plan done", 25*time.Minute, nil, domain.EnforcementWarn); err != nil {
t.Fatalf("start: %v", err)
}
if err := c.Complete(); err != nil {
@@ -1158,7 +1163,7 @@ func TestCarryForwardSurvivesRestart(t *testing.T) {
if err := first.EnterPlanning(); err != nil {
t.Fatalf("planning: %v", err)
}
if err := first.StartManualCommitment("a", "b", 25*time.Minute, nil); err != nil {
if err := first.StartManualCommitment("a", "b", 25*time.Minute, nil, domain.EnforcementWarn); err != nil {
t.Fatalf("start: %v", err)
}
if err := first.Complete(); err != nil {
@@ -1181,3 +1186,27 @@ func TestCarryForwardSurvivesRestart(t *testing.T) {
t.Fatalf("carry-forward not restored onto planning: %+v", st.Reflection)
}
}
func TestEnforcementLevelPersists(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "state.json")
first, err := New(path)
if err != nil {
t.Fatal(err)
}
first.SetClock(func() time.Time { return time.Unix(1000, 0) })
if err := first.EnterPlanning(); err != nil {
t.Fatal(err)
}
if err := first.StartManualCommitment("write report", "drafted", 25*time.Minute, []string{"code"}, domain.EnforcementBlock); err != nil {
t.Fatal(err)
}
second, err := New(path)
if err != nil {
t.Fatal(err)
}
if got := second.EnforcementLevelForTest(); got != domain.EnforcementBlock {
t.Fatalf("enforcement level not restored: got %q want %q", got, domain.EnforcementBlock)
}
}