Skip to content

feat(bounty): compensation pattern for create/claim/settle [BLOCKED ON #240]#241

Closed
windoliver wants to merge 1 commit intomainfrom
bounty-saga-compensation
Closed

feat(bounty): compensation pattern for create/claim/settle [BLOCKED ON #240]#241
windoliver wants to merge 1 commit intomainfrom
bounty-saga-compensation

Conversation

@windoliver
Copy link
Copy Markdown
Owner

⚠️ DO NOT MERGE. This is a draft PR tracking work blocked on design issue #240.

Summary

Adds compensation try/catch logic around `createBountyOperation`, `claimBountyOperation`, and `settleBountyOperation` so failed store writes can be rolled back cleanly. Also extracts a shared error message constant and adds a pre-flight `open` status check before claiming.

Why it's blocked

Three independent codex adversarial-review rounds all flagged the same correctness bugs in this compensation logic. Details + design options are in #240.

  • [CRITICAL] Settlement commits terminal `settled` state before `creditsService.capture()` runs. A failed capture leaves the bounty "paid" with no funds transferred and no reconciler to fix it.
  • [HIGH] `createBounty` catch voids the reservation on any error, including post-commit failures from `NexusBountyStore`'s two-step write, leaking an open bounty with empty escrow.
  • [HIGH] `claimBounty` catch releases the claim on any error, leaving the bounty stuck in `claimed` pointing at a released lease.

The compensation code is correct for pre-persistence failures but wrong for post-persistence failures, and the NexusBountyStore layer doesn't tell the caller which mode a given throw came from.

Design options (from #240)

  • (a) Full saga: `pending_settlement` state + background reconciler + CAS confirmation on post-commit failures. ~200-500 lines, 1-2 days.
  • (b) CAS confirmation only: re-read before compensating. Doesn't fix the settle-before-pay race. ~100-200 lines.
  • (c) Revert the compensation refactor, keep only the cosmetic changes (`MISSING_BOUNTY_STORE` const + pre-flight claim status check). Relies on reservation/lease expiry for recovery. ~50-100 lines.

Recommendation: start with (c) to stop the bleed, then do (a) as a proper RFC-backed saga-log project.

Why this PR exists

This code was sitting uncommitted in the `ancient-nibbling-anchor` worktree alongside the TUI/MCP session-scoping work in #239. Rather than leave it in limbo where it could be accidentally restaged, this PR makes it visible, reviewable, and explicitly linked to the design issue it depends on.

Current test state

`bun test src/core/operations/bounty.test.ts` → 55 pass, 0 fail. The tests cover the happy path and basic error injection; the codex findings are latent partial-failure scenarios that the current test harness doesn't reproduce (they need a way to mock `NexusBountyStore.transitionBounty` to throw after the bounty document CAS-write but before the status-index write, which isn't currently possible with the in-memory store used here).

Checklist before merging

…ED ON #240)

**DO NOT MERGE** — this is a draft PR tracking in-progress work on bounty
operation compensation. Three HIGH/CRITICAL correctness bugs have been
flagged by adversarial review across three independent rounds; see #240
for the findings, design options, and recommended next steps.

## What this adds

- `createBountyOperation`: try/catch that voids the credit reservation
  when `bountyStore.createBounty()` throws, so escrow is returned on
  creation failure.
- `claimBountyOperation`: pre-flight check that only `open` bounties
  can be claimed, plus try/catch that releases the claim record when
  `bountyStore.claimBounty()` throws so other agents aren't blocked
  until the lease expires.
- `settleBountyOperation`: reordered to persist `completed → settled`
  state transitions BEFORE calling `creditsService.capture()`, with a
  comment explaining the intent (make the failure recoverable by retry).
- Test coverage for each compensation path using mock stores.
- Extract `MISSING_BOUNTY_STORE` constant for the repeated error
  message.

## Known bugs (see #240 for details)

1. **[CRITICAL]** Settlement now commits terminal `settled` state
   before `capture()` runs. A capture failure leaves the bounty paid
   according to the state machine but with no funds actually
   transferred, and there is no reconciler in the codebase to repair
   it. src/core/operations/bounty.ts:319-333

2. **[HIGH]** The `createBounty` catch voids the reservation on any
   error, including post-commit failures from the NexusBountyStore
   two-step write (bounty doc first, then status index). A late
   failure leaks an "open" bounty with no escrow backing it.
   src/core/operations/bounty.ts:156-176

3. **[HIGH]** Same shape on `claimBounty`: the compensation releases
   the claim even when the bounty state transition actually persisted,
   leaving the bounty stuck in `claimed` pointing at a released claim.
   src/core/operations/bounty.ts:258-269

## Design options for the fix

See #240 — three paths, roughly:
- **(a)** Full saga: `pending_settlement` state + reconciler + CAS
  confirmation on post-commit failures. ~200-500 lines, 1-2 days.
- **(b)** CAS confirmation only: re-read before compensating so we
  don't rollback committed state. Doesn't fix the settle-before-pay
  race. ~100-200 lines.
- **(c)** Revert the compensation refactor, keep only the cosmetic
  changes (constant extract + pre-flight claim check). Relies on
  reservation/lease expiry for recovery. ~50-100 lines.

The issue recommends starting with (c) to stop the bleed, then doing
(a) as a proper saga-log project with its own RFC.

## Why open this as a draft PR now

This code existed in an uncommitted worktree alongside the
`fix(tui,mcp,acpx)` work in PR #239. Rather than leave it in a
limbo state where it can be accidentally restaged on the next push,
this branch makes it visible, reviewable, and explicitly linked to
the design issue it depends on.

Do not merge until #240 has a resolved design and the code matches.
@windoliver
Copy link
Copy Markdown
Owner Author

Closing — consolidating all bounty saga work into issue #240, which is now the single tracking issue.

The compensation refactor here had three HIGH/CRITICAL correctness bugs that codex flagged across three adversarial-review rounds (see #240 for full analysis), and the fix direction is still under design. Keeping the code around as a draft PR added ambiguity — it looked actionable when it wasn't.

If someone picks up option (c) from #240 (the revert + cosmetic keeper), they should cut a fresh branch from `main` and reference #240 directly rather than resurrecting this one.

@windoliver windoliver closed this Apr 11, 2026
@windoliver windoliver deleted the bounty-saga-compensation branch April 11, 2026 05:19
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.

1 participant