Skip to content

ENG-49: implement reserveShares and voidReservation effects#113

Merged
Connorbelez merged 6 commits intomainfrom
ENG-49
Mar 17, 2026
Merged

ENG-49: implement reserveShares and voidReservation effects#113
Connorbelez merged 6 commits intomainfrom
ENG-49

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 17, 2026

ENG-49: implement reserveShares and voidReservation effects

  • Add reservationId field to deals schema
  • Add getInternalDeal query and setReservationId mutation
  • Implement reserveShares effect: creates ledger reservation on lawyer approval
  • Implement voidReservation effect: voids reservation on deal cancellation
  • Update effect registry to point to real handlers

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com

Implementation Details

The reserveShares effect fires when deals transition to lawyer approval, calling the ledger to reserve fractional shares with proper idempotency handling. On failure, deals remain in pending state for reconciliation.

The voidReservation effect handles deal cancellations by voiding existing reservations, gracefully handling cases where deals were cancelled before shares were reserved.

Both effects include comprehensive error handling and logging for operational visibility.

Summary by CodeRabbit

  • New Features

    • Implemented share reservation functionality to lock deal shares during the closing process, with proper idempotency and error handling
    • Added reservation voiding capability to release shares when deals are cancelled
  • Documentation

    • Added detailed specifications and test planning for reservation workflows, including edge cases and error scenarios
  • Chores

    • Updated data schema to support reservation tracking across deal lifecycle

@linear
Copy link
Copy Markdown

linear Bot commented Mar 17, 2026

@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 14 minutes and 21 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: 89556a4e-4912-4207-a44d-e43ef5c9f5d2

📥 Commits

Reviewing files that changed from the base of the PR and between 6a434ff and 8b07a3b.

⛔ Files ignored due to path filters (1)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
📒 Files selected for processing (14)
  • convex/deals/__tests__/dealClosing.test.ts
  • convex/deals/queries.ts
  • convex/engine/effects/dealClosing.ts
  • convex/engine/effects/registry.ts
  • convex/ledger/queries.ts
  • convex/schema.ts
  • specs/ENG-49/chunks/chunk-01-query-helper/context.md
  • specs/ENG-49/chunks/chunk-01-query-helper/tasks.md
  • specs/ENG-49/chunks/chunk-02-effects/context.md
  • specs/ENG-49/chunks/chunk-02-effects/tasks.md
  • specs/ENG-49/chunks/chunk-03-tests/context.md
  • specs/ENG-49/chunks/chunk-03-tests/tasks.md
  • specs/ENG-49/chunks/manifest.md
  • specs/ENG-49/tasks.md
📝 Walkthrough

Walkthrough

This PR implements ENG-49 deal closing effects by introducing reservation management for deals. It adds getInternalDeal and setReservationId helpers, two new internalActions (reserveShares and voidReservation) for managing ledger reservations, updates the schema with a reservationId field, and registers the effects in the effect registry.

Changes

Cohort / File(s) Summary
Deal Query & Mutation Helpers
convex/deals/queries.ts
Added getInternalDeal internalQuery to fetch a deal by ID; added setReservationId internalMutation to update or clear a deal's reservationId field.
Deal Closing Effects
convex/engine/effects/dealClosing.ts
New file implementing two internalActions: reserveShares (fetches deal, computes effectiveDate, calls ledger reserveShares, stores reservationId) and voidReservation (fetches deal, validates reservation state, calls ledger voidReservation, clears reservationId). Both include graceful error handling and idempotency keys.
Effect Registry
convex/engine/effects/registry.ts
Updated registry mappings: reserveShares and voidReservation entries replaced from placeholder references to actual implementations in convex/engine/effects/dealClosing.
Database Schema
convex/schema.ts
Added optional reservationId field to deals table, linking to ledger\_reservations.
Specification & Documentation
specs/ENG-49/chunks/..., specs/ENG-49/tasks.md
Added comprehensive specification files including task definitions (T-001 to T-007), context documentation for query helpers and effects, test plans, and chunk manifest for phased implementation.

