Skip to content

ENG-148: Complete 9-step cash ledger posting pipeline#244

Merged
Connorbelez merged 4 commits intomainfrom
Connorbelez/eng148-posting-pipeline
Mar 21, 2026
Merged

ENG-148: Complete 9-step cash ledger posting pipeline#244
Connorbelez merged 4 commits intomainfrom
Connorbelez/eng148-posting-pipeline

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 21, 2026

Close remaining gaps in postCashEntry pipeline

  • Strengthen CORRECTION constraint check: require causedBy (REQ-242), actorId, admin actorType, and reason (was missing causedBy + actorId)
  • Add BORROWER_RECEIVABLE to NEGATIVE_BALANCE_EXEMPT_FAMILIES alongside CONTROL (Tech Design §9.1 Step 5)
  • Wire NUDGE step 9 as no-op placeholder for Phase 4 cursor consumers
  • Extract validators to validators.ts for reuse across mutations

Summary by CodeRabbit

  • Bug Fixes

    • Balance checks now exempt additional account family for negative-balance rules, fixing inconsistent behavior.
  • Refactor

    • Validation for cash entry submissions reworked to use a centralized schema and stricter rules for CORRECTION entries (requires admin actor, causal fields, and reason).
  • Tests

    • Added tests covering correction/reversal constraints and the negative-balance exemption behavior.

Close remaining gaps in postCashEntry pipeline:
- Strengthen CORRECTION constraint check: require causedBy (REQ-242),
  actorId, admin actorType, and reason (was missing causedBy + actorId)
- Add BORROWER_RECEIVABLE to NEGATIVE_BALANCE_EXEMPT_FAMILIES alongside
  CONTROL (Tech Design §9.1 Step 5)
- Wire NUDGE step 9 as no-op placeholder for Phase 4 cursor consumers
- Extract validators to validators.ts for reuse across mutations

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

linear bot commented Mar 21, 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 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c58bfac-5b4e-4c33-a716-63d011f230bc

📥 Commits

Reviewing files that changed from the base of the PR and between 42bdda4 and fb843af.

📒 Files selected for processing (3)
  • convex/payments/cashLedger/__tests__/constraintsAndBalanceExemption.test.ts
  • convex/payments/cashLedger/postEntry.ts
  • convex/payments/cashLedger/types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • convex/payments/cashLedger/types.ts

📝 Walkthrough

Walkthrough

The changes extract and centralize post-entry argument validation, add a negative-balance exemption set including BORROWER_RECEIVABLE, tighten CORRECTION entry constraints (admin actor, actorId, causedBy, reason), and sequence post-entry persistence to await a new async nudge step before returning.

Changes

Cohort / File(s) Summary
Validation Infrastructure
convex/payments/cashLedger/validators.ts
Added cashEntryTypeValidator and postCashEntryArgsValidator object schema replacing inline validation for postCashEntry.
Type Definitions
convex/payments/cashLedger/types.ts
Added exported NEGATIVE_BALANCE_EXEMPT_FAMILIES ReadonlySet including "CONTROL" and "BORROWER_RECEIVABLE".
Core Logic Updates
convex/payments/cashLedger/postEntry.ts
Switched to postCashEntryArgsValidator; balance checks now skip accounts whose family is in NEGATIVE_BALANCE_EXEMPT_FAMILIES for both debit/credit; tightened CORRECTION constraint validations (requires admin actorType, actorId, causedBy, reason); refactored flow to capture persistEntry(...) result and await a new async nudge(ctx) before returning.
Tests
convex/payments/cashLedger/__tests__/constraintsAndBalanceExemption.test.ts
Added tests validating CORRECTION/REVERSAL constraint enforcement and negative-balance exemption behavior for BORROWER_RECEIVABLE vs non-exempt accounts.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client
participant Service as CashLedgerService
participant Validator as Validator
participant DB as Database
participant Nudge as NudgeService
style Client fill:rgba(135,206,235,0.5)
style Service fill:rgba(100,149,237,0.5)
style Validator fill:rgba(144,238,144,0.5)
style DB fill:rgba(255,228,181,0.5)
style Nudge fill:rgba(221,160,221,0.5)

