Skip to content

fix: defer registry sync during profile deletion - #1538

Open
ben-kaufman wants to merge 1 commit into
masterfrom
codex/android-profile-cleanup-registry-20261016
Open

ben-kaufman wants to merge 1 commit into
masterfrom
codex/android-profile-cleanup-registry-20261016

Conversation

@ben-kaufman

@ben-kaufman ben-kaufman commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Closes #1539

Related to #1522 and pubky/paykit-rs#197. This is a partial improvement, not a complete fix for deletion latency.

Twin: synonymdev/bitkit-ios#979

This PR avoids an intermediate App Registry update during full profile deletion.

Description

  • Withdraw private payment lists as before, then leave registry reconciliation to the existing final public cleanup. Public endpoint removal and the final disabled-capability update still run.
  • Keep the existing private cleanup marker pending until deletion succeeds or normal cleanup retry completes. Failure, cancellation and undelivered withdrawals retain pending cleanup. Disconnect and sharing-disable behavior are unchanged.
  • Keep authorization, identity, profile-revision checks, session revocation and uncertain-write safety waits unchanged. There is no Paykit SDK, binding, dependency, protocol or storage-format change.

Out of Scope

  • Paykit SDK request-count optimizations, Homeserver changes and safety-wait reductions.
  • Deletion progress UI and a fixed completion-time target. The larger deletion latency issue remains open.
  • Automatic-work admission changes in fix: pause paykit work during profile deletion #1510; this PR is independently based on master.

Design

N/A - no UI changes.

Preview

N/A - no visual changes.

QA Notes

Journeys

  • updated delete-profile.xml - record zero-contact deletion time, then repeat the existing 62-contact cases; progress remains visible and reopening Profile does not restore the deleted profile.

Manual Tests

  • Interrupt deletion after private withdrawal and before profile removal, then restart the same identity: cleanup remains pending and normal retry reconciles the registry; a failed retry retains the marker. Requires controlled operation and network gates.
  • Hold a private withdrawal undelivered: it stays pending rather than being acknowledged by deferred registry reconciliation. Disconnect and disabling private sharing must still reconcile normally. Requires controlled outbound delivery gates.

Automated Checks

  • added PrivatePaykitRepoTest.kt - private withdrawal, deferred registry reconciliation, persisted-marker retry, cancellation and undelivered withdrawal retention.
  • added PubkyServiceTest.kt - final public cleanup attempts the disabled-capability update after endpoint removal, including endpoint and registry failures.
  • updated EditProfileViewModelTest.kt - only full deletion opts into deferral, before profile removal; failure retains private state.

Local validation: 366 focused tests and all 4,256 native tests across 239 suites passed, with zero failures/errors/skips. Compile and dev APK build passed. Full Detekt, including formatting, has exactly the same 1,401 findings as an independently run exact-master baseline (eb32cdd7); no introduced compiler diagnostics. Five tests added, none removed. Frozen independent correctness, repository-native and polish review found no actionable issue.

The controlled repository retry test reuses a persistent-store fixture; it is not an actual process-restart fault test. Author-run device fault-injection and broader populated private-contact acceptance remain incomplete; qualified subsequent reviewer evidence is recorded below. No funds were sent and no safety wait was shortened.

Device timing limitation: the baseline zero-contact flow reached onboarding between 47.29 and 52.54 seconds (coarse accessibility polling). After installing the final candidate without clearing data, the deleted profile stayed absent on restart. Candidate timing was then blocked before deletion by an input-dispatch ANR while typing a new profile name. Its main-thread stack was in Compose keyboard-modifier dispatch, with emulator CPU pressure; the creation code is unchanged, but the exact ANR cause is not established. The failed run was stopped and preserved, not counted as a deletion pass. No Android before/after speedup is claimed.

Reviewer Device Evidence

