Skip to content

feat(bond): show the taker when their anti-abuse bond is slashed. Phase 4 #604

Open
Catrya wants to merge 9 commits into
mainfrom
phase-4-bond
Open

feat(bond): show the taker when their anti-abuse bond is slashed. Phase 4 #604
Catrya wants to merge 9 commits into
mainfrom
phase-4-bond

Conversation

@Catrya
Copy link
Copy Markdown
Member

@Catrya Catrya commented May 26, 2026

Handles the bond-slashed notice that Mostro sends to a taker whose anti-abuse bond is slashed on a waiting-state timeout, and surfaces it to the user.

  • Recognize the bond-slashed action so the message no longer fails to parse and gets dropped; its SmallOrder (bond
    amount, null status) never overwrites the tracked order/status.
  • Persist a notification and show it in the notifications screen; tapping it opens a dialog with the order details
    in prose and a green close button.
  • Defer deleting a canceled bonded order's session for 60s so the trailing bond-slashed notice can still be received
    and decrypted; release the session as soon as it arrives. Out-of-order safe.
  • Add OrderState regression tests for bond-slashed.

Summary by CodeRabbit

  • New Features

    • Added bond-slashed notifications with dedicated dialog and detailed fields (amount, order ID, payment method, fiat info).
    • Notification details view omits empty detail blocks for cleaner display.
  • Bug Fixes / Behavior

    • Preserve order status when bond-slashed notices arrive.
    • Deferred deletion of canceled bonded sessions with restart reconciliation; user-initiated cancels delete immediately.
  • Localization

    • Added bond-slashed strings in English, German, Spanish, French, and Italian.
  • Tests

    • Added tests for bond-slashed order handling and notification tap/dialog behavior.

Review Change Stack

Catrya added 5 commits May 26, 2026 13:37
  - Add Action.bondSlashed so the slash forfeiture notice parses instead of being dropped in MostroMessage.fromJson
  - order_state.dart: its SmallOrder has a null status and bond-sized amount; preserve the tracked order's real status amount instead of overwriting
  - notifications: add bondSlashed as no-op to the exhaustive switches, grouped with the other not-yet-wired bond actions
  - Extractor builds the notification data for bondSlashed from the payload (bond amount, order id, fiat, payment
  method)
  - Add localized title/message keys (en/es/it/de/fr) and a bond amount label
  - Map bondSlashed to the cancellation notification type
  - List shows a short message; tapping opens a dialog with the full order details and a close button
  - NotificationDetails renders nothing instead of an empty box when an action has no detail rows
  - canceled handler: if the order had a bond, defer deleting the session 60s so a trailing bond-slashed notice can still be received and decrypted; otherwise delete immediately as before
  - Add bondSlashed handler: cancels the deferred deletion and releases the session once the notice arrives
  - Out-of-order safe: if bond-slashed arrives first, the later canceled finds no session and no-ops
  - Add OrderState.updateWith regression tests for bondSlashed
  - Assert the current order status is preserved (not reset to pending by the null-status SmallOrder payload)
  - Assert the tracked trade order is kept instead of the bond-sized SmallOrder
  - Replace the bordered detail rows with a single prose sentence including the order id, bond amount, fiat and
  payment method
  - Add parameterized notification_bond_slashed_detail string (en/es/it/de/fr); drop the now-unused bond amount label
  - Close button uses the standard green ElevatedButton style instead of a plain text button
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

@Catrya, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 6 minutes and 45 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 070eb612-663f-4390-9e84-330f4cd34543

📥 Commits

Reviewing files that changed from the base of the PR and between 41b5dff and 70f40da.

📒 Files selected for processing (4)
  • lib/features/order/notifiers/abstract_mostro_notifier.dart
  • lib/features/order/notifiers/order_notifier.dart
  • lib/shared/utils/bond_cancel_helpers.dart
  • test/shared/utils/bond_cancel_helpers_test.dart

Walkthrough

Adds Action.bondSlashed end-to-end: enum, notification extraction and localization keys, UI dialog/details, order-state preservation, deferred session-deletion timers in notifier with cancel-on-bondSlashed, multi-language strings, and tests.

Changes

Bond-slashed action implementation