Client->>Service: postCashEntry(args)
Service->>Validator: validate args (postCashEntryArgsValidator)
Validator-->>Service: validated args
Service->>Service: balanceCheck (skip families in NEGATIVE_BALANCE_EXEMPT_FAMILIES)
Service->>Service: constraintCheck (CORRECTION/REVERSAL rules)
Service->>DB: persistEntry(...)
DB-->>Service: result
Service->>Nudge: await nudge(ctx)
Nudge-->>Service: ack
Service-->>Client: return result

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through validators, neat and spry,
Balanced sets let some accounts fly,
Corrections ask for admin's nod,
Persistence saved, then nudged abroad,
A tiny rabbit cheers: done — oh my!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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 directly relates to the main objective: completing the 9-step cash ledger posting pipeline by strengthening constraints, adding balance exemptions, and wiring the final NUDGE step.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Connorbelez/eng148-posting-pipeline

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
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 21, 2026 20:52
Copilot AI review requested due to automatic review settings March 21, 2026 20:52
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

Completes remaining pieces of the cash-ledger postCashEntry 9-step posting pipeline by tightening CORRECTION constraints, expanding negative-balance exemptions per the tech design, wiring a step-9 NUDGE placeholder, and centralizing Convex arg validators for reuse.

Changes:

  • Extracted postCashEntry args validation into a shared validators.ts.
  • Added NEGATIVE_BALANCE_EXEMPT_FAMILIES (CONTROL + BORROWER_RECEIVABLE) and applied it in balance checks.
  • Strengthened CORRECTION constraint checks and wired the 9-step pipeline flow including a no-op NUDGE step.

Reviewed changes

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

File Description
convex/payments/cashLedger/validators.ts Adds reusable Convex validators for postCashEntry inputs.
convex/payments/cashLedger/types.ts Introduces NEGATIVE_BALANCE_EXEMPT_FAMILIES for balance-check exemptions.
convex/payments/cashLedger/postEntry.ts Applies new exemption set, strengthens CORRECTION constraints, and wires step-9 NUDGE + validator extraction.

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

Comment thread convex/payments/cashLedger/types.ts Outdated
Comment thread convex/payments/cashLedger/postEntry.ts
Comment thread convex/payments/cashLedger/postEntry.ts
Comment thread convex/payments/cashLedger/postEntry.ts
Copy link
Copy Markdown
Owner Author

@coderabbitai please review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 21, 2026

@Connorbelez Sure! I'll start reviewing PR #244 (ENG-148: Complete 9-step cash ledger posting pipeline) right away.


🧠 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: 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: 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/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.

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: 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.
✅ 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.

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.

🧹 Nitpick comments (2)
convex/payments/cashLedger/postEntry.ts (1)

155-159: Placeholder is clear; consider removing async until Phase 4.

The nudge function is declared async but performs no async operations. While harmless, removing async would avoid creating an unnecessary microtask on each call until Phase 4 wires real functionality.

♻️ Optional simplification
-async function nudge(_ctx: MutationCtx): Promise<void> {
+function nudge(_ctx: MutationCtx): void {
 	// Intentionally empty — Phase 4 will wire cursor consumer notifications here.
 }

Then at the call site (line 252):

-	await nudge(ctx);
+	nudge(ctx);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/payments/cashLedger/postEntry.ts` around lines 155 - 159, The nudge
function is declared async but does no async work; remove the async keyword from
the nudge declaration (keep signature async-free: function nudge(_ctx:
MutationCtx): void) and then update its call sites to drop unnecessary awaits
(e.g., replace await nudge(ctx) with nudge(ctx) where invoked in postEntry or
related functions) so no microtask is created until Phase 4.
convex/payments/cashLedger/validators.ts (1)

4-16: Consider deriving validator from the source constant to stay DRY.

The cashEntryTypeValidator duplicates the 11 entry types already defined in CASH_ENTRY_TYPES (types.ts). If one is updated without the other, they'll drift.

You could derive the validator programmatically, though v.union requires explicit arguments for type inference. Provide the first two validators directly, then spread the rest:

♻️ Suggested refactor
+import { CASH_ENTRY_TYPES } from "./types";
+
-export const cashEntryTypeValidator = v.union(
-	v.literal("OBLIGATION_ACCRUED"),
-	v.literal("CASH_RECEIVED"),
-	v.literal("CASH_APPLIED"),
-	v.literal("LENDER_PAYABLE_CREATED"),
-	v.literal("SERVICING_FEE_RECOGNIZED"),
-	v.literal("LENDER_PAYOUT_SENT"),
-	v.literal("OBLIGATION_WAIVED"),
-	v.literal("OBLIGATION_WRITTEN_OFF"),
-	v.literal("REVERSAL"),
-	v.literal("CORRECTION"),
-	v.literal("SUSPENSE_ESCALATED")
-);
+export const cashEntryTypeValidator = v.union(
+	v.literal(CASH_ENTRY_TYPES[0]),
+	v.literal(CASH_ENTRY_TYPES[1]),
+	...CASH_ENTRY_TYPES.slice(2).map((t) => v.literal(t))
+);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/payments/cashLedger/validators.ts` around lines 4 - 16,
cashEntryTypeValidator duplicates the literals in CASH_ENTRY_TYPES; change
cashEntryTypeValidator to derive its union from the source constant by
constructing the v.union call with the first two v.literal(...) arguments
explicitly and then spreading the rest generated from CASH_ENTRY_TYPES (map each
remaining entry to v.literal(entry)) so the validator stays DRY and updates
automatically when CASH_ENTRY_TYPES changes; reference cashEntryTypeValidator,
CASH_ENTRY_TYPES, and v.union to locate and implement this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@convex/payments/cashLedger/postEntry.ts`:
- Around line 155-159: The nudge function is declared async but does no async
work; remove the async keyword from the nudge declaration (keep signature
async-free: function nudge(_ctx: MutationCtx): void) and then update its call
sites to drop unnecessary awaits (e.g., replace await nudge(ctx) with nudge(ctx)
where invoked in postEntry or related functions) so no microtask is created
until Phase 4.

In `@convex/payments/cashLedger/validators.ts`:
- Around line 4-16: cashEntryTypeValidator duplicates the literals in
CASH_ENTRY_TYPES; change cashEntryTypeValidator to derive its union from the
source constant by constructing the v.union call with the first two
v.literal(...) arguments explicitly and then spreading the rest generated from
CASH_ENTRY_TYPES (map each remaining entry to v.literal(entry)) so the validator
stays DRY and updates automatically when CASH_ENTRY_TYPES changes; reference
cashEntryTypeValidator, CASH_ENTRY_TYPES, and v.union to locate and implement
this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b788078a-f5b1-4188-92e8-6f98bad4a293

📥 Commits

Reviewing files that changed from the base of the PR and between 01e3402 and 42bdda4.

📒 Files selected for processing (3)
  • convex/payments/cashLedger/postEntry.ts
  • convex/payments/cashLedger/types.ts
  • convex/payments/cashLedger/validators.ts

Connorbelez and others added 2 commits March 21, 2026 19:48
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Addresses PR review comments by adding integration tests for:
- CORRECTION constraint validation (admin actorType, actorId, causedBy, reason)
- REVERSAL constraint validation (causedBy required)
- BORROWER_RECEIVABLE negative balance exemption
- Non-exempt family (TRUST_CASH) negative balance rejection

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Connorbelez Connorbelez merged commit a898c25 into main Mar 21, 2026
1 of 3 checks passed
Connorbelez added a commit that referenced this pull request Apr 20, 2026
## Close remaining gaps in postCashEntry pipeline

- Strengthen CORRECTION constraint check: require causedBy (REQ-242), actorId, admin actorType, and reason (was missing causedBy + actorId)
- Add BORROWER_RECEIVABLE to NEGATIVE_BALANCE_EXEMPT_FAMILIES alongside CONTROL (Tech Design §9.1 Step 5)
- Wire NUDGE step 9 as no-op placeholder for Phase 4 cursor consumers
- Extract validators to validators.ts for reuse across mutations

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

* **Bug Fixes**
  * Balance checks now exempt additional account family for negative-balance rules, fixing inconsistent behavior.

* **Refactor**
  * Validation for cash entry submissions reworked to use a centralized schema and stricter rules for CORRECTION entries (requires admin actor, causal fields, and reason).

* **Tests**
  * Added tests covering correction/reversal constraints and the negative-balance exemption behavior.
<!-- 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