Ovi review 5554760779 approves exact d6899001 with no findings. The reviewer used two Android 15 emulators on staging, with disclosed uncommitted registry/marker logging, deletion holds and a contact-import fixture. The reviewer reports:

  • With one linked contact and a published private list, full deletion made one final privatePayments=false App Registry update versus two updates on instrumented base eb32cdd. The deleted profile remained absent after reopening. Counts came from the inserted logging and private cache, not a parent HTTP trace.
  • Killing the app after private withdrawal but before profile removal retained cleanup pending across restart. Offline retries retained the marker; after an online restart with a published list, two retries encountered shared_state_busy, and a later retry reconciled about two minutes after relaunch. The retained identity then republished its private list.
  • Killing after profile removal but before local clearing left no profile on relaunch. The leftover marker caused immediate background identity_error retries without a session until creating a new profile cleared both cleanup markers. This is an observed recovery limitation, not evidence that those retries never occur.
  • Turning contact payments off and Disconnect still synchronized the registry after private withdrawal. An undelivered withdrawal reported PrivateUnavailable, retained the pending marker and published-list flag, and made no intermediate registry update before later delivery/retry.
  • Deletion with 62 imported random contact keys made one registry update and reached onboarding in reported 25s and 68s samples, depending on whether contact preparation had settled. None of those contacts was linked, so there was no private list to withdraw; this does not establish 62 active private-contact cleanup. No uncertain-write wait was reported in these samples, and no safety wait was shortened.

These are attributed reviewer results, not author replication. The 34s linked-contact sample and 25s/68s imported-contact samples are not a matched before/after timing benchmark or a maximum-duration guarantee. The zero-contact delete-profile.xml journey was carried from jvsena's review 5552784324, which reported 7/7 actions and 28.7s on the CI APK, not rerun by Ovi. The instrumented review did not execute the native unit suite or journey file. Author fault-injection replication and broader populated-contact acceptance remain incomplete; the compound manual checkboxes remain open.

@ovitrif ovitrif added this to the 3.0.5 milestone Oct 16, 2026
@greptile-apps

greptile-apps Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable issues were found.

Summary

Full profile deletion now skips the intermediate registry update after withdrawing private payment lists. Final public cleanup still attempts the disabled-capability update.

  • Profile deletion skips an extra registry sync before removing the profile.

ben-kaufman explicitly identifies deletion latency as only partly addressed. Request-count changes, shorter safety waits, and a fixed completion-time target are out of scope.

Reviews (1) · Last reviewed commit: "fix: defer registry sync during profile ..." · Reviewed by Greptile

@github-actions

Copy link
Copy Markdown
Contributor

Regtest APK

Built from d689900 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@talosmachina talosmachina left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings. Defers the App Registry sync in full profile deletion to the final public cleanup in signOut, and keeps the private cleanup marker pending until closeAndClear succeeds. Reviewed d689900, full tier.

What I checked, and 4 candidates I ruled out

Read in full: removePublishedEndpointsForCleanup, retryPendingEndpointRemoval, syncPaykitAppAfterCleanup, closeAndClear (PrivatePaykitRepo.kt); attemptDeleteProfile / disconnectProfile (EditProfileViewModel.kt); deleteProfileWithSessionRetry, deleteProfile, signOut, clearLocalState (PubkyRepo.kt); removeBitkitPaymentEndpoints (PubkyService.kt); syncPaykitApp (PublicPaykitRepo.kt, PaykitSdkService.kt); retryPendingPaykitEndpointRemoval (AppViewModel.kt).
Call sites traced: removePublishedEndpointsForCleanup has four callers (EditProfileViewModel delete and disconnect, ProfileViewModel:168, WipeWalletUseCase:91). Only full deletion passes deferAppSync = true; the rest keep the single-argument overload, which behaves as before.
CI: lint, detekt and build-local green. build and the e2e-tests-local shards were still running when I reviewed. On the Bitkit track this box does not build Android branches, so this review comes from reading the code and from CI.

Ruled out

  • The deferred sync drops a capability update the registry needed: no. The removed intermediate call is publicPaykitRepo.syncPaykitApp() with no argument, which resolves privateSharingEnabled from settings.sharesPrivatePaykitEndpoints. That setting is still true at that point, because paykitDisabled() only runs inside deleteProfile. So the old call republished privatePayments = true. The capability actually changes in removeBitkitPaymentEndpoints (syncPaykitApp(privatePaymentsEnabled = false)), and that call still runs during signOut.
  • The marker stays set after a successful deletion: no. closeAndClear runs cacheStore.reset(), which brings cleanupPending back to its default false. The new repo test asserts this.
  • A failed deletion strands private payments: no. The marker stays true, so retryPendingEndpointRemoval (from the maintenance tick and refreshPrivatePaykitEndpointsIfEnabled) withdraws again, syncs the registry and clears it. That path is unchanged and is covered by the persisted-marker retry test. Until that tick, contact preparation stays paused, which is the behaviour the PR describes.
  • Public cleanup fails and nothing retries the registry: no. When removeBitkitPaymentEndpoints fails, signOut calls clearLocalState(publicPaykitCleanupPending = true). hadPaykitState stays true there because deleteProfile sets publicPaykitCleanupPending first. This is the same handling as before the change.

