diff --git a/native/redux/update-roles-and-permissions.js b/native/redux/legacy-update-roles-and-permissions.js
rename from native/redux/update-roles-and-permissions.js
rename to native/redux/legacy-update-roles-and-permissions.js
--- a/native/redux/update-roles-and-permissions.js
+++ b/native/redux/legacy-update-roles-and-permissions.js
@@ -6,11 +6,13 @@
   makePermissionsBlob,
   makePermissionsForChildrenBlob,
 } from 'lib/permissions/thread-permissions.js';
+import { assertAllThreadInfosAreLegacy } from 'lib/shared/thread-utils.js';
 import type { ThreadPermissionsBlob } from 'lib/types/thread-permission-types.js';
 import type {
   LegacyRawThreadInfo,
   ThreadStoreThreadInfos,
   LegacyMemberInfo,
+  RawThreadInfos,
 } from 'lib/types/thread-types.js';
 import { values } from 'lib/utils/objects.js';
 
@@ -48,10 +50,13 @@
   +[member: string]: ?ThreadPermissionsBlob,
 };
 
-function updateRolesAndPermissions(
-  threadStoreInfos: ThreadStoreThreadInfos,
-): ThreadStoreThreadInfos {
-  const updatedThreadStoreInfos = { ...threadStoreInfos };
+// This migration utility can only be used with LegacyRawThreadInfos
+function legacyUpdateRolesAndPermissions(
+  threadStoreInfos: RawThreadInfos,
+): RawThreadInfos {
+  const updatedThreadStoreInfos = assertAllThreadInfosAreLegacy({
+    ...threadStoreInfos,
+  });
 
   const recursivelyUpdateRoles = (
     node: $ReadOnly<ThreadTraversalNode>,
@@ -166,4 +171,4 @@
   return updatedThreadStoreInfos;
 }
 
-export { updateRolesAndPermissions };
+export { legacyUpdateRolesAndPermissions };
diff --git a/native/redux/persist.js b/native/redux/persist.js
--- a/native/redux/persist.js
+++ b/native/redux/persist.js
@@ -101,11 +101,11 @@
 } from './client-db-utils.js';
 import { defaultState } from './default-state.js';
 import { migrateThreadStoreForEditThreadPermissions } from './edit-thread-permission-migration.js';
+import { legacyUpdateRolesAndPermissions } from './legacy-update-roles-and-permissions.js';
 import { persistMigrationForManagePinsThreadPermission } from './manage-pins-permission-migration.js';
 import { persistMigrationToRemoveSelectRolePermissions } from './remove-select-role-permissions.js';
 import type { AppState } from './state-types.js';
 import { unshimClientDB } from './unshim-utils.js';
-import { updateRolesAndPermissions } from './update-roles-and-permissions.js';
 import { commCoreModule } from '../native-modules.js';
 import { defaultDeviceCameraInfo } from '../types/camera.js';
 import { isTaskCancelledError } from '../utils/error-handling.js';
@@ -600,10 +600,16 @@
     return state;
   },
   [38]: (state: AppState) =>
-    updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions),
+    updateClientDBThreadStoreThreadInfos(
+      state,
+      legacyUpdateRolesAndPermissions,
+    ),
   [39]: (state: AppState) => unshimClientDB(state, [messageTypes.EDIT_MESSAGE]),
   [40]: (state: AppState) =>
-    updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions),
+    updateClientDBThreadStoreThreadInfos(
+      state,
+      legacyUpdateRolesAndPermissions,
+    ),
   [41]: (state: AppState) => {
     const queuedReports = state.reportStore.queuedReports.map(report => ({
       ...report,
@@ -979,7 +985,10 @@
     return state;
   },
   [60]: (state: AppState) =>
-    updateClientDBThreadStoreThreadInfos(state, updateRolesAndPermissions),
+    updateClientDBThreadStoreThreadInfos(
+      state,
+      legacyUpdateRolesAndPermissions,
+    ),
 };
 
 // After migration 31, we'll no longer want to persist `messageStore.messages`
diff --git a/native/redux/update-roles-and-permissions.test.js b/native/redux/update-roles-and-permissions.test.js
--- a/native/redux/update-roles-and-permissions.test.js
+++ b/native/redux/update-roles-and-permissions.test.js
@@ -1,29 +1,31 @@
 // @flow
 
+import { legacyUpdateRolesAndPermissions } from './legacy-update-roles-and-permissions.js';
 import {
   threadStoreThreads,
   threadStoreThreadsWithEmptyRolePermissions,
   threadStoreThreadsWithEmptyRolePermissionsAndMemberPermissions,
   threadStoreThreadsWithEmptyRoleAndMemberAndCurrentUserPermissions,
 } from './update-roles-and-permissions-test-data.js';
-import { updateRolesAndPermissions } from './update-roles-and-permissions.js';
 
 describe.skip('updateRolesAndPermissions()', () => {
   it('should leave threadStoreThreads from server unchanged', () => {
-    expect(updateRolesAndPermissions(threadStoreThreads)).toStrictEqual(
+    expect(legacyUpdateRolesAndPermissions(threadStoreThreads)).toStrictEqual(
       threadStoreThreads,
     );
   });
 
   it('should construct role permissions when missing from existing store', () => {
     expect(
-      updateRolesAndPermissions(threadStoreThreadsWithEmptyRolePermissions),
+      legacyUpdateRolesAndPermissions(
+        threadStoreThreadsWithEmptyRolePermissions,
+      ),
     ).toStrictEqual(threadStoreThreads);
   });
 
   it('should construct role permissions AND member permissions when missing from existing store', () => {
     expect(
-      updateRolesAndPermissions(
+      legacyUpdateRolesAndPermissions(
         threadStoreThreadsWithEmptyRolePermissionsAndMemberPermissions,
       ),
     ).toStrictEqual(threadStoreThreads);
@@ -31,7 +33,7 @@
 
   it('should construct role permissions AND member permissions AND current user permissions when missing from existing store', () => {
     expect(
-      updateRolesAndPermissions(
+      legacyUpdateRolesAndPermissions(
         threadStoreThreadsWithEmptyRoleAndMemberAndCurrentUserPermissions,
       ),
     ).toStrictEqual(threadStoreThreads);