Skip to content

eng-172-pre_review#276

Merged
Connorbelez merged 8 commits intomainfrom
eng-172-v2
Mar 25, 2026
Merged

eng-172-pre_review#276
Connorbelez merged 8 commits intomainfrom
eng-172-v2

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 25, 2026

ENG-172: REVERSAL Entry Type with Posting Group Semantics

This pull request implements payment reversal functionality for the cash ledger system, enabling the reversal of settled payments through a cascade mechanism that maintains posting group integrity.

Core Reversal Functions

Added assertReversalAmountValid() helper with Number.isSafeInteger + > 0 guard and INVALID_REVERSAL_AMOUNT error code for amount validation.

Implemented postPaymentReversalCascade() - a multi-leg reversal orchestrator that reverses entire settlement posting groups atomically. The function handles:

  • CASH_RECEIVED entry reversal (step 1)
  • LENDER_PAYABLE_CREATED entry reversals (step 2)
  • SERVICING_FEE_RECOGNIZED entry reversal (step 3)
  • Conditional LENDER_PAYOUT_SENT reversals for clawback scenarios (step 4)

All reversal entries share a single postingGroupId for atomicity and include proper causedBy linkage to original entries.

Added postTransferReversal() for single-entry transfer reversals with TRANSFER_REQUEST_MISMATCH error code validation.

Reconciliation Detection

Implemented findSettledObligationsWithNonZeroBalance() to detect settled obligations where journal-derived receivable balance is non-zero (the reversal indicator). Added doc comment noting Phase 1 acceptability with Phase 2 pagination recommendations.

Added findSettledObligationsWithNonZeroBalanceInternal internalQuery wrapper for external consumption.

Audit Trail & Compliance

Added auditLog.log() call at step 13 with severity "warning", including postingGroupId, clawbackRequired, and entryCount metadata for O.Reg 189/08 compliance.

Test Coverage

Created comprehensive test suites covering:

  • Full reversal cascade scenarios (T-006 through T-013)
  • Reconciliation detection logic (T-014 through T-016)
  • End-to-end integration flows (T-017 through T-020)

Registered auditLogTest in test harnesses and verified zero regressions in existing cash ledger tests.

Quality Assurance

All code passes lint, type checking, and codegen validation. The implementation maintains append-only journal integrity while providing idempotent reversal operations with proper posting group semantics.

Summary by CodeRabbit

  • New Features

    • Added payment reversal cascade functionality for handling settlement reversals with automatic payout clawback detection
    • Added transfer-scoped reversal capability for single-entry reversals
    • Added reconciliation detection to identify reversed obligations with outstanding balances
    • Enabled optional dispersal entry and obligation tracking for payout operations
  • Tests

    • Added comprehensive reversal cascade behavior tests covering clawback scenarios, idempotency, and amount validation
    • Added integration tests validating end-to-end reversal flows with account balance reconciliation
    • Added reversal reconciliation detection tests

@linear
Copy link
Copy Markdown

linear bot commented Mar 25, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

Warning

Rate limit exceeded

@Connorbelez has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 57 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: 3747cb28-8c3a-4f53-9de1-966a74e4f840

📥 Commits

Reviewing files that changed from the base of the PR and between a77b1b6 and 5367a83.

📒 Files selected for processing (3)
  • convex/payments/cashLedger/__tests__/reversalCascade.test.ts
  • convex/payments/cashLedger/integrations.ts
  • specs/ENG-172/chunks/chunk-01-core-reversal-cascade/context.md
📝 Walkthrough

Walkthrough

Implements ENG-172 payment reversal cascade feature for cash ledger. Adds two idempotent reversal mutations (postPaymentReversalCascade for multi-entry cascades and postTransferReversal for transfer-scoped reversals), reconciliation queries to detect obligations with reversed amounts, comprehensive unit/integration/reconciliation tests, and specification documentation with task tracking.

Changes

