Skip to content

Extract ledger view helpers#366

Merged
ramimbo merged 3 commits into
ramimbo:mainfrom
TUPM96:codex/bounty-320-ledger-view-helpers
May 26, 2026
Merged

Extract ledger view helpers#366
ramimbo merged 3 commits into
ramimbo:mainfrom
TUPM96:codex/bounty-320-ledger-view-helpers

Conversation

@TUPM96
Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 commented May 26, 2026

Refs #320

Summary

  • Extract ledger proof-hash lookup, recent ledger serialization, single-entry serialization, and account/wallet transaction listing into app/ledger_views.py.
  • Reuse the helper from REST ledger endpoints, account and wallet pages, and the MCP get_ledger_entry path so app/main.py no longer owns duplicate ledger/proof query shaping.
  • Add focused helper tests for proof-hash attachment, missing ledger entries, and account transaction filtering across both sides of a ledger entry.
  • Keep current checks green by preserving explicit ledger CursorResult typing for rowcount checks and by sanitizing percent-decoded OAuth next paths covered by the existing OAuth regression test.

Complexity reduced

Ledger display/API/MCP code now shares one query boundary for proof-backed ledger rows. Route handlers stay focused on HTTP status, auth, and template selection, while transaction lookup and proof hash attachment are directly testable outside app/main.py.

Tests

  • python -m pytest -q -> 337 passed, 1 Windows asyncio warning
  • python -m pytest tests/test_ledger_views.py tests/test_api_mcp.py::test_mcp_get_ledger_entry_includes_payment_proof_hash tests/test_bounty_pages.py::test_ledger_and_proof_pages_make_bounty_payments_scannable tests/test_wallet_api.py::test_github_login_stores_safe_default_for_backslash_next -q -> 5 passed
  • python -m ruff format --check app/main.py app/ledger/service.py app/ledger_views.py tests/test_ledger_views.py
  • python -m ruff check app/main.py app/ledger/service.py app/ledger_views.py tests/test_ledger_views.py
  • python -m mypy app

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced OAuth login redirect validation to check both original and decoded URL paths for unsafe patterns, including control characters and redirect ambiguity attempts.
  • Tests

    • Added comprehensive test coverage for ledger transaction data serialization and account-specific transaction filtering.
    • Expanded OAuth redirect security test cases with additional redirect ambiguity scenarios.

Review Change Stack

Copilot AI review requested due to automatic review settings May 26, 2026 02:07
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ced8ba60-17a9-48e8-beb7-439d66d42ff5

📥 Commits

Reviewing files that changed from the base of the PR and between 9935752 and 715503f.

📒 Files selected for processing (3)
  • app/main.py
  • tests/test_ledger_views.py
  • tests/test_wallet_api.py

📝 Walkthrough

Walkthrough

The PR extracts ledger query and serialization logic into a new dedicated module, refactors main.py endpoints and pages to use reusable helpers, hardens OAuth redirect validation with URL decoding, and adds comprehensive test coverage for the changes.

Changes

Ledger Views Refactoring & OAuth Enhancements

Layer / File(s) Summary
New ledger views query and serialization module
app/ledger_views.py
Five helper functions query ledger data via SQLAlchemy and serialize it: proof_hashes_by_sequence maps sequence numbers to proof hashes, ledger_entries_to_dicts enriches ORM entries with hashes, recent_ledger_entries returns latest entries, ledger_entry_to_dict fetches a single entry, and account_ledger_transactions filters by account and returns ordered results.
OAuth redirect sanitization with URL decoding
app/main.py
Add unquote import and enhance _safe_next_path to validate both raw and decoded redirect parameters against unsafe patterns (protocol-relative, backslashes, control characters); return /me if either form is unsafe or missing.
Refactored ledger API endpoints, pages, and MCP tool
app/main.py
Import new ledger helpers, remove local _proof_hashes_by_sequence helper, refactor /api/v1/ledger endpoints and account/wallet pages to use recent_ledger_entries, ledger_entry_to_dict, and account_ledger_transactions, update the get_ledger_entry MCP tool to call the new helper directly with 404 handling.
Tests for ledger views and OAuth sanitization
tests/test_ledger_views.py, tests/test_wallet_api.py
Add two tests validating ledger view helpers include correct proof hashes and filter account transactions on both sides; expand OAuth sanitization test with additional redirect-ambiguity patterns.

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Extract ledger view helpers' accurately and concisely summarizes the main change—moving ledger-related query and serialization logic into a new module.
Description check ✅ Passed The description covers the main objectives, includes evidence of testing and tooling validation, but the Test Evidence checklist items are not explicitly marked as completed.
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

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

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR centralizes ledger view serialization (including proof-hash attachment) into reusable helpers, updates API/page endpoints to use them, and adds tests to validate correct behavior.