Sequence Diagram

sequenceDiagram
    participant Effect as Deal Closing Effect
    participant Deal as Deal (Query/Mutation)
    participant Ledger as Ledger Service
    participant Registry as Effect Registry

    rect rgba(100, 150, 200, 0.5)
        Note over Effect,Registry: reserveShares Flow
        Effect->>Deal: getInternalDeal(dealId)
        Deal-->>Effect: deal {fractionalShare, sellerId, buyerId, ...}
        Effect->>Ledger: reserveShares(deal details, idempotencyKey)
        Ledger-->>Effect: reservationId
        Effect->>Deal: setReservationId(dealId, reservationId)
        Deal-->>Effect: success
    end

    rect rgba(200, 150, 100, 0.5)
        Note over Effect,Registry: voidReservation Flow
        Effect->>Deal: getInternalDeal(dealId)
        Deal-->>Effect: deal {machineContext.reservationId}
        alt reservationId exists
            Effect->>Ledger: voidReservation(reservationId, reason, date)
            Ledger-->>Effect: success
            Effect->>Deal: setReservationId(dealId, undefined)
            Deal-->>Effect: cleared
        else no reservationId
            Effect-->>Effect: exit early
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • ENG-49: This PR directly implements the specification and tasks outlined in ENG-49 for reserveShares and voidReservation deal closing effects, query helpers, and registry updates.

Possibly related PRs

  • PR #102: The new voidReservation internalAction calls the ledger voidReservation mutation that PR #102 implements, establishing a direct functional dependency.
  • PR #99: The reserveShares and voidReservation effects depend on ledger.mutations.reserveShares and ledger.mutations.voidReservation implementations provided in PR #99.
  • PR #108: Both PRs introduce and manage the reservationId field on DealMachineContext; this PR adds schema field, setReservationId mutation, and effects that populate/clear it.

Poem

🐰 Deals now hold their sacred key,
Reservations locked so carefully.
Share by share, the ledger knows,
When to void as business flows.
ENG-49 hops with glee!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main implementation work: adding two new effects (reserveShares and voidReservation) as part of ENG-49, which aligns with the core changes across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 ENG-49
📝 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.

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

Copy link
Copy Markdown
Owner Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@Connorbelez Connorbelez marked this pull request as ready for review March 17, 2026 17:07
Copilot AI review requested due to automatic review settings March 17, 2026 17:07
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 17, 2026

Greptile Summary

This PR implements the reserveShares and voidReservation Convex effects for deal closing (ENG-49), adds the getInternalDeal query and setReservationId mutation helpers, and wires both effects into the effect registry. While the high-level design and control flow are sound, the implementation in convex/engine/effects/dealClosing.ts has four critical bugs that will prevent it from working in production.

Critical issues (will break at runtime or type check):

  • ctx.db used inside internalAction — Convex actions do not have direct DB access. Lines 26–31 (ledger_reservations query), 47–62 (ledger_accounts queries ×2), and 143 (ctx.db.get) inside the two internalAction handlers will throw at runtime. Each lookup must be extracted into an internalQuery and called via ctx.runQuery.
  • Non-deterministic idempotency keys — Both effects append Date.now() to their idempotency keys. This means each retry generates a unique key, so a failed reservation attempt will create a duplicate reservation instead of resuming the existing one — a core correctness violation. The spec explicitly requires stable keys (deal:<dealId>:reserve / deal:<dealId>:void).
  • Wrong internal API namespacegetInternalDeal and setReservationId live in convex/deals/queries.ts, so their correct path is internal.deals.queries.*. The code references internal.deals.internal.* (5 occurrences), which points to a non-existent file and will fail at codegen / type check.
  • Wrong field in withIndex("by_mortgage_and_lender", ...) — The index is defined as ["mortgageId", "lenderId"], but the account lookups chain .eq("type", "POSITION") as an index field. type is not indexed here, causing a TypeScript error; the filter should be a .filter() call instead.