Cohort / File(s) Summary
Reversal Core Implementation
convex/payments/cashLedger/integrations.ts
Added validation helpers (assertReversalAmountValid, assertDimensionMatch), payout lookup (findPayoutEntryForClawback), and two exported mutations: postPaymentReversalCascade (orchestrates cascading reversals across CASH_RECEIVED, allocation entries, and optional payout clawback with idempotency via postingGroupId) and postTransferReversal (single-entry transfer-scoped reversal).
Payout Mutation Update
convex/payments/cashLedger/mutations.ts
Updated postLenderPayout signature to accept optional dispersalEntryId and obligationId parameters, forwarded into internal posting call.
Reconciliation Queries
convex/payments/cashLedger/reconciliation.ts
Added ReversalIndicator interface and findSettledObligationsWithNonZeroBalance() query to detect settled obligations where journal reversals have created non-zero outstanding balances; includes internal query wrapper for serialization.
Unit Tests — Reversal Cascade
convex/payments/cashLedger/__tests__/reversalCascade.test.ts
Test suite (T-006 to T-013) covering cascade behavior without/with payouts, clawback detection, idempotency, amount validation with REVERSAL_EXCEEDS_ORIGINAL error, causedBy linkage verification, posting group integrity via getPostingGroupSummary, and transfer reversal.
Unit Tests — Reconciliation
convex/payments/cashLedger/__tests__/reversalReconciliation.test.ts
Test suite (T-014 to T-016) verifying findSettledObligationsWithNonZeroBalance() detects reversed obligations, excludes unreversed settled obligations, and validates getJournalSettledAmountForObligation returns 0n post-reversal.
Integration Test — End-to-End Reversal Flow
convex/payments/cashLedger/__tests__/reversalIntegration.test.ts
End-to-end integration test executing full settlement lifecycle (obligation accrual, cash receipt, allocation, reversal cascade), validating post-reversal account balances across borrower/trust/lender/servicing/control posting groups, posting group netting-to-zero, and reconciliation detection.
Specification Chunks & Manifests
specs/ENG-172/chunks/chunk-*/{context,tasks}.md, specs/ENG-172/chunks/manifest.md, specs/ENG-172/tasks.md
Detailed behavior specifications for core reversal functions, validation helpers, test expectations, execution order, and master task list tracking completion across 6 implementation chunks.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Mutation as postPaymentReversalCascade
    participant Database as Journal DB
    participant Validation as Validators
    participant Cascade as Reversal Logic

    Client->>Mutation: Call with attemptId/transferRequestId + identifiers
    Mutation->>Database: Load CASH_RECEIVED entry
    Database-->>Mutation: Original entry
    Mutation->>Validation: Validate dimension match + amount bounds
    Validation-->>Mutation: Valid
    
    Mutation->>Database: Check if posting group exists (idempotency)
    alt Posting Group Exists
        Database-->>Mutation: Return existing reversals
    else New Reversal Cascade
        Mutation->>Cascade: Post REVERSAL for CASH_RECEIVED
        Cascade->>Database: Insert REVERSAL entry
        Database-->>Cascade: Entry ID
        
        Mutation->>Database: Load allocation entries (LENDER_PAYABLE_CREATED, SERVICING_FEE_RECOGNIZED)
        Database-->>Mutation: Allocation entries
        
        Mutation->>Cascade: Post REVERSAL for each allocation
        Cascade->>Database: Insert allocation REVERSAL entries
        Database-->>Cascade: Entry IDs
        
        Mutation->>Database: Find LENDER_PAYOUT_SENT per lender (clawback detection)
        Database-->>Mutation: Payout entries (if any)
        
        alt Payouts Found
            Mutation->>Cascade: Post REVERSAL entries for LENDER_PAYOUT_SENT
            Cascade->>Database: Insert payout REVERSAL entries
            Database-->>Cascade: Entry IDs
            Cascade-->>Mutation: clawbackRequired=true
        else No Payouts
            Mutation-->>Cascade: clawbackRequired=false
        end
        
        Mutation->>Database: Return all reversal entries
    end
    
    Mutation-->>Client: {reversalEntries[], postingGroupId, clawbackRequired}
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Rationale: The reversal cascade logic in integrations.ts is dense with multiple validation layers (dimension matching, amount bounds), tiered lookup patterns (findPayoutEntryForClawback), idempotency via posting groups, and cascading REVERSAL entries across multiple entry types. The test suites are extensive (eight unit test cases, three reconciliation tests, comprehensive integration test) and require understanding clawback conditions, posting group netting, and reconciliation detection logic. Reconciliation query changes and mutation signature updates are straightforward but depend on reversal core logic.

Possibly Related Issues

Possibly Related PRs

Poem

