docs(cashu): specs for Tracks B/C/D (release, coop-cancel, dispute)#833
docs(cashu): specs for Tracks B/C/D (release, coop-cancel, dispute)#833grunch wants to merge 1 commit into
Conversation
Add the three remaining feature-track specs to the Cashu series, mirroring the structure of 01-fundamentals.md / 02-track-a-lock.md (goal & scope, flow + state transitions, what it consumes from Fundamentals + Track A, handler algorithm, PR breakdown, issues table, Definition of Done, cross-track obligations). Spec only — no implementation. - 03-track-b-release.md — happy-path settlement: `FiatSent` (with the §4B remaining-locktime guard + `escrow_settlement_margin_days`) then `Release`; the seller's release signature travels P2P, Mostro only advances state and makes the trade rateable. Executes the §4B `FiatSent` guard obligation. - 04-track-c-coop-cancel.md — cooperative unwind: both parties `Cancel`, the buyer signs so the seller reclaims, and Mostro refunds the seller the fee (`2 * order.fee`) — the first executor of the Track A §4A refund obligation (single-shot). - 05-track-d-dispute.md — arbitration: the only track where `P_M` signs. `sign_with_pm` + `CashuPmSignature` delivery to the dispute winner (`AdminSettle`→buyer, `AdminCancel`→seller), the §4B near-locktime solver alert, and the seller-wins fee refund. Uses the frozen `CashuPmSignature`/`CashuSignatures` 0.13.0 protocol surface — no new variant. Each spec is off-by-default and additive; every blocked `dispatch_cashu` action is given an owner, closing the CF-5 §6 action-ownership matrix. README status table updated (03/04/05 Planned → Draft). Refs: docs/cashu/01-fundamentals.md, docs/cashu/02-track-a-lock.md
WalkthroughAdds draft documentation for Cashu escrow release, cooperative cancellation, and dispute resolution, including state transitions, signature delivery, locktime protection, refunds, dispatch routing, implementation breakdowns, and completion criteria. The Cashu README now links to Tracks B–D and marks them as drafts. ChangesCashu Escrow Lifecycle
Estimated code review effort: 2 (Simple) | ~10 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@docs/cashu/03-track-b-release.md`:
- Around line 104-115: Update the CashuSettings implementation and startup
validation to add escrow_settlement_margin_days and require it to be strictly
below escrow_locktime_days. In the Cashu branch of fiat_sent_action, explicitly
reject expired or missing locktimes with the appropriate CantDo reason, and use
checked or saturating subtraction for locktime-versus-now calculations so
underflow cannot disable every FiatSent.
- Around line 163-167: The documented track ordering is inconsistent: update
docs/cashu/03-track-b-release.md lines 163-167 to remove or qualify Track B’s
parallelism with Tracks C/D, update docs/cashu/04-track-c-coop-cancel.md lines
154-161 to state Track B as a dependency if it is required for merge or runtime
behavior, and update docs/cashu/05-track-d-dispute.md lines 176-186 to state
Track C as a dependency if required for implementation.
In `@docs/cashu/04-track-c-coop-cancel.md`:
- Around line 102-109: Define one crash-safe, single-shot seller-refund contract
in docs/cashu/04-track-c-coop-cancel.md lines 102-109: specify atomic refund
claiming, durable retry/idempotency behavior, and explicit fee-state semantics
for minting. In docs/cashu/05-track-d-dispute.md lines 137-143, require
seller-win disputes to call the same refund helper and follow the same contract,
including races between cancel and dispute terminal states.
In `@docs/cashu/05-track-d-dispute.md`:
- Around line 132-135: Make the near-locktime handling in
admin_take_dispute_action enforceable by defining a hard resolution
deadline/guard based on the escrow’s remaining locktime and the resolution SLA,
and prevent finalization that would produce an unusable P_M signature. Specify
the explicit recovery path when the guard is reached, rather than relying only
on logging and priority-flagging.
🪄 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: d3555a40-4543-47e2-96e8-2d911d3ac05c
📒 Files selected for processing (4)
docs/cashu/03-track-b-release.mddocs/cashu/04-track-c-coop-cancel.mddocs/cashu/05-track-d-dispute.mddocs/cashu/README.md
| - New `#[serde(default)]` key on `CashuSettings`: **`escrow_settlement_margin_days`, | ||
| default 3**. Added by Track B (not needed during foundation). | ||
| - In the Cashu branch of `fiat_sent_action`: read the stored escrow token's | ||
| `locktime`; reject `FiatSent` with a clear `CantDo` (e.g. | ||
| `CashuEscrowNotLocked` if the token/locktime is missing, else a | ||
| settlement-window reason) when | ||
| `locktime - now < escrow_settlement_margin_days`. Fiat can never be sent inside | ||
| the danger window, so the seller cannot weaponise the locktime. | ||
|
|
||
| The margin must be comfortably below the locktime floor (`escrow_locktime_days`, | ||
| default 15) so a normal trade has ample settlement room; the difference (≈12 | ||
| days) is the usable fiat-settlement window. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Specify margin validation and expired-locktime handling.
The supplied CashuSettings currently has no escrow_settlement_margin_days; the implementation plan should require startup validation that the margin is below escrow_locktime_days. Also define expired-locktime behavior explicitly and require checked/saturating subtraction, otherwise a configurable margin can disable every FiatSent and unsigned locktime - now arithmetic can underflow.
🤖 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 `@docs/cashu/03-track-b-release.md` around lines 104 - 115, Update the
CashuSettings implementation and startup validation to add
escrow_settlement_margin_days and require it to be strictly below
escrow_locktime_days. In the Cashu branch of fiat_sent_action, explicitly reject
expired or missing locktimes with the appropriate CantDo reason, and use checked
or saturating subtraction for locktime-versus-now calculations so underflow
cannot disable every FiatSent.
| | **TB-1** | `escrow_settlement_margin_days` + `FiatSent` §4B guard | CF-1, Track A | Tracks C/D | `config/*`, `fiat_sent.rs` | Low-Medium | | ||
| | **TB-2** | `release_action` Cashu branch + unblock `Release`/`RateUser` | CF-5, Track A, TB-1 | Tracks C/D | `release.rs`, `app.rs` | Medium | | ||
|
|
||
| All of Track B is parallel with Tracks C/D (disjoint handler files); the shared | ||
| touch point is the `dispatch_cashu` allow-list, edited one arm at a time. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Make the documented track ordering consistent.
docs/cashu/03-track-b-release.md#L163-L167: remove or qualify the claim that Track B is parallel with Tracks C/D.docs/cashu/04-track-c-coop-cancel.md#L154-L161: reflect the stated dependency on Track B if it is a merge/runtime dependency.docs/cashu/05-track-d-dispute.md#L176-L186: reflect the stated dependency on Track C if it is required for implementation.
📍 Affects 3 files
docs/cashu/03-track-b-release.md#L163-L167(this comment)docs/cashu/04-track-c-coop-cancel.md#L154-L161docs/cashu/05-track-d-dispute.md#L176-L186
🤖 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 `@docs/cashu/03-track-b-release.md` around lines 163 - 167, The documented
track ordering is inconsistent: update docs/cashu/03-track-b-release.md lines
163-167 to remove or qualify Track B’s parallelism with Tracks C/D, update
docs/cashu/04-track-c-coop-cancel.md lines 154-161 to state Track B as a
dependency if it is required for merge or runtime behavior, and update
docs/cashu/05-track-d-dispute.md lines 176-186 to state Track C as a dependency
if required for implementation.
| - Refund value = **`2 * order.fee`** (the whole Mostro fee), sent as a fresh | ||
| token to the seller's **trade** pubkey. | ||
| - Idempotency: gate the refund on the fee state so a replayed/duplicate cancel | ||
| never double-refunds — e.g. only refund when `cashu_fee_token IS NOT NULL` and | ||
| the order has not already been refunded (stamp a `cashu_fee_refunded_at`, or | ||
| reuse `cashu_fee_redeemed_at` semantics: a redeemed fee is refunded by minting | ||
| fresh ecash back, a not-yet-redeemed fee is simply not collected). The exact | ||
| bookkeeping is a TC decision, but it **must** be single-shot. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Define one crash-safe, idempotent seller-refund contract.
docs/cashu/04-track-c-coop-cancel.md#L102-L109: specify atomic claiming, durable retry/idempotency behavior, and fee-state semantics around minting.docs/cashu/05-track-d-dispute.md#L137-L143: require seller-win disputes to use the same refund helper and idempotency contract, including cancel/dispute terminal-state races.
🧰 Tools
🪛 LanguageTool
[style] ~104-~104: The adverb ‘never’ is usually put before the verb ‘cancel’.
Context: ...n the fee state so a replayed/duplicate cancel never double-refunds — e.g. only refund when ...
(ADVERB_WORD_ORDER)
📍 Affects 2 files
docs/cashu/04-track-c-coop-cancel.md#L102-L109(this comment)docs/cashu/05-track-d-dispute.md#L137-L143
🤖 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 `@docs/cashu/04-track-c-coop-cancel.md` around lines 102 - 109, Define one
crash-safe, single-shot seller-refund contract in
docs/cashu/04-track-c-coop-cancel.md lines 102-109: specify atomic refund
claiming, durable retry/idempotency behavior, and explicit fee-state semantics
for minting. In docs/cashu/05-track-d-dispute.md lines 137-143, require
seller-win disputes to call the same refund helper and follow the same contract,
including races between cancel and dispute terminal states.
| ### 5B · `admin_take_dispute_action` (Cashu branch) | ||
| Assign the solver as today; additionally, **alert the solver** when the escrow's | ||
| remaining locktime is below the resolution SLA (§4B) — a late `P_M` signature is | ||
| worthless once the seller can reclaim via the refund path. Log + priority-flag. |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Make the near-locktime condition enforceable.
The document states that a late P_M signature is worthless once the seller can reclaim, but the proposed behavior only logs and priority-flags the solver. Define a hard deadline/guard or explicit recovery path; otherwise Mostro can finalize a dispute and deliver an unusable signature.
🤖 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 `@docs/cashu/05-track-d-dispute.md` around lines 132 - 135, Make the
near-locktime handling in admin_take_dispute_action enforceable by defining a
hard resolution deadline/guard based on the escrow’s remaining locktime and the
resolution SLA, and prevent finalization that would produce an unusable P_M
signature. Specify the explicit recovery path when the guard is reached, rather
than relying only on logging and priority-flagging.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1867913fc6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| instead advance `FiatSent → Success`, publish the order event, acknowledge the | ||
| seller's release signature to the buyer (relay/ack the P2P DM), and make the | ||
| trade rateable (`RateUser` becomes valid). The fee was already collected at lock |
There was a problem hiding this comment.
Require a verified release signature before success
In this flow the seller's Cashu signature is only described as a P2P DM, while the daemon advances FiatSent to Success on the seller's Release. If a seller sends Release after FiatSent but never sends a valid NUT-11 signature to the buyer, Mostro would make the trade terminal/rateable even though the buyer cannot redeem and may no longer have a usable dispute path. Please make this transition depend on a daemon-verifiable signature delivery or an explicit buyer/redeem acknowledgement, not solely on the seller's release message.
Useful? React with 👍 / 👎.
| reuse `cashu_fee_redeemed_at` semantics: a redeemed fee is refunded by minting | ||
| fresh ecash back, a not-yet-redeemed fee is simply not collected). The exact |
There was a problem hiding this comment.
Refund unredeemed fee tokens instead of ignoring them
For the Track A fee model, the fee token is locked 1-of-1 to P_M, so the seller cannot spend it. If cooperative cancel happens before cashu_fee_redeemed_at is stamped, treating the fee as “simply not collected” does not refund the seller; it leaves their fee ecash locked to Mostro's key. The cancel path needs to redeem-and-return fresh ecash, provide a usable P_M signature/refund path, or require the self-service-refund refinement before this no-op is safe.
Useful? React with 👍 / 👎.
| - **Do not** touch LND (there is no hold invoice). | ||
| - Advance the order to `Canceled`, publish the order event. |
There was a problem hiding this comment.
Preserve the cooperative-cancel terminal status
The existing active-order cooperative cancel path persists Status::CooperativelyCanceled in cancel_cooperative_execution_step_2; Status::Canceled is used for other cancel flows. Specifying Canceled for the Cashu branch would make the new branch diverge from the current cooperative-cancel semantics and can break code that distinguishes cooperative completion from ordinary cancellation/admin cancellation. Please keep the internal terminal state aligned with the existing handler and only collapse to a public “canceled” status where the current NIP-33 mapping already does so.
Useful? React with 👍 / 👎.
What & why
Adds the three remaining feature-track specs to the Cashu series, so the whole escrow feature is planned end-to-end before the code lands. Spec only — no implementation. Each mirrors the structure of
01-fundamentals.md/02-track-a-lock.md(goal & scope, flow + state transitions, what it consumes from Fundamentals + Track A, handler algorithm, PR breakdown, issues table, DoD, cross-track obligations).03-track-b-release.mdFiatSent(with the §4B remaining-locktime guard +escrow_settlement_margin_days) thenRelease. The seller's release signature travels P2P; Mostro only advances state and makes the trade rateable.04-track-c-coop-cancel.mdCancel, the buyer signs so the seller reclaims, and Mostro refunds the seller the fee (2 * order.fee) — the first executor of the Track A §4A refund obligation, single-shot.05-track-d-dispute.mdP_Msigns.sign_with_pm+CashuPmSignaturedelivery to the winner (AdminSettle→buyer,AdminCancel→seller), the §4B near-locktime solver alert, and the seller-wins fee refund.Design continuity
mostro-coreprotocol variant. Track D uses the frozenCashuPmSignature/CashuSignaturessurface already onmain(0.13.0 baseline). The only new daemon-side capability issign_with_pminCashuClient.dispatch_cashuaction gets an owner, closing the CF-5 §6 action-ownership matrix:FiatSent/Release/RateUser(B),Cancel(C),Dispute/AdminTakeDispute/AdminSettle/AdminCancel/AdminAddSolver(D).AddInvoice/AddBondInvoicestay permanently blocked by design.FiatSentguard + solver alert) are each assigned to the executing track's DoD.Notes
main, independent of the Track A implementation stack (feat: cashu boot + run_cashu + dispatch seam — Cashu foundation CF-5 #828–feat(cashu): seller-funded fee token (Option 2) — Track A TA-1f #832). It documents the next tracks; it does not implement them.Refs:
docs/cashu/01-fundamentals.md,docs/cashu/02-track-a-lock.md.Summary by CodeRabbit
🧪 Manual testing — step by step
This is a docs-only PR (no code, no runtime), so "testing" here means verifying the specs render and are internally consistent. Nothing to build or run.
1. The three new specs render and are complete
docs/cashu/03-track-b-release.mddocs/cashu/04-track-c-coop-cancel.mddocs/cashu/05-track-d-dispute.md2. Links and cross-references resolve
docs/cashu/README.md, click the 03/04/05 rows — they now link to the new files (Status column shows Draft, no longer "Planned"). ✔️01-fundamentals.md,02-track-a-lock.md, and../CASHU_ESCROW_ARCHITECTURE.mdresolve. ✔️3. Design continuity holds
FiatSentguard + solver alert → Tracks B & D. ✔️dispatch_cashuaction blocked in CF-5 §6 gets an owner across B/C/D (FiatSent/Release/RateUser→ B;Cancel→ C;Dispute/AdminTakeDispute/AdminSettle/AdminCancel/AdminAddSolver→ D), and thatAddInvoice/AddBondInvoiceremain permanently blocked. ✔️mostro-coreprotocol variant is required — Track D uses the frozenCashuPmSignature/CashuSignaturessurface already onmain. ✔️4. Optional lint
Expected: clean (or only pre-existing repo-wide rules). ✔️