Reject non-object proof metadata#256
Conversation
weilixiong
left a comment
There was a problem hiding this comment.
Review ✅ APPROVED
LOW — rejects non-object proof metadata via Mapping check. Clean validation. MW #219.
Karry2019web
left a comment
There was a problem hiding this comment.
Review ✅ NO BLOCKERS
Files inspected: app/ledger/service.py, tests/test_ledger.py
What was verified
-
Change rationale —
_clean_proof_metadata()was typed asdict[str, Any]butdict(verifier_result)on a non-object (e.g.[]) would silently produce an empty dict{}, creating a payout with no verifier metadata instead of failing closed. The fix adds an explicitisinstance(verifier_result, Mapping)guard beforedict(). -
Type safety — Uses
collections.abc.Mapping(abstract) instead ofdict(concrete), so it correctly acceptsdict,OrderedDict, and other Mapping subclasses while rejectinglist,str,int,None, and other non-Mapping types. -
Edge cases checked:
[](list):isinstance([], Mapping)→False→LedgerError✅""(empty string):isinstance("", Mapping)→False→LedgerError✅None:isinstance(None, Mapping)→False→LedgerError✅{}(empty dict):isinstance({}, Mapping)→True→ passes through todict()→{}✅{"key": "val"}: passes through, control-char validation still applies ✅
-
Regression coverage — The test verifies supply conservation: balance remains in reserve (
50_000_000), no Submission or Proof rows are created, andLedgerErroris raised. This follows the existingconserve_supplypattern. -
No side effects — No changes to
pay_bounty()signature, no new exports, no config changes. TheMappingimport in service.py is the only new dependency.
Minor suggestion (optional)
The test labels its fake bounty issue_number=228 and submission_url points to PR #228 — this is technically the same issue the PR references. Not a bug since the test doesn't validate the submission URL against the bounty's issue number, but a distinct number (e.g. 999) would make the test intent clearer.
Bounty #219
Summary
verifier_resultvalues before proof metadata is copied or serialized.Why
pay_bounty()expects verifier metadata to be a JSON object, but_clean_proof_metadata()useddict(verifier_result)directly. That means an empty list could be accepted as{}and still record a bounty payout, while other non-object inputs could fail inconsistently. Accepted payout proofs should fail closed when proof metadata is not an object.Refs #228
Validation
python -m pytest tests/test_ledger.py::test_pay_bounty_rejects_non_object_verifier_result -q-> 1 passedpython -m pytest tests/test_ledger.py -q-> 17 passedpython -m pytest -q-> 206 passed, 2 warningspython -m ruff check app/ledger/service.py tests/test_ledger.py-> passedpython -m ruff check .-> passed, with non-fatal ruff cache write warnings from the local temp checkoutpython -m ruff format --check app/ledger/service.py tests/test_ledger.py-> passedpython -m ruff format --check .-> 37 files already formattedpython -m mypy app-> successpython scripts/docs_smoke.py-> docs smoke okpython scripts/check_agents.py-> AGENTS.md okgit diff --check-> cleanNo secrets, wallet material, private vulnerability details, deployment values, payout details, or MRWK price claims are included.