🐰 Reversals flow like carrot juice through the ledger's veins,
Cascading down with debit swaps and causedBy chains,
Payouts clawed back when NSF brings dismay,
Idempotent posting groups keep duplicates at bay,
The journal balances again—oh, what a day! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'eng-172-pre_review' is vague and non-descriptive; it references only a ticket number and does not convey the primary change (payment reversal and reconciliation functionality). Revise the title to be more descriptive of the main changes, e.g., 'Implement payment reversal cascade and reconciliation detection for cash ledger' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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-172-v2

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 25, 2026 19:56
Copilot AI review requested due to automatic review settings March 25, 2026 19:56
Copy link
Copy Markdown

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Connorbelez has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

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 payment reversal support in the cash-ledger by introducing a REVERSAL cascade (with posting-group semantics), adding a reconciliation query to detect reversed settled obligations, and adding unit/integration tests plus accompanying ENG-172 spec artifacts.

Changes:

  • Added postPaymentReversalCascade() (multi-leg settlement reversal) and postTransferReversal() plus shared amount validation in integrations.ts.
  • Added findSettledObligationsWithNonZeroBalance() and an internalQuery wrapper to surface reversal indicators from reconciliation.
  • Added new test suites covering reversal cascade, reconciliation detection, and an end-to-end pipeline.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
specs/ENG-172/tasks.md Master task list for ENG-172 implementation and verification steps.
specs/ENG-172/chunks/manifest.md Chunk breakdown and execution order for ENG-172 work.
specs/ENG-172/chunks/chunk-01-core-reversal-cascade/tasks.md Task checklist for adding reversal cascade + transfer reversal.
specs/ENG-172/chunks/chunk-01-core-reversal-cascade/context.md Detailed design/context for reversal posting sequence and constraints.
specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md Task checklist for adding reversal-indicator reconciliation query.
specs/ENG-172/chunks/chunk-02-reconciliation-detection/context.md Detailed design/context for the reversal-indicator query and internal wrapper.
specs/ENG-172/chunks/chunk-03-unit-tests-cascade/tasks.md Task checklist for cascade unit tests.
specs/ENG-172/chunks/chunk-03-unit-tests-cascade/context.md Guidance for building cascade tests and expected accounting behavior.
specs/ENG-172/chunks/chunk-04-unit-tests-reconciliation/tasks.md Task checklist for reconciliation unit tests.
specs/ENG-172/chunks/chunk-04-unit-tests-reconciliation/context.md Guidance for building reconciliation detection tests.
specs/ENG-172/chunks/chunk-05-integration-test/tasks.md Task checklist for end-to-end integration test.
specs/ENG-172/chunks/chunk-05-integration-test/context.md Guidance for end-to-end pipeline test setup and assertions.
specs/ENG-172/chunks/chunk-06-quality-gate/tasks.md Checklist for lint/typecheck/codegen/test gate.
specs/ENG-172/chunks/chunk-06-quality-gate/context.md Commands and common issue patterns for the quality gate.
convex/payments/cashLedger/reconciliation.ts Adds reversal-indicator reconciliation query and internal wrapper.
convex/payments/cashLedger/integrations.ts Adds reversal amount validation helper, reversal cascade orchestrator, and transfer reversal helper.
convex/payments/cashLedger/tests/reversalReconciliation.test.ts New unit tests for reversal-indicator reconciliation behavior.
convex/payments/cashLedger/tests/reversalIntegration.test.ts New end-to-end test exercising full settlement pipeline + reversal + detection.
convex/payments/cashLedger/tests/reversalCascade.test.ts New unit tests for reversal cascade, clawback handling, idempotency, and transfer reversal.

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

Comment thread convex/payments/cashLedger/integrations.ts Outdated
Comment thread convex/payments/cashLedger/integrations.ts Outdated
Comment thread convex/payments/cashLedger/integrations.ts
Comment thread convex/payments/cashLedger/__tests__/reversalCascade.test.ts Outdated
Comment thread convex/payments/cashLedger/__tests__/reversalIntegration.test.ts Outdated
Copy link
Copy Markdown
Owner Author

@coderabbitai please review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

@Connorbelez Sure, I'll start reviewing PR #276 now!


