Skip to content

Extract proof helpers from app.main#361

Closed
TUPM96 wants to merge 12 commits into
ramimbo:mainfrom
TUPM96:codex/bounty-320-proof-helpers
Closed

Extract proof helpers from app.main#361
TUPM96 wants to merge 12 commits into
ramimbo:mainfrom
TUPM96:codex/bounty-320-proof-helpers

Conversation

@TUPM96
Copy link
Copy Markdown
Contributor

@TUPM96 TUPM96 commented May 26, 2026

Refs #320

Summary

  • Extract proof hash validation, proof lookup helpers, public proof payload shaping, and MCP proof response shaping from app/main.py into app/proofs.py.
  • Reuse the helpers from ledger API responses, account transaction views, /api/v1/proofs/{hash}, and MCP get_ledger_entry / get_proof without changing public behavior.
  • Add focused proof helper tests for hash normalization/rejection, sequence proof lookup, API payload shaping, and MCP payload shaping.

Complexity reduced

app/main.py no longer owns proof-specific hash parsing, ledger-sequence proof lookup, or MCP proof serialization. Proof behavior can now be reviewed in one small module with direct tests, while route code stays focused on wiring.

Tests

  • python -m pytest -> 337 passed
  • python -m pytest tests/test_proofs.py tests/test_api_mcp.py::test_mcp_get_ledger_entry_includes_payment_proof_hash tests/test_api_mcp.py::test_mcp_get_proof_returns_public_proof_details tests/test_api_mcp.py::test_explorer_links_ledger_proof_and_account -> 5 passed
  • python -m ruff format --check . -> 50 files already formatted
  • python -m ruff check . -> All checks passed
  • python -m mypy app -> Success: no issues found in 15 source files
  • git diff --check -> clean
  • Hosted Quality, readiness, docs, and image checks -> passed
  • CodeRabbit -> passed

No private keys, seed material, secrets, deployment credentials, private vulnerability details, payout credentials, or MRWK price claims are included.

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

coderabbitai Bot commented May 26, 2026

Warning

Review limit reached

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

More reviews will be available in 4 minutes and 48 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 588719b7-c006-4985-b9fa-480cada820f0

📥 Commits

Reviewing files that changed from the base of the PR and between f86d4ab and 5f6fa53.

📒 Files selected for processing (2)
  • app/proofs.py
  • tests/test_proofs.py
📝 Walkthrough

Walkthrough

This PR centralizes proof-hash validation, lookup, and payload serialization logic from app/main.py into a new app/proofs.py module, then refactors app/main.py to import and use the centralized utilities across API endpoints, page rendering, and MCP tools.

Changes

Proof utilities centralization

Layer / File(s) Summary
Proof utilities module
app/proofs.py
New module provides proof_hash_from_path() for hash validation, proof_hashes_by_sequence() and proof_hash_for_sequence() for database lookups, public_proof_payload() for JSON parsing, and mcp_proof_to_dict() for MCP serialization.
Proof utilities tests
tests/test_proofs.py
Tests validate hash normalization and malformed rejection, then exercise database lookups and payload shaping via a full ledger-and-bounty integration scenario.
app/main.py refactoring
app/main.py
Removes local _proof_hashes_by_sequence, _proof_hash_from_path, and HEX_HASH_RE; imports the new centralized functions; updates /api/v1/ledger, /api/v1/ledger/{sequence}, /api/v1/proofs/{proof_hash}, account and wallet pages, and MCP get_ledger_entry and get_proof tools to use the imported utilities.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

mrwk:accepted

Suggested reviewers

  • weilixiong
  • g8rr5dg2p7-svg
🚥 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 clearly and concisely summarizes the main change: extracting proof helpers from app.main into a dedicated module.
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.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering summary, complexity reduction, and detailed test results.

✏️ 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 extracts “proof” lookup/serialization helpers into a dedicated module and updates API/tooling code to reuse them, adding targeted tests for the new helpers.

Changes:

  • Added app/proofs.py with proof-hash parsing, DB lookup helpers, and payload serialization helpers.
  • Rewired existing endpoints/tool handlers in app/main.py to call the new helpers and removed the inlined implementations.
  • Added tests/test_proofs.py to validate hash normalization/rejection and payload shapes for API vs MCP.

Reviewed changes

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

