@@ -481,18 +481,25 @@ func TestUndoRestoreCheckpoint_RestoresBaselineRollbackGuardPaths(t *testing.T)
481481 fixture := newRuntimeCheckpointFixture (t )
482482 targetA := filepath .Join (fixture .workdir , "baseline-a.txt" )
483483 targetB := filepath .Join (fixture .workdir , "baseline-b.txt" )
484+ targetC := filepath .Join (fixture .workdir , "baseline-c.txt" )
484485 if err := os .WriteFile (targetA , []byte ("a before" ), 0o644 ); err != nil {
485486 t .Fatalf ("WriteFile(targetA before) error = %v" , err )
486487 }
487488 if err := os .WriteFile (targetB , []byte ("b before" ), 0o644 ); err != nil {
488489 t .Fatalf ("WriteFile(targetB before) error = %v" , err )
489490 }
491+ if err := os .WriteFile (targetC , []byte ("c before" ), 0o644 ); err != nil {
492+ t .Fatalf ("WriteFile(targetC before) error = %v" , err )
493+ }
490494 if _ , err := fixture .perEditStore .CapturePreWrite (targetA ); err != nil {
491495 t .Fatalf ("CapturePreWrite(targetA) error = %v" , err )
492496 }
493497 if _ , err := fixture .perEditStore .CapturePreWrite (targetB ); err != nil {
494498 t .Fatalf ("CapturePreWrite(targetB) error = %v" , err )
495499 }
500+ if _ , err := fixture .perEditStore .CapturePreWrite (targetC ); err != nil {
501+ t .Fatalf ("CapturePreWrite(targetC) error = %v" , err )
502+ }
496503
497504 state := newRunState ("run-baseline-undo" , fixture .session )
498505 if err := fixture .service .createStartOfTurnCheckpoint (context .Background (), & state ); err != nil {
@@ -515,20 +522,26 @@ func TestUndoRestoreCheckpoint_RestoresBaselineRollbackGuardPaths(t *testing.T)
515522 if err := os .WriteFile (targetB , []byte ("b after" ), 0o644 ); err != nil {
516523 t .Fatalf ("WriteFile(targetB after) error = %v" , err )
517524 }
525+ if err := os .WriteFile (targetC , []byte ("c after" ), 0o644 ); err != nil {
526+ t .Fatalf ("WriteFile(targetC after) error = %v" , err )
527+ }
518528
519529 if _ , err := fixture .service .RestoreCheckpoint (context .Background (), GatewayRestoreInput {
520530 SessionID : fixture .session .ID ,
521531 CheckpointID : cpRecord .CheckpointID ,
522532 Mode : "baseline" ,
523- Paths : []string {"baseline-a.txt" },
533+ Paths : []string {"baseline-a.txt" , "baseline-b.txt" },
524534 }); err != nil {
525535 t .Fatalf ("RestoreCheckpoint(baseline) error = %v" , err )
526536 }
527537 if got := string (mustReadRuntimeFile (t , targetA )); got != "a before" {
528538 t .Fatalf ("baseline restored targetA = %q, want a before" , got )
529539 }
530- if got := string (mustReadRuntimeFile (t , targetB )); got != "b after" {
531- t .Fatalf ("baseline restored targetB = %q, want b after" , got )
540+ if got := string (mustReadRuntimeFile (t , targetB )); got != "b before" {
541+ t .Fatalf ("baseline restored targetB = %q, want b before" , got )
542+ }
543+ if got := string (mustReadRuntimeFile (t , targetC )); got != "c after" {
544+ t .Fatalf ("baseline restored targetC = %q, want c after" , got )
532545 }
533546
534547 if _ , err := fixture .service .UndoRestoreCheckpoint (context .Background (), fixture .session .ID ); err != nil {
@@ -540,6 +553,9 @@ func TestUndoRestoreCheckpoint_RestoresBaselineRollbackGuardPaths(t *testing.T)
540553 if got := string (mustReadRuntimeFile (t , targetB )); got != "b after" {
541554 t .Fatalf ("undo targetB = %q, want b after" , got )
542555 }
556+ if got := string (mustReadRuntimeFile (t , targetC )); got != "c after" {
557+ t .Fatalf ("undo targetC = %q, want c after" , got )
558+ }
543559}
544560
545561func TestRestoreCheckpointBaselineRejectsPathsThatNormalizeEmpty (t * testing.T ) {
@@ -606,6 +622,81 @@ func TestRestoreCheckpointBaselineWrapsRestoreBaselineError(t *testing.T) {
606622 }
607623}
608624
625+ func TestRestoreCheckpointBaselineMarksGuardBrokenWhenRestoreFails (t * testing.T ) {
626+ fixture := newRuntimeCheckpointFixture (t )
627+ target := filepath .Join (fixture .workdir , "baseline.txt" )
628+ if err := os .WriteFile (target , []byte ("before baseline" ), 0o644 ); err != nil {
629+ t .Fatalf ("WriteFile(before baseline) error = %v" , err )
630+ }
631+ if _ , err := fixture .perEditStore .CapturePreWrite (target ); err != nil {
632+ t .Fatalf ("CapturePreWrite() error = %v" , err )
633+ }
634+
635+ state := newRunState ("run-baseline-failed-restore" , fixture .session )
636+ if err := fixture .service .createStartOfTurnCheckpoint (context .Background (), & state ); err != nil {
637+ t .Fatalf ("createStartOfTurnCheckpoint() error = %v" , err )
638+ }
639+ records , err := fixture .checkpointStore .ListCheckpoints (context .Background (), fixture .session .ID , checkpoint.ListCheckpointOpts {})
640+ if err != nil {
641+ t .Fatalf ("ListCheckpoints() error = %v" , err )
642+ }
643+ if len (records ) != 1 {
644+ t .Fatalf ("records = %#v, want 1" , records )
645+ }
646+ cpRecord := records [0 ]
647+ if err := fixture .checkpointStore .UpdateCheckpointStatus (context .Background (), cpRecord .CheckpointID , agentsession .CheckpointStatusAvailable ); err != nil {
648+ t .Fatalf ("UpdateCheckpointStatus() error = %v" , err )
649+ }
650+ if err := os .Remove (target ); err != nil {
651+ t .Fatalf ("Remove(target) error = %v" , err )
652+ }
653+ if err := os .Mkdir (target , 0o755 ); err != nil {
654+ t .Fatalf ("Mkdir(target) error = %v" , err )
655+ }
656+
657+ _ , _ , err = fixture .service .restoreCheckpointBaseline (context .Background (), fixture .session .ID , cpRecord .CheckpointID , []string {"baseline.txt" })
658+ if err == nil || ! strings .Contains (err .Error (), "baseline restore code" ) {
659+ t .Fatalf ("restoreCheckpointBaseline() error = %v, want baseline restore code" , err )
660+ }
661+
662+ records , err = fixture .checkpointStore .ListCheckpoints (context .Background (), fixture .session .ID , checkpoint.ListCheckpointOpts {})
663+ if err != nil {
664+ t .Fatalf ("ListCheckpoints(all) error = %v" , err )
665+ }
666+ seenBrokenGuard := false
667+ for _ , record := range records {
668+ if record .Reason != agentsession .CheckpointReasonGuard {
669+ continue
670+ }
671+ if record .Status != agentsession .CheckpointStatusBroken {
672+ t .Fatalf ("guard status = %q, want broken" , record .Status )
673+ }
674+ if record .CodeCheckpointRef != "" {
675+ if perEditID := checkpoint .PerEditCheckpointIDFromRef (record .CodeCheckpointRef ); perEditID != "" {
676+ if err := fixture .perEditStore .RestoreExact (context .Background (), perEditID ); err == nil {
677+ t .Fatalf ("failed restore guard %q still has restorable per-edit metadata" , perEditID )
678+ }
679+ }
680+ }
681+ seenBrokenGuard = true
682+ }
683+ if ! seenBrokenGuard {
684+ t .Fatal ("expected failed baseline restore to leave a broken guard record" )
685+ }
686+
687+ available , err := fixture .checkpointStore .ListCheckpoints (context .Background (), fixture .session .ID , checkpoint.ListCheckpointOpts {
688+ RestorableOnly : true ,
689+ })
690+ if err != nil {
691+ t .Fatalf ("ListCheckpoints(restorable) error = %v" , err )
692+ }
693+ for _ , record := range available {
694+ if record .Reason == agentsession .CheckpointReasonGuard {
695+ t .Fatalf ("broken guard %q should not be returned as restorable" , record .CheckpointID )
696+ }
697+ }
698+ }
699+
609700func TestUndoRestoreCheckpoint_RestoresGuardState (t * testing.T ) {
610701 fixture := newRuntimeCheckpointFixture (t )
611702 target := filepath .Join (fixture .workdir , "undo.txt" )
0 commit comments