Style:

  • Lines 184–186 contain a leftover "thinking out loud" developer comment that should be removed before merge.

Not implemented per spec:

  • T-006/T-007 (unit tests and verification run) specified in specs/ENG-49 were not completed as part of this PR.

Confidence Score: 0/5

  • Not safe to merge — four critical runtime/type-check bugs in the core effect implementation.
  • The central file convex/engine/effects/dealClosing.ts contains four critical bugs that will cause runtime failures or type errors: (1) ctx.db is called directly inside internalAction handlers, which Convex does not support; (2) non-deterministic idempotency keys using Date.now() break the retry-safety guarantee; (3) all five ctx.runQuery/ctx.runMutation calls reference the non-existent internal.deals.internal.* namespace instead of internal.deals.queries.*; and (4) the by_mortgage_and_lender index is queried with an incorrect field. Any one of these would break production behaviour; together they mean the effects will never execute successfully.
  • convex/engine/effects/dealClosing.ts requires significant fixes before this PR can be merged.

Important Files Changed

Filename Overview
convex/engine/effects/dealClosing.ts New file implementing reserveShares and voidReservation effects — has 4 critical bugs: ctx.db called directly inside internalAction (Convex forbids this), Date.now()-based idempotency keys that defeat retry safety, wrong internal API path (internal.deals.internal.* instead of internal.deals.queries.*), and incorrect withIndex field usage for by_mortgage_and_lender. Will fail at type check and at runtime.
convex/deals/queries.ts Adds getInternalDeal internalQuery and setReservationId internalMutation — both are well-formed. Minor divergence from spec (spec placed setReservationId in dealClosing.ts, not queries.ts) but functionally correct. The internalQuery returns null rather than throwing on missing deal, which is a slight deviation from the spec's ConvexError pattern but consistent with how callers handle it.
convex/engine/effects/registry.ts Swaps reserveShares and voidReservation from placeholder to real handlers — straightforward change, correct references to the new dealClosing module.
convex/schema.ts Adds reservationId as v.optional(v.id("ledger_reservations")) to the deals table — clean schema addition, correctly optional to handle deals cancelled before reservation.

Sequence Diagram

sequenceDiagram
    participant Engine as Deal Engine
    participant RSEffect as reserveShares (internalAction)
    participant VREffect as voidReservation (internalAction)
    participant Queries as deals.queries (internalQuery/Mutation)
    participant Ledger as ledger.mutations

    Engine->>RSEffect: fire on lawyerApproval (dealId)
    RSEffect->>Queries: runQuery(getInternalDeal, dealId)
    Queries-->>RSEffect: deal

    Note over RSEffect: ctx.db.query ledger_reservations (invalid - no ctx.db in action)
    Note over RSEffect: ctx.db.query ledger_accounts x2 (invalid - no ctx.db in action)

    RSEffect->>Ledger: runMutation(reserveShares, {mortgageId, sellerLenderId, buyerLenderId, amount, idempotencyKey})
    Ledger-->>RSEffect: reservationId
    RSEffect->>Queries: runMutation(setReservationId, {dealId, reservationId})

    Engine->>VREffect: fire on deal cancellation (dealId)
    VREffect->>Queries: runQuery(getInternalDeal, dealId)
    Queries-->>VREffect: deal

    alt deal.reservationId exists
        Note over VREffect: ctx.db.get(reservationId) (invalid - no ctx.db in action)
        VREffect->>Ledger: runMutation(voidReservation, {reservationId, reason, idempotencyKey})
        VREffect->>Queries: runMutation(setReservationId, {dealId, reservationId: undefined})
    else no reservationId
        VREffect-->>Engine: return early (clean exit)
    end
Loading

Last reviewed commit: 6a434ff

Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
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

