Skip to content

Add read and read-write dispute solver permissions#708

Open
mostronatorcoder[bot] wants to merge 5 commits intomainfrom
feat/solver-read-write-permissions
Open

Add read and read-write dispute solver permissions#708
mostronatorcoder[bot] wants to merge 5 commits intomainfrom
feat/solver-read-write-permissions

Conversation

@mostronatorcoder
Copy link
Copy Markdown
Contributor

@mostronatorcoder mostronatorcoder bot commented Apr 16, 2026

Summary

Implement solver permission levels for disputes:

  • read solvers can take disputes and communicate with users
  • read-write solvers can additionally execute admin-settle and admin-cancel

This also upgrades mostro-core to 0.8.4 and uses CantDoReason::NotAuthorized when an assigned read-only solver attempts a write action.

Changes

Runtime

  • upgrade mostro-core from 0.8.3 to 0.8.4
  • add solver categories in admin_add_solver_action
    • npub1... -> defaults to read-write
    • npub1...:read -> read-only
    • npub1...:read-write -> read-write
    • npub1...:write -> alias for read-write
  • add DB helper to verify write permission for the assigned solver
  • reject admin-settle with CantDoReason::NotAuthorized when an assigned solver is read-only
  • reject admin-cancel with CantDoReason::NotAuthorized when an assigned solver is read-only

Documentation

  • add docs/SOLVER_PERMISSION_LEVELS.md
  • update:
    • docs/ADMIN_RPC_AND_DISPUTES.md
    • docs/ORDERS_AND_ACTIONS.md
    • docs/ARCHITECTURE.md
    • docs/README.md

Notes

This keeps the implementation simple and backward compatible:

  • it reuses the existing users.category column
  • existing admin-add-solver payloads without suffix still register full solvers
  • RPC AddSolverRequest remains backward compatible and still defaults to read-write until extended later

Validation

  • cargo fmt
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test
  • 211 tests passed, 0 failed

Related:

Summary by CodeRabbit

  • New Features

    • Solver permission levels (read-only and read-write) enforced for admin actions; add-solver now accepts optional permission suffixes (defaults to read-write) and rejects invalid tokens.
  • Documentation

    • Added solver permission levels guide and updated admin RPC, architecture, orders/actions, and README to reflect permission rules and audit guidance.
  • Dependencies

    • Updated core dependency to v0.8.4

Migration note

  • This PR includes an automatic data migration for legacy solver records: migrations/20260417145500_solver_category_backfill.sql.
  • On startup, Mostro runs embedded SQLx migrations (sqlx::migrate!()), so operators do not need to update users.category manually when deploying a build that includes this PR.
  • The backfill sets category = 2 for existing rows where is_solver = 1 AND category = 0, preserving historical settle/cancel authority for legacy solvers.
  • New solvers added through admin-add-solver still default to read-write (category = 2) unless :read is explicitly provided.

@grunch
Copy link
Copy Markdown
Member

grunch commented Apr 16, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

Walkthrough

Implements solver permission levels (read vs read-write), parses optional permission in admin-add-solver, enforces write-permission checks for admin-settle and admin-cancel via a new DB helper, updates docs, and bumps mostro-core to 0.8.4 for CantDoReason::NotAuthorized.

Changes

