Skip to content

fix: defer automatic paykit work during payments - #1054

Open
ben-kaufman wants to merge 7 commits into
fix/paykit-review-app-lockfrom
fix/paykit-background-publication
Open

ben-kaufman wants to merge 7 commits into
fix/paykit-review-app-lockfrom
fix/paykit-background-publication

Conversation

@ben-kaufman

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

Copy link
Copy Markdown
Contributor

Related to contact payment latency #868.

This PR defers automatic Paykit contact preparation and private endpoint publication while a payment is being reviewed or submitted, then resumes the retained work safely.

Stacked on: app-lock recovery #1039, above payment latency #1025.

Description

  • Check payment activity when routine identity, peer and scheduling reads enter the SDK, after preparatory suspension, and before their existing bounded contention retry.
  • Defer automatic private endpoint publication before mutation while preserving explicit publication, withdrawals and recovery.
  • Retain deferred contact preparation, including forced refresh and link-only preparation, until payment review ends and the app is active; revalidate identity, session, generation and saved contacts before continuing.
  • Release publication and link coordination locks before waiting, and retain retry intent without advancing network backoff for payment admission deferral.
  • Wake parked Background retries when review ends or another contact’s Interactive retry becomes due, preserving ordinary transport-failure backoff.

Out of Scope

  • SDK transport and remote lease contention: #868. This change does not preempt admitted operations or alter SDK retries, payment authority or settlement.

Design

N/A — no UI changes.

Preview

N/A — no layout changes.

QA Notes

Deterministic tests exercise the background-admission and lifecycle races; no measured app latency improvement is claimed.

Setup

Use the journey guide and Payment Request setup at a161084d with disposable regtest wallets and a saved, linked fixture issuer. This source uses Paykit 0.1.1-rc1 and Core 0.8.0-rc10. Keep ordinary payment authorization enabled. The controlled transport/admission faults named below require additional fixture control; compatible issuer/homeserver access and staging merchant/Locks unlock must be established separately. Missing prerequisites leave the affected cases blocked.

Journeys

Manual Tests

  • 1. regression: defer automatic contact publication during payment review → background the app, end review, then return to the foreground → pending forced preparation resumes once with current identity and contact membership — deterministic SDK admission-race injection not in Capabilities.
  • 2. Defer automatic publication → explicitly disable contact payments or remove the contact → cleanup completes and deferred work does not republish it — deterministic publication-lock and admission-race injection not in Capabilities.

Automated Checks

  • added PaykitSdkIdentityCheckTests.swift cases — payment starting during the existing contention delay prevents a second Background SDK read.
  • added PaykitSdkOperationLockTests.swift cases — explicit publication and withdrawal remain available during payment activity.
  • added PrivatePaykitServiceTests.swift cases — the actual preparation owner retains deferred work without holding publication locks, waits for foreground in either lifecycle callback order, rejects stale ownership or removed contacts, and retries identity deferral when publication is unavailable. Additional cases preserve backoff through pre/post-write admission deferral and wake another contact’s Interactive retry at its deadline.
  • updated PaykitSdkOperationLockTests.swift — queued reads and automatic publication recheck admission after preparatory work without dirtying backup state.
  • updated PrivatePaykitServiceTests.swift — identity, linking and scheduling deferral preserve retry intent before missing-peer pruning; owner-level cases cover recovery, session changes, removed contacts, cancellation and ordinary missing-app errors.

Evidence

Final integration validation

Integration with master 4530ff7b at a161084d retains both USDT-warning cancellation and the payment-review callback. Bottom #1025: all-target build passed (19 targets), 378 focused tests passed, 0 failures/skips. One combined #1070 + #1081 + #1084 + #1085 composition, tree 8f1e72d3 from #1070 7cd096f4 plus #1081 8967dff8, #1084 b7561c08 and #1085 310e689a, covers the stack and those sibling changes: all-target build passed (19 targets) and 2,779 tests passed, 0 failures/skips using the six existing CI exclusions. These are not eight independent full-suite runs.