File Description
tests/test_proofs.py Adds tests covering proof-hash parsing and helper outputs for API/MCP payloads.
app/proofs.py Introduces reusable proof parsing/lookup/serialization helpers.
app/main.py Replaces local proof helpers with imports from app.proofs across endpoints and tool handlers.

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

Comment thread app/proofs.py
Comment on lines +38 to +54
def public_proof_payload(proof: Proof) -> dict[str, Any]:
payload = json.loads(proof.public_json)
if not isinstance(payload, dict):
raise HTTPException(status_code=500, detail="invalid proof payload")
return payload


def mcp_proof_to_dict(proof: Proof) -> dict[str, Any]:
return {
"hash": proof.hash,
"kind": proof.kind,
"ledger_sequence": proof.ledger_sequence,
"bounty_id": proof.bounty_id,
"submission_id": proof.submission_id,
"created_at": proof.created_at.isoformat(),
"proof": public_proof_payload(proof),
}
Comment thread tests/test_proofs.py
Comment on lines +48 to +62

proof_by_sequence = proof_hashes_by_sequence(session, [proof.ledger_sequence, 999])
single_proof_hash = proof_hash_for_sequence(session, proof.ledger_sequence)
missing_proof_hash = proof_hash_for_sequence(session, 999)
api_payload = public_proof_payload(proof)
mcp_payload = mcp_proof_to_dict(proof)
empty_lookup = proof_hashes_by_sequence(session, [])

assert empty_lookup == {}
assert proof_by_sequence == {proof.ledger_sequence: proof.hash}
assert single_proof_hash == proof.hash
assert missing_proof_hash is None
assert api_payload["kind"] == "bounty_payment"
assert api_payload["submission_url"] == "https://github.com/ramimbo/mergework/pull/320"
assert mcp_payload["hash"] == proof.hash
Comment thread tests/test_proofs.py
Comment on lines +48 to +62

proof_by_sequence = proof_hashes_by_sequence(session, [proof.ledger_sequence, 999])
single_proof_hash = proof_hash_for_sequence(session, proof.ledger_sequence)
missing_proof_hash = proof_hash_for_sequence(session, 999)
api_payload = public_proof_payload(proof)
mcp_payload = mcp_proof_to_dict(proof)
empty_lookup = proof_hashes_by_sequence(session, [])

assert empty_lookup == {}
assert proof_by_sequence == {proof.ledger_sequence: proof.hash}
assert single_proof_hash == proof.hash
assert missing_proof_hash is None
assert api_payload["kind"] == "bounty_payment"
assert api_payload["submission_url"] == "https://github.com/ramimbo/mergework/pull/320"
assert mcp_payload["hash"] == proof.hash
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: 1

Caution

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

⚠️ Outside diff range comments (1)
tests/test_proofs.py (1)

27-64: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add negative-path tests for malformed/non-object proof payloads.

The new helper behavior includes invalid-payload handling, but this test only covers happy-path payload shaping. Please add assertions for malformed JSON and non-dict JSON to lock in the error contract.

Suggested test addition
+from types import SimpleNamespace
@@
 def test_proof_helpers_shape_api_and_mcp_payloads(sqlite_url: str) -> None:
@@
     assert mcp_payload["hash"] == proof.hash
     assert mcp_payload["proof"] == api_payload
+
+
+def test_public_proof_payload_rejects_malformed_or_non_object_payloads() -> None:
+    for raw_payload in ("{", "[]"):
+        proof = SimpleNamespace(public_json=raw_payload)
+        with pytest.raises(HTTPException) as exc_info:
+            public_proof_payload(proof)  # type: ignore[arg-type]
+        assert exc_info.value.status_code == 500
+        assert exc_info.value.detail == "invalid proof payload"

As per coding guidelines, tests/**/*.py: Add or update tests for changed behavior.