Implements ENG-49 deal-closing ledger reservation effects by adding a reservationId field to deals, wiring effect registry entries to real handlers, and introducing internal helpers to read/patch deals for the effects.

Changes:

  • Add reservationId to the deals table schema.
  • Add internal deal helpers (getInternalDeal, setReservationId) to support effects.
  • Add reserveShares / voidReservation effect handlers and update the effect registry to reference them.
  • Add ENG-49 spec/task planning docs.

Reviewed changes

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

Show a summary per file
File Description
convex/schema.ts Adds optional reservationId on deals.
convex/deals/queries.ts Adds internal query/mutation helpers used by effects.
convex/engine/effects/dealClosing.ts Implements reserveShares and voidReservation internal actions.
convex/engine/effects/registry.ts Points reserveShares / voidReservation effect names to real handlers.
specs/ENG-49/** Adds implementation plan/task breakdown documentation for ENG-49.

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

Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts
Comment thread convex/engine/effects/dealClosing.ts
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/deals/queries.ts
Comment thread convex/deals/queries.ts
Comment thread convex/engine/effects/registry.ts
Comment thread convex/engine/effects/dealClosing.ts
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: 8

🧹 Nitpick comments (1)
convex/engine/effects/dealClosing.ts (1)

184-186: Remove transient implementation notes from production code.

Line 184-186 contains conversational scratch comments. Please remove them now that behavior is decided.

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

In `@convex/engine/effects/dealClosing.ts` around lines 184 - 186, Remove the
conversational scratch comments in dealClosing.ts that discuss checking the
schema and deciding to patch reservationId with undefined (the transient lines
referencing "Let me check the schema" and similar); replace them with either no
comment or a concise single-line note like "clear reservationId by patching
undefined per schema" near the code that patches reservationId (reference: the
patch operation that sets reservationId to undefined and the reservationId:
v.optional(v.id("ledger_reservations")) schema mention). Ensure no
informal/debugging text remains in production code.
🤖 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/engine/effects/dealClosing.ts`:
- Line 90: The idempotency keys currently include Date.now() which makes them
non-deterministic and breaks safe retries; update both occurrences that set
idempotencyKey (the `reserve-...` and the corresponding `void-...` creation in
this file) to use a stable key derived solely from the deal identifier and
operation (e.g., "reserve-<dealId>" and "void-<dealId>") or another
deterministic value provided by the caller, so retries/replays produce the same
idempotencyKey and avoid duplicate side effects.
- Around line 49-51: The index query is incorrectly using the non-indexed field
"type" in withIndex("by_mortgage_and_lender"); update both occurrences where you
call withIndex("by_mortgage_and_lender", ...) so the range expression uses the
indexed keys (use .eq("mortgageId", deal.mortgageId).eq("lenderId",
deal.lenderId) instead of .eq("type", "POSITION")), and move the `.eq("type",
"POSITION")` into a subsequent `.filter(q => q.eq("type", "POSITION"))` so the
query only uses indexed fields for the range and filters by type separately.
- Around line 26-30: The DB reads inside the internalAction handler (e.g., the
existingReservation query using ctx.db.query("ledger_reservations") and other
direct ctx.db accesses at the occurrences around lines 26, 47, 56, and 143) must
be moved into internal query functions and invoked via ctx.runQuery(); create
two internal queries (one that encapsulates the ledger_reservations read(s) and
one for ledger_accounts reads) and replace direct calls to ctx.db.query(...) in
the internalAction with ctx.runQuery("yourLedgerReservationsQuery", {dealId})
and ctx.runQuery("yourLedgerAccountsQuery", {accountId}) respectively, following
the existing pattern used earlier (ctx.runQuery()/ctx.runMutation()) so actions
no longer access ctx.db directly.

In `@specs/ENG-49/chunks/chunk-01-query-helper/context.md`:
- Around line 5-6: Update the spec text to match the current implementation:
replace references that claim the internalMutation setReservationId lives in
convex/engine/effects/dealClosing.ts and that it writes machineContext, and
instead state that setReservationId is implemented in convex/deals/queries.ts
and updates the top-level deals.reservationId field; make the same replacements
for the other occurrences noted (lines referenced as 29-31, 41-43, 73-74) so
test plans target the current contract and code locations.

In `@specs/ENG-49/chunks/chunk-01-query-helper/tasks.md`:
- Line 19: The spec currently declares reservationId as
v.id("ledger_reservations") (required) but the implementation (e.g.,
voidReservation calling setReservationId with reservationId: undefined) needs to
accept undefined; update the parameter/type declaration for reservationId to use
v.optional(v.id("ledger_reservations")) wherever the task/type is defined
(referencing reservationId, setReservationId and voidReservation) so the
function accepts undefined and can clear the field.
- Line 8: getInternalDeal currently returns null from ctx.db.get(dealId) which
diverges from the spec that requires throwing ConvexError("DEAL_NOT_FOUND");
update the getInternalDeal implementation so that after calling
ctx.db.get(dealId) it checks the result and throws new
ConvexError("DEAL_NOT_FOUND") when null/undefined. Reference getInternalDeal and
its callers reserveShares and voidReservation so you ensure those effects no
longer need null-checks and rely on the thrown error for missing deals.

In `@specs/ENG-49/chunks/chunk-02-effects/context.md`:
- Around line 93-94: The repo currently duplicates reservationId between
deal.reservationId and machineContext.reservationId causing drift; pick one
authoritative field and make reads/writes consistent: either (A) retire
machineContext.reservationId by removing references and always read/update
deal.reservationId (update code paths in dealClosing.ts that currently
read/write only deal.reservationId, the spec check in context.md that reads
machineContext.reservationId, and remove seedDeal.ts initialization), or (B)
keep machineContext.reservationId authoritative by changing mutations like
setReservationId and all dealClosing.ts reads/updates to set and read
machineContext.reservationId (and update seedDeal.ts and any spec checks
accordingly); ensure the chosen approach updates every occurrence (checks,
setters, seed) so there is a single source of truth.

In `@specs/ENG-49/tasks.md`:
- Line 6: The task currently points to dealClosing.ts but the actual
internalMutation is exported as setReservationId from the deals queries module;
update the task description to reference the module that exports
setReservationId (the deals queries module) instead of
convex/engine/effects/dealClosing.ts so the spec correctly identifies the
implementation location and export name.

---

Nitpick comments:
In `@convex/engine/effects/dealClosing.ts`:
- Around line 184-186: Remove the conversational scratch comments in
dealClosing.ts that discuss checking the schema and deciding to patch
reservationId with undefined (the transient lines referencing "Let me check the
schema" and similar); replace them with either no comment or a concise
single-line note like "clear reservationId by patching undefined per schema"
near the code that patches reservationId (reference: the patch operation that
sets reservationId to undefined and the reservationId:
v.optional(v.id("ledger_reservations")) schema mention). Ensure no
informal/debugging text remains in production code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6c99ac7c-4389-4cdc-99dd-ac4ca6a421b5

📥 Commits

Reviewing files that changed from the base of the PR and between 6b07d10 and 6a434ff.

📒 Files selected for processing (12)
  • convex/deals/queries.ts
  • convex/engine/effects/dealClosing.ts
  • convex/engine/effects/registry.ts
  • convex/schema.ts
  • specs/ENG-49/chunks/chunk-01-query-helper/context.md
  • specs/ENG-49/chunks/chunk-01-query-helper/tasks.md
  • specs/ENG-49/chunks/chunk-02-effects/context.md
  • specs/ENG-49/chunks/chunk-02-effects/tasks.md
  • specs/ENG-49/chunks/chunk-03-tests/context.md
  • specs/ENG-49/chunks/chunk-03-tests/tasks.md
  • specs/ENG-49/chunks/manifest.md
  • specs/ENG-49/tasks.md

Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread convex/engine/effects/dealClosing.ts Outdated
Comment thread specs/ENG-49/chunks/chunk-01-query-helper/context.md Outdated
Comment thread specs/ENG-49/chunks/chunk-01-query-helper/tasks.md
Comment thread specs/ENG-49/chunks/chunk-01-query-helper/tasks.md Outdated
Comment thread specs/ENG-49/chunks/chunk-02-effects/context.md Outdated
Comment thread specs/ENG-49/tasks.md Outdated
@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed in commit 191fa43: Updated internal API namespace paths from internal.deals.internal.* to internal.deals.queries.* to match the correct Convex generated API structure.

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: Removed leftover developer note at lines 184-186 - this was already addressed in commit 191fa43 'responding to feedback'

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: Updated generated Convex API types to include the new dealClosing, dealAccess, and deals modules. Committed as 5965fba.

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: The implementation already throws ConvexError('DEAL_NOT_FOUND') as specified - both the spec and implementation are aligned. No changes needed.

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: Updated spec to use deal.reservationId as single source of truth

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: Updated reservationId type to v.optional(v.id('ledger_reservations'))

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: Updated spec to reflect setReservationId location (convex/deals/queries.ts) and deal.reservationId field (not machineContext)

@Connorbelez
Copy link
Copy Markdown
Owner Author

Fixed: Added unit tests covering idempotency, missing deal, and missing reservationId paths

Tests added to convex/deals/tests/dealClosing.test.ts:

  • reserveShares: tests that DEAL_NOT_FOUND is thrown when deal is deleted
  • voidReservation: tests idempotency when deal has no reservationId (early return)
  • voidReservation: tests that DEAL_NOT_FOUND is thrown when deal is deleted
  • voidReservation: tests early return when reservationId is undefined

Note: 6 tests are skipped due to a limitation in convex-test where internal queries cannot be called from within internal actions in the test harness.

Connorbelez and others added 6 commits March 17, 2026 15:53
- Add reservationId field to deals schema
- Add getInternalDeal query and setReservationId mutation
- Implement reserveShares effect: creates ledger reservation on lawyer approval
- Implement voidReservation effect: voids reservation on deal cancellation
- Update effect registry to point to real handlers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add missing imports/exports for dealClosing, dealAccess, and deals modules.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update setReservationId location to convex/deals/queries.ts
- Change reservationId references from machineContext to top-level field
- Fix parameter types to use optional for reservationId
- Update idempotency key pattern in spec documentation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Connorbelez Connorbelez merged commit 0dcf69e into main Mar 17, 2026
1 of 3 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Mar 28, 2026
Connorbelez added a commit that referenced this pull request Apr 20, 2026
# ENG-49: implement reserveShares and voidReservation effects

- Add reservationId field to deals schema
- Add getInternalDeal query and setReservationId mutation  
- Implement reserveShares effect: creates ledger reservation on lawyer approval
- Implement voidReservation effect: voids reservation on deal cancellation
- Update effect registry to point to real handlers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

## Implementation Details

The reserveShares effect fires when deals transition to lawyer approval, calling the ledger to reserve fractional shares with proper idempotency handling. On failure, deals remain in pending state for reconciliation.

The voidReservation effect handles deal cancellations by voiding existing reservations, gracefully handling cases where deals were cancelled before shares were reserved.

Both effects include comprehensive error handling and logging for operational visibility.

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

## Summary by CodeRabbit

* **New Features**
  * Implemented share reservation functionality to lock deal shares during the closing process, with proper idempotency and error handling
  * Added reservation voiding capability to release shares when deals are cancelled

* **Documentation**
  * Added detailed specifications and test planning for reservation workflows, including edge cases and error scenarios

* **Chores**  
  * Updated data schema to support reservation tracking across deal lifecycle

<!-- 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