Cohort / File(s) Summary
Dependency Update
Cargo.toml
Updated mostro-core from 0.8.3 to 0.8.4 to rely on CantDoReason::NotAuthorized.
Permission System Documentation
docs/SOLVER_PERMISSION_LEVELS.md
New doc defining solver categories (0/1/2), allowed actions per category, parsing rules for admin-add-solver payloads, RPC implications, and daemon-enforced security model.
Documentation Updates
docs/ADMIN_RPC_AND_DISPUTES.md, docs/ARCHITECTURE.md, docs/ORDERS_AND_ACTIONS.md, docs/README.md
Clarified that admin-settle/admin-cancel require read-write permission, noted admin modules are permission-gated, added quick link to permission docs, and documented admin-add-solver payload formats.
Permission Parsing & Registration
src/app/admin_add_solver.rs
Added SOLVER_CATEGORY_READ_ONLY = 1, SOLVER_CATEGORY_READ_WRITE = 2; implemented parse_solver_payload() to accept npub with optional :read, :read-write, or :write (default read-write), tightened payload validation, converted DB errors to internal errors, and added unit tests.
Permission Authorization Guards
src/app/admin_cancel.rs, src/app/admin_settle.rs
Inserted solver_has_write_permission(...) guard after assignment checks; return MostroCantDo(CantDoReason::NotAuthorized) if caller lacks write permission (prevents fund-affecting ops).
Take-Dispute Logic
src/app/admin_take_dispute.rs
Extended pubkey_event_can_solve signature and logic to accept dispute context (current_solver_pubkey, dispute_order_id) and incorporate write-permission checks for InProgress disputes while preserving read-only take behavior for Initiated.
Permission Verification Helper
src/db.rs
Added pub async fn solver_has_write_permission(pool, solver_pubkey, order_id) -> Result<bool, MostroError> to SELECT EXISTS assigned solver with is_solver = 1 and category = 2; converts SQLx errors to MostroInternalErr.

Sequence Diagram

sequenceDiagram
    participant Client as Admin Handler
    participant Dispute as Dispute Service
    participant DB as Database
    participant Daemon as Daemon Logic
    participant Error as Error Response

    rect rgba(200,150,255,0.5)
    Note over Client,Error: Permission Authorization Flow
    end

    Client->>Dispute: Load dispute (order ID, solver pubkey)
    Dispute-->>Client: current solver pubkey, order ID
    Client->>DB: solver_has_write_permission(solver_pubkey, order_id)
    DB-->>Client: true / false
    alt true (category=2)
        Client->>Daemon: proceed with admin_settle/admin_cancel
        Daemon->>Daemon: execute fund-affecting operations
        Daemon-->>Client: success
    else false (category=1)
        Client->>Error: return CantDoReason::NotAuthorized
        Error-->>Client: reject action
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I nibble on bytes and hop through the code,
Two keys in my pouch — read and read-write bestowed,
I guide and I chat, but I cannot unspool the coin,
The daemon keeps watch, so no funds are purloined,
Hooray for clear gates where responsibilities bide.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add read and read-write dispute solver permissions' directly and clearly summarizes the main change: introducing two permission levels for dispute solvers.
Linked Issues check ✅ Passed The PR implements all core acceptance criteria from #707: parser for permission levels with read-write default, write permission checks in settle/cancel, continued support for take-dispute, and appropriate use of CantDoReason::NotAuthorized.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing solver permission levels: dependency upgrade to 0.8.4, documentation updates, parsing logic, DB helper, and authorization gates in settle/cancel/take-dispute.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/solver-read-write-permissions

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.

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/db.rs (1)

784-809: Document this public permission helper.

Lines 784-809 encode a non-obvious auth contract: true means the dispute is assigned to solver_pubkey and that user is in the read-write solver category. Please add a /// comment so callers do not have to infer the category = 2 rule from the SQL.

As per coding guidelines, "Document non-obvious public APIs with /// doc comments".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/db.rs` around lines 784 - 809, Document the non-obvious auth contract in
the public helper solver_has_write_permission: add a /// doc comment above the
function that explains that a returned true means the given solver_pubkey is
assigned to the dispute (matches disputes.solver_pubkey and order_id) AND the
corresponding user has is_solver = true and category = 2 (i.e., belongs to the
read-write solver category); reference the fields checked
(disputes.solver_pubkey, order_id, users.is_solver, users.category) so callers
don't need to infer the "category = 2" rule from the SQL.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/ORDERS_AND_ACTIONS.md`:
- Line 41: Update the docs entry for the admin-add-solver command to describe
the full accepted payload syntax: state that the payload may be a bare public
key (interpreted as read-write), the public key with suffix :read, :write (alias
for read-write), or :read-write, and show examples for each; reference the
admin-add-solver command name and the parser behavior so readers know the bare
pubkey and :write alias are valid and equivalent to read-write.

