@@ -1575,6 +1575,98 @@ describe("codex-cli sync", () => {
15751575 expect ( restored ?. accounts [ 0 ] ?. refreshToken ) . toBe ( "refresh-old" ) ;
15761576 } ) ;
15771577
1578+ it . each ( [
1579+ [ "null checkpoint" , null ] ,
1580+ [
1581+ "blank checkpoint path" ,
1582+ {
1583+ name : "accounts-codex-cli-sync-snapshot-invalid" ,
1584+ path : " " ,
1585+ } ,
1586+ ] ,
1587+ ] satisfies Array <
1588+ [
1589+ string ,
1590+ { name : string ; path : string } | null ,
1591+ ]
1592+ > ) (
1593+ "falls back to the newest valid rollback checkpoint when a newer manual change has a %s" ,
1594+ async ( _label , invalidSnapshot ) => {
1595+ const snapshotPath = join ( tempDir , "rollback-fallback-snapshot.json" ) ;
1596+ await writeFile (
1597+ snapshotPath ,
1598+ JSON . stringify (
1599+ {
1600+ version : 3 ,
1601+ accounts : [
1602+ {
1603+ accountId : "acc_old" ,
1604+ accountIdSource : "token" ,
1605+ email : "old@example.com" ,
1606+ refreshToken : "refresh-old" ,
1607+ accessToken : "access-old" ,
1608+ addedAt : 1 ,
1609+ lastUsed : 1 ,
1610+ } ,
1611+ ] ,
1612+ activeIndex : 0 ,
1613+ activeIndexByFamily : { codex : 0 } ,
1614+ } satisfies AccountStorageV3 ,
1615+ null ,
1616+ 2 ,
1617+ ) ,
1618+ "utf-8" ,
1619+ ) ;
1620+
1621+ const summary = {
1622+ sourceAccountCount : 1 ,
1623+ targetAccountCountBefore : 1 ,
1624+ targetAccountCountAfter : 1 ,
1625+ addedAccountCount : 0 ,
1626+ updatedAccountCount : 1 ,
1627+ unchangedAccountCount : 0 ,
1628+ destinationOnlyPreservedCount : 0 ,
1629+ selectionChanged : false ,
1630+ } ;
1631+ const validRun : CodexCliSyncRun = {
1632+ outcome : "changed" ,
1633+ runAt : 10 ,
1634+ sourcePath : accountsPath ,
1635+ targetPath : targetStoragePath ,
1636+ summary,
1637+ trigger : "manual" ,
1638+ rollbackSnapshot : {
1639+ name : "accounts-codex-cli-sync-snapshot-valid" ,
1640+ path : snapshotPath ,
1641+ } ,
1642+ } ;
1643+ const newerInvalidRun : CodexCliSyncRun = {
1644+ outcome : "changed" ,
1645+ runAt : 20 ,
1646+ sourcePath : accountsPath ,
1647+ targetPath : targetStoragePath ,
1648+ summary,
1649+ trigger : "manual" ,
1650+ rollbackSnapshot : invalidSnapshot ,
1651+ } ;
1652+
1653+ await appendSyncHistoryEntry ( {
1654+ kind : "codex-cli-sync" ,
1655+ recordedAt : validRun . runAt ,
1656+ run : validRun ,
1657+ } ) ;
1658+ await appendSyncHistoryEntry ( {
1659+ kind : "codex-cli-sync" ,
1660+ recordedAt : newerInvalidRun . runAt ,
1661+ run : newerInvalidRun ,
1662+ } ) ;
1663+
1664+ const plan = await getLatestCodexCliSyncRollbackPlan ( ) ;
1665+ expect ( plan . status ) . toBe ( "ready" ) ;
1666+ expect ( plan . snapshot ) . toEqual ( validRun . rollbackSnapshot ) ;
1667+ } ,
1668+ ) ;
1669+
15781670 it ( "marks the rollback plan unavailable when the checkpoint file is missing" , async ( ) => {
15791671 const missingRun : CodexCliSyncRun = {
15801672 outcome : "changed" ,
0 commit comments