Layer / File(s) Summary
Action enum and notification type mapping
lib/data/models/enums/action.dart, lib/data/models/notification.dart
Action.bondSlashed defined and mapped to NotificationType.cancellation.
Notification data extraction and message key mapping
lib/features/notifications/utils/notification_data_extractor.dart, lib/features/notifications/utils/notification_message_mapper.dart
Extracts bond-slash payload fields (amount, order_id, fiat, payment) into notification values; adds title/message keys and resolves them to localized S fields.
Notification UI details and dialog
lib/features/notifications/widgets/notification_details.dart, lib/features/notifications/widgets/notification_item.dart
Precomputes detail rows and hides container when empty; adds bondSlashed tap branch and _showBondSlashedDialog displaying localized title/detail and payment/order context.
Order state preservation for bond events
lib/features/order/models/order_state.dart
Treats bondSlashed like bond ack events so status-preservation logic prevents overwriting tracked order status or trade order identity.
Notifier: deferred session deletion for canceled bonded orders
lib/features/order/notifiers/abstract_mostro_notifier.dart, lib/features/order/notifiers/order_notifier.dart
Adds per-order timers to defer session deletion (60s) when canceled orders had bonds; cancels timers and deletes immediately on bondSlashed; includes bond-detection, timer helpers, reconciliation on startup, and a sync-time hook to trigger reconciliation.
Localization additions
lib/l10n/intl_de.arb, lib/l10n/intl_en.arb, lib/l10n/intl_es.arb, lib/l10n/intl_fr.arb, lib/l10n/intl_it.arb
Adds notification_bond_slashed_{title,message,detail} keys and placeholder metadata across five locales.
Tests: order state and notification tap behavior
test/features/order/models/order_state_bond_slashed_test.dart, test/features/notifications/widgets/notification_item_tap_test.dart
Adds unit tests verifying OrderState.updateWith for bondSlashed and widget tests that tapping a bondSlashed notification opens an AlertDialog.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • Anti-Abuse Bond Support #591: Implements anti-abuse bond handling (Action.bondSlashed) and its notification/order/session flows described in that issue.

Possibly related PRs

Suggested reviewers

  • AndreaDiazCorreia
  • BraCR10
  • ermeme

Poem

🐰 I nibble bytes and hop through code,
A bond once slashed, I map the road,
Sixty seconds the timer keeps,
Dialog pops where detail peeps,
Tests stamp paws — the build serenely glows.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main feature: handling and showing notifications when a taker's anti-abuse bond is slashed, which aligns with the comprehensive changes across action handling, notification display, and session management.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-4-bond

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/features/notifications/widgets/notification_item.dart (1)

112-137: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bond-slashed dialog is triggered for unrelated notification actions due to switch fallthrough.

_showBondSlashedDialog(context) currently runs for every action in this case group (e.g., cantDo, newOrder, takeSell, etc.), not just bondSlashed. This changes tap behavior incorrectly for many notification types.

Proposed fix
       case mostro_action.Action.cantDo:
       case mostro_action.Action.rateReceived:
       case mostro_action.Action.newOrder:
       case mostro_action.Action.takeSell:
       case mostro_action.Action.takeBuy:
       case mostro_action.Action.fiatSent:
       case mostro_action.Action.release:
       case mostro_action.Action.cancel:
       case mostro_action.Action.cooperativeCancelAccepted:
       case mostro_action.Action.buyerInvoiceAccepted:
       case mostro_action.Action.holdInvoicePaymentCanceled:
       case mostro_action.Action.rateUser:
       case mostro_action.Action.dispute:
       case mostro_action.Action.adminCancel:
       case mostro_action.Action.adminSettle:
       case mostro_action.Action.adminAddSolver:
       case mostro_action.Action.adminTakeDispute:
       case mostro_action.Action.adminTookDispute:
       case mostro_action.Action.invoiceUpdated:
       case mostro_action.Action.tradePubkey:
       case mostro_action.Action.restore:
       case mostro_action.Action.orders:
       case mostro_action.Action.lastTradeIndex:
+        break;
       case mostro_action.Action.bondSlashed:
         _showBondSlashedDialog(context);
         break;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/notifications/widgets/notification_item.dart` around lines 112 -
137, The switch grouping in notification_item.dart incorrectly falls through so
_showBondSlashedDialog(context) is called for many actions; update the switch in
the widget (the cases around mostro_action.Action.bondSlashed and the
surrounding cases) so only the single case mostro_action.Action.bondSlashed
invokes _showBondSlashedDialog(context) — either remove
mostro_action.Action.bondSlashed from the multi-case group and place it in its
own case that calls _showBondSlashedDialog(context) with a break, or add
explicit breaks/returns after each unrelated case to prevent fallthrough.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@lib/features/notifications/widgets/notification_item.dart`:
- Around line 112-137: The switch grouping in notification_item.dart incorrectly
falls through so _showBondSlashedDialog(context) is called for many actions;
update the switch in the widget (the cases around
mostro_action.Action.bondSlashed and the surrounding cases) so only the single
case mostro_action.Action.bondSlashed invokes _showBondSlashedDialog(context) —
either remove mostro_action.Action.bondSlashed from the multi-case group and
place it in its own case that calls _showBondSlashedDialog(context) with a
break, or add explicit breaks/returns after each unrelated case to prevent
fallthrough.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8707975f-62dd-4e55-9e2d-80d9ab57a80c

