Skip to content

feat: implement commitReservation/voidReservation mutations and reservation+concurrency test suites (ENG-33, ENG-36)#105

Merged
Connorbelez merged 2 commits intomainfrom
Connorbelez/eng33-lifecycle-tests
Mar 17, 2026
Merged

feat: implement commitReservation/voidReservation mutations and reservation+concurrency test suites (ENG-33, ENG-36)#105
Connorbelez merged 2 commits intomainfrom
Connorbelez/eng33-lifecycle-tests

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 17, 2026

  • Add commitReservation internalMutation: finalizes pending reservation by
    posting SHARES_COMMITTED entry, clearing pending fields, and updating
    reservation status to "committed"
  • Add voidReservation internalMutation: cancels pending reservation by
    posting SHARES_VOIDED entry (audit-only), releasing pending holds, and
    updating reservation status to "voided"
  • Fix balanceCheck ordering: clear pending fields before postEntry so
    available balance reflects the units being committed
  • Add 7 reservation lifecycle tests: commit/void happy paths,
    double-commit/void prevention, cross-state rejection, mutex lifecycle
  • Add 6 concurrency tests: min-fraction transfer enforcement, double-mint
    OCC, sequence number gap-freedom, supply invariant under mixed scenarios

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

Summary by CodeRabbit

  • Bug Fixes

    • Improved idempotency behavior and stronger consistency for reservation commits/voids.
    • Ensured pending balances are cleared and reflected correctly when reservations finalize.
    • Tightened error reporting and validation for reservation and account states.
  • Tests

    • Added extensive concurrency and lifecycle tests covering transfers, minting, sequencing, and supply invariants.
    • Added utilities to extract and validate error codes in tests.

@linear
Copy link
Copy Markdown

linear bot commented Mar 17, 2026

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Sorry @Connorbelez, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

Warning

Rate limit exceeded

@Connorbelez has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, 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 have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37737efa-aae7-4c3a-9933-29f94f51b92e

📥 Commits

Reviewing files that changed from the base of the PR and between 29baa5e and 319996a.

📒 Files selected for processing (1)
  • convex/ledger/mutations.ts
📝 Walkthrough

Walkthrough

Refactors ledger mutation internals (commitReservation, voidReservation, reserveShares) to change return shapes and linkage to use reservation._id and reservation account fields; adds pre-postEntry pending-field clearing; introduces extensive new and updated tests for concurrency, reservation lifecycles, idempotency, and invariants.

Changes

Cohort / File(s) Summary
Mutation Logic Updates
convex/ledger/mutations.ts
Return shapes changed: commitReservation and voidReservation now return journal entries directly (idempotent path returns existing entry directly); linkage and patches use reservation._id; pending updates target reservation.sellerAccountId / reservation.buyerAccountId; pre-postEntry pending-field clearing added.
Reservation Tests & Helpers
convex/ledger/__tests__/reservation.test.ts, convex/ledger/__tests__/testUtils.ts
Adds typed mutation wrappers for commit/void, imports and uses getConvexErrorCode utility, reorganizes/respecifies reserve/commit/void tests (idempotency, mutex, lifecycle, error expectations) and updates assertions to match new return shapes and state semantics.
Concurrency Test Suite
convex/ledger/__tests__/concurrency.test.ts
New comprehensive concurrency-focused tests and harness: concurrent transfers, double-mint prevention, sequence-number ordering checks, supply invariant scenarios, typed wrappers for internal mutation handlers, and balance/assertion helpers.
Package Manifest
package.json
Large changes (deps/scripts/etc.) reflected — long diff present (test-related additions likely).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hopping through journals, I tidy the trace,
Reservations linked with a careful embrace.
Commits and voids now return what they made,
Tests chase concurrency in a bushel parade.
Minted and balanced — a rabbit’s small cheer. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: implementing commitReservation/voidReservation mutations and adding comprehensive test suites for reservation lifecycle and concurrency scenarios.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Connorbelez/eng33-lifecycle-tests
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Connorbelez Connorbelez marked this pull request as ready for review March 17, 2026 10:02
Copilot AI review requested due to automatic review settings March 17, 2026 10:02
Copy link
Copy Markdown
Owner Author

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 17, 2026

Greptile Summary