🧠 Learnings used
Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 5
File: specs/governed-transitions/chunks/chunk-01-schema-machine/context.md:209-213
Timestamp: 2026-03-13T03:17:28.559Z
Learning: In the Connorbelez/tanstackTemplate repository, do not flag .md files for markdown formatting/linting issues (e.g., MD040 fenced code block language). Only reference .md files (specs, PRDs, design docs) when they reveal architectural drift or implementation inconsistencies against actual code.

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-16T16:49:07.666Z
Learning: After completing a major unit of work, run `coderabbit review --plain` to get a code review summary and check for potential issues or improvements

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-16T06:17:10.192Z
Learning: After completing a major unit of work, run `coderabbit review --plain` to get a code review summary and check for potential issues or improvements

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T18:28:26.681Z
Learning: After completing a major unit of work, run `coderabbit review --plain` to get a code review summary and check for potential issues or improvements

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-16T15:27:40.455Z
Learning: Use React, TanStack Router, TanStack Query with Convex query integration, Tailwind CSS, and ShadCN UI for frontend development

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 270
File: convex/payments/cashLedger/__tests__/auditTrail.test.ts:54-125
Timestamp: 2026-03-24T21:03:33.095Z
Learning: In Connorbelez/tanstackTemplate, `convex/payments/cashLedger/__tests__/auditTrail.test.ts` is deliberately a unit test for audit trail insertion logic in isolation. The `createEntryAndAudit` helper manually patches accounts, inserts journal rows, and calls `auditTrail.insert()` directly — this is intentional, not a deficiency. The full integration path (`postCashEntryInternal` → `nudge()` → workflow → `processCashLedgerHashChainStep()`) is covered in `cashReceiptIntegration.test.ts`, which registers workflow/workpool components. `makeHarness()` deliberately sets `DISABLE_CASH_LEDGER_HASHCHAIN=true` because it does not register workflow components. Do NOT flag the manual balance patching and direct `auditTrail.insert()` calls in `auditTrail.test.ts` as bypassing the system under test — the test layering is intentional:
- `auditTrail.test.ts` → Unit: audit insertion, hash chain, rejection records
- `cashReceiptIntegration.test.ts` → Integration: full pipeline (post → nudge → workflow → hash chain)
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Connorbelez and others added 5 commits March 25, 2026 16:33
…rror code Renumbered sequentially 1→13 throughout postPaymentReversalCascade

Added auditLog.log() call at step 13 with severity “warning”, including postingGroupId, clawbackRequired, entryCount. Registered auditLogTest in test harnesses.
Added doc comment noting Phase 1 acceptability + Phase 2 pagination
Added mismatch check with TRANSFER_REQUEST_MISMATCH error code
…tion test

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

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

- Comment 1: Add dispersalEntryId and obligationId optional args to
  postLenderPayout so payout entries carry stable settlement linkage.
  Extract tiered payout detection into findPayoutEntryForClawback helper
  with 4-tier search strategy (dispersalEntryId, lender-scoped dispersalEntryId,
  postingGroupId, lenderId+mortgageId fallback).

- Comment 2: postTransferReversal now requires original.transferRequestId
  to be present (NOT_A_TRANSFER_ENTRY error) before checking equality,
  preventing transfer-scoped reversals for non-transfer entries.

- Comment 3: Add assertDimensionMatch helper that validates args.mortgageId
  and args.obligationId match the original CASH_RECEIVED entry dimensions
  before posting reversals. Throws DIMENSION_MISMATCH on inconsistency.

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

@coderabbitai where's that review?

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 25, 2026

@Connorbelez Apologies for the delay! Let me kick off a full review of PR #276 right away.


🧠 Learnings used
Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 115
File: convex/engine/effects/dealClosingPayments.ts:37-47
Timestamp: 2026-03-18T03:33:54.456Z
Learning: In Connorbelez/tanstackTemplate, `internalAction` handlers in Convex cannot atomically combine `ctx.runQuery` + `ctx.runMutation` — they are always separate transactions. The read-then-write idempotency pattern in effect handlers (e.g., `updatePaymentSchedule` in `convex/engine/effects/dealClosingPayments.ts`, `prorateAccrualBetweenOwners` in `convex/engine/effects/dealClosingProrate.ts`) is intentionally acceptable because the Transition Engine's OCC serializes concurrent transitions for the same entity upstream, preventing duplicate effect scheduling. Do NOT flag the split read-then-write pattern in `internalAction` effect handlers as a race condition requiring atomic fix — the upstream OCC is the primary guard. A defense-in-depth improvement (atomic `createIfNotExists` mutation) is tracked as a follow-up but is not a blocking issue.

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-16T16:49:07.666Z
Learning: After completing a major unit of work, run `coderabbit review --plain` to get a code review summary and check for potential issues or improvements

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-21T18:28:26.681Z
Learning: After completing a major unit of work, run `coderabbit review --plain` to get a code review summary and check for potential issues or improvements