📥 Commits

Reviewing files that changed from the base of the PR and between e46854f and 507c022.

📒 Files selected for processing (14)
  • lib/data/models/enums/action.dart
  • lib/data/models/notification.dart
  • lib/features/notifications/utils/notification_data_extractor.dart
  • lib/features/notifications/utils/notification_message_mapper.dart
  • lib/features/notifications/widgets/notification_details.dart
  • lib/features/notifications/widgets/notification_item.dart
  • lib/features/order/models/order_state.dart
  • lib/features/order/notifiers/abstract_mostro_notifier.dart
  • lib/l10n/intl_de.arb
  • lib/l10n/intl_en.arb
  • lib/l10n/intl_es.arb
  • lib/l10n/intl_fr.arb
  • lib/l10n/intl_it.arb
  • test/features/order/models/order_state_bond_slashed_test.dart

Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

Good direction overall, and I agree with the OrderState protection for bond-sized SmallOrder payloads. But I do see one real behavior regression that should be fixed before merge.

Blocking issue:

  • In NotificationItem, the tap handler now routes Action.bondSlashed into _showBondSlashedDialog(context) from the same switch group that previously just breaked for many unrelated actions (cantDo, newOrder, takeSell, adminCanceled, settleBondedHoldInvoice, etc.). With the current structure, those unrelated actions will also open the bond-slashed dialog on tap instead of preserving their old no-op/detail behavior. That is a user-visible behavior regression outside the scope of this feature.

Please make the dialog path exclusive to Action.bondSlashed and keep the previous handling for the other actions intact.

Aside from that, the rest looks reasonable to me on this revision: the deferred session deletion logic is coherent, the out-of-order note makes sense, and the OrderState regression tests cover the payload-overwrite risk well.

  - bondSlashed shared the no-op group's empty case labels, so unrelated actions (cantDo, newOrder, etc.) fell through
   into the bond-slashed dialog on tap
  - Move it to its own case after the no-op group's break
  - Add a widget test asserting bondSlashed opens the dialog and a no-op action does not
mostronatorcoder[bot]
mostronatorcoder Bot previously approved these changes May 26, 2026
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

Re-reviewed the latest head together with the follow-up fix. The blocker I raised is resolved on this revision.

What changed and why this clears the regression:

  • Action.bondSlashed now has its own dedicated switch branch in NotificationItem, instead of sharing the old no-op group. That makes the dialog path exclusive to the forfeiture notification and restores the previous tap behavior for unrelated actions.
  • You also added a focused widget regression test covering both sides of the dispatch: bondSlashed opens the dialog, while a no-op action from the same former group (cantDo) does not. That is exactly the guard this area needed.

I re-checked the rest of the PR and still think the other pieces are reasonable: the OrderState protection against bond-sized SmallOrder payloads is correct, the deferred session deletion logic is coherent, and CI is green.