This PR implements the final two legs of the two-phase reservation lifecycle — commitReservation and voidReservation — and backs them with 13 new tests spanning happy paths, double-operation prevention, cross-state rejection, supply invariant checks, and sequence number gap-freedom. The implementation correctly leverages postEntry's 9-step pipeline and respects the AUDIT_ONLY designation for SHARES_VOIDED (which skips cumulative updates). The balanceCheck ordering fix in commitReservation (clearing pending fields before calling postEntry) is well-motivated and correct.

Key concerns found:

  • Missing reservationId ownership check in idempotency bypass (P1) — Both commitReservation (line 583) and voidReservation (line 657) return an existing journal entry matched by idempotencyKey without verifying that existingEntry.reservationId === args.reservationId. If the same key is accidentally reused for a different reservation, the second operation silently no-ops and that reservation's pending holds are frozen indefinitely. reserveShares in the same file guards against exactly this case with an IDEMPOTENT_REPLAY_FAILED error — the new mutations should do the same.

  • No underflow guard on pending-field decrements (P1) — Both mutations subtract amountDelta from pendingCredits/pendingDebits without asserting that the current value is ≥ amountDelta. A negative bigint result would silently persist and cause getAvailableBalance to return an inflated value, which could allow over-committing shares.

  • getConvexErrorCode duplicated between test files (P2) — The helper function is copy-pasted into both concurrency.test.ts and reservation.test.ts with a subtle behavioural difference (the concurrency version throws instead of using expect().toBeInstanceOf()). This should be extracted into a shared test utility.

Confidence Score: 2/5

  • Not safe to merge as-is — two P1 logic gaps in production mutation code need to be addressed before shipping.
  • The test coverage is solid and the overall architecture is correct, but the idempotency check in both new mutations lacks the reservationId ownership validation that the existing reserveShares mutation enforces. A reused idempotency key across different reservations would silently leave a reservation permanently pending with frozen balance holds. Additionally, the unchecked pending-field decrements could silently corrupt account state if any invariant is ever violated. These are real production risks in a financial ledger, warranting a lower confidence score despite the otherwise high-quality implementation.
  • convex/ledger/mutations.ts — specifically the idempotency early-return blocks in commitReservation (lines 583–591) and voidReservation (lines 657–664), and the pending-field decrement blocks in both handlers.

Important Files Changed

Filename Overview
convex/ledger/mutations.ts Adds commitReservation and voidReservation internalMutations; both have a P1 logic gap in the idempotency check (no reservationId ownership validation) and a missing underflow guard on pending-field decrements that could silently corrupt account state.
convex/ledger/tests/reservation.test.ts Extends the reservation test suite with 7 new lifecycle tests covering commit/void happy paths and all double-operation / cross-state rejection scenarios; coverage is thorough and assertions verify both balance state and reservation document fields.
convex/ledger/tests/concurrency.test.ts New file with 6 concurrency/invariant tests; well-structured with clear helpers, though getConvexErrorCode is duplicated from reservation.test.ts with a subtly different implementation (missing the expect(error).toBeInstanceOf(ConvexError) assertion guard).

Sequence Diagram

sequenceDiagram
    participant Caller
    participant commitReservation
    participant voidReservation
    participant DB as Convex DB
    participant postEntry

    Note over Caller,postEntry: reserveShares (existing)
    Caller->>DB: patch sellerAccount.pendingCredits += amount
    Caller->>DB: patch buyerAccount.pendingDebits += amount
    Caller->>DB: insert ledger_reservations {status: "pending"}

    Note over Caller,postEntry: commitReservation (new)
    Caller->>commitReservation: {reservationId, idempotencyKey, effectiveDate}
    commitReservation->>DB: query by_idempotency (early return if exists)
    commitReservation->>DB: get reservation → assert status == "pending"
    commitReservation->>DB: patch sellerAccount.pendingCredits -= amount
    commitReservation->>DB: patch buyerAccount.pendingDebits -= amount
    commitReservation->>postEntry: SHARES_COMMITTED (debit=buyer, credit=seller)
    postEntry->>DB: balanceCheck on seller (sees cleared pendingCredits ✓)
    postEntry->>DB: update cumulativeDebits/Credits
    postEntry->>DB: insert ledger_journal_entries
    commitReservation->>DB: patch reservation {status:"committed", commitJournalEntryId, resolvedAt}
    commitReservation-->>Caller: journalEntry

    Note over Caller,postEntry: voidReservation (new)
    Caller->>voidReservation: {reservationId, reason, idempotencyKey, effectiveDate}
    voidReservation->>DB: query by_idempotency (early return if exists)
    voidReservation->>DB: get reservation → assert status == "pending"
    voidReservation->>postEntry: SHARES_VOIDED [AUDIT_ONLY] (debit=seller, credit=buyer)
    postEntry->>DB: skip balanceCheck (AUDIT_ONLY)
    postEntry->>DB: skip cumulative updates (AUDIT_ONLY)
    postEntry->>DB: insert ledger_journal_entries
    voidReservation->>DB: patch sellerAccount.pendingCredits -= amount
    voidReservation->>DB: patch buyerAccount.pendingDebits -= amount
    voidReservation->>DB: patch reservation {status:"voided", voidJournalEntryId, resolvedAt}
    voidReservation-->>Caller: journalEntry