Changes:

  • Added app/ledger_views.py helpers for recent entries, per-entry lookup, and account transaction lists with consistent proof_hash handling.
  • Refactored app/main.py ledger endpoints/pages to use the new helpers and tightened _safe_next_path against encoded path tricks.
  • Added focused tests covering ledger view helpers (proof hash attachment + account-side filtering).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
tests/test_ledger_views.py Adds tests validating proof-hash attachment and account transaction filtering behavior.
app/main.py Replaces inline ledger/proof query logic with ledger_views helpers and improves _safe_next_path decoding checks.
app/ledger_views.py Introduces centralized query/serialization helpers for ledger-related views.
app/ledger/service.py Simplifies SQLAlchemy update execution by removing cast(...) usage around update results.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/main.py
Comment on lines 276 to 290
def _safe_next_path(next_path: str | None) -> str:
decoded_next_path = unquote(next_path) if next_path else ""
if (
not next_path
or not next_path.startswith("/")
or next_path.startswith("//")
or len(next_path) > 2048
or "\\" in next_path
or decoded_next_path.startswith("//")
or "\\" in decoded_next_path
or any(ord(char) < 32 or 127 <= ord(char) < 160 for char in next_path)
or any(ord(char) < 32 or 127 <= ord(char) < 160 for char in decoded_next_path)
):
return "/me"
return next_path
Comment thread app/ledger/service.py Outdated
Comment on lines 659 to 671
claimed = session.execute(
update(Bounty)
.where(Bounty.id == bounty.id, Bounty.awards_paid < Bounty.max_awards)
.values(
awards_paid=Bounty.awards_paid + 1,
status=case(
(Bounty.awards_paid + 1 >= Bounty.max_awards, "paid"),
else_="open",
),
)
)
if claimed.rowcount != 1:
raise LedgerError("bounty already paid")
Comment thread tests/test_ledger_views.py Outdated
detail = ledger_entry_to_dict(session, proof.ledger_sequence)
missing = ledger_entry_to_dict(session, 9999)

payment_row = next(row for row in rows if row["sequence"] == proof.ledger_sequence)
Copy link
Copy Markdown

@liangtovi-debug liangtovi-debug left a comment

Choose a reason for hiding this comment

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

Thanks for the focused extraction. I found one merge-blocking issue: the PR removes the CursorResult casts around the two SQLAlchemy update() calls in app/ledger/service.py, but the code still reads claimed.rowcount afterward. Under this project's strict mypy settings, session.execute(update(...)) is inferred as Result[Any], which does not expose rowcount, so the hosted quality check is failing.

Repro from PR head 9935752:

uv run --extra dev python -m mypy app/ledger/service.py app/main.py app/ledger_views.py
app/ledger/service.py:670: error: "Result[Any]" has no attribute "rowcount"  [attr-defined]
app/ledger/service.py:741: error: "Result[Any]" has no attribute "rowcount"  [attr-defined]
Found 2 errors in 1 file (checked 3 source files)

The focused behavior checks pass (tests/test_ledger_views.py, the MCP ledger proof hash test, the bounty proof page test, and the OAuth next-path regression: 5 passed), and ruff check/format pass for touched files. Restoring the typed CursorResult[Any] handling, or another typed equivalent, should unblock the PR.