In `@src/app/admin_add_solver.rs`:
- Around line 33-37: The code currently treats Some("") (an empty permission
token from parts.next()) as SOLVER_CATEGORY_READ_WRITE which silently grants
write access for inputs like "npub1test:", so change the match in
admin_add_solver.rs to treat Some("") as invalid by removing Some("") from the
read-write arm and handling it to return
Err(MostroCantDo(CantDoReason::InvalidParameters)); ensure the match still maps
Some("read") to SOLVER_CATEGORY_READ_ONLY and Some("read-write")/Some("write")
to SOLVER_CATEGORY_READ_WRITE, and add a regression test asserting that an input
like "npub1test:" yields MostroCantDo(CantDoReason::InvalidParameters).
- Around line 72-84: The code currently logs add_new_user failures but proceeds
to send the AdminAddSolver success DM; change the match on add_new_user to
return early on Err instead of continuing: in the Err(ee) arm log the error and
return Err(MostroInternalErr(ServiceError::DatabaseError(ee.to_string()))) (or
map ee into the appropriate ServiceError variant your codebase uses), so only
the Ok(r) path constructs Message::new_dispute and calls send_dm; update
references involved: add_new_user, Message::new_dispute, send_dm,
MostroInternalErr and ServiceError.

In `@src/app/admin_settle.rs`:
- Around line 216-223: Add behavioral tests that exercise the solver permission
gate by invoking admin_settle_action and admin_cancel_action with two solver
fixtures: one configured as read-only and one with read-write permissions. For
each action, call admin_settle_action and admin_cancel_action using the
read-only solver and assert the handler returns
MostroCantDo(CantDoReason::NotAuthorized); then call the same handlers with the
read-write solver and assert successful execution (no CantDo error). Use the
existing test helpers/fixtures to construct solvers with the appropriate
permission flags and reference the functions admin_settle_action,
admin_cancel_action, the error enum CantDoReason::NotAuthorized, and the wrapper
variant MostroCantDo when asserting results.

---

Nitpick comments:
In `@src/db.rs`:
- Around line 784-809: Document the non-obvious auth contract in the public
helper solver_has_write_permission: add a /// doc comment above the function
that explains that a returned true means the given solver_pubkey is assigned to
the dispute (matches disputes.solver_pubkey and order_id) AND the corresponding
user has is_solver = true and category = 2 (i.e., belongs to the read-write
solver category); reference the fields checked (disputes.solver_pubkey,
order_id, users.is_solver, users.category) so callers don't need to infer the
"category = 2" rule from the SQL.
🪄 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: fe6387d0-822a-4a75-b7f7-6ffb8b3206f9

📥 Commits

Reviewing files that changed from the base of the PR and between d0787dd and 5a6769a.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml
  • docs/ADMIN_RPC_AND_DISPUTES.md
  • docs/ARCHITECTURE.md
  • docs/ORDERS_AND_ACTIONS.md
  • docs/README.md
  • docs/SOLVER_PERMISSION_LEVELS.md
  • src/app/admin_add_solver.rs
  • src/app/admin_cancel.rs
  • src/app/admin_settle.rs
  • src/db.rs

Comment thread docs/ORDERS_AND_ACTIONS.md Outdated
Comment thread src/app/admin_add_solver.rs
Comment thread src/app/admin_add_solver.rs
Comment thread src/app/admin_settle.rs
Comment on lines +216 to 223
// New error for authenticated callers lacking enough permissions
let unauthorized_error = CantDoReason::NotAuthorized;
assert_eq!(format!("{:?}", unauthorized_error), "NotAuthorized");

// Verify they are different error types
assert_ne!(regular_error, admin_error);
assert_ne!(admin_error, unauthorized_error);
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n --type rust -C3 '\b(admin_settle_action|admin_cancel_action|solver_has_write_permission|NotAuthorized)\b'

Repository: MostroP2P/mostro

Length of output: 6405


🏁 Script executed:

#!/bin/bash
# Get the full test module from admin_settle.rs
fd "admin_settle.rs" -type f | head -1 | xargs wc -l