All eight heads passed SwiftFormat. Translation checks report 0 errors and 15 warnings, all matching master by key, language, file and occurrence count, including security__use_pin. Final toolchain/runtime and complete diagnostic disposition: Xcode 26.1 / iOS 27.0 simulator, serial tests; complete diagnostics inspected, with six SDK-construction QoS warnings and retained fixture/appearance messages matching baseline evidence; no introduced finding. Final-head CI after publication: pending.

Gaps

Earlier diagnostics included appearance warnings reproduced in earlier validation but not the immediate baseline, and simulator launch-telemetry messages not reproduced on either baseline. These remain disclosed historical findings, not verified baseline classifications for the final run.

The final Android 206 / iOS 215 comparison cohort was blocked before financial authorization, with no debit. It supplies no final payment-latency result or native benefit claim. Cases J1, 1, 2 remain unchecked and unexecuted on this integrated head. Native USDT-warning, PIN and biometric flows have not been verified by this integration validation.

Models used

  • Planning/scoping: gpt-6-astra (reasoning: xhigh)
  • Implementation: gpt-6-astra (reasoning: xhigh)
  • Review: gpt-6-astra (reasoning: xhigh)

@ben-kaufman
ben-kaufman marked this pull request as ready for review October 20, 2026 20:39
@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes are contained to Paykit payment and saved-contact workflows.

Retain missing-peer link preparation across payment deferral before merging.

Findings

  1. P1 Deferred contact linking is lost ▶

Summary

Defers automatic Paykit reads and endpoint publication during payment review or submission.

  • Background Paykit work waits when a payment is active.
  • Saved-contact preparation resumes after payment activity ends.
  • Payment-admission deferrals keep private-message retries available.

Tests were inspected, not run.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Automatic contact preparation] --> B[Wait for payment and review to end]
    B --> C[Wait for foreground]
    C --> D[Check session and publication availability]
    D --> E[Prepare links and publish endpoints]
    E -->|Admission deferred| B
    D -->|Publication unavailable| F[Prepare links only]
    F --> G[Read remaining work and schedule retries]
    G --> H[Finish preparation]
Loading

Reviews (1) · Last reviewed commit: "chore: rename changelog fragment" · Reviewed by Greptile

Comment thread Bitkit/Services/PrivatePaykitService+Contacts.swift
@ovitrif ovitrif added this to the 3.1.0 milestone Oct 20, 2026
@ovitrif

ovitrif commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Triage: milestone 3.1.0, with its twin synonymdev/bitkit-android#1626. It closes no issue, so this PR carries the milestone itself. Not a release blocker.

@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 automatic Paykit contact preparation and private endpoint publication while a payment is reviewed or submitted, and resumes it once review ends and the app is back in the foreground. Reviewed 4ddd593, full tier, against the stack base fix/paykit-review-app-lock. I reasoned from the code and did not build it: iOS does not build on this reviewer's Linux host.

What I checked, and 6 candidates I ruled out

Read in full: the diff in PrivatePaykitService+Contacts.swift, +Invoices.swift, +Payments.swift, PubkyService.swift, PaykitPaymentRequestService.swift. I also read their surroundings: withSdk, withStateRevisionTracking, withBackupStateRevisionTracking, checkRefreshAdmission, PaykitPaymentActivity, scheduleContactPreparation, syncLocalEndpointPublication, the drain retry loop and buildLocalEndpoints.

Call sites traced: refreshSavedContactEndpointsReturningError (contact preparation, pre-payment publication, reconcile), PubkyService.currentPublicKey(priority:), privateEndpointPublicationUnavailabilityReason, beginReview/begin in SendSheet, SendPendingScreen and the confirm views.

CI: SwiftFormat green. Run Tests was still pending when I reviewed.

