feat(meshcore): opt-in auto-retry for automated channel sends (#3979 Part 2)#3988
Conversation
…-heard miss (#3979) Part 2 of #3979 (builds on merged Part 1, PR #3987, which made the "heard repeaters" echo-attribution trustworthy). A MeshCore channel/broadcast send is an unacked, fire-and-forget flood, so there is no firmware ACK to confirm delivery. When the Part 1 echo signal reports that ZERO repeaters re-flooded our packet within 30s, the message likely reached no one. This adds an opt-in, one-shot resend for that case. - New global setting `meshcoreChannelRetryEnabled` (default OFF), wired through VALID_SETTINGS_KEYS, SettingsContext, and a new "MeshCore Messaging" section in global Settings (SettingsTab). - Applies to AUTOMATED channel senders only (Automation Engine action.sendMessage, Auto-Acknowledge, auto-responder, auto-announce, timer triggers), threaded via a new `autoRetryOnMiss` opt-in flag on sendMessage/sendMessageWithResult/performScopedSend. User-initiated route sends never opt in, so they never retry. - New channel-retry state machine (`pendingChannelRetries`, `maybeArmChannelRetry`, `handleChannelRetryTimeout`) parallel to and non-colliding with the DM ack-retry (#3977/#3980): the DM path keys on the firmware ACK CRC (toPublicKey set); the channel path keys on the echo-heard signal (channelIdx set) — mutually exclusive branches of performScopedSend. - One-shot: the resend goes out with isAutoRetry=true (no new message row, no `message`/bus emit — so it cannot spawn a fresh automation) and autoRetryOnMiss=false (re-registered for echo correlation but never re-armed). - Pending channel retries cleared in the disconnect() bulk cleanup. - Tests: src/server/meshcoreManager.channelRetry.test.ts covers zero-heard resend, heard→no-resend, no-bus-reentry, one-shot, user-send never arms, setting-off never arms, disconnect clears, and DM/channel non-collision. - Docs: docs/features/meshcore.md (new section) + automation-engine.md note. Closes #3979 Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015AFBA76hsjqhsXe1BdnYub
|
Claude finished @Yeraze's task —— View job PR Review: feat(meshcore): opt-in auto-retry for automated channel sendsStatus: Review complete ✅ Tasks:
Overall AssessmentThis is a clean, well-scoped implementation. The architecture is sound: fire-and-forget channel floods genuinely have no firmware ACK, so leaning on the Part 1 echo-attribution signal is the right approach. The one-shot, automated-only, opt-in constraints are all correctly enforced and clearly tested. Below are some observations ranging from minor to worthy of discussion. Correctness / Logic1.
Consider asserting the relationship explicitly, e.g. via a comment 2.
3.
Error Handling4. No logging on the
5. Setting read failure on the
Test Coverage6. Tests are thorough for the happy paths — one missing scenario The 8 tests cover: zero-heard → retry; heard → no retry; no bus re-entry; one-shot; user send; setting off; disconnect; DM/channel non-collision. Well done. Missing: setting read throws → no retry armed. This would verify the it('(h) DB error reading setting → retry not armed (fail-safe)', async () => {
const { manager, bridgeCalls } = makeManager({ retryEnabled: true });
vi.spyOn(databaseService.settings, 'getSettingAsBoolean').mockRejectedValueOnce(new Error('DB down'));
await manager.sendMessage('hi', undefined, 0, undefined, true);
expect((manager as any).pendingChannelRetries.size).toBe(0);
await vi.advanceTimersByTimeAsync(40_000);
expect(sends(bridgeCalls)).toHaveLength(1);
});7. Lines 34, 44, 74 — updated expectations now assert the trailing Settings / UI8.
9.
10. Inline checkbox in
DocumentationThe new doc sections in Summary
No blocking issues. The implementation is solid and well-tested. The two most actionable items before merge would be (1) adding the DB-error-abort test and (2) either unifying the two 30 s constants or adding an explicit assertion/comment about their required equality. |
Summary
Part 2 (final) of #3979 — an opt-in, one-shot auto-retry for automated MeshCore channel/broadcast sends when zero repeaters were heard within 30s. Builds directly on the now-merged Part 1 (PR #3987), which made the "heard repeaters" echo-attribution signal trustworthy.
A MeshCore channel send is an unacked, fire-and-forget flood — there is no firmware ACK. When Part 1's echo signal reports that no repeater re-flooded our packet within 30s, the message likely reached no one, so we resend it exactly once.
Behavior
meshcoreChannelRetryEnabled, default OFF. Toggle lives in a new MeshCore Messaging section of global Settings (SettingsTab).action.sendMessage, Auto-Acknowledge, auto-responder, auto-announce, and timer triggers. Threaded via a newautoRetryOnMissopt-in flag.meshcoreRoutes.tssend route does not opt in.isAutoRetry=true(no new bubble, nomessage/data-bus emit — so it can't trigger a fresh automation) andautoRetryOnMiss=false(re-registered for echo correlation but never re-armed).disconnect()bulk cleanup.Non-collision with the DM retry (#3977/#3980)
The DM ack-retry keys on the firmware ACK CRC (
toPublicKeyset); the channel retry keys on the echo-heard signal (channelIdxset). They are mutually exclusive branches ofperformScopedSend— a test asserts a DM send arms onlypendingDmRetriesand a channel send arms onlypendingChannelRetries.Heard-count read race
The 30s retry window equals the echo-attribution window (
HEARD_WINDOW_MS), andcorrelateChannelEchoawaits its DB write before returning, so any repeater heard for a genuine echo is persisted before the timer reads it. A repeater heard after the read (extremely-late echo) yields at most one accepted duplicate — which the opt-in explicitly tolerates. A failed heard-count read aborts the retry (no needless duplicate).Tests
New
src/server/meshcoreManager.channelRetry.test.ts(8 tests, fake timers): zero-heard → one resend; heard → no resend; resend emits no message event / no bus re-entry; one-shot; user send never arms; setting-off never arms; disconnect clears; DM/channel non-collision. Full suite green (8181 passed, 0 failed); tsc clean.Docs
docs/features/meshcore.md(new "Automated Channel-Send Auto-Retry" section) and a note indocs/features/automation-engine.md— opt-in, channel-only, one-shot, distinct from the DM retry.Closes #3979
🤖 Generated with Claude Code
https://claude.ai/code/session_015AFBA76hsjqhsXe1BdnYub