Approved on the current revision.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/features/notifications/widgets/notification_item_tap_test.dart`:
- Line 7: Replace the direct import of NotificationModel from
data/models/notification.dart with the barrel export by changing the import to
package:mostro_mobile/data/models.dart; locate the import line that mentions
NotificationModel and update it so the test file uses the re-exported model
(NotificationModel) from models.dart, ensuring all references to
NotificationModel in notification_item_tap_test.dart continue to compile
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f2f60dfa-0144-4220-add6-7cf0f968044f

📥 Commits

Reviewing files that changed from the base of the PR and between 507c022 and b226af3.

📒 Files selected for processing (2)
  • lib/features/notifications/widgets/notification_item.dart
  • test/features/notifications/widgets/notification_item_tap_test.dart

Comment thread test/features/notifications/widgets/notification_item_tap_test.dart
Copy link
Copy Markdown

@ermeme ermeme Bot left a comment

Choose a reason for hiding this comment

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

Code Review Summary

Verdict: Comment.

The PR looks solid overall: the bond-slashed flow is wired end-to-end, the state preservation change is correct, and the regression tests cover the main path.

One minor suggestion:

  • lib/features/order/notifiers/abstract_mostro_notifier.dart:747 — the deferred cleanup timer calls deleteSession(orderId) without awaiting the returned Future. That means async failures will bypass the try/catch, and the success log can fire before the delete actually completes. Consider awaiting the call or handling the future explicitly.

  - The deferred bond-cancel deletion relied on an in-memory timer that
    was lost if the app closed within the 60s grace window, leaving the
    session orphaned in My Trades and blocking retaking.
  - Add reconcileCanceledBondedSession(), called from OrderNotifier.sync()
    when state rebuilds as canceled: delete now if the grace window elapsed
    or bond-slashed already arrived; re-arm the timer otherwise.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
lib/features/order/notifiers/abstract_mostro_notifier.dart (1)

766-796: 🏗️ Heavy lift

Cancel bond-cancel grace timer on notifier disposal (defensive), though current lifecycle makes the stale-ref scenario unlikely

orderNotifierProvider in lib/features/order/providers/order_notifier_provider.dart is a non-autoDispose StateNotifierProvider.family, and there are no ref.invalidate/refresh usages for orderNotifierProvider in the codebase. So the specific “provider auto-disposed/recreated mid-grace → stale ref → reconcile can’t recover due to _bondCancelDeletionTimers key” path doesn’t appear to apply.

Still, _startBondCancelDeletion captures the notifier ref inside a static Timer; adding cleanup to dispose() (or using ref.onDispose(timer.cancel)) prevents any potential stale-ref execution if lifecycle changes (or the container is disposed during the grace window):

`@override`
void dispose() {
  subscription?.close();
  _sessionTimeouts[orderId]?.cancel();
  _sessionTimeouts.remove(orderId);

  _bondCancelDeletionTimers[orderId]?.cancel();
  _bondCancelDeletionTimers.remove(orderId);

  super.dispose();
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/features/order/notifiers/abstract_mostro_notifier.dart` around lines 766
- 796, The notifier leaves bond-cancel timers in _bondCancelDeletionTimers and
captures ref in a Timer started by _startBondCancelDeletion; ensure timers are
cleaned up on notifier disposal by cancelling and removing
_bondCancelDeletionTimers[orderId] in dispose() (or register timer.cancel with
ref.onDispose when creating the Timer in _startBondCancelDeletion) so stale
timers won't run against a disposed/stale ref; update dispose() (or
_startBondCancelDeletion) to cancel and remove the entry for orderId and call
super.dispose().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/features/order/notifiers/abstract_mostro_notifier.dart`:
- Around line 766-796: The notifier leaves bond-cancel timers in
_bondCancelDeletionTimers and captures ref in a Timer started by
_startBondCancelDeletion; ensure timers are cleaned up on notifier disposal by
cancelling and removing _bondCancelDeletionTimers[orderId] in dispose() (or
register timer.cancel with ref.onDispose when creating the Timer in
_startBondCancelDeletion) so stale timers won't run against a disposed/stale
ref; update dispose() (or _startBondCancelDeletion) to cancel and remove the
entry for orderId and call super.dispose().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9668c3c3-74bc-46da-ab9b-f11e92d0ad4c

📥 Commits

Reviewing files that changed from the base of the PR and between b226af3 and 5c80a1e.

📒 Files selected for processing (2)
  • lib/features/order/notifiers/abstract_mostro_notifier.dart
  • lib/features/order/notifiers/order_notifier.dart

Catrya added 2 commits May 28, 2026 17:31
  - A voluntary cancel returns the taker's bond (no slash), so the daemon
    never sends bond-slashed; deferring deletion 60s was pure downside and
    risked an orphaned session if the app closed in that window.
  - Track user-initiated cancels in memory (markUserInitiatedCancel from
    cancelOrder) and delete the session immediately on the canceled
    response; keep the 60s defer only for non-user-initiated bonded cancels
    (the real timeout-slash case).
  - Not persisted on purpose: Action.cancel resolves to Status.canceled,
    so a stored marker would corrupt sync()-rebuilt state on a rejected or
    cooperative cancel. The restart case is handled by the reconcile path.
…n retake

  - clearBondCancelDeletion(orderId) on retake (takeSell/takeBuyOrder) cancels
    any pending grace timer + clears the user-cancel flag, so retaking within
    the 60s window can't delete the new session.
  - Extract decision logic to bond_cancel_helpers.dart and add 13 regression
    tests (defer truth table, reconcile none/deleteNow/rearm, window boundary).
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

I re-reviewed the current head. The restart/reconcile work is a good improvement, but I do see one remaining correctness issue that I would fix before merge.

Blocking issue:

  • OrderNotifier.cancelOrder() marks the order as user-initiated after awaiting mostroService.cancelOrder(orderId). That creates a race with the incoming Action.canceled event: if the daemon responds quickly and the event is processed before that await returns, the canceled handler will not see the user-initiated flag yet. In that case it can wrongly take the timeout-slash path and defer session deletion for 60 seconds on a voluntary cancel, which is exactly the case this patch is trying to avoid.

I think the intent here is right, but the marker needs to be set before the async send (and then cleared/reverted if the send itself fails) so the event path can observe it reliably.

The rest of the new direction looks reasonable to me: extracting the decision logic into pure helpers was a good move, the retake cleanup guard makes sense, and the added tests improve confidence. But I would still block on the race above because it changes cancel behavior in a real timing-dependent way.

@Catrya
Copy link
Copy Markdown
Member Author

Catrya commented May 29, 2026

I re-reviewed the current head. The restart/reconcile work is a good improvement, but I do see one remaining correctness issue that I would fix before merge.

Blocking issue:

  • OrderNotifier.cancelOrder() marks the order as user-initiated after awaiting mostroService.cancelOrder(orderId). That creates a race with the incoming Action.canceled event: if the daemon responds quickly and the event is processed before that await returns, the canceled handler will not see the user-initiated flag yet. In that case it can wrongly take the timeout-slash path and defer session deletion for 60 seconds on a voluntary cancel, which is exactly the case this patch is trying to avoid.

I think the intent here is right, but the marker needs to be set before the async send (and then cleared/reverted if the send itself fails) so the event path can observe it reliably.

The rest of the new direction looks reasonable to me: extracting the decision logic into pure helpers was a good move, the retake cleanup guard makes sense, and the added tests improve confidence. But I would still block on the race above because it changes cancel behavior in a real timing-dependent way.

I don't think the race is reachable, and moving the marker before the send would actually reintroduce the bug this phase fixes.

There are two sources of Action::Canceled to the taker (confirmed in the daemon):

  1. Voluntary cancel — releases the bond, republishes, no bond-slashed.
  2. Waiting-timeout slash — daemon autonomously sends Canceled, then bond-slashed ~150ms later.

For a real voluntary cancel, the Canceled is the daemon's response to the event we publish, so it's causally after publishEvent. markUserInitiatedCancel runs in the microtask continuation right after that await (no await between), and incoming relay events are I/O macrotasks that can't preempt the microtask drain, so the flag is always set first. The "voluntary cancel wrongly deferred" path isn't reachable.

The only window where the flag isn't set yet is when an autonomous timeout-slash Canceled overlaps a user cancel, and there, deferring is correct, because a bond-slashed is coming:

  • Marker after send (current): userInitiated=false → defer → bond-slashed arrives → notice surfaced. ✅
  • Marker before send (proposed): userInitiated=true → delete immediately → trade key dropped → trailing bond-slashed
    can't be decrypted → notice lost. ❌

revert on failure doesn't help, since the cancel send succeeds — the daemon just slashed first. The current ordering is the correct one; I can add a comment documenting why the marker is set after the send.

Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder Bot left a comment

Choose a reason for hiding this comment

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

I re-read your explanation carefully, and I agree the blocker I raised does not hold given the actual event sources and ordering you described.

What changed my mind:

  • For a voluntary cancel, the taker's Action.canceled is causally downstream of the cancel event we publish, so the relevant question is whether an incoming relay callback can run before the post-await continuation that sets the marker. Your explanation about Dart scheduling here is reasonable: markUserInitiatedCancel(orderId) runs in the microtask continuation immediately after the awaited publish returns, while the inbound relay event arrives on a later I/O task. Under that model, the specific "voluntary cancel gets misclassified because canceled arrived before the marker" path is not actually reachable.
  • More importantly, setting the marker before the send would indeed blur the autonomous timeout-slash case. In the overlap scenario you described, pre-marking would make the slash-driven canceled look user-initiated and could cause immediate session deletion before the trailing bondSlashed arrives, which is worse than the race I was worried about.

So I’m retracting that blocker. I still think a short comment in cancelOrder() explaining why the marker is intentionally set after the awaited send would be helpful for the next reviewer, but I no longer consider it merge-blocking.

Given that, and since the earlier tap-handler regression was already fixed, I’m approving the current revision.

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