Skip to content

Model B: fix opportunistic sent messages stuck at single checkmark#93

Merged
torlando-tech merged 1 commit into
mainfrom
fix/model-b-delivery-proof
Jun 20, 2026
Merged

Model B: fix opportunistic sent messages stuck at single checkmark#93
torlando-tech merged 1 commit into
mainfrom
fix/model-b-delivery-proof

Conversation

@torlando-tech

Copy link
Copy Markdown
Owner

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 .delivered depended 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 in pending_outbound and 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):

  • Opportunistic messages stay in pendingOutbound at .sent and re-send every DELIVERY_RETRY_WAIT (=10s, python parity), removed only on .delivered or MAX_DELIVERY_ATTEMPTS. nextDeliveryAttempt gates re-sends (no storm).
  • handleDeliveryProofReceived flips the in-memory entry to .delivered (python __mark_delivered) so the loop stops re-sending.
  • loadPendingOutbound reloads opportunistic .sent messages, so a proof missed during NE suspension is recovered on relaunch. Scoped to .opportunistic (.sent is 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_echo smoke 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

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-apps

greptile-apps Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bumps the LXMF-swift dependency pin from commit 584c30c to b2611b3 to pull in a fix for opportunistic sent messages getting stuck at a single checkmark under Model B. The actual logic changes (retry-until-delivered semantics, loadPendingOutbound reload on relaunch, handleDeliveryProofReceived state flip) live entirely in the external library.

  • Dependency pin updated: lxmf-swift revision changed to b2611b385d53eadc40e618821ebb5f2c171f9b2b on the feat/lxmfdb-appgroup-sharing branch. No other pins changed.
  • Branch-tracked dependency: lxmf-swift, lxst-swift, and reticulum-swift are all pinned to branches rather than tagged releases — a pre-existing pattern in this project — meaning swift package update will advance the resolved commit beyond the one exercised in the smoke test.

Confidence Score: 4/5

Safe 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

Filename Overview
Columba.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved Single revision bump for lxmf-swift (584c30c → b2611b3) on the feat/lxmfdb-appgroup-sharing branch; all other pins unchanged. Dependency is branch-tracked, so a future swift package update will advance past this tested commit.

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
Loading
%%{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
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix 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

Comment on lines 36 to 38
"branch" : "feat/lxmfdb-appgroup-sharing",
"revision" : "584c30cc82622e0b6920e654efd9b182110490ad"
"revision" : "b2611b385d53eadc40e618821ebb5f2c171f9b2b"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Fix in Claude Code

@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@torlando-tech torlando-tech merged commit 2deeb15 into main Jun 20, 2026
3 checks passed
@torlando-tech torlando-tech deleted the fix/model-b-delivery-proof branch June 20, 2026 00:02
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.

1 participant