Skip to content

Add advisory bounty attempt reservations#326

Merged
ramimbo merged 3 commits into
ramimbo:mainfrom
liangtovi-debug:codex/bounty-321-attempt-reservations
May 25, 2026
Merged

Add advisory bounty attempt reservations#326
ramimbo merged 3 commits into
ramimbo:mainfrom
liangtovi-debug:codex/bounty-321-attempt-reservations

Conversation

@liangtovi-debug
Copy link
Copy Markdown

@liangtovi-debug liangtovi-debug commented May 25, 2026

Bounty #321
/claim #321

Summary

  • Add a bounty_attempts model and Alembic migration for advisory attempt reservations.
  • Add REST endpoints to register, list, and release bounty attempts without touching ledger/payment state.
  • Derive expired attempts deterministically, reject duplicate active attempts for the same submitter, warn on multiple active attempts, and reject closed/paid/exhausted bounties.
  • Document the agent workflow and API examples.

Validation

  • uv run --extra dev python -m pytest tests/test_bounty_attempts.py -q -> 3 passed
  • uv run --extra dev python -m pytest tests/test_api_mcp.py tests/test_bounty_pages.py tests/test_docs_public_urls.py -q -> 91 passed before formatting, 94 passed after adding attempt tests to the slice
  • uv run --extra dev python -m pytest -q -> 291 passed
  • uv run --extra dev ruff check app/main.py app/models.py tests/test_bounty_attempts.py migrations/versions/0004_bounty_attempts.py -> passed
  • uv run --extra dev ruff format --check app/main.py app/models.py tests/test_bounty_attempts.py migrations/versions/0004_bounty_attempts.py -> passed
  • uv run --extra dev mypy app -> passed
  • uv run --extra dev python scripts/docs_smoke.py -> docs smoke ok
  • git diff --check -> clean

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

Summary by CodeRabbit

  • New Features

    • Advisory bounty-attempt reservations: register, list (optional expired), and release attempts; TTL-based expiry, one active attempt per submitter, per-bounty warnings, and conflict handling.
  • Migrations

    • Database migration adding persistent bounty-attempt records, indexes, and uniqueness enforcement for active attempts.
  • Documentation

    • Expanded API examples and agent guide with registration/list/release examples and checklist updates.
  • Tests

    • End-to-end tests covering create/list/duplicate/expire/release flows and expected warning/409 behaviors.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 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: 675f0f12-75da-4138-af21-dafbc5aa35bd

📥 Commits

Reviewing files that changed from the base of the PR and between fa6d143 and c2b2dc2.

📒 Files selected for processing (1)
  • docs/agent-guide.md

📝 Walkthrough

Walkthrough

Adds advisory bounty attempt reservations: new BountyAttempt ORM and migration, UTC TTL helpers and serializers, endpoints to list/register/release attempts with expiration and conflict handling, integration tests covering behavior, and documentation updates illustrating API usage and the PR checklist step.

Changes

Bounty Attempt Reservation Feature

Layer / File(s) Summary
Data model & migration
app/models.py, migrations/versions/0004_bounty_attempts.py
Adds BountyAttempt model and Bounty.attempts relationship; Alembic migration creates bounty_attempts table, indexes, and partial unique index enforcing one active attempt per (bounty_id, submitter_account).
UTC helpers, TTL constants, serialization
app/main.py
Timezone-aware datetime imports and TTL bounds (DEFAULT_ATTEMPT_TTL_SECONDS, MIN_ATTEMPT_TTL_SECONDS, MAX_ATTEMPT_TTL_SECONDS); helpers to normalize to UTC, compute effective status (active/expired), serialize attempts including effective status, SQLAlchemy predicate for active attempts, warnings computation, and bulk-expire helper.
Submitter validation helper
app/main.py
Internal helper to derive/validate submitter_account from authenticated GitHub login and reject mismatched requests.
GET /api/v1/bounties/{bounty_id}/attempts
app/main.py
Lists attempts ordered by recency, optional include_expired flag, returns per-bounty warnings and serialized attempts.
POST /api/v1/bounties/{bounty_id}/attempts (register)
app/main.py
Registers new attempt with TTL bounds and optional source_url normalization, expires stale attempts for submitter, checks bounty availability and award slots, prevents duplicate active attempts per submitter, handles IntegrityError races, returns 201 or 409 with conflict code and warnings/attempt payloads.
POST /api/v1/bounty-attempts/{attempt_id}/release
app/main.py
Releases attempt only for owning submitter and only when effectively active; otherwise responds with already_<status>. On success updates status to released and returns updated attempt.
Integration tests
tests/test_bounty_attempts.py
Adds end-to-end tests for registration, duplicate active-attempt rejection, warnings/slot behavior, listing and include_expired, release ownership and state transitions, expired-attempt seeding and re-registration, and rejection for closed/paid bounties with warnings.
Documentation
docs/agent-guide.md, docs/api-examples.md
Documents new endpoints, updates curl example and “Before opening a bounty PR” guidance to include registering/releasing advisory attempts, and adds API examples for listing/registering/releasing attempts and conflict/warning responses.