Ruled out

  • Retry loop spins: checkRefreshAdmission throws only while shouldDeferRefresh holds (a payment or a review). waitUntilIdle(includingReviews: true) waits on that same condition, so each retry blocks until it clears.
  • Publication lock held across the wait: a deferral inside withPublicationLock (the identity re-read or syncPaymentLists) is caught outside the lock and returns to the loop, so the lock is released before waitUntilIdle.
  • Deferred write marks backup state dirty: in withBackupStateRevisionTracking the admission check runs in beforeOperation, before the do/catch that calls onChange, so an unattempted sync does not reset the snapshot.
  • Explicit paths deferred: checkRefreshAdmission acts only on .background. Withdrawals keep .ordered (withdraws ? .ordered : .background), and requireImmediatePublication reads use .ordered, so explicit publication and cleanup are not admission-gated.
  • Stale identity or removed contact republished after the wait: each pass re-checks the session, the generation, canPublish and the identity against the first pass's identity. Inside the lock it re-checks knownSavedContactKeys and cleanupPendingKey.
  • Pre-payment publication blocks the contact payment: the task is detached, and SendContactSelectView is not a review route (isReviewingPayment covers .confirm, .pin and .hardwareSign), so beginContactPayment retries run while it proceeds.

Merge confidence: 4/5, no findings, but the PR was not built or tested here and Run Tests is still pending.

@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 90e954c: no HIGH/MEDIUM. Two LOWs are inline, both reproduced with failing tests at this head. This PR is based on #1039, which has merge conflicts at the moment.

Checked and clean:

  • Every exit path ends deferral:
    • The wait and the admission check use the same condition, so there's no tight loop.
    • Back, cancel and disappear end the review, and payments end it in defer.
    • The generation bumps (identity switch, profile deletion, sharing disable) exit the loop.
  • Explicit publication, withdrawals and recovery run ordered and skip both the wait and admission.
  • No lock is held while waiting: the throw happens inside withPublicationLock, whose defer unlocks, and activeLinkPreparationKeys is cleared in defer.
  • No self-deadlock: presentation uses the ungated interactive prepareAndResolve.
  • On a deferral: the backup isn't marked changed, and recipient discovery keeps its targets.
  • 90e954c propagates the deferral from drainPendingPrivateMessages, which answers greptile's P1.
  • Parity with synonymdev/bitkit-android#1626: the four ops left unchecked in #1025 (identityStatus, linkedPeers, the scheduling snapshot, automatic sync) are now checked. Contact preparation waits on an event here, where Android polls every second.
  • Open latency: this PR adds no waits on the confirmation path.

Device gate (partial): 90e954c — not run yet. Pending:

  • review-refresh.xml;
  • contact publication deferred while Confirm is open, then resuming after Back, after paying, and after Home;
  • open → ready timing against the base.

Device results will follow in a separate review.

Comment thread Bitkit/Services/PrivatePaykitService+Contacts.swift
Comment thread Bitkit/Services/PrivatePaykitService+Contacts.swift Outdated
@ben-kaufman
ben-kaufman requested a review from jvsena42 October 20, 2026 22:28

@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: ⛔️ Request changes — a cancelled retry worker can delete a replacement retry.

Findings:

  • MEDIUM — Recheck retry ownership after the MainActor hop — Bitkit/Services/PrivatePaykitService+Contacts.swift:1149. Invalidation can cancel the worker and queue a new generation's retry during the new shouldDeferRefresh await. The cancelled continuation then selects that new entry. Its isCurrent() check fails on cancellation, and the initial failure branch in drainPendingPrivateMessageRetry removes the replacement at lines 1259–1261. Contact linking or private-message recovery then waits for another trigger to queue it again. Recheck cancellation and retryGeneration immediately after the await, before reading the retry dictionary. Test 5 reproduces this against unchanged production source; the same test passes with that guard alone.

QA for 0077196: iOS 26.5 simulator. The lifecycle fixture invokes the actual preparation and cleanup owners, injects SDK operations, and reads real UIApplication activity. Its publication/withdrawal counters describe injected operations, not network delivery.

Test 1: payment review and PIN lifecycle ✅ Retained base-head device coverage: cancellation, later request arrival and app unlock preserved one authorized payment.

I relied on the base-head device report and recordings at e9b45c2. It covers direct Bitcoin and amount→Bitcoin review, 30-second confirmation/PIN waits, cancellation, later request arrival, unlock restoration, one 1,000-sat payment and an unpaid second request. This diff leaves those confirmation, authorization, restoration and settlement paths unchanged; I did not repeat that journey.

Test 2: deferred forced preparation across background ✅ Review ended in the background; the original forced preparation stayed parked and completed once after foreground return.

The fixture rejected automatic publication with operationInProgress, ended review before the background-pause callback, and retained the same saved contact and force-refresh operation. The recording and app trace show inactive-state waiting, then one publication and one owner completion after return. Both lifecycle callback orders, session/identity changes, contact removal, invalidation and cancellation also passed in the hosted tests.


Real simulator lifecycle with a controlled SDK rejection, at 2×.

Test 3: disable sharing or remove the deferred contact ✅ Explicit withdrawal completed during review, and ending review did not republish either contact.

I drove both variants through the actual cleanup owner. Each finished withdrawal while automatic preparation remained deferred. After review ended, the preparation owner completed with zero publications. The recording shows the counters and event order; app traces independently confirm it. These are injected SDK operations, not a Settings-screen or network-delivery test.


Sharing-disable and contact-removal variants, at 2×.

Test 4: existing admission and retry regressions ✅ The pristine head built and its focused hosted suites passed.

I verified 420 passed tests, with no failures or skips, in the Xcode result bundle. Coverage includes queued admission and contention retries, backup-state preservation, explicit publication/withdrawal, retained missing-peer work, foreground ordering, ordinary network backoff and Interactive retry deadlines. The recording shows hosted execution; assertions were checked in the test result.


Hosted test execution, at 2×.

Test 5: retry replacement during invalidation ❌ The cancelled worker deleted the next generation's retry in all three controlled interleavings.

I added testCancelledRetryWorkerMustNotDeleteReplacementRetryAfterMainActorHop without changing production service code. It holds MainActor, starts the old due retry, invalidates it, pauses background work, and inserts/schedules a replacement. After releasing MainActor and awaiting the old task, the replacement is gone despite its own worker remaining paused:

XCTAssertTrue failed - A cancelled worker removed the next generation's retry in iteration 0
XCTAssertTrue failed - A cancelled worker removed the next generation's retry in iteration 1
XCTAssertTrue failed - A cancelled worker removed the next generation's retry in iteration 2

The same test passes when the only production change is a cancellation/generation guard immediately after the new await. That control was local and is not part of this PR. The failure is established by XCTest assertions and source tracing; the recording only shows the hosted runner.


Hosted reproduction against the reviewed service code, at 2×.

Earlier findings verified fixed at this head:

Coverage: full e9b45c2..0077196, all nine changed files and reached admission, publication, retry, lifecycle, cancellation, identity/reset and backup-state paths. Native transport and measured latency improvement were not tested; the new device checks use injected SDK boundaries.

Twin: synonymdev/bitkit-android#1626 fix: defer automatic paykit work during payments matches the admission, retained-work and explicit-cleanup intent at 5bd39a8. iOS uses event-based waiting and has the retry-ownership race above.


Reviewed by gpt-6.1-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest

Comment thread Bitkit/Services/PrivatePaykitService+Contacts.swift Outdated
@jvsena42

Copy link
Copy Markdown
Member

Device gate (partial): deferral during payment review at 90e954c (A = iOS, simulator) with B = Android synonymdev/bitkit-android#1626 6cf67cd.