Learnt from: CR
Repo: Connorbelez/tanstackTemplate PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-16T06:17:10.192Z
Learning: After completing a major unit of work, run `coderabbit review --plain` to get a code review summary and check for potential issues or improvements

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 115
File: convex/engine/effects/dealClosingProrate.ts:47-57
Timestamp: 2026-03-18T03:33:56.251Z
Learning: In Connorbelez/tanstackTemplate, the split idempotency pattern (ctx.runQuery to check existence, then ctx.runMutation to insert) used in Convex internalAction effect handlers (e.g., convex/engine/effects/dealClosingProrate.ts, convex/engine/effects/dealClosingPayments.ts) is intentional and correct. Convex actions cannot colocate a query and a mutation in a single transaction — runQuery and runMutation are always separate round-trips inside an action. The race condition is prevented at the scheduling level: the Transition Engine's OCC guarantees that only one effect dispatch can be in-flight for a given dealId at a time, so the pre-check window is never concurrent. Do NOT flag this split check-then-insert pattern in Convex action effect handlers as a race condition.

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 270
File: convex/payments/cashLedger/__tests__/auditTrail.test.ts:54-125
Timestamp: 2026-03-24T21:03:33.095Z
Learning: In Connorbelez/tanstackTemplate, `convex/payments/cashLedger/__tests__/auditTrail.test.ts` is deliberately a unit test for audit trail insertion logic in isolation. The `createEntryAndAudit` helper manually patches accounts, inserts journal rows, and calls `auditTrail.insert()` directly — this is intentional, not a deficiency. The full integration path (`postCashEntryInternal` → `nudge()` → workflow → `processCashLedgerHashChainStep()`) is covered in `cashReceiptIntegration.test.ts`, which registers workflow/workpool components. `makeHarness()` deliberately sets `DISABLE_CASH_LEDGER_HASHCHAIN=true` because it does not register workflow components. Do NOT flag the manual balance patching and direct `auditTrail.insert()` calls in `auditTrail.test.ts` as bypassing the system under test — the test layering is intentional:
- `auditTrail.test.ts` → Unit: audit insertion, hash chain, rejection records
- `cashReceiptIntegration.test.ts` → Integration: full pipeline (post → nudge → workflow → hash chain)

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 107
File: convex/engine/machines/deal.machine.ts:35-51
Timestamp: 2026-03-17T12:29:07.205Z
Learning: In Connorbelez/tanstackTemplate, the action stubs in `convex/engine/machines/deal.machine.ts` (reserveShares, notifyAllParties, createDocumentPackage, createDealAccess, archiveSignedDocuments, confirmFundsReceipt, commitReservation, prorateAccrualBetweenOwners, updatePaymentSchedule, voidReservation, notifyCancellation, revokeAllDealAccess) are intentional no-op stubs scoped to ENG-44 (machine definition + test matrix only). The actual effect handlers will be registered in `convex/engine/effects/registry.ts` in subsequent tickets. Do NOT flag these stubs as missing effect registrations — `scheduleEffects()` handles unregistered effects gracefully (warns and skips), so transitions persist correctly and there is no silent data loss. This is the intended incremental wiring pattern.

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 5
File: specs/governed-transitions/chunks/chunk-01-schema-machine/context.md:209-213
Timestamp: 2026-03-13T03:17:28.559Z
Learning: In the Connorbelez/tanstackTemplate repository, do not flag .md files for markdown formatting/linting issues (e.g., MD040 fenced code block language). Only reference .md files (specs, PRDs, design docs) when they reveal architectural drift or implementation inconsistencies against actual code.

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 5
File: specs/governed-transitions/design.md:158-159
Timestamp: 2026-03-13T03:23:52.156Z
Learning: In Connorbelez/tanstackTemplate, the `hasCompleteData` guard in `convex/demo/machines/loanApplication.machine.ts` reads from `context.data` as a POC/mock pattern. In production, it may be necessary to read from the event payload instead (e.g., a SUBMIT event carrying the latest form values). Both `specs/governed-transitions/design.md` and the machine file should carry a note calling this out to avoid forgetting the distinction.

Learnt from: Connorbelez
Repo: Connorbelez/tanstackTemplate PR: 5
File: specs/governed-transitions/design.md:158-159
Timestamp: 2026-03-13T03:23:52.156Z
Learning: In Connorbelez/tanstackTemplate, the `hasCompleteData` guard in `convex/demo/machines/loanApplication.machine.ts` is explicitly a POC/demo pattern: it reads from `context.data` (hydrated from the entity at creation time), NOT the event payload. The `SUBMIT` event carries no payload fields. In production, the guard may need to read from `event` instead (e.g., a SUBMIT event carrying latest form values). Both `specs/governed-transitions/design.md` and the machine file carry a TODO/POC note documenting this distinction.
✅ Actions performed