Repository: MostroP2P/mostro

Length of output: 249


🏁 Script executed:

# Read the test module section from admin_settle.rs
fd "admin_settle.rs" -type f | head -1 | xargs tail -100

Repository: MostroP2P/mostro

Length of output: 230


🏁 Script executed:

# Also check admin_cancel.rs tests
fd "admin_cancel.rs" -type f | head -1 | xargs tail -100

Repository: MostroP2P/mostro

Length of output: 230


🏁 Script executed:

#!/bin/bash
# Find and display the test module from admin_settle.rs
find . -name "admin_settle.rs" | head -1 | xargs tail -150

Repository: MostroP2P/mostro

Length of output: 5708


🏁 Script executed:

# Find and display the test module from admin_cancel.rs
find . -name "admin_cancel.rs" | head -1 | xargs tail -150

Repository: MostroP2P/mostro

Length of output: 5759


🏁 Script executed:

#!/bin/bash
# Check full length and tail of admin_cancel.rs to see if there's a test module
find . -name "admin_cancel.rs" | head -1 | xargs wc -l

Repository: MostroP2P/mostro

Length of output: 89


🏁 Script executed:

# Search for test functions that might exercise the handlers with different permissions
rg -n --type rust '(#\[test\]|fn test_.*admin_(settle|cancel))' src/app/

Repository: MostroP2P/mostro

Length of output: 1168


🏁 Script executed:

# Look for any integration tests or tests that call admin_settle_action or admin_cancel_action
rg -n --type rust 'admin_(settle|cancel)_action' src/ | grep -i test

Repository: MostroP2P/mostro

Length of output: 42


🏁 Script executed:

#!/bin/bash
# Check the end of admin_cancel.rs to confirm whether it has a test module
find . -name "admin_cancel.rs" | head -1 | xargs tail -50

Repository: MostroP2P/mostro

Length of output: 2086


🏁 Script executed:

# Search for any tests or fixtures that might use solver permissions
rg -n --type rust 'solver.*permission|read.*only.*solver|write.*permission' src/ -i

Repository: MostroP2P/mostro

Length of output: 679


🏁 Script executed:

#!/bin/bash
# Final confirmation: check if solver_has_write_permission is actually tested anywhere
rg -n --type rust 'solver_has_write_permission' src/ -A 5 | grep -A 5 'test\|#\[test\]'

Repository: MostroP2P/mostro

Length of output: 42


Add behavioral tests for the new solver permission gate.

The current test at lines 216-223 only verifies that CantDoReason::NotAuthorized exists as an enum variant via debug formatting. It does not exercise the authorization check in admin_settle_action or admin_cancel_action.

Since this PR introduces write-permission enforcement (lines 53-54 in admin_settle.rs and lines 76-77 in admin_cancel.rs), add at least one test that:

  • Calls admin_settle_action with a read-only solver and asserts the handler returns MostroCantDo(NotAuthorized)
  • Calls admin_settle_action with a read-write solver and asserts successful execution