Loading

Comments Outside Diff (1)

  1. convex/ledger/mutations.ts, line 1120-1127 (link)

    P1 pendingCredits / pendingDebits can silently underflow to a negative bigint

    Both commitReservation (here, lines 1122–1127) and voidReservation (lines 1204–1210) compute:

    pendingCredits: sellerAccount.pendingCredits - amountDelta
    pendingDebits:  buyerAccount.pendingDebits  - amountDelta

    Neither guards against the case where the stored pending value is already less than amountDelta. A corrupted document, an externally applied patch, or any future code path that modifies these fields without going through the reservation flow could make pendingCredits < amountDelta, producing a negative bigint that persists silently in the database. Negative pending fields would cause getAvailableBalance to return a falsely inflated number (posted - (negative pending) > posted), which could allow over-committing shares.

    The reserveShares path only ever increments these fields, so it cannot underflow — but the resolution paths have no equivalent guard. A defensive minimum-zero clamp or an explicit invariant check (throw if pendingCredits < amountDelta) would prevent silent corruption:

    if (sellerAccount.pendingCredits < amountDelta) {
      throw new ConvexError({
        code: "INVARIANT_VIOLATION" as const,
        message: `sellerAccount.pendingCredits (${sellerAccount.pendingCredits}) < reservation amount (${amountDelta})`,
      });
    }

Last reviewed commit: 31bbeab

Comment thread convex/ledger/__tests__/concurrency.test.ts Outdated
Comment thread convex/ledger/mutations.ts
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

Adds the remaining steps of the two-phase “share reservation” workflow in the Convex ledger by implementing reservation commit/void mutations and expanding the test suite to cover reservation lifecycle and invariants.

Changes:

  • Added commitReservation internal mutation to finalize a pending reservation, clear pending fields, and post a SHARES_COMMITTED entry.
  • Added voidReservation internal mutation to void a pending reservation, release pending fields, and post a SHARES_VOIDED audit entry.
  • Expanded and added vitest coverage for reservation lifecycle behaviors, supply invariants, and sequence ordering.

Reviewed changes

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

File Description
convex/ledger/mutations.ts Implements commitReservation and voidReservation internal mutations, including idempotency checks, pending-field updates, and reservation finalization.
convex/ledger/tests/reservation.test.ts Adds tests for commit/void happy paths and state-machine edge cases (double commit/void, commit-after-void, void-after-commit, mutex lifecycle).
convex/ledger/tests/concurrency.test.ts Adds broader “concurrency/invariant” style coverage: min-fraction scenarios, double-mint prevention, sequence ordering, and supply invariant checks across reserve/commit/void flows.

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

Comment thread convex/ledger/mutations.ts
Comment thread convex/ledger/mutations.ts
Comment thread convex/ledger/mutations.ts
Comment thread convex/ledger/__tests__/reservation.test.ts
@Connorbelez Connorbelez force-pushed the Connorbelez/eng33-lifecycle-tests branch from 31bbeab to 502f923 Compare March 17, 2026 10:33
Connorbelez and others added 2 commits March 17, 2026 06:49
…vation+concurrency test suites (ENG-33, ENG-36)

- Add commitReservation internalMutation: finalizes pending reservation by
  posting SHARES_COMMITTED entry, clearing pending fields, and updating
  reservation status to "committed"
- Add voidReservation internalMutation: cancels pending reservation by
  posting SHARES_VOIDED entry (audit-only), releasing pending holds, and
  updating reservation status to "voided"
- Fix balanceCheck ordering: clear pending fields before postEntry so
  available balance reflects the units being committed