Full review triggered.

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: 4

🧹 Nitpick comments (3)
specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md (1)

1-4: Tasks appear complete but unchecked.

Both findSettledObligationsWithNonZeroBalance() and findSettledObligationsWithNonZeroBalanceInternal are implemented in reconciliation.ts. Consider marking these tasks as complete ([x]) to reflect the current state.

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

In `@specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md` around lines
1 - 4, The checklist in
specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md is stale: both
findSettledObligationsWithNonZeroBalance() and
findSettledObligationsWithNonZeroBalanceInternal are already implemented in
reconciliation.ts, so update the markdown to mark T-004 and T-005 as completed
by changing their checkboxes from [ ] to [x]; ensure the two task lines
referencing those exact function names are updated.
specs/ENG-172/chunks/chunk-01-core-reversal-cascade/tasks.md (1)

1-5: Tasks appear complete but unchecked.

Per the PR summary, assertReversalAmountValid(), postPaymentReversalCascade(), and postTransferReversal() are implemented in integrations.ts. Consider marking these tasks as complete ([x]) to reflect the current state.

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

In `@specs/ENG-172/chunks/chunk-01-core-reversal-cascade/tasks.md` around lines 1
- 5, The checklist in tasks.md still shows T-001, T-002, and T-003 as
incomplete; update the markdown to mark these tasks as done by replacing the
unchecked boxes with checked ones for assertReversalAmountValid(),
postPaymentReversalCascade(), and postTransferReversal so the file reflects that
these functions in integrations.ts are implemented.
convex/payments/cashLedger/reconciliation.ts (1)

270-306: Potential N+1 query pattern — acceptable for Phase 1, but worth noting for Phase 2.

The function iterates over all settled obligations and calls getJournalSettledAmountForObligation for each, which itself queries the journal. This is an N+1 pattern that could become expensive as obligation count grows.

The doc comment already acknowledges pagination for Phase 2. Consider also batching the journal lookups or pre-fetching all relevant entries in a single query when optimizing.

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