Repeat the same for admin_cancel_action to confirm the authorization boundary works end-to-end.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin_settle.rs` around lines 216 - 223, Add behavioral tests that
exercise the solver permission gate by invoking admin_settle_action and
admin_cancel_action with two solver fixtures: one configured as read-only and
one with read-write permissions. For each action, call admin_settle_action and
admin_cancel_action using the read-only solver and assert the handler returns
MostroCantDo(CantDoReason::NotAuthorized); then call the same handlers with the
read-write solver and assert successful execution (no CantDo error). Use the
existing test helpers/fixtures to construct solvers with the appropriate
permission flags and reference the functions admin_settle_action,
admin_cancel_action, the error enum CantDoReason::NotAuthorized, and the wrapper
variant MostroCantDo when asserting results.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I did not implement the suggested admin_settle_action / admin_cancel_action success-path behavioral tests in this pass, because after verifying the current code they are not a direct bug fix and would require building non-trivial DB/LND fixtures to make the tests meaningful rather than synthetic. I’d prefer to handle that as a separate focused test-coverage improvement if needed.

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.

@grunch, understood — that's a reasonable call. Wiring up admin_settle_action / admin_cancel_action end-to-end in tests requires a live SQLite pool, mock LND connector, and valid Nostr key fixtures, which is a non-trivial undertaking that goes well beyond the scope of this change. The structural check you've added confirms the NotAuthorized variant is present and distinct, which keeps the CI signal intact.

Would you like me to open a follow-up GitHub issue to track adding proper behavioral coverage for the solver write-permission gate in admin_settle_action and admin_cancel_action? I can link back to this PR and the relevant lines as context.


🧠 Learnings used
Learnt from: arkanoider
Repo: MostroP2P/mostro PR: 530
File: src/app/order.rs:278-279
Timestamp: 2025-10-09T14:06:50.067Z
Learning: In the Mostro project, tests in files like `src/app/order.rs` may use structural checks to ensure functions like `order_action` do not panic during message construction, rather than validating specific error variants.

Learnt from: CR
Repo: MostroP2P/mostro PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-25T20:58:01.066Z
Learning: PRs must link the motivating issue, include `cargo test` output, and call out schema or config changes to ease verification

Learnt from: grunch
Repo: MostroP2P/mostro PR: 566
File: src/nip33.rs:1-3
Timestamp: 2026-01-16T12:05:35.401Z
Learning: Do not redefine NOSTR_REPLACEABLE_EVENT_KIND in files like src/config/constants.rs or import it explicitly. If mostro_core::prelude::* is imported, the constant is available automatically. Rely on the prelude wildcard import across Rust files; if a file compiles without a local definition, no import or definition is needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yes please

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.

🧩 Analysis chain

🏁 Script executed:

gh issue create \
  --repo MostroP2P/mostro \
  --title "Add behavioral tests for solver write-permission gate in admin_settle_action and admin_cancel_action" \
  --body "## Context

This issue was raised during review of PR `#708` (Add read and read-write dispute solver permissions) in comment https://github.com/MostroP2P/mostro/pull/708#discussion_r3094389866, requested by `@grunch`.

## Problem

PR `#708` introduced write-permission enforcement in \`admin_settle_action\` (\`src/app/admin_settle.rs\`, lines 53–54) and \`admin_cancel_action\` (\`src/app/admin_cancel.rs\`, lines 76–77) via \`solver_has_write_permission\`. The only test currently covering this path is a structural check that verifies \`CantDoReason::NotAuthorized\` exists as an enum variant; it does **not** exercise the authorization boundary end-to-end.

## Goal

Add behavioral tests that:

1. Call \`admin_settle_action\` with a **read-only** solver fixture and assert the handler returns \`MostroCantDo(CantDoReason::NotAuthorized)\`.
2. Call \`admin_settle_action\` with a **read-write** solver fixture and assert successful execution (no \`CantDo\` error).
3. Repeat steps 1–2 for \`admin_cancel_action\`.

These tests require non-trivial fixtures (in-memory SQLite pool seeded with dispute + solver rows, mock LND connector, valid Nostr key pairs), which is why they were deferred from PR `#708`.

## Acceptance Criteria

