feat(marmot): add Reset Marmot State safety valve in settings
Gives users a last-resort "nuclear" option when Marmot local state is corrupted or otherwise unrecoverable — wipes every MLS group, retained epoch secret, persisted KeyPackage bundle, subscription and in-memory chatroom for the current account. Does not broadcast leave/SelfRemove commits, since a graceful teardown may be impossible in exactly the scenarios where users reach for this option. The KeyPackage is republished lazily on the next sync so the account stays reachable. Adds clearAllState() helpers on MlsGroupManager and KeyPackageRotationManager, a resetAllState() orchestrator on MarmotManager, an Account.resetMarmotState() entry point, and a destructive row + confirm dialog in the AllSettingsScreen Danger Zone that mirrors the existing Request-to-Vanish pattern. https://claude.ai/code/session_01Unm6uLHGLj9UcBY7hWfJVW
This commit is contained in:
+20
@@ -157,6 +157,26 @@ class KeyPackageRotationManager(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe all in-memory KeyPackage bundles plus the persisted snapshot.
|
||||
* Intended for user-initiated Marmot resets — the caller is responsible
|
||||
* for re-publishing KeyPackages afterwards (e.g. via
|
||||
* `Account.ensureMarmotKeyPackagePublished`).
|
||||
*/
|
||||
suspend fun clearAllState() =
|
||||
mutex.withLock {
|
||||
activeBundles.clear()
|
||||
pendingRotations.clear()
|
||||
eventIdToSlot.clear()
|
||||
namedSlotDTags.clear()
|
||||
val store = store ?: return@withLock
|
||||
try {
|
||||
store.delete()
|
||||
} catch (e: Exception) {
|
||||
Log.w("KeyPackageRotationManager", "clearAllState(): failed to delete snapshot: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private data class Snapshot(
|
||||
val bundles: Map<String, KeyPackageBundle>,
|
||||
val pending: Set<String>,
|
||||
|
||||
+26
@@ -520,6 +520,32 @@ class MlsGroupManager(
|
||||
store.delete(nostrGroupId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Wipe all MLS group state, both in-memory and on-disk. Intended as a
|
||||
* user-initiated "nuclear" reset for the Marmot subsystem — does NOT
|
||||
* publish any SelfRemove/leave events, because the reset path is meant
|
||||
* to recover from unusable local state where graceful leave may not
|
||||
* even be possible.
|
||||
*
|
||||
* The union of in-memory group IDs and [MlsGroupStateStore.listGroups]
|
||||
* is used so any orphaned on-disk state (e.g. a prior restore that
|
||||
* failed to decode into memory) is also removed.
|
||||
*/
|
||||
suspend fun clearAllState() =
|
||||
mutex.withLock {
|
||||
val ids = (groups.keys + store.listGroups().toSet()).toList()
|
||||
Log.d(TAG) { "clearAllState(): wiping ${ids.size} group(s): $ids" }
|
||||
for (id in ids) {
|
||||
try {
|
||||
removeGroupStateUnlocked(id)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG) { "clearAllState(): failed to wipe $id: ${e.message}" }
|
||||
}
|
||||
}
|
||||
groups.clear()
|
||||
retainedEpochs.clear()
|
||||
}
|
||||
|
||||
// --- Key Export ---
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user