- Add 7 reservation lifecycle tests: commit/void happy paths,
  double-commit/void prevention, cross-state rejection, mutex lifecycle
- Add 6 concurrency tests: min-fraction transfer enforcement, double-mint
  OCC, sequence number gap-freedom, supply invariant under mixed scenarios

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… utility, replay tests

- Add entryType + reservationId validation to commitReservation/voidReservation
  idempotency checks, mirroring the reserveShares safety pattern
- Extract getConvexErrorCode to shared testUtils.ts to eliminate duplication
- Add idempotency replay tests for both commitReservation and voidReservation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

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

🧹 Nitpick comments (2)
convex/ledger/__tests__/reservation.test.ts (1)

523-563: Add same-key replay coverage for commitReservation and voidReservation.

These blocks only exercise retries with a fresh idempotency key. Since the mutation code changed the same-key replay path too, I'd add one retry per mutation that reuses the original key, asserts the same journal entry _id comes back, and verifies balances/pending fields stay unchanged.

Also applies to: 658-697

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/ledger/__tests__/reservation.test.ts` around lines 523 - 563, Add
same-key replay tests for commitReservation and voidReservation by reusing the
original idempotency keys and asserting idempotent behavior: after the first
successful executeCommitReservation call capture the returned journal entry id,
call executeCommitReservation again with the same idempotencyKey and assert the
returned journal entry _id is identical and balances/pending fields remain
unchanged (use getAccount and getPostedBalance to verify). Mirror this pattern
in the voidReservation test (the block around the other test at 658-697): after
the first executeVoidReservation capture its journal id, call
executeVoidReservation with the same idempotencyKey, assert the journal entry
_id matches the original, and verify balances and reservation pending state are
unchanged. Ensure you reference functions executeReserveShares,
executeCommitReservation, executeVoidReservation, getAccount, and
getPostedBalance when adding these assertions.
convex/ledger/__tests__/concurrency.test.ts (1)

20-233: Extract the shared ledger test helpers.

This harness, mutation casts, and getConvexErrorCode are essentially duplicated from convex/ledger/__tests__/reservation.test.ts. Pulling them into one helper will keep future mutation-shape changes from drifting across the suites.

Based on learnings, "Extract shared logic into separate modules instead of duplicating code across multiple files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/ledger/__tests__/concurrency.test.ts` around lines 20 - 233, The test
file duplicates harness, helpers and mutation-cast logic that should be
extracted into a shared test helper module: create a new helper module that
exports createTestHarness, asLedgerUser, SYS_SOURCE, initCounter, mintAndIssue,
getAccount, the Reserve/Commit/Void types and the cast constants
reserveSharesMutation, commitReservationMutation, voidReservationMutation, the
executeReserveShares/executeCommitReservation/executeVoidReservation wrappers,
and getConvexErrorCode; then remove the duplicated implementations from this
test (and from convex/ledger/__tests__/reservation.test.ts) and replace them
with imports from the shared helper, updating any tests to use the exported
symbols (e.g., reserveSharesMutation, executeReserveShares, getConvexErrorCode)
so mutation shapes and error-parsing stay consistent across suites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@convex/ledger/__tests__/concurrency.test.ts`:
- Around line 299-320: The test currently awaits the first mint before starting
the second, so it doesn't exercise OCC contention; change the test to start two
concurrent calls to auth.mutation(api.ledger.mutations.mintMortgage) (e.g.,
create p1 and p2 promises with different idempotencyKey values like
"mint-double-1" and "mint-double-2") without awaiting p1 before starting p2,
then await both (Promise.allSettled or Promise.all) and assert that one of the
results is a rejection containing /ALREADY_MINTED/ while the other completed
successfully; keep references to createTestHarness, asLedgerUser, initCounter
and api.ledger.mutations.mintMortgage to locate the code.

In `@convex/ledger/mutations.ts`:
- Around line 582-590: The idempotency lookup in the ledger_journal_entries
query returns any entry with the same idempotencyKey but doesn't verify
entryType or reservationId, allowing collisions across reservations; update the
lookup in the affected branches (the one using
ctx.db.query("ledger_journal_entries").withIndex("by_idempotency", ...) and the
similar block around lines 655-663) to also filter by args.entryType and
args.reservationId (or the expected reservation id) so you only treat an entry
as a replay for the same reservation and operation (e.g., within
commitReservation and voidReservation) before returning it.
- Around line 641-646: The code currently patches reservation.status directly
using ctx.db.patch(reservation._id, { status: "committed", commitJournalEntryId:
journalEntry._id, resolvedAt: Date.now() }), which bypasses the Transition
Engine; replace that direct patch with the canonical Transition Engine call
(e.g., TransitionEngine.transitionReservation or
ReservationRepository.transitionTo) so the commit transition flows through the
state machine and audit/journaling logic — pass the journalEntry id and
resolvedAt as transition metadata so the engine records commitJournalEntryId and
timestamp; apply the same replacement for the other inline patch at the other
location (the block around lines 712-717) so all status changes go through the
Transition Engine.

---

Nitpick comments:
In `@convex/ledger/__tests__/concurrency.test.ts`:
- Around line 20-233: The test file duplicates harness, helpers and
mutation-cast logic that should be extracted into a shared test helper module:
create a new helper module that exports createTestHarness, asLedgerUser,
SYS_SOURCE, initCounter, mintAndIssue, getAccount, the Reserve/Commit/Void types
and the cast constants reserveSharesMutation, commitReservationMutation,
voidReservationMutation, the
executeReserveShares/executeCommitReservation/executeVoidReservation wrappers,
and getConvexErrorCode; then remove the duplicated implementations from this
test (and from convex/ledger/__tests__/reservation.test.ts) and replace them
with imports from the shared helper, updating any tests to use the exported
symbols (e.g., reserveSharesMutation, executeReserveShares, getConvexErrorCode)
so mutation shapes and error-parsing stay consistent across suites.

In `@convex/ledger/__tests__/reservation.test.ts`:
- Around line 523-563: Add same-key replay tests for commitReservation and
voidReservation by reusing the original idempotency keys and asserting
idempotent behavior: after the first successful executeCommitReservation call
capture the returned journal entry id, call executeCommitReservation again with
the same idempotencyKey and assert the returned journal entry _id is identical
and balances/pending fields remain unchanged (use getAccount and
getPostedBalance to verify). Mirror this pattern in the voidReservation test
(the block around the other test at 658-697): after the first
executeVoidReservation capture its journal id, call executeVoidReservation with
the same idempotencyKey, assert the journal entry _id matches the original, and
verify balances and reservation pending state are unchanged. Ensure you
reference functions executeReserveShares, executeCommitReservation,
executeVoidReservation, getAccount, and getPostedBalance when adding these
assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 24507227-18be-423d-bcb9-7e0c0ea51eb3

📥 Commits

Reviewing files that changed from the base of the PR and between 89255c3 and 502f923.

📒 Files selected for processing (3)
  • convex/ledger/__tests__/concurrency.test.ts
  • convex/ledger/__tests__/reservation.test.ts
  • convex/ledger/mutations.ts

Comment thread convex/ledger/__tests__/concurrency.test.ts Outdated
Comment thread convex/ledger/mutations.ts Outdated
Comment thread convex/ledger/mutations.ts
@Connorbelez Connorbelez force-pushed the Connorbelez/eng33-lifecycle-tests branch from 29baa5e to 319996a Compare March 17, 2026 10:56
Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (2)
convex/ledger/__tests__/reservation.test.ts (1)

71-112: Consider extracting mutation wrapper types to testUtils.ts.

The typed mutation wrappers (CommitReservationArgs, CommitReservationMutation, VoidReservationArgs, VoidReservationMutation) are duplicated between this file and concurrency.test.ts. Consider moving them to testUtils.ts to reduce duplication and ensure consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/ledger/__tests__/reservation.test.ts` around lines 71 - 112, Extract
the duplicated typed mutation wrappers into a shared test utils module:
create/export the types CommitReservationArgs, CommitReservationMutation,
VoidReservationArgs, VoidReservationMutation from testUtils (or add them to an
existing testUtils.ts), then replace the local declarations in
reservation.test.ts and concurrency.test.ts with imports of those types and cast
helpers (commitReservationMutation, voidReservationMutation) to the shared
types; ensure you export the types and update both files to import the types and
the const casts so compilation and type-checking of commitReservation/_handler
and voidReservation/_handler continue to work.
convex/ledger/__tests__/concurrency.test.ts (1)