- [ ] At least one test per action (\`admin_settle_action\`, \`admin_cancel_action\`) that covers the read-only rejection path.
- [ ] At least one test per action that covers the read-write success path (or reaches the next meaningful gate without a permission error).
- [ ] Tests use the existing test helpers/fixtures in the Mostro codebase where possible.
- [ ] \`cargo test\` passes with all new tests included.

## References

- PR `#708`: https://github.com/MostroP2P/mostro/pull/708
- Closes issue `#707` (parent feature)
- Relevant files: \`src/app/admin_settle.rs\`, \`src/app/admin_cancel.rs\`, \`src/db.rs\` (\`solver_has_write_permission\`)
" \
  --assignee grunch

Length of output: 46


@grunch, done! Follow-up issue created and assigned to you: #709

It captures the full context — the deferred behavioral tests for the read-only rejection path and read-write success path in both admin_settle_action and admin_cancel_action, along with links back to this PR and the relevant source files.


🧠 Learnings used
Learnt from: arkanoider
Repo: MostroP2P/mostro PR: 530
File: src/app/order.rs:278-279
Timestamp: 2025-10-09T14:06:50.067Z
Learning: In the Mostro project, tests in files like `src/app/order.rs` may use structural checks to ensure functions like `order_action` do not panic during message construction, rather than validating specific error variants.

Learnt from: CR
Repo: MostroP2P/mostro PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-25T20:58:01.066Z
Learning: PRs must link the motivating issue, include `cargo test` output, and call out schema or config changes to ease verification

Learnt from: grunch
Repo: MostroP2P/mostro PR: 566
File: src/nip33.rs:1-3
Timestamp: 2026-01-16T12:05:35.401Z
Learning: Do not redefine NOSTR_REPLACEABLE_EVENT_KIND in files like src/config/constants.rs or import it explicitly. If mostro_core::prelude::* is imported, the constant is available automatically. Rely on the prelude wildcard import across Rust files; if a file compiles without a local definition, no import or definition is needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I implemented the behavior change so a read-write solver can take over a dispute that is already in-progress when the current assignee is a read-only solver, while keeping the existing restrictions for other reassignment cases.

Fix commit: 08fe6aa (Allow write solvers to take over read-only disputes).

Note: I pushed it to branch solver-permissions while preparing the update locally, so it still needs to be moved onto this PR branch before the thread can be marked fully resolved here.

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.

Skipped: comment is from another GitHub bot.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 08fe6aa and now pushed to the actual PR branch (feat/solver-read-write-permissions). This allows a read-write solver to take over a dispute already taken by a read-only solver, while preserving the existing restrictions for other reassignment cases.

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.

Skipped: comment is from another GitHub bot.

Copy link
Copy Markdown
Member

@Catrya Catrya left a comment

Choose a reason for hiding this comment

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

If a solver with read-only permissions takes a dispute, a solver with read-write permissions cannot take it, Mostro returns cant_do: invalid_pubkey.
Therefore, that solver will not be able to send settleorder or cancelorder.

Currently, only the admin can take a dispute that another solver has already taken, this behavior is inherited from main.

I believe this should be changed to allow a solver with read-write permissions to take a dispute that has already been taken by a solver with read-only permissions.

@grunch
Copy link
Copy Markdown
Member

grunch commented Apr 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@grunch
Copy link
Copy Markdown
Member

grunch commented Apr 17, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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

🧹 Nitpick comments (1)
src/app/admin_take_dispute.rs (1)

107-140: Optional: flatten the nested if chain and hoist the imports.

The InProgress branch has grown 4–5 levels deep, which hurts readability, and crate::app::admin_add_solver::SOLVER_CATEGORY_READ_ONLY is referenced by fully-qualified path inline. Consider early returns plus a top-level use:

♻️ Proposed refactor
-use crate::db::{find_solver_pubkey, is_user_present, solver_has_write_permission};
+use crate::app::admin_add_solver::SOLVER_CATEGORY_READ_ONLY;
+use crate::db::{find_solver_pubkey, is_user_present, solver_has_write_permission};
-    if let Ok(solver) = find_solver_pubkey(pool, ev_pubkey.to_string()).await {
-        if solver.is_solver != 0_i64 {
-            if status == DisputeStatus::Initiated {
-                return true;
-            }
-
-            if status == DisputeStatus::InProgress {
-                let sender_pubkey = ev_pubkey.to_string();
-                if current_solver_pubkey == Some(sender_pubkey.as_str()) {
-                    return true;
-                }
-
-                if let Some(current_solver_pubkey) = current_solver_pubkey {
-                    let sender_can_write =
-                        solver_has_write_permission(pool, sender_pubkey.as_str(), dispute_order_id)
-                            .await
-                            .unwrap_or(false);
-
-                    if sender_can_write {
-                        if let Ok(current_solver) =
-                            find_solver_pubkey(pool, current_solver_pubkey.to_string()).await
-                        {
-                            if current_solver.is_solver != 0_i64
-                                && current_solver.category
-                                    == crate::app::admin_add_solver::SOLVER_CATEGORY_READ_ONLY
-                            {
-                                return true;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
+    let Ok(solver) = find_solver_pubkey(pool, ev_pubkey.to_string()).await else {
+        return false;
+    };
+    if solver.is_solver == 0_i64 {
+        return false;
+    }
+    match status {
+        DisputeStatus::Initiated => return true,
+        DisputeStatus::InProgress => {
+            let sender_pubkey = ev_pubkey.to_string();
+            if current_solver_pubkey == Some(sender_pubkey.as_str()) {
+                return true;
+            }
+            // Takeover path: sender must have write permission and current solver must be read-only.
+            // NOTE: depends on the fix for the critical comment above.
+            // ...
+        }
+        _ => {}
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/admin_take_dispute.rs` around lines 107 - 140, The InProgress branch
in admin_take_dispute.rs is deeply nested; refactor by early-returning on
negative checks (e.g., if find_solver_pubkey(...) fails or solver.is_solver == 0
return false; if status != DisputeStatus::InProgress return false) and pull
crate::app::admin_add_solver::SOLVER_CATEGORY_READ_ONLY into a top-level use so
you can compare current_solver.category == SOLVER_CATEGORY_READ_ONLY without the
fully-qualified path; also locally bind sender_pubkey and current_solver_pubkey
to variables, then call solver_has_write_permission and find_solver_pubkey with
those bindings and return true as soon as the conditions are satisfied to
flatten the nesting.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/admin_take_dispute.rs`:
- Around line 119-137: The current branch in admin_take_dispute.rs is dead
because solver_has_write_permission queries disputes.solver_pubkey so a
prospective taker (sender_pubkey != current solver) will always get false; add a
new helper (e.g., user_has_solver_write_permission) in db.rs that checks users
by pubkey for is_solver = true AND category = 2, or alternatively modify
solver_has_write_permission to accept a mode that checks only the users table,
then call that helper from admin_take_dispute (replace the
solver_has_write_permission call) to allow write-capable solvers to take over
read-only disputes; update tests to add a regression test where a write-capable
solver takes over an InProgress dispute assigned to a read-only solver.

---

Nitpick comments:
In `@src/app/admin_take_dispute.rs`:
- Around line 107-140: The InProgress branch in admin_take_dispute.rs is deeply
nested; refactor by early-returning on negative checks (e.g., if
find_solver_pubkey(...) fails or solver.is_solver == 0 return false; if status
!= DisputeStatus::InProgress return false) and pull
crate::app::admin_add_solver::SOLVER_CATEGORY_READ_ONLY into a top-level use so
you can compare current_solver.category == SOLVER_CATEGORY_READ_ONLY without the
fully-qualified path; also locally bind sender_pubkey and current_solver_pubkey
to variables, then call solver_has_write_permission and find_solver_pubkey with
those bindings and return true as soon as the conditions are satisfied to
flatten the nesting.
🪄 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: 03cd3ccf-facd-4232-9107-801d8c9db064

📥 Commits

Reviewing files that changed from the base of the PR and between 5a6769a and 08fe6aa.

📒 Files selected for processing (4)
  • docs/ORDERS_AND_ACTIONS.md
  • src/app/admin_add_solver.rs
  • src/app/admin_take_dispute.rs
  • src/db.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/db.rs
  • src/app/admin_add_solver.rs
  • docs/ORDERS_AND_ACTIONS.md

Comment thread src/app/admin_take_dispute.rs Outdated
grunch and others added 2 commits April 17, 2026 17:26
solver_has_write_permission joins disputes on the assigned solver, so
the InProgress takeover check always returned false for a prospective
taker. Introduce user_has_solver_write_permission (users table only)
and use it in pubkey_event_can_solve, flatten its nesting with early
returns, drop the now-unused dispute_order_id parameter, and add
regression tests for the read-only to write-capable takeover path.
@grunch grunch requested a review from Catrya April 17, 2026 21:04
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.

Add solver permission levels: read-only and read-write for dispute resolution

2 participants