Copy link
Copy Markdown

@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: 2

🤖 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 `@app/ledger/service.py`:
- Around line 736-741: The mypy error is caused by session.execute(...) being
inferred as Result[Any] which lacks rowcount; restore a precise type so claimed
exposes rowcount. Wrap or annotate the call to session.execute in
app/ledger/service.py (the expression assigning claimed from
session.execute(update(Bounty)...)) with a cast/annotation to
sqlalchemy.engine.Result (e.g., cast(Result, session.execute(...)) or using a
typed Session.execute return like Session.execute[...]) and ensure you import
typing.cast and sqlalchemy.engine.Result so mypy recognizes claimed.rowcount.
- Around line 659-670: The mypy failure comes from losing the typed Result that
exposes rowcount when removing the cast around session.execute; restore a proper
type annotation/cast so `claimed` is typed as SQLAlchemy Result (the same typed
Result used previously) before checking `claimed.rowcount` in the update(Bounty)
block, i.e. reintroduce the cast/annotation around the call to
session.execute(...) that assigns to `claimed` so mypy knows `claimed` has a
`rowcount` attribute.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 93633b00-3b0a-4fe2-9ffa-3c24b98aa1ea

📥 Commits

Reviewing files that changed from the base of the PR and between ac99d79 and 9935752.

📒 Files selected for processing (4)
  • app/ledger/service.py
  • app/ledger_views.py
  • app/main.py
  • tests/test_ledger_views.py

Comment thread app/ledger/service.py Outdated
Comment thread app/ledger/service.py Outdated
@TUPM96
Copy link
Copy Markdown
Contributor Author

TUPM96 commented May 26, 2026

Follow-up pushed in 5e8fa9e to fix the hosted mypy failure.

What changed:

  • Restored explicit CursorResult[Any] typing around the pay_bounty() and close_bounty() update results before checking .rowcount.
  • Updated the PR body so the validation/scope text no longer claims the ledger casts were removed.

Validation:

  • uv run --extra dev python -m mypy app -> passed.
  • uv run --extra dev python -m pytest tests/test_ledger_views.py tests/test_api_mcp.py::test_mcp_get_ledger_entry_includes_payment_proof_hash tests/test_bounty_pages.py::test_ledger_and_proof_pages_make_bounty_payments_scannable tests/test_wallet_api.py::test_github_login_stores_safe_default_for_backslash_next -q -> 5 passed.
  • uv run --extra dev python -m pytest -q -> 337 passed.
  • uv run --extra dev ruff check app/main.py app/ledger/service.py app/ledger_views.py tests/test_ledger_views.py -> passed.
  • uv run --extra dev ruff format --check app/main.py app/ledger/service.py app/ledger_views.py tests/test_ledger_views.py -> passed.
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok.
  • git diff --check origin/main...HEAD -> clean.
  • Hosted CI and CodeRabbit are green; mergeState is CLEAN.

@TUPM96
Copy link
Copy Markdown
Contributor Author

TUPM96 commented May 26, 2026

Follow-up pushed in 715503f for the remaining review nits.

What changed:

  • Added decoded-path length validation in _safe_next_path().
  • Made the ledger view proof-row assertion fail with a clear len(...) == 1 assertion instead of StopIteration.
  • Added an overlong OAuth next regression case.

Validation:

  • python -m pytest tests/test_ledger_views.py tests/test_wallet_api.py::test_oauth_next_path_rejects_redirect_ambiguity tests/test_wallet_api.py::test_github_login_stores_safe_default_for_backslash_next tests/test_api_mcp.py::test_mcp_get_ledger_entry_includes_payment_proof_hash tests/test_bounty_pages.py::test_ledger_and_proof_pages_make_bounty_payments_scannable -q -> 9 passed.
  • python -m pytest -q -> 338 passed.
  • python -m mypy app -> passed.
  • python -m ruff check . -> passed.
  • python -m ruff format --check . -> 50 files already formatted.
  • python scripts/docs_smoke.py -> docs smoke ok.
  • Hosted CI -> passed.
  • CodeRabbit -> passed/no actionable comments.
  • GitHub mergeState -> CLEAN.