In `@convex/payments/cashLedger/reconciliation.ts` around lines 270 - 306, The
current implementation of findSettledObligationsWithNonZeroBalance performs an
N+1 query by calling getJournalSettledAmountForObligation for each obligation;
to fix, change the workflow to fetch journal data in bulk (e.g., query the
journal once for all obligation IDs returned by ctx.db.query("obligations") and
aggregate settled amounts per obligation into a Map), then iterate the
settledObligations and compute outstandingBalance using the precomputed
journalSettledAmount from that Map; alternatively add a batch API like
getJournalSettledAmountsForObligations(obligationIds: string[]) and use it in
findSettledObligationsWithNonZeroBalance to avoid per-obligation journal
queries.
🤖 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/payments/cashLedger/__tests__/reversalCascade.test.ts`:
- Around line 262-290: The tests exercising postTransferReversal are seeding
state.cashReceivedEntry with only attemptId, but postTransferReversal now
rejects originals missing transferRequestId; update the tests (around use of
postCashEntryInternal / state.cashReceivedEntry) to create a CASH_RECEIVED entry
that includes the same transferRequestId used by each reversal test (or add a
reusable fixture helper that calls postCashEntryInternal with transferRequestId
and returns that created entry), so T-010 and T-013 use a transfer-backed
original instead of hitting NOT_A_TRANSFER_ENTRY.

In `@convex/payments/cashLedger/integrations.ts`:
- Around line 1354-1364: The postPaymentReversalCascade logic currently prefers
attemptId when both identifiers are provided; change this to either (a) require
exactly one identifier by throwing a ConvexError if both attemptId and
transferRequestId are present, or (b) if you must accept both, locate the
CASH_RECEIVED row (using postingGroupId logic) and verify its transferRequestId
matches the provided transferRequestId and its attemptId matches the provided
attemptId, throwing a ConvexError on mismatch; update the postingGroupId
resolution and the duplicate-check around postPaymentReversalCascade (also
adjust the analogous check at the second occurrence around the 1393–1399 block)
to enforce this validation so stale/wrong transferRequestId cannot be silently
ignored.
- Around line 1315-1323: The current fallback returns the first
LENDER_PAYOUT_SENT for a lender+mortgage which can be ambiguous; instead collect
all lenderEntries matching entryType === "LENDER_PAYOUT_SENT" && mortgageId ===
mortgageId, and if exactly one match return it, otherwise fail closed by
returning null (or throwing) so ambiguity is surfaced for manual handling;
reference the query to "cash_ledger_journal_entries"
withIndex("by_lender_and_sequence") and the lenderEntries variable—do not ignore
obligationId/dispersalEntryId here, but refuse to auto-select when multiple
matches exist.

In `@specs/ENG-172/chunks/chunk-01-core-reversal-cascade/context.md`:
- Around line 36-54: Update the spec to reflect the implemented reversal
contract: modify the assertReversalAmountValid() description to state it throws
INVALID_REVERSAL_AMOUNT for non-positive or unsafe integer reversalAmount (not
just over-limit), and update references to how clawbacks are resolved to use
findPayoutEntryForClawback() with the by_lender_and_sequence and
by_posting_group lookups; remove or replace any mention of
by_lender_and_entry_type and ensure examples and any pointers (e.g., the
paragraph previously pointing to line 166) reference the correct function names
and index names.

---

Nitpick comments:
In `@convex/payments/cashLedger/reconciliation.ts`:
- Around line 270-306: The current implementation of
findSettledObligationsWithNonZeroBalance performs an N+1 query by calling
getJournalSettledAmountForObligation for each obligation; to fix, change the
workflow to fetch journal data in bulk (e.g., query the journal once for all
obligation IDs returned by ctx.db.query("obligations") and aggregate settled
amounts per obligation into a Map), then iterate the settledObligations and
compute outstandingBalance using the precomputed journalSettledAmount from that
Map; alternatively add a batch API like
getJournalSettledAmountsForObligations(obligationIds: string[]) and use it in
findSettledObligationsWithNonZeroBalance to avoid per-obligation journal
queries.

In `@specs/ENG-172/chunks/chunk-01-core-reversal-cascade/tasks.md`:
- Around line 1-5: The checklist in tasks.md still shows T-001, T-002, and T-003
as incomplete; update the markdown to mark these tasks as done by replacing the
unchecked boxes with checked ones for assertReversalAmountValid(),
postPaymentReversalCascade(), and postTransferReversal so the file reflects that
these functions in integrations.ts are implemented.

In `@specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md`:
- Around line 1-4: The checklist in
specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md is stale: both
findSettledObligationsWithNonZeroBalance() and
findSettledObligationsWithNonZeroBalanceInternal are already implemented in
reconciliation.ts, so update the markdown to mark T-004 and T-005 as completed
by changing their checkboxes from [ ] to [x]; ensure the two task lines
referencing those exact function names are updated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a348a82c-b083-418b-a6d9-3dd038e213c3

📥 Commits

Reviewing files that changed from the base of the PR and between 7efd93a and a77b1b6.

📒 Files selected for processing (20)
  • convex/payments/cashLedger/__tests__/reversalCascade.test.ts
  • convex/payments/cashLedger/__tests__/reversalIntegration.test.ts
  • convex/payments/cashLedger/__tests__/reversalReconciliation.test.ts
  • convex/payments/cashLedger/integrations.ts
  • convex/payments/cashLedger/mutations.ts
  • convex/payments/cashLedger/reconciliation.ts
  • specs/ENG-172/chunks/chunk-01-core-reversal-cascade/context.md
  • specs/ENG-172/chunks/chunk-01-core-reversal-cascade/tasks.md
  • specs/ENG-172/chunks/chunk-02-reconciliation-detection/context.md
  • specs/ENG-172/chunks/chunk-02-reconciliation-detection/tasks.md
  • specs/ENG-172/chunks/chunk-03-unit-tests-cascade/context.md
  • specs/ENG-172/chunks/chunk-03-unit-tests-cascade/tasks.md
  • specs/ENG-172/chunks/chunk-04-unit-tests-reconciliation/context.md
  • specs/ENG-172/chunks/chunk-04-unit-tests-reconciliation/tasks.md
  • specs/ENG-172/chunks/chunk-05-integration-test/context.md
  • specs/ENG-172/chunks/chunk-05-integration-test/tasks.md
  • specs/ENG-172/chunks/chunk-06-quality-gate/context.md
  • specs/ENG-172/chunks/chunk-06-quality-gate/tasks.md
  • specs/ENG-172/chunks/manifest.md
  • specs/ENG-172/tasks.md

Comment thread convex/payments/cashLedger/__tests__/reversalCascade.test.ts
Comment thread convex/payments/cashLedger/integrations.ts
Comment thread convex/payments/cashLedger/integrations.ts Outdated
Comment thread specs/ENG-172/chunks/chunk-01-core-reversal-cascade/context.md
Connorbelez and others added 3 commits March 25, 2026 17:19
- assertReversalAmountValid() now documents both INVALID_REVERSAL_AMOUNT
  (non-positive/unsafe integer) and REVERSAL_EXCEEDS_ORIGINAL checks
- Replaced stale by_lender_and_entry_type reference with the actual
  findPayoutEntryForClawback() tiered lookup using by_lender_and_sequence
  and by_posting_group indexes
- Updated step 11 in the cascade logic to reference the tiered strategy

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

T-010 and T-013 used state.cashReceivedEntry which lacks transferRequestId,
causing postTransferReversal to reject with NOT_A_TRANSFER_ENTRY before
exercising the intended assertions. Each test now creates a dedicated
transfer-backed CASH_RECEIVED entry with the matching transferRequestId.

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

- Tier 4 fallback in findPayoutEntryForClawback now returns undefined when
  multiple LENDER_PAYOUT_SENT entries match by lenderId+mortgageId, preventing
  wrong clawback attachment on ambiguous payout matching.
- postPaymentReversalCascade now rejects with AMBIGUOUS_REVERSAL_IDENTIFIER
  when both attemptId and transferRequestId are supplied, preventing silent
  mismatch on conflicting identifier combinations.
- Extracted assertExactlyOneReversalIdentifier helper to reduce cognitive
  complexity below the linter threshold.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Connorbelez Connorbelez merged commit fdcda68 into main Mar 25, 2026
1 of 3 checks passed
This was referenced Mar 26, 2026
Connorbelez added a commit that referenced this pull request Apr 20, 2026
# ENG-172: REVERSAL Entry Type with Posting Group Semantics

This pull request implements payment reversal functionality for the cash ledger system, enabling the reversal of settled payments through a cascade mechanism that maintains posting group integrity.

## Core Reversal Functions

Added `assertReversalAmountValid()` helper with Number.isSafeInteger + > 0 guard and INVALID_REVERSAL_AMOUNT error code for amount validation.

Implemented `postPaymentReversalCascade()` - a multi-leg reversal orchestrator that reverses entire settlement posting groups atomically. The function handles:
- CASH_RECEIVED entry reversal (step 1)
- LENDER_PAYABLE_CREATED entry reversals (step 2) 
- SERVICING_FEE_RECOGNIZED entry reversal (step 3)
- Conditional LENDER_PAYOUT_SENT reversals for clawback scenarios (step 4)

All reversal entries share a single postingGroupId for atomicity and include proper causedBy linkage to original entries.

Added `postTransferReversal()` for single-entry transfer reversals with TRANSFER_REQUEST_MISMATCH error code validation.

## Reconciliation Detection

Implemented `findSettledObligationsWithNonZeroBalance()` to detect settled obligations where journal-derived receivable balance is non-zero (the reversal indicator). Added doc comment noting Phase 1 acceptability with Phase 2 pagination recommendations.

Added `findSettledObligationsWithNonZeroBalanceInternal` internalQuery wrapper for external consumption.

## Audit Trail & Compliance

Added auditLog.log() call at step 13 with severity "warning", including postingGroupId, clawbackRequired, and entryCount metadata for O.Reg 189/08 compliance.

## Test Coverage

Created comprehensive test suites covering:
- Full reversal cascade scenarios (T-006 through T-013)
- Reconciliation detection logic (T-014 through T-016) 
- End-to-end integration flows (T-017 through T-020)

Registered auditLogTest in test harnesses and verified zero regressions in existing cash ledger tests.

## Quality Assurance

All code passes lint, type checking, and codegen validation. The implementation maintains append-only journal integrity while providing idempotent reversal operations with proper posting group semantics.

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

## Summary by CodeRabbit

* **New Features**
  * Added payment reversal cascade functionality for handling settlement reversals with automatic payout clawback detection
  * Added transfer-scoped reversal capability for single-entry reversals
  * Added reconciliation detection to identify reversed obligations with outstanding balances
  * Enabled optional dispersal entry and obligation tracking for payout operations

* **Tests**
  * Added comprehensive reversal cascade behavior tests covering clawback scenarios, idempotency, and amount validation
  * Added integration tests validating end-to-end reversal flows with account balance reconciliation
  * Added reversal reconciliation detection 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