Merge confidence: 4/5. No findings, but build and the e2e shards had not finished at review time and the branch was not built here.

@piotr-iohk piotr-iohk left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QA review

Scope: Reviewed the full PR diff against its merge base, at d6899001.

No new actionable code findings.

Full profile deletion withdraws private payment lists and leaves the private cleanup marker pending, so the intermediate App Registry sync no longer runs while sharing is still enabled. Final public cleanup still removes public endpoints and publishes the disabled private-payments capability before session revocation. A failed, cancelled, or interrupted deletion keeps that marker for a later retry, and a failed retry retains it. Disconnect, wallet wipe, and disabling private sharing still sync immediately.

Compared with bitkit-ios#979 at b948a469: both defer that sync only for full deletion. iOS keeps the public repair marker and checks again before public reconciliation; Android keeps the private cleanup marker and does not enter retry once deletion is already active. Successful deletion still clears the private marker by resetting the cache after public cleanup.

Validation: inspected PrivatePaykitRepoTest, PubkyServiceTest, and EditProfileViewModelTest; did not execute them. Build and lint passed on this revision.

Device testing: not performed in this review.

Ready for device testing.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code scan at d689900: no findings. I reviewed it as a Paykit endpoint-cleanup change.

Checked:

  • The skipped intermediate call published privatePayments = true, so deferring it loses nothing. The only false publish is the final cleanup, which still runs before the session is revoked: PubkyRepo.deleteProfile():1069 → signOut():1751-1772 → PubkyService.removeBitkitPaymentEndpoints():66-77. The new PubkyServiceTest asserts the order syncPublicEndpoints(empty) → syncPaykitApp(false) across failures.
  • The marker is set at PrivatePaykitRepo.kt:492 and kept through failure and cancellation (:494-499; cancelled profile withdrawal preserves cleanup for retry). It is retried from foreground, network and the 30/60 s maintenance tick, and cleared by closeAndClear() → cacheStore.reset() on success.
  • A deferred sync cannot acknowledge an undelivered withdrawal: non-empty pendingOutboundPrivateCounterparties throws PrivateUnavailable before the deferAppSync branch (:1836-1846).
  • Retries are gated during deletion (:399), and endProfileDeletion runs in finally under NonCancellable. Back is blocked while busy.
  • The other callers (disconnectProfile, ProfileViewModel, WipeWalletUseCase) keep deferAppSync = false.
  • This does not change the policy_error … private_payments in synonymdev/bitkit-ios#984 either way.

Performance-relevant: it removes one publishPaykitApp round trip from every full deletion.

Device gate (partial): d689900 — not run yet. Pending: delete-profile.xml with one publishPaykitApp per deletion in the log, and the profile not returning on reopen. The device result will follow.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at d689900. The code scan from my earlier review stands, with no findings.

Device gate: d689900 — delete-profile.xml: 7/7 actions pass on Android, using the CI APK and a fresh profile with Paykit on and 0 contacts. During deletion a spinner replaces Delete Profile, and Save and Cancel are disabled with their labels visible. Onboarding appears when it finishes. The profile does not return on reopen or after a force-stop and relaunch (Found no saved paykit session). No crash or ANR. iOS twin synonymdev/bitkit-ios#979 passes the same journey, with exactly one Paykit app publish during deletion.

perf: delete profile ~28.7 s at 0 contacts (19:33:37.8 → 19:34:06.5 UTC). Earlier Android runs measured ~31–35 s on this setup. l10n: no UI text changed.

⚠️ Not verified on device: the publish count on Android. The app logs only a failed publishPaykitApp (0 in the window), never a successful one. Confirming exactly one needs a debug log at the call site or homeserver request logs. The approval rests on the code trace and PubkyServiceTest for the order. The 62-contact variant was not run either.

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ✅ Approve

Findings: none.