🤖 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 `@tests/test_proofs.py` around lines 27 - 64, The test only covers happy-path
shaping for public_proof_payload and mcp_proof_to_dict; add negative-path
assertions to lock the contract when proof.payload is malformed or not a JSON
object. In test_proof_helpers_shape_api_and_mcp_payloads, create additional
proof variants (or modify the existing proof before calling public_proof_payload
and mcp_proof_to_dict) where proof.payload is invalid JSON (e.g., a broken
string) and where proof.payload parses to a non-dict (e.g., JSON array or
string); assert that public_proof_payload and mcp_proof_to_dict either return a
safe default (empty dict) or raise the documented exception per the helper
behavior, and also add corresponding checks for proof_hashes_by_sequence and
proof_hash_for_sequence when payloads are bad to ensure they still map/return as
expected. Ensure you reference the functions public_proof_payload,
mcp_proof_to_dict, proof_hashes_by_sequence, and proof_hash_for_sequence in the
new assertions.
🤖 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/proofs.py`:
- Around line 39-41: Wrap the json.loads(proof.public_json) call in a try/except
that catches json.JSONDecodeError and converts it into the same
HTTPException(detail="invalid proof payload") so malformed JSON doesn't
propagate as a raw JSONDecodeError; update the code around the
public_proof_payload handling in app/proofs.py (the payload =
json.loads(proof.public_json) location) to catch json.JSONDecodeError and raise
HTTPException(status_code=500, detail="invalid proof payload").

---

Outside diff comments:
In `@tests/test_proofs.py`:
- Around line 27-64: The test only covers happy-path shaping for
public_proof_payload and mcp_proof_to_dict; add negative-path assertions to lock
the contract when proof.payload is malformed or not a JSON object. In
test_proof_helpers_shape_api_and_mcp_payloads, create additional proof variants
(or modify the existing proof before calling public_proof_payload and
mcp_proof_to_dict) where proof.payload is invalid JSON (e.g., a broken string)
and where proof.payload parses to a non-dict (e.g., JSON array or string);
assert that public_proof_payload and mcp_proof_to_dict either return a safe
default (empty dict) or raise the documented exception per the helper behavior,
and also add corresponding checks for proof_hashes_by_sequence and
proof_hash_for_sequence when payloads are bad to ensure they still map/return as
expected. Ensure you reference the functions public_proof_payload,
mcp_proof_to_dict, proof_hashes_by_sequence, and proof_hash_for_sequence in the
new assertions.
🪄 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: 273f1cc0-491b-4473-bfb2-113d0582d221

📥 Commits

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

📒 Files selected for processing (3)
  • app/main.py
  • app/proofs.py
  • tests/test_proofs.py

Comment thread app/proofs.py Outdated
Copy link
Copy Markdown
Contributor

@Karry2019web Karry2019web left a comment

Choose a reason for hiding this comment

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

Review ✅ APPROVED

FILES INSPECTED:

  • app/main.py — removed _proof_hashes_by_sequence, _proof_hash_from_path, HEX_HASH_RE; replaced inline proof-payload parse with public_proof_payload() call; replaced raw proof lookup with proof_hash_for_sequence()
  • app/proofs.py — new module (54 lines) containing proof_hash_from_path, proof_hashes_by_sequence, proof_hash_for_sequence, public_proof_payload, mcp_proof_to_dict
  • tests/test_proofs.py — new test file (85 lines) with test_proof_hash_from_path_normalizes_and_rejects_malformed_hashes and test_proof_helpers_shape_api_and_mcp_payloads

BEHAVIOR CHECKED:

  • proof_hash_from_path: preserves the exact same validation logic (strip check → lowercase → HEX_HASH_RE match → 400 on failure) that was in the original _proof_hash_from_path in main.py. Regex constant was renamed from HEX_HASH_RE to PROOF_HASH_RE without behavior change.
  • proof_hashes_by_sequence: identical logic to the original _proof_hashes_by_sequence — early return on empty, same SQLAlchemy query, same dict comprehension.
  • proof_hash_for_sequence: new helper — a clean session.scalar(select(Proof.hash).where(Proof.ledger_sequence == sequence).limit(1)) replacing the inline session.get(Proof, sequence) + .hash accessor pattern. Semantically equivalent.
  • public_proof_payload: extracts the json.loads + type guard that was inlined in api_proof() into a focused function. Safe.
  • mcp_proof_to_dict: new serialization helper for MCP use. No callers changed in this PR — ensures safe reuse.
  • All call sites in main.py (api_ledger, api_ledger_entry, api_proof, account_page, wallet_page) now reference the module functions instead of private helpers.

TESTS VERIFIED:

  • test_proof_hash_from_path_normalizes_and_rejects_malformed_hashes: Covers uppercase → lowercase normalization, whitespace-prefix rejection, non-hex rejection, hex-g (invalid) rejection. All with proper HTTPException(400) assertions.
  • test_proof_helpers_shape_api_and_mcp_payloads: Integration-style test that creates a real bounty + payout, then exercises all five exported functions. Verifies: empty sequence lookup returns {}, hashes-by-sequence returns correct mapping, single-proof lookup returns correct hash, missing sequence returns None, API payload has correct kind/submission_url, MCP payload embeds the full API payload. Supply conservation is implicit (no access to balance here — the test scope is strictly the proof helpers).
  • Both tests are deterministic and self-contained (SQLite-based via sqlite_url fixture).
  • No time.sleep or flaky patterns.

CODE QUALITY:

  • Clean extraction: the removed code from main.py is exactly the logic moved to proofs.py with no behavioral changes.
  • The from __future__ import annotations at the top of proofs.py is consistent with the repo's convention (check: does main.py use it? yes, it's in scope via annotations import).
  • No new dependencies beyond app.models.Proof and stdlib re/json.
  • The proof_hash_for_sequence returns str | None, which matches the new call site in api_ledger_entry: ledger_to_dict(entry, proof_hash_for_sequence(...)). The original inline code returned None when no proof existed, so this is type-equivalent.
  • No whitespace churn in main.py — only the targeted removals, import addition, and 6 call-site changes.

SUMMARY: Clean, well-scoped extraction with thorough tests. The mcp_proof_to_dict function is purely additive (no existing caller changed) and follows the established proof-serialization pattern. APPROVED — no blockers.

@TUPM96
Copy link
Copy Markdown
Contributor Author

TUPM96 commented May 26, 2026

Follow-up pushed in 3b551a3 for the proof payload review item.

What changed:

  • public_proof_payload() now converts malformed JSON into the same HTTPException(status_code=500, detail="invalid proof payload") contract already used for non-object JSON.
  • Added negative-path coverage for malformed JSON and non-object JSON through both public_proof_payload() and mcp_proof_to_dict().
  • Kept the PR focused on proof helpers; no route behavior changes.

Validation:

  • uv run --extra dev python -m pytest tests/test_proofs.py tests/test_api_mcp.py -q -> 79 passed.
  • uv run --extra dev ruff check app/proofs.py app/main.py tests/test_proofs.py -> passed.
  • uv run --extra dev ruff format --check app/proofs.py app/main.py tests/test_proofs.py -> passed.
  • uv run --extra dev python -m mypy app/proofs.py app/main.py -> passed.

Copy link
Copy Markdown
Contributor

@ayskobtw-lil ayskobtw-lil left a comment

Choose a reason for hiding this comment

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

Follow-up review after the proof-payload fixes on current head 5f6fa53.

Evidence checked:

  • Rechecked the branch after CodeRabbit requested malformed/non-object proof payload coverage and the author pushed the follow-up commits.
  • Inspected app/proofs.py, app/main.py, and tests/test_proofs.py; verified public_proof_payload() now converts malformed JSON and non-object JSON into the stable HTTPException(status_code=500, detail="invalid proof payload") path.
  • Verified mcp_proof_to_dict() is covered for the same invalid payload cases and that proof hash lookup helpers still return hashes independently of payload validity.
  • Confirmed API/MCP proof call sites continue to use the extracted helpers rather than duplicating parsing behavior.

Validation:

  • python -m pytest tests/test_proofs.py tests/test_api_mcp.py::test_mcp_get_ledger_entry_includes_payment_proof_hash tests/test_api_mcp.py::test_mcp_get_proof_returns_public_proof_details -q -> 5 passed.
  • python -m pytest tests/test_proofs.py tests/test_api_mcp.py -q -> 79 passed.
  • python -m ruff check app/proofs.py app/main.py tests/test_proofs.py tests/test_api_mcp.py -> passed.
  • python -m ruff format --check app/proofs.py app/main.py tests/test_proofs.py -> 3 files already formatted.
  • python -m mypy app/proofs.py app/main.py -> success.
  • git diff --check origin/main...HEAD -> clean.
  • Hosted Quality/readiness/docs/image check is green and GitHub reports the PR mergeable.

The earlier invalid proof payload review item is resolved; I do not see remaining blockers from this follow-up pass.

@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.

@ramimbo
Copy link
Copy Markdown
Owner

ramimbo commented May 26, 2026

Closing this stale bounty submission. It still references a closed/filled bounty round and is dirty or unstable against current main after the route/test extraction batches. A future attempt needs a fresh PR against current main, a currently open bounty reference, and scope distinct from work already accepted.

@ramimbo ramimbo added the mrwk:rejected Submission rejected label May 26, 2026
@ramimbo ramimbo closed this May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mrwk:needs-info More information needed mrwk:rejected Submission rejected

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants