Model B: fix opportunistic sent messages stuck at single checkmark#93
Conversation
Picks up LXMF-swift fix for Model-B sent messages intermittently stuck at a single checkmark: opportunistic messages now stay in pendingOutbound at .sent and re-send until the delivery proof advances them to .delivered (or MAX_DELIVERY_ATTEMPTS), matching python LXMRouter; .sent opportunistic messages reload on NE relaunch so a proof missed during NE suspension is recovered. Verified on device (iPhone 14, Model B): opp_echo smoke scenario passes — device sends OPPORTUNISTIC, echo bot round-trips, device processes the delivery proof (delivery confirmed) with no resend storm. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Greptile SummaryThis PR bumps the
Confidence Score: 4/5Safe to merge; the only change is a first-party dependency revision bump that the author has smoke-tested on device. All logic changes are inside the LXMF-swift library and are not directly reviewable from this diff. The dependency is pinned to a branch, so the resolved commit could advance unintentionally on the next package update, but this is a pre-existing pattern in the project and not a new risk introduced here. No files require special attention beyond the branch-tracking note on Package.resolved. Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App as Columba App
participant NE as Network Extension
participant Router as LXMRouter (lxmf-swift)
participant Peer as Recipient Node
App->>Router: enqueue opportunistic message
Router->>Peer: send (attempt 1)
Note over Router: message stays in pendingOutbound (.sent)
alt NE suspended / proof lost (old behavior)
Peer--xNE: delivery proof (lost/dropped)
Note over Router: message stranded at .sent forever ❌
end
alt Fixed behavior (b2611b3)
Peer-->>NE: delivery proof
NE->>Router: handleDeliveryProofReceived
Router->>Router: flip state → .delivered
Router->>App: notify delivered ✅
end
alt NE relaunch after suspension (new)
Router->>Router: loadPendingOutbound reloads .sent opportunistic
Router->>Peer: resend (attempt N, deduped by hash)
Peer-->>NE: delivery proof
NE->>Router: handleDeliveryProofReceived
Router->>App: notify delivered ✅
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App as Columba App
participant NE as Network Extension
participant Router as LXMRouter (lxmf-swift)
participant Peer as Recipient Node
App->>Router: enqueue opportunistic message
Router->>Peer: send (attempt 1)
Note over Router: message stays in pendingOutbound (.sent)
alt NE suspended / proof lost (old behavior)
Peer--xNE: delivery proof (lost/dropped)
Note over Router: message stranded at .sent forever ❌
end
alt Fixed behavior (b2611b3)
Peer-->>NE: delivery proof
NE->>Router: handleDeliveryProofReceived
Router->>Router: flip state → .delivered
Router->>App: notify delivered ✅
end
alt NE relaunch after suspension (new)
Router->>Router: loadPendingOutbound reloads .sent opportunistic
Router->>Peer: resend (attempt N, deduped by hash)
Peer-->>NE: delivery proof
NE->>Router: handleDeliveryProofReceived
Router->>App: notify delivered ✅
end
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
Columba.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:36-38
**Branch-tracked pin can drift on next `swift package update`**
`lxmf-swift` (and `lxst-swift`, `reticulum-swift`) are resolved against a branch rather than a tagged release, so `Package.resolved` will silently advance past the smoke-tested revision the next time any team member runs `swift package update` or Xcode resolves packages. If an untested commit lands on `feat/lxmfdb-appgroup-sharing`, the carefully-verified retry semantics could regress without any explicit version bump in the PR history. Consider tagging `b2611b3` in the library and pinning to that tag here, or at minimum adding a comment in CI that reminds contributors not to run `swift package update` without re-running the `opp_echo` scenario.
Reviews (1): Last reviewed commit: "Bump LXMF-swift → b2611b3 (opportunistic..." | Re-trigger Greptile |
| "branch" : "feat/lxmfdb-appgroup-sharing", | ||
| "revision" : "584c30cc82622e0b6920e654efd9b182110490ad" | ||
| "revision" : "b2611b385d53eadc40e618821ebb5f2c171f9b2b" | ||
| } |
There was a problem hiding this comment.
Branch-tracked pin can drift on next
swift package update
lxmf-swift (and lxst-swift, reticulum-swift) are resolved against a branch rather than a tagged release, so Package.resolved will silently advance past the smoke-tested revision the next time any team member runs swift package update or Xcode resolves packages. If an untested commit lands on feat/lxmfdb-appgroup-sharing, the carefully-verified retry semantics could regress without any explicit version bump in the PR history. Consider tagging b2611b3 in the library and pinning to that tag here, or at minimum adding a comment in CI that reminds contributors not to run swift package update without re-running the opp_echo scenario.
Prompt To Fix With AI
This is a comment left during a code review.
Path: Columba.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
Line: 36-38
Comment:
**Branch-tracked pin can drift on next `swift package update`**
`lxmf-swift` (and `lxst-swift`, `reticulum-swift`) are resolved against a branch rather than a tagged release, so `Package.resolved` will silently advance past the smoke-tested revision the next time any team member runs `swift package update` or Xcode resolves packages. If an untested commit lands on `feat/lxmfdb-appgroup-sharing`, the carefully-verified retry semantics could regress without any explicit version bump in the PR history. Consider tagging `b2611b3` in the library and pinning to that tag here, or at minimum adding a comment in CI that reminds contributors not to run `swift package update` without re-running the `opp_echo` scenario.
How can I resolve this? If you propose a fix, please make it concise.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Problem
Under Model B, sent messages intermittently stayed at a single checkmark even when the recipient confirmed delivery — the incoming delivery proof was not always processed.
Root cause
LXMRouter.processOutbound(LXMF-swift) dequeued an opportunistic message the instant it reached.sent, so advancement to.delivereddepended entirely on the in-memory proof callback in the transport (pendingProofCallbacks, not persisted). A single lost packet or proof — or, far more commonly under Model B, the iOS Network Extension being suspended/jetsammed during the proof round-trip window — stranded the message at one checkmark forever. Python doesn't hit this: its long-lived process keeps the message inpending_outboundand re-sends until delivered.Diagnosed across LXMF-swift / reticulum-swift / the NE against the python reference; confirmed Columba always sends
.opportunistic(direct is only a large-message fallback).Fix (LXMF-swift
b2611b3, this PR bumps the pin)Restores python's retry-until-delivered semantics for the opportunistic path (
LXMRouter.py:2566-2592):pendingOutboundat.sentand re-send everyDELIVERY_RETRY_WAIT(=10s, python parity), removed only on.deliveredorMAX_DELIVERY_ATTEMPTS.nextDeliveryAttemptgates re-sends (no storm).handleDeliveryProofReceivedflips the in-memory entry to.delivered(python__mark_delivered) so the loop stops re-sending.loadPendingOutboundreloads opportunistic.sentmessages, so a proof missed during NE suspension is recovered on relaunch. Scoped to.opportunistic(.sentis terminal for PROPAGATED; DIRECT unchanged).Resends are deduped recipient-side by the stable message hash; PROVE_ALL means a duplicate resend still earns the proof that clears a stuck check. Documented in
port-deviations.md. DIRECT full parity (link-receipt timeout retry) tracked as follow-up.Verification
Device (iPhone 14, Model B):
opp_echosmoke scenario passes — device sends OPPORTUNISTIC → Mac echo bot round-trips → device processes the delivery proof (delivery confirmed) and removes the message from the queue (no resend storm). NE boots clean on the new lib.🤖 Generated with Claude Code