Pair PR synonymdev/bitkit-ios#979 at b948a46 matches: it defers the same sync only for full deletion, and keeps a public repair marker where Android keeps the private one.

QA for d689900:
Tested on two Android 15 emulators on staging: wallet A deletes, wallet B is its linked contact. The build is d689900 plus debug-only log lines at each App Registry publish and cleanup-marker change, two holds and a contact seed (patch below, not pushed). Registry update counts and marker states come from those lines and the app's private Paykit cache.

Test 1: full deletion updates the App Registry once ✅ With one published private list, deletion made one registry update (the final privatePayments=false) where base eb32cdd made two (true, then false), and the profile stayed gone on reopen.
case1.2x.mp4

Base eb32cdd with the same log lines, on wallet B:

base-eb32cdd.2x.mp4

Tap to onboarding took 34 s, with the spinner on Delete Profile and Save and Cancel disabled. The deletions in tests 3 and 4 also made exactly one registry update each.

Test 2: deletion interrupted after the private withdrawal ✅ Killed after a successful private step and before profile removal, the app kept cleanup pending (base clears it at that point), and the first retry that reached the homeserver made one registry update and cleared the marker, with the profile intact.
case2a.2x.mp4
case2b.2x.mp4

The first run had no contacts and relaunched offline: the retries failed and kept the marker until the network came back. The second run withdrew B's published list at the hold and relaunched online: two retries met the write lock the killed process left (shared_state_busy), and the third, about 2 minutes after relaunch, reconciled. A then published its private list for B again.

Test 3: deletion interrupted after profile removal ✅ Killed after the profile was removed and before local state was cleared, the app relaunched with no profile (Profile showed onboarding), and the leftover marker only made background retries fail until a new profile cleared it and published the registry.
case3.2x.mp4

Without a session each retry failed at once (identity_error) on foreground and USDT-availability triggers, with nothing on screen. Setting up the new profile cleared both the private and the public cleanup markers.

Test 4: deletion with 62 contacts ✅ With 62 imported contacts, deletion made one registry update and reached onboarding in 25 s once preparation had settled, and in 68 s when started 8 s after the import.
case4a.2x.mp4
case4b.2x.mp4

The 62 are random keys imported through PubkyRepo.importContacts, so none linked and the private step had nothing to withdraw. The 68 s run is deletion competing with preparation of the just-imported contacts, the latency #1522 tracks; the spinner stayed visible throughout. No uncertain-write wait appeared in the app or Paykit log.

Test 5: turning off contact payments and Disconnect ✅ Turning contact payments off and Disconnect each still updated the registry right after the private withdrawal.
case5.2x.mp4

Disconnect was reached from the Unable to Delete Profile dialog after a deletion attempted offline. That failed deletion kept cleanup pending and the retry on reconnect cleared it; Disconnect's own cleanup then synced the registry before the final privatePayments=false update.

Test 6: undelivered private withdrawal ✅ Right after re-linking with B, the SDK reported the withdrawal to B still pending, and the deferred step failed with PrivateUnavailable, kept the marker and the published-list flag, and made no registry update until a later retry delivered it.
case6.2x.mp4

The pending withdrawal shows only in the app log and private cache; the recording shows the deletion attempt held at that point.

test hook

Not pushed. Logs each App Registry publish with its privatePayments value, each cleanup-marker change and each deletion step; holds before deleteProfileWithSessionRetry or closeAndClear when a flag file exists; seeds N contacts when Edit Profile opens.

diff --git a/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt b/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt
index 1afcf97e..50786baf 100644
--- a/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt
+++ b/app/src/main/java/to/bitkit/repositories/PrivatePaykitRepo.kt
@@ -396,10 +396,12 @@ class PrivatePaykitRepo @Inject constructor(
         savedPublicKeys: Collection<String>,
     ): Result<Unit> = withContext(serializedDispatcher) {
         runSuspendCatching {
+            android.util.Log.i("TestHook", "retryPendingEndpointRemoval isDeletingProfile=$isDeletingProfile") // test hook
             if (isDeletingProfile) return@runSuspendCatching
             val settings = settingsStore.data.first()
             val hasDisabledPublications = !settings.sharesPrivatePaykitEndpoints && hasPublishedPrivateEndpoints()
             val cleanupPending = isContactSharingCleanupPending()
+            android.util.Log.i("TestHook", "retryPendingEndpointRemoval cleanupPending=$cleanupPending hasDisabledPublications=$hasDisabledPublications") // test hook
             if (cleanupPending || hasDisabledPublications) {
                 if (!cleanupPending) updateContactSharingCleanupPending(true)
                 removePublishedEndpoints().getOrThrow()
@@ -408,7 +410,9 @@ class PrivatePaykitRepo @Inject constructor(
                 updateContactSharingCleanupPending(false)
             }
             retryPendingDeletedContactEndpointRemoval(savedPublicKeys).getOrThrow()
+            android.util.Log.i("TestHook", "retryPendingEndpointRemoval done") // test hook
         }.onFailure {
+            android.util.Log.i("TestHook", "retryPendingEndpointRemoval failed: ${it.message}") // test hook
             Logger.warn("Failed to retry pending Paykit contact endpoint removal", it, context = TAG)
         }
     }
@@ -502,6 +506,7 @@ class PrivatePaykitRepo @Inject constructor(
     }
 
     suspend fun closeAndClear(): Result<Unit> = withContext(serializedDispatcher) {
+        android.util.Log.i("TestHook", "closeAndClear") // test hook
         runSuspendCatching {
             invalidateContactPreparation()
             publicationMutex.withLock {
@@ -1897,7 +1902,7 @@ class PrivatePaykitRepo @Inject constructor(
     }
 
     private suspend fun syncPaykitAppAfterCleanup(): Result<Unit> =
-        publicPaykitRepo.syncPaykitApp().onFailure {
+        publicPaykitRepo.syncPaykitApp().also { android.util.Log.i("TestHook", "syncPaykitAppAfterCleanup ok=${it.isSuccess}") }.onFailure { // test hook
             runSuspendCatching { settingsStore.update { it.copy(publicPaykitCleanupPending = true) } }
                 .onFailure(it::addSuppressed)
         }
@@ -2114,6 +2119,7 @@ class PrivatePaykitRepo @Inject constructor(
         isStillCurrent: (() -> Boolean)? = null,
     ) {
         if (isPending) invalidateContactPreparation()
+        android.util.Log.i("TestHook", "cleanupPending <- $isPending") // test hook
         cacheStore.update {
             if (isStillCurrent?.invoke() == false) it else it.copy(cleanupPending = isPending)
         }
diff --git a/app/src/main/java/to/bitkit/services/PaykitSdkService.kt b/app/src/main/java/to/bitkit/services/PaykitSdkService.kt
index cca86961..854a8aa1 100644
--- a/app/src/main/java/to/bitkit/services/PaykitSdkService.kt
+++ b/app/src/main/java/to/bitkit/services/PaykitSdkService.kt
@@ -806,6 +806,7 @@ class PaykitSdkService @Inject constructor(
         operationLock.withLock(priority) {
             withStateRevisionTracking { handle ->
                 val capabilities = appCapabilities(completeSdkCall { handle.identityStatus() })
+                android.util.Log.i("TestHook", "publishPaykitApp site=syncPaykitApp privatePayments=${capabilities.privatePayments && privatePaymentsEnabled} requested=$privatePaymentsEnabled caller=${Throwable().stackTrace.drop(1).filter { it.className.startsWith("to.bitkit") && !it.className.contains("PaykitSdkService") }.take(4).joinToString(" < ") { it.className.substringAfterLast('.') + "." + it.methodName + ":" + it.lineNumber }}") // test hook
                 completeSdkCall {
                     handle.publishPaykitApp(
                         displayName = "Bitkit",
@@ -1448,6 +1449,7 @@ class PaykitSdkService @Inject constructor(
             val capabilities = appCapabilities(identityStatus)
             if (capabilities.privatePayments) {
                 val privatePayments = settingsStore.data.first().sharesPrivatePaykitEndpoints
+                android.util.Log.i("TestHook", "publishPaykitApp site=publishAppIfLiveSessionAvailable privatePayments=$privatePayments") // test hook
                 completeSdkCall {
                     handle.publishPaykitApp("Bitkit", capabilities.copy(privatePayments = privatePayments))
                 }
diff --git a/app/src/main/java/to/bitkit/services/PubkyService.kt b/app/src/main/java/to/bitkit/services/PubkyService.kt
index 3ef8918b..21fc626c 100644
--- a/app/src/main/java/to/bitkit/services/PubkyService.kt
+++ b/app/src/main/java/to/bitkit/services/PubkyService.kt
@@ -64,6 +64,7 @@ class PubkyService @Inject constructor(
     }
 
     suspend fun removeBitkitPaymentEndpoints() = ServiceQueue.CORE.background {
+        android.util.Log.i("TestHook", "removeBitkitPaymentEndpoints") // test hook
         val endpointError = runSuspendCatching {
             val report = paykitSdkService.syncPublicEndpoints(emptyList())
             if (report.failed.isNotEmpty()) throw AppError("Failed to remove Paykit payment endpoints")
diff --git a/app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt b/app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt
index b8da23ac..ca830890 100644
--- a/app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt
+++ b/app/src/main/java/to/bitkit/ui/screens/profile/EditProfileViewModel.kt
@@ -48,8 +48,28 @@ class EditProfileViewModel @Inject constructor(
 
     init {
         loadProfile()
+        seedTestContacts() // test hook
     }
 
+    private fun seedTestContacts() { // test hook
+        val file = java.io.File(context.filesDir, "test-hook-seed-contacts") // test hook
+        if (!file.exists()) return // test hook
+        val keys = file.readLines().map { it.trim() }.filter { it.isNotEmpty() } // test hook
+        file.delete() // test hook
+        viewModelScope.launch { // test hook
+            android.util.Log.i("TestHook", "seed: importing ${keys.size} contacts") // test hook
+            val result = pubkyRepo.importContacts( // test hook
+                keys.mapIndexed { i, k -> // test hook
+                    to.bitkit.models.PubkyProfile( // test hook
+                        publicKey = k, name = "Seed ${i + 1}", bio = "", imageUrl = null, // test hook
+                        links = emptyList(), status = null, // test hook
+                    ) // test hook
+                }, // test hook
+            ) // test hook
+            android.util.Log.i("TestHook", "seed: import ok=${result.isSuccess} ${result.exceptionOrNull()?.message}") // test hook
+        } // test hook
+    } // test hook
+
     private fun loadProfile() {
         viewModelScope.launch {
             _uiState.update { it.copy(isLoading = true) }
@@ -269,9 +289,22 @@ class EditProfileViewModel @Inject constructor(
         }
         try {
             privatePaykitRepo.beginProfileDeletion()
-            privatePaykitRepo.removePublishedEndpointsForCleanup(TAG, deferAppSync = true)
+            android.util.Log.i("TestHook", "delete: start") // test hook
+            val cleanup = privatePaykitRepo.removePublishedEndpointsForCleanup(TAG, deferAppSync = true) // test hook: keep result
+            android.util.Log.i("TestHook", "delete: private withdrawal done ok=${cleanup.isSuccess}") // test hook
+            if (java.io.File(context.filesDir, "test-hook-hold-delete").exists()) { // test hook
+                android.util.Log.i("TestHook", "delete: HOLD before deleteProfileWithSessionRetry") // test hook
+                while (!java.io.File(context.filesDir, "test-hook-release").exists()) kotlinx.coroutines.delay(500) // test hook
+                android.util.Log.i("TestHook", "delete: released") // test hook
+            } // test hook
             val result = pubkyRepo.deleteProfileWithSessionRetry()
+            android.util.Log.i("TestHook", "delete: deleteProfileWithSessionRetry ok=${result.isSuccess}") // test hook
             if (result.isSuccess) {
+                if (java.io.File(context.filesDir, "test-hook-hold-clear").exists()) { // test hook
+                    android.util.Log.i("TestHook", "delete: HOLD before closeAndClear") // test hook
+                    while (!java.io.File(context.filesDir, "test-hook-release").exists()) kotlinx.coroutines.delay(500) // test hook
+                    android.util.Log.i("TestHook", "delete: released") // test hook
+                } // test hook
                 privatePaykitRepo.closeAndClear()
                 ToastEventBus.send(
                     type = Toast.ToastType.SUCCESS,

Not driven: the zero-contact delete-profile.xml run, validated by @jvsena42 at d689900 (review).

Review: full, eb32cdd...d689900, 8 files. Every changed app line ran in tests 1 to 6; the unit tests, changelog and journey notes were read, not run.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: profile deletion updates the app registry twice

5 participants