Trigger: B pays A 1,000 sats over Lightning by contact while A has a contact-payment Confirm open. That runs handleReceivedPayment → refreshSavedContactEndpoints → scheduleContactPreparation, which is automatic background work.

case hold length with no publication by A review ended A syncPrivatePaymentListsWithReservations finished B sees the new list
(a) Back 4 min 33 s (B: Waiting … newer than 303 ×4) 23:30:41 23:31:00.1 (work 13.1 s) v309 at B's next poll, 23:31:30
(b) paid 2 min 28 s PIN 23:35:27 23:35:51.9 (work 12.8 s) v310 (B's polls were blocked by its own "Received Bitcoin" sheet)
(c) Home 2 min 27 s background 23:47:33, back 23:47:52 23:48:36.4, after Invoice created at 23:48:22 v311 at 23:49:25

Every hold deferred the publication, and each way of ending the review published the retained update.

  • Case (c) and my LOW1: after Home and return, A created a new invoice and published, so the forced refresh wasn't lost in this run. The race window is narrow, and one run can't rule it out; the unit test in my inline comment reproduces it deterministically.
  • Explicit link while Confirm is open isn't reachable in the UI, because the confirmation sheet is modal. That control is left to the unit tests.

Still running: review-refresh.xml.

@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. I verified the retry-ownership finding is fixed: cancelled or superseded workers exit after admission suspends, before selecting a retry, and leave a replacement worker's handle intact.

QA for 7ed5b69: iOS 26.5 simulator. Tests 4 and 5 ran here; their assertions were checked in the Xcode results. Their recordings show the hosted runner, not internal retry state. All recordings are at 2×.

Test 1: payment review and PIN lifecycle ✅ Retained device coverage proves cancellation, later request arrival and app unlock preserve one authorized payment.

I retained the base-head report and recordings, run at e9b45c2. It covers direct Bitcoin and amount→Bitcoin review, confirmation/PIN waits, cancellation, later request arrival, unlock restoration, one 1,000-sat payment and an unpaid second request. Those payment and restoration paths remain unchanged; I did not repeat the journey.

Test 2: deferred forced preparation across background ✅ Retained lifecycle evidence proves forced preparation waits while inactive and completes once after foreground return.

I retained Test 2 of the previous report, run at 0077196. The actual preparation owner retained the original forced operation when review ended before the background-pause callback. Its preparation and foreground-wait paths are unchanged in this delta. SDK publication was injected; this establishes owner/lifecycle behavior, not network delivery.

foreground-retained.2x.mp4

Retained recording from 0077196.

Test 3: disable sharing or remove the deferred contact ✅ Retained cleanup evidence proves withdrawal completes during review and deferred work does not republish either contact.

I retained Test 3 of the previous report, run at 0077196. Both variants used the actual cleanup owner with injected SDK operations. The cleanup and publication-owner paths remain unchanged in this delta; this was not a Settings-screen or network-delivery test.

cleanup-retained.2x.mp4

Retained sharing-disable and contact-removal recording from 0077196.

Test 4: exact-head admission and retry regressions ✅ The pristine head built and its focused hosted suites passed, including cancellation with and without a replacement generation.

The private-contact, SDK identity/admission, operation-lock, payment-activity and request-service suites passed without failures or skips. The new regression verifies retained retry identity, cleanup of the cancelled worker's own handle, preservation of a replacement handle, and one completed intake under the current identity. Existing cases cover foreground ordering, admission deferral, explicit cleanup, backoff and Interactive retry deadlines.

focused.2x.mp4

Hosted focused-suite execution; assertions verified in the test result.

Test 5: original retry-replacement reproduction ✅ All three previously failing MainActor interleavings preserved the replacement retry.

I reran the independent reproduction from the previous review. Only the test file was extended; production source stayed at 7ed5b69. The test holds MainActor, starts the old due retry, invalidates it, pauses background work and schedules a replacement. After releasing MainActor and awaiting the old worker, the replacement remains in every iteration. XCTest assertions establish the result.

replacement.2x.mp4

Hosted execution of the original reproduction at the fixed head.

Coverage: contiguous delta 0077196..7ed5b69, both changed files and reached retry ownership, cancellation, invalidation and cleanup paths, plus the prior open finding. The changed race was exercised by hosted tests; no fresh payment journey or network-latency measurement was driven.

Twin: bitkit-android#1626 fix: defer automatic paykit work during payments at 5bd39a8 matches the automatic-work deferral, retained retry and explicit-cleanup intent. This worker-ownership fix is specific to the iOS retry implementation.


Reviewed by gpt-6.1-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest

@ovitrif ovitrif 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.

utAck

@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 7ed5b69.

No new actionable code findings.

Automatic contact publication and background private-message retries wait out payment review and submission, then resume without holding the publication lock or advancing network backoff. Explicit publication, withdrawals, and recovery stay on the ordered path. A deferred recipient refresh keeps the previous targets. The same admission contract was compared with bitkit-android#1626 at 5bd39a88: that change requeues contact preparation after one second, and this change waits until payment and review activity is idle.

Validation: Run Tests and SwiftFormat passed on this revision. Those checks were not re-executed in this review.

Device testing: not performed in this review.

Ready for device testing. review-refresh.xml and the two manual publication and cleanup cases in the PR are still unchecked.

@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.

Delta at 7ed5b69: no issues found, and both my LOWs are fixed.

  • Deferred forced preparation (0077196): when publishing is unavailable and the app is inactive, preparation now waits for the foreground (Contacts.swift:299-313, :361-364). The waiter registers before the active check, so a resume in between isn't missed. Invalidation and cancellation release it.
  • Drain-retry backoff (0077196, 7ed5b69): a due Background retry during review now waits for idle or the next Interactive deadline without trying (:1164-1184). Deferral no longer advances backoff on the identity, scheduling-snapshot or drain paths (:1228, :1297-1304, :1335-1338).
  • No hot loop: the deferral check and the idle wait use the same condition (PaykitPaymentActivity.swift:51-52, :86-87). The only SDK operationInProgress comes from the Background admission check, and transport failures still back off.
  • Cancel re-check: the check after the activity await (:1151) covers ovi-reviewer's MEDIUM.

Device gate (partial): 7ed5b69 — from the 90e954c deferral gate, Back and pay still hold, since the app stays active on both. Home has to be re-run, because that path changed.

Pending:

  • the Home case;
  • link-contact-after-resume.xml with iOS first;
  • review-refresh.xml.

@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: Re-reviewed the full PR at 2bbd2d2e after the merge base changed since the saved assessment. Coverage is the complete diff from merge base c1e06c20, with no inherited coverage.

No new actionable code findings.

Automatic contact publication and background private-message retries wait out payment review and submission, then resume without holding the publication lock or advancing network backoff. Explicit publication, withdrawal, and recovery stay available. A deferred recipient refresh keeps the previous targets. The same admission contract was compared with bitkit-android#1626 at 5bd39a88: that change requeues contact preparation after the first private-message retry delay, and this change waits until payment and review activity is idle.

Validation: Run Tests and SwiftFormat passed on this revision. Those checks were not re-executed in this review.

Device testing: not performed in this review.

Ready for device testing. review-refresh.xml and the two manual publication and cleanup cases in the PR are still unchecked.

@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. The integration preserves the earlier preparation, backoff and retry-ownership fixes.

QA for 2bbd2d2: Three cases ran on iOS 26.5 simulators. Four passing cases are retained at their original tested revisions because their covered paths remain unchanged. Recordings are at 2×.

Test 1: payment review and PIN lifecycle ✅ Retained native coverage proves cancellation, later request arrival, unlock restoration and one authorized Bitcoin payment.

The device report at e9b45c2 covers all review-refresh.xml steps, including direct and amount→Bitcoin entry, confirmation/PIN waits, cancellation, later request arrival and app-lock restoration. The requester and regtest backend confirmed one 1,000-sat payment and an unpaid second request. Those Bitcoin paths remain unchanged.

The linked report contains both paired recordings.

Test 2: deferred forced preparation across background ✅ Retained lifecycle evidence proves the original forced operation waits while inactive and completes once after foreground return.

Test 2 at 0077196 exercises the actual preparation owner, ending review before the background-pause callback. Publication is injected; this proves ownership and real application lifecycle behavior, rather than network delivery. The covered owner and foreground-wait paths remain unchanged.

foreground-retained.2x.mp4

Retained lifecycle recording at 0077196.

Test 3: disable sharing or remove a deferred contact ✅ Retained cleanup evidence proves withdrawal completes during review without deferred republication.

Test 3 at 0077196 covers both variants through the actual cleanup owner with injected SDK operations. Cleanup and publication-owner paths remain unchanged. This does not claim a fresh Settings-screen or remote-withdrawal test.

cleanup-retained.2x.mp4

Retained sharing-disable and contact-removal recording at 0077196.

Test 4: admission and retry ownership ✅ Retained passing regressions preserve replacement retries across all three controlled MainActor interleavings.

The passing report at 7ed5b69 supersedes the earlier failed reproduction. It verifies cancellation after admission, retained retry identity and preservation of a replacement worker's handle. All covered admission/retry service and test files are identical in this delta. Its hosted recordings and Xcode assertions establish the result.

Test 5: link contact after resume ✅ A newly saved contact became request-capable after Home/resume and remained listed once.

I drove link-contact-after-resume.xml with two fresh native iOS identities and reciprocal sharing. After Contact Saved showed the peer's name, I pressed Home, waited five seconds and resumed. Request or Pay with enabled Request was first observed 119 seconds after Save, including tooling and sampled retries. Initial attempts preceded eligibility; this is an observed bound, not a link-completion benchmark. Request opened Amount. I closed without sending and verified one contact entry.

link-resume.2x.mp4

Save, Home/resume, sampled retries, Request Amount and final contact list.

Test 6: integration regressions ✅ The pristine head built and its focused hosted suites passed without failures or skips.

I checked the Xcode result bundle for scanner routing, migrations, USDT payment links, confirmation lifecycle, swipe guards and payment requests. The recording shows hosted execution; the assertions are established by the result bundle.

integration-regressions.2x.mp4

Hosted execution of the focused integration suites.

Test 7: hosted USDT address navigation ✅ Manual and clipboard inputs stayed in the Send sheet, reached USDT Amount and returned through the correct Back route.

Manual EVM input changed the heading to Send USDT; Continue opened Amount, and Back restored the entered address. Clipboard input opened Amount and returned to scanner/options on Back. Dismissing the sheet returned Home. This unfunded check did not exercise quote review, authorization or USDT submission.

usdt-route.2x.mp4

Manual-entry and clipboard navigation, Back and sheet dismissal.

Coverage: contiguous delta 7ed5b69..2bbd2d2, including the merged send routes and reached lifecycle, scanner, migration, receive and workflow changes, plus earlier findings. Receive wraparound, activity-copy UI and CI workflows were reviewed without a new device case. No listed case remains unrun; retained injected-operation cases do not establish fresh network delivery or a latency improvement.

Twin: synonymdev/bitkit-android#1626 fix: defer automatic Paykit work during payments at 5bd39a8 matches automatic-work deferral, retained retry intent and explicit cleanup. iOS retains its event-based waiting implementation.

Reviewer model: gpt-6.1-sol
Reasoning effort: high


Reviewed by gpt-6.1-sol-high via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest

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.

5 participants