99-172: Mutation wrapper types duplicated from reservation.test.ts.

These type definitions and wrapper constants are identical to those in reservation.test.ts. As suggested in that file's review, consider consolidating these into testUtils.ts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/ledger/__tests__/concurrency.test.ts` around lines 99 - 172, The
duplicate mutation wrapper types and constants (ReserveSharesArgs,
ReserveSharesMutation, reserveSharesMutation, CommitReservationArgs,
CommitReservationMutation, commitReservationMutation, VoidReservationArgs,
VoidReservationMutation, voidReservationMutation) should be extracted into a
shared test utilities module (e.g., testUtils.ts); create the consolidated type
declarations and the three wrapper constants there, export them, then replace
the copies in this file with imports from testUtils.ts so both
concurrency.test.ts and reservation.test.ts consume the single shared
definitions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@convex/ledger/__tests__/testUtils.ts`:
- Around line 23-25: The regex used when extracting error codes (the call to
value.match(...) that assigns to match in testUtils.ts) is missing the
IDEMPOTENT_REPLAY_FAILED token; update the pattern to include
IDEMPOTENT_REPLAY_FAILED among the alternation list (alongside INVALID_AMOUNT,
SAME_ACCOUNT, etc.) so tests detect that error code thrown by the mutations (see
occurrences in mutations.ts).

---

Nitpick comments:
In `@convex/ledger/__tests__/concurrency.test.ts`:
- Around line 99-172: The duplicate mutation wrapper types and constants
(ReserveSharesArgs, ReserveSharesMutation, reserveSharesMutation,
CommitReservationArgs, CommitReservationMutation, commitReservationMutation,
VoidReservationArgs, VoidReservationMutation, voidReservationMutation) should be
extracted into a shared test utilities module (e.g., testUtils.ts); create the
consolidated type declarations and the three wrapper constants there, export
them, then replace the copies in this file with imports from testUtils.ts so
both concurrency.test.ts and reservation.test.ts consume the single shared
definitions.

In `@convex/ledger/__tests__/reservation.test.ts`:
- Around line 71-112: Extract the duplicated typed mutation wrappers into a
shared test utils module: create/export the types CommitReservationArgs,
CommitReservationMutation, VoidReservationArgs, VoidReservationMutation from
testUtils (or add them to an existing testUtils.ts), then replace the local
declarations in reservation.test.ts and concurrency.test.ts with imports of
those types and cast helpers (commitReservationMutation,
voidReservationMutation) to the shared types; ensure you export the types and
update both files to import the types and the const casts so compilation and
type-checking of commitReservation/_handler and voidReservation/_handler
continue to work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a228d695-b7b4-42c0-a872-494fd34173ed

📥 Commits

Reviewing files that changed from the base of the PR and between 502f923 and 29baa5e.

📒 Files selected for processing (4)
  • convex/ledger/__tests__/concurrency.test.ts
  • convex/ledger/__tests__/reservation.test.ts
  • convex/ledger/__tests__/testUtils.ts
  • convex/ledger/mutations.ts

Comment thread convex/ledger/__tests__/testUtils.ts
@Connorbelez Connorbelez merged commit 41d8d16 into main Mar 17, 2026
1 of 3 checks passed
Connorbelez added a commit that referenced this pull request Apr 20, 2026
…vation+concurrency test suites (ENG-33, ENG-36) (#105)

- Add commitReservation internalMutation: finalizes pending reservation by
  posting SHARES_COMMITTED entry, clearing pending fields, and updating
  reservation status to "committed"
- Add voidReservation internalMutation: cancels pending reservation by
  posting SHARES_VOIDED entry (audit-only), releasing pending holds, and
  updating reservation status to "voided"
- Fix balanceCheck ordering: clear pending fields before postEntry so
  available balance reflects the units being committed
- Add 7 reservation lifecycle tests: commit/void happy paths,
  double-commit/void prevention, cross-state rejection, mutex lifecycle
- Add 6 concurrency tests: min-fraction transfer enforcement, double-mint
  OCC, sequence number gap-freedom, supply invariant under mixed scenarios

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **Bug Fixes**
  * Improved idempotency behavior and stronger consistency for reservation commits/voids.
  * Ensured pending balances are cleared and reflected correctly when reservations finalize.
  * Tightened error reporting and validation for reservation and account states.

* **Tests**
  * Added extensive concurrency and lifecycle tests covering transfers, minting, sequencing, and supply invariants.
  * Added utilities to extract and validate error codes in tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

2 participants