Sequence Diagram

sequenceDiagram
  participant Agent
  participant BountyAPI as POST /attempts
  participant DB
  participant ReleaseAPI as POST /release
  
  Agent->>BountyAPI: Register attempt (ttl_seconds, source_url)
  BountyAPI->>DB: Check bounty status & award slots
  BountyAPI->>DB: Query for existing active attempt
  alt Duplicate active attempt exists
    BountyAPI->>Agent: 409 duplicate_active_attempt
  else Bounty not open or no slots
    BountyAPI->>Agent: 409 not_available
  else Success
    BountyAPI->>DB: Insert BountyAttempt (expires_at = now + ttl)
    BountyAPI->>Agent: 201 with attempt data
    Agent->>ReleaseAPI: Release attempt (attempt_id, submitter)
    ReleaseAPI->>DB: Check effective status (active, expired, released)
    alt Attempt not currently active
      ReleaseAPI->>Agent: response with already_<status>
    else Active
      ReleaseAPI->>DB: Update status to released
      ReleaseAPI->>Agent: 200 released
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

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: adding advisory bounty attempt reservation functionality.
Description check ✅ Passed The description covers the main objectives, includes validation results, and references the related bounty, but does not include explicit test evidence checkmarks as shown in the template.
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

@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/main.py`:
- Around line 1059-1087: The current check-then-insert using select(... where
*_active_attempt_conditions(...)) followed by creating a BountyAttempt is
vulnerable to races; enforce the invariant at DB/transaction level by adding a
uniqueness constraint/index that prevents more than one active attempt per
(bounty_id, submitter_account) (or a partial unique index for status='active'),
then change the insertion path around the BountyAttempt creation to catch the DB
integrity error (IntegrityError/UniqueViolation) produced by
session.commit()/flush(); on that error, query for the existing active attempt
(using select with _active_attempt_conditions and
BountyAttempt.submitter_account == submitter_account) and return the same
JSONResponse payload using bounty_attempt_to_dict(existing, now) and
bounty_attempt_warnings(session, bounty, now) to map the conflict to the
duplicate_active_attempt response.
- Around line 1033-1034: The code reads submitter_account from the request body
using _required_str/_normalized_account, which allows a caller to act on behalf
of another account; instead, derive submitter_account from the authenticated
principal (e.g., the request/session auth context or get_authenticated_account()
used elsewhere), ignore or reject the body-supplied submitter_account, and use
that server-side principal when creating or releasing attempts (update both the
create flow and the release flow that currently call
_normalized_account/_required_str). Also add a server-side ownership check: if
the body includes submitter_account still, validate it matches the authenticated
principal and reject the request on mismatch so only the authenticated identity
is used to reserve or release attempts.
🪄 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: 45a9a57b-8381-4023-b8bf-70d1c5fcea02

📥 Commits

Reviewing files that changed from the base of the PR and between 55608e3 and bff2104.

📒 Files selected for processing (6)
  • app/main.py
  • app/models.py
  • docs/agent-guide.md
  • docs/api-examples.md
  • migrations/versions/0004_bounty_attempts.py
  • tests/test_bounty_attempts.py

Comment thread app/main.py Outdated
Comment thread app/main.py Outdated
Comment on lines +1059 to +1087
existing = session.scalar(
select(BountyAttempt)
.where(
*_active_attempt_conditions(bounty_id, now),
BountyAttempt.submitter_account == submitter_account,
)
.order_by(BountyAttempt.created_at.desc(), BountyAttempt.id.desc())
.limit(1)
)
if existing is not None:
return JSONResponse(
status_code=409,
content={
"status": "duplicate_active_attempt",
"attempt": bounty_attempt_to_dict(existing, now),
"warnings": bounty_attempt_warnings(session, bounty, now),
},
)
attempt = BountyAttempt(
bounty_id=bounty_id,
submitter_account=submitter_account,
source_url=source_url,
status="active",
expires_at=now + timedelta(seconds=ttl_seconds),
created_at=now,
updated_at=now,
)
session.add(attempt)
session.flush()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make duplicate-attempt rejection atomic.

This is a check-then-insert sequence; concurrent requests can both pass the lookup and create duplicate active attempts for the same submitter and bounty.

Enforce this invariant atomically at the database/transaction level and map the resulting conflict to the existing duplicate_active_attempt response.

🤖 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 `@app/main.py` around lines 1059 - 1087, The current check-then-insert using
select(... where *_active_attempt_conditions(...)) followed by creating a
BountyAttempt is vulnerable to races; enforce the invariant at DB/transaction
level by adding a uniqueness constraint/index that prevents more than one active
attempt per (bounty_id, submitter_account) (or a partial unique index for
status='active'), then change the insertion path around the BountyAttempt
creation to catch the DB integrity error (IntegrityError/UniqueViolation)
produced by session.commit()/flush(); on that error, query for the existing
active attempt (using select with _active_attempt_conditions and
BountyAttempt.submitter_account == submitter_account) and return the same
JSONResponse payload using bounty_attempt_to_dict(existing, now) and
bounty_attempt_warnings(session, bounty, now) to map the conflict to the
duplicate_active_attempt response.

@bitdamii
Copy link
Copy Markdown
Contributor

Reviewed PR #326 for bounty #310.

Evidence checked:

  • Inspected app/main.py, app/models.py, migrations/versions/0004_bounty_attempts.py, tests/test_bounty_attempts.py, and the new docs examples.
  • Confirmed the feature is advisory only in the covered path: tests assert no LedgerEntry is created, closed/paid bounties return 409 not_available, TTL is bounded, duplicate same-submitter attempts return 409, release requires matching submitter_account, and source_url is validated as a public URL.
  • Ran validation locally on the PR head:
    • uv run --extra dev python -m pytest tests/test_bounty_attempts.py -q -> 3 passed
    • uv run --extra dev ruff check app/main.py app/models.py tests/test_bounty_attempts.py migrations/versions/0004_bounty_attempts.py -> passed
    • uv run --extra dev ruff format --check app/main.py app/models.py tests/test_bounty_attempts.py migrations/versions/0004_bounty_attempts.py -> passed
    • git diff --check origin/main...HEAD -> clean

One merge-risk I would address before relying on this in production: the same submitter/same bounty active-attempt invariant is enforced only by an application-level select-before-insert in api_create_bounty_attempt(). The model and migration add normal indexes, but no database uniqueness constraint or IntegrityError handling catches concurrent POSTs. Two simultaneous create requests for the same bounty_id + submitter_account can both observe no active attempt and then insert active rows, which defeats the duplicate_active_attempt guarantee under race. A focused fix would add a DB-backed uniqueness strategy for active attempts or serialize/catch the insert conflict and map it back to the existing 409 duplicate_active_attempt response.

No secrets, wallet private keys, payout credentials, private vulnerability details, or price claims were inspected or included.

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)
docs/agent-guide.md (1)

192-193: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Clarify checklist item phrasing.

The phrase "before opening a PR when the bounty is active" creates ambiguity—it's unclear whether "when" modifies "register" or "opening." Consider restructuring for clarity.

♻️ Suggested revision
-2. Register an advisory attempt with `/api/v1/bounties/{id}/attempts` before
-   opening a PR when the bounty is active and has open award slots.
+2. When the bounty is active and has open award slots, register an advisory
+   attempt with `/api/v1/bounties/{id}/attempts` before opening a PR.
🤖 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/agent-guide.md` around lines 192 - 193, The checklist item is ambiguous
about whether "when the bounty is active" modifies "register" or "opening a PR";
reword the sentence containing the endpoint `/api/v1/bounties/{id}/attempts` to
make sequence and conditions explicit (e.g., "When the bounty is active and has
open award slots, register an advisory attempt at
`/api/v1/bounties/{id}/attempts` before opening a PR.") so readers clearly
understand they must register first under those conditions.
🤖 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/agent-guide.md`:
- Around line 64-65: The sentence break between "useful work.
`submitter_account` must match the authenticated GitHub login." and "Release
your attempt when you stop working:" is awkward; update the text so it reads
smoothly (e.g., combine into one sentence or add a transition), preserving the
emphasis on `submitter_account` and the instruction to release the attempt —
locate the fragment mentioning `submitter_account` in the docs/agent-guide.md
and rewrite the two sentences into a single clearer sentence or add a connector
so the flow reads naturally.

---

Outside diff comments:
In `@docs/agent-guide.md`:
- Around line 192-193: The checklist item is ambiguous about whether "when the
bounty is active" modifies "register" or "opening a PR"; reword the sentence
containing the endpoint `/api/v1/bounties/{id}/attempts` to make sequence and
conditions explicit (e.g., "When the bounty is active and has open award slots,
register an advisory attempt at `/api/v1/bounties/{id}/attempts` before opening
a PR.") so readers clearly understand they must register first under those
conditions.
🪄 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: 6a75fc3d-60ed-469f-b4ab-cb2583123f0c

📥 Commits

Reviewing files that changed from the base of the PR and between bff2104 and fa6d143.

📒 Files selected for processing (6)
  • app/main.py
  • app/models.py
  • docs/agent-guide.md
  • docs/api-examples.md
  • migrations/versions/0004_bounty_attempts.py
  • tests/test_bounty_attempts.py

Comment thread docs/agent-guide.md Outdated
Copy link
Copy Markdown
Contributor

@shootingallday shootingallday 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 on current head fa6d143cfa4488e2c9447f74f0b39883fd47d22f.

Evidence checked:

  • Inspected the attempt reservation flow in app/main.py, the BountyAttempt model/indexes in app/models.py, the Alembic migration, and tests/test_bounty_attempts.py.
  • Verified the earlier identity concern is addressed: submitter_account is derived from the authenticated GitHub login via require_github_login, and any body-supplied submitter_account is rejected on mismatch in both create and release flows.
  • Verified the earlier duplicate-active-attempt race concern is addressed with a partial unique index on (bounty_id, submitter_account) for active attempts plus IntegrityError handling that maps the race back to the existing 409 duplicate_active_attempt response shape.
  • Verified the feature remains advisory: it does not create ledger entries or payouts, and unavailable/closed/exhausted bounty states return warning/conflict responses rather than mutating award state.

Validation run locally:

  • uv run --extra dev python -m pytest tests/test_bounty_attempts.py -q -> 3 passed.
  • uv run --extra dev python -m pytest tests/test_api_mcp.py tests/test_bounty_pages.py tests/test_security.py -q -> 127 passed.
  • uv run --extra dev ruff check app/main.py app/models.py tests/test_bounty_attempts.py migrations/versions/0004_bounty_attempts.py -> passed.
  • uv run --extra dev ruff format --check app/main.py app/models.py tests/test_bounty_attempts.py migrations/versions/0004_bounty_attempts.py -> passed.
  • uv run --extra dev python -m mypy app/main.py app/models.py -> success.
  • git diff --check origin/main...HEAD -> clean.

Residual risk is that this is still an advisory reservation signal and cannot prevent different accounts from choosing overlapping work, but that matches the bounty scope.

@ramimbo ramimbo merged commit 19ec23f into ramimbo:main May 25, 2026
2 checks passed
@ramimbo ramimbo added the mrwk:accepted Maintainer accepted for payout label May 25, 2026
@ramimbo ramimbo added the mrwk:paid Ledger payment recorded label May 25, 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.

4 participants