Copy link
Copy Markdown
Contributor

@tolga-tom-nook tolga-tom-nook left a comment

Choose a reason for hiding this comment

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

No blockers from my review of current head 715503f.

Evidence checked:

  • Inspected the app.ledger_views extraction and the app.main call sites for /api/v1/ledger, /api/v1/ledger/{sequence}, account/wallet pages, and the MCP get_ledger_entry path.
  • Verified proof hashes are still attached through a single helper path, missing ledger entries still return None before the API turns them into 404s, and account transactions still include both sender and recipient entries in descending sequence order.
  • Confirmed the current diff no longer includes the earlier app/ledger/service.py rowcount typing issue mentioned by another review.

Validation run locally on this PR head:

  • ./.venv/bin/python -m pytest tests/test_ledger_views.py tests/test_wallet_api.py tests/test_api_mcp.py -q — 109 passed
  • ./.venv/bin/python -m ruff check app/ledger_views.py app/main.py tests/test_ledger_views.py tests/test_wallet_api.py — passed
  • ./.venv/bin/python -m ruff format --check app/ledger_views.py app/main.py tests/test_ledger_views.py tests/test_wallet_api.py — passed
  • ./.venv/bin/python -m mypy app — passed
  • git diff --check origin/main...HEAD — passed

@ramimbo ramimbo added the mrwk:needs-info More information needed label May 26, 2026
@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Maintainer note after #320 filled: this PR still references closed bounty #320. If you want it considered for the current code-health round, rebase against main, update the bounty reference to #377, and keep the scope distinct from already accepted #328, #337, #364, #375, and #376. This is not accepted or paid as-is.

Copy link
Copy Markdown
Contributor

@newmattock newmattock left a comment

Choose a reason for hiding this comment

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

No blockers from my pass on current head 715503f.

Evidence checked:

  • Inspected app/ledger_views.py, the route call sites in app/main.py, and tests/test_ledger_views.py.
  • Verified recent_ledger_entries() and ledger_entry_to_dict() share the same proof-hash attachment path, so /api/v1/ledger, /api/v1/ledger/{sequence}, /ledger, and MCP get_ledger_entry keep bounty-payment proof hashes consistent.
  • Verified missing ledger entries still return None from the helper before the API converts that to the existing 404 behavior.
  • Verified account_ledger_transactions() still matches both from_account and to_account, orders newest-first, honors the default account/wallet page limit, and leaves non-proof ledger rows with proof_hash: None.
  • Rechecked that the earlier app/ledger/service.py rowcount typing regression is no longer present in the current diff.

Validation:

  • uv run --extra dev python -m pytest tests/test_ledger_views.py tests/test_api_mcp.py::test_mcp_get_ledger_entry_includes_payment_proof_hash tests/test_bounty_pages.py::test_ledger_and_proof_pages_make_bounty_payments_scannable tests/test_wallet_api.py::test_wallet_pages_expose_transfer_and_github_claim_flows -q -> 5 passed.
  • uv run --extra dev python -m mypy app/ledger_views.py app/main.py -> success.
  • uv run --extra dev python -m ruff check app/ledger_views.py app/main.py tests/test_ledger_views.py tests/test_wallet_api.py -> passed.
  • uv run --extra dev python -m ruff format --check app/ledger_views.py app/main.py tests/test_ledger_views.py tests/test_wallet_api.py -> 4 files already formatted.
  • git diff --check origin/main...HEAD -> clean.

@ramimbo ramimbo merged commit 1b5a1e5 into ramimbo:main May 26, 2026
2 checks passed
@ramimbo ramimbo added mrwk:accepted Maintainer accepted for payout mrwk:paid Ledger payment recorded and removed mrwk:needs-info More information needed labels May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:accepted Maintainer accepted for payout mrwk:paid Ledger payment recorded

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants