Skip to content

ENG-27#90

Merged
Connorbelez merged 3 commits intomainfrom
ENG-27
Mar 17, 2026
Merged

ENG-27#90
Connorbelez merged 3 commits intomainfrom
ENG-27

Conversation

@Connorbelez
Copy link
Copy Markdown
Owner

@Connorbelez Connorbelez commented Mar 17, 2026

specs

ft. ENG-27: Extract postEntry 9-step pipeline as single ledger write path

Implement the core postEntry pipeline in convex/ledger/postEntry.ts — the
only code path that modifies accounts or inserts journal entries. Refactor
mutations.ts to delegate all validation to the extracted pipeline.

  • Create 9-step pipeline: validate → idempotency → resolve → type check →
    balance check → constraint check → sequence → persist → nudge
  • All errors now structured ConvexError with specific codes
  • AUDIT_ONLY entry types (SHARES_RESERVED, SHARES_VOIDED) skip cumulative
    balance updates
  • Balance checks use available balance (posted - pendingCredits)
  • WORLD account exempt from balance constraints
  • Replace public postEntry with postEntryDirect internalMutation
  • 23 new pipeline tests covering all 9 entry types and rejection rules

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

Summary by CodeRabbit

Release Notes

  • New Features

    • Added audit-only entry types (SHARES_RESERVED, SHARES_VOIDED) that create journal entries without affecting cumulative balance calculations.
    • Enhanced ledger entry validation with specific error codes for improved error handling.
  • Tests

    • Expanded test coverage for ledger entry operations, including validation scenarios, idempotency checks, and error handling cases.

@linear
Copy link
Copy Markdown

linear bot commented Mar 17, 2026

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new postEntry pipeline module that centralizes ledger entry validation and posting logic, refactors mutations.ts to delegate to this pipeline and expose postEntryDirect as an internal mutation for testing, adds AUDIT_ONLY_ENTRY_TYPES constant for audit-only entries that skip cumulative updates, and includes comprehensive test coverage including a new postEntry.test.ts file and updates to existing tests.

Changes

Cohort / File(s) Summary
Core Pipeline Implementation
convex/ledger/postEntry.ts, convex/ledger/constants.ts
Introduces a new 9-step postEntry pipeline exporting PostEntryInput interface and postEntry function. Covers input validation, idempotency checks, account resolution, type validation (with mortgage alignment and CORRECTION preconditions), balance checks (with WORLD/AUDIT_ONLY exemptions), per-entry-type constraint checks, sequence assignment, atomic persistence with cumulative skip for AUDIT_ONLY types, and a nudge placeholder. Adds AUDIT_ONLY_ENTRY_TYPES constant (ReadonlySet with "SHARES_RESERVED" and "SHARES_VOIDED") for entries that create journal records without updating cumulatives.
Mutations Refactor
convex/ledger/mutations.ts
Removes ~290 lines of internal postEntry implementation and validation helpers. Imports postEntry from ./postEntry and exports postEntryDirect as an internalMutation for test access. Updates all convenience mutations (mintMortgage, burnMortgage, issueShares, transferShares, redeemShares) to delegate to the imported postEntry. Migrates error handling to ConvexError with specific codes. Cleans up unused imports (MIN_FRACTION, getNextSequenceNumber, various type enums).
Test Updates
convex/ledger/__tests__/accounts.test.ts
Adds initialization of sequence counter via api.ledger.sequenceCounter.initializeSequenceCounter before minting mortgage in getTreasuryAccount test to satisfy postEntry pipeline requirements.
Test Migration
convex/ledger/__tests__/ledger.test.ts
Switches test cases from public api.ledger.mutations.postEntry to internal.ledger.mutations.postEntryDirect. Updates imports to include internal from "../../_generated/api". Adjusts error assertion text to match new ConvexError semantics (e.g., "violates minimum position" → "violates minimum"). Modifies test setup for CORRECTION entry tests with updated account directions and source field handling.
New Test Suite
convex/ledger/__tests__/postEntry.test.ts
Comprehensive integration test suite (~952 lines) covering: happy-path tests for 6 entry types (MORTGAGE_MINTED, SHARES_ISSUED, SHARES_TRANSFERRED, SHARES_REDEEMED, MORTGAGE_BURNED, CORRECTION), 3 reservation types (SHARES_RESERVED, SHARES_COMMITTED, SHARES_VOIDED), extensive rejection tests for ConvexError codes (INVALID_AMOUNT, ACCOUNT_NOT_FOUND, TYPE_MISMATCH, INSUFFICIENT_BALANCE, CORRECTION preconditions, etc.), idempotency behavior, sequence monotonicity validation, sell-all scenario, and WORLD account exemption checks.
Documentation & Specs
specs/ENG-27/tasks.md, specs/ENG-27/chunks/manifest.md, specs/ENG-27/chunks/chunk-*.../context.md, specs/ENG-27/chunks/chunk-*.../tasks.md
Adds specification and task documentation for ENG-27 feature: chunk-01 covers postEntry pipeline implementation, chunk-02 details mutations refactor, chunk-03 outlines test updates. Includes context, detailed task lists, and completion manifest.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Test Client
    participant MutCtx as MutationCtx
    participant Val as Validators
    participant Accts as Accounts
    participant SeqCtr as Sequence Counter
    participant DB as Database
    
    Client->>MutCtx: postEntry(ctx, args)
    
    MutCtx->>Val: validateInput (amount, accounts)
    Val-->>MutCtx: ✓ or Error
    
    MutCtx->>DB: checkIdempotency (idempotencyKey)
    DB-->>MutCtx: exists? or new
    
    MutCtx->>Accts: resolveAccounts (debit, credit)
    Accts->>DB: fetch account docs
    DB-->>Accts: account data
    Accts-->>MutCtx: accounts or ACCOUNT_NOT_FOUND
    
    MutCtx->>Val: typeCheck (entryType, accounts, mortgage)
    Val-->>MutCtx: ✓ or TYPE_MISMATCH/CORRECTION error
    
    MutCtx->>Accts: balanceCheck (debit balance vs amount)
    Accts-->>MutCtx: ✓ or INSUFFICIENT_BALANCE
    
    MutCtx->>Val: constraintCheck (entry-type specific rules)
    Val-->>MutCtx: ✓ or constraint violation
    
    MutCtx->>SeqCtr: getNextSequenceNumber ()
    SeqCtr->>DB: read sequence counter
    DB-->>SeqCtr: current value
    SeqCtr-->>MutCtx: next sequence
    
    MutCtx->>DB: persist (update cumulative, insert journal entry)
    DB-->>MutCtx: journal entry doc
    
    MutCtx->>Client: return ledger_journal_entries doc
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

The diff spans multiple heterogeneous components: a new 593-line postEntry pipeline with intricate validation logic, a substantial ~290-line mutations.ts refactor with error mapping, a comprehensive 952-line test suite with diverse scenarios, and supporting documentation. While individual file changes follow consistent patterns, the logic density (particularly in postEntry.ts constraint checks, balance validation, and error handling), the breadth of affected test files, and the architectural shift from public to internal mutation exposure demand careful cross-component reasoning.

Possibly related PRs

  • ft. ENG-26 — Implement sequence counter helpers for monotonic gap-free numbering #84: Implements sequenceCounter module (initializeSequenceCounter, getNextSequenceNumber) and updates tests to initialize the counter—directly enables the sequence assignment step in the new postEntry pipeline.
  • debug #8: Modifies ledger codebase including mutations.ts, constants.ts, and ledger test files with test mutation path updates—shares overlapping code surface with this PR's mutations refactor and test migration.

Poem

🐰 A ledger pipeline, nine steps strong,
Validation, idempotency, balances throng,
Constraints and sequences now centralized tight,
Mutations refactored, the tests shining bright!
AUDIT_ONLY hops past the cumulatives chain,
PostEntry pipeline—ledger's refrain! ✨

🚥 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-27' is a ticket identifier but does not clearly summarize the main change; it lacks descriptive information about what the pull request actually implements. Revise the title to be more descriptive of the core change, such as 'Extract postEntry pipeline into dedicated module' or 'Refactor ledger mutations to centralize entry posting'.
✅ 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 docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ENG-27
📝 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

Connorbelez commented Mar 17, 2026

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

@Connorbelez Connorbelez changed the title specs ENG-27 Mar 17, 2026
@Connorbelez Connorbelez marked this pull request as ready for review March 17, 2026 00:33
Copilot AI review requested due to automatic review settings March 17, 2026 00:33
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 17, 2026

Greptile Summary

This PR extracts the core ledger write path into a structured 9-step pipeline (convex/ledger/postEntry.ts) and makes it the single code path for all account mutations and journal entry inserts. The old inline postEntryInternal in mutations.ts is removed entirely, and the previous public postEntry mutation is replaced with a postEntryDirect internalMutation to prevent external callers from bypassing higher-level convenience mutations. All errors are now structured ConvexError objects with specific codes, and AUDIT_ONLY entry types (SHARES_RESERVED, SHARES_VOIDED) correctly skip cumulative balance updates.

Key changes:

  • New convex/ledger/postEntry.ts with 9 clearly-labeled pipeline steps
  • convex/ledger/types.ts extracted with ENTRY_TYPE_ACCOUNT_MAP and shared type definitions
  • UNITS_PER_MORTGAGETOTAL_SUPPLY, MIN_POSITION_UNITSMIN_FRACTION renames propagated throughout
  • 23 new pipeline tests covering all entry types and structured error codes

Issues found:

  • reservationId is present in PostEntryInput and written in persist(), but is absent from postEntryArgsValidator — Convex will silently strip it on any call through postEntryDirect
  • constraintCorrection no longer checks source.actor, which was enforced by the previous implementation — any { type: "user" } source (with no actor identity) now passes the CORRECTION admin gate
  • CORRECTION_REQUIRES_CAUSED_BY is checked in both Step 4 (typeCheck) and Step 6 (constraintCorrection), making the Step 6 check dead code and causing the wrong error to surface when both causedBy and admin authorization are missing
  • constraintSharesTransferred uses getPostedBalance for the seller min-fraction check while constraintSharesReserved uses getAvailableBalance, creating an inconsistency that could allow a seller's available balance to drop below MIN_FRACTION

Confidence Score: 2/5

  • Not safe to merge — contains a security regression (CORRECTION no longer requires source.actor) and a data-loss bug (reservationId stripped by validator).
  • Two logic-level issues block a clean merge: (1) postEntryArgsValidator is missing reservationId, silently discarding it for all postEntryDirect calls, and (2) constraintCorrection dropped the source.actor requirement present in the prior code, weakening the CORRECTION audit trail. Additionally, the duplicate causedBy check in Step 4 causes misleading error codes when CORRECTION authorization is absent. The pipeline architecture itself is solid and well-tested, but these correctness issues need to be addressed first.
  • convex/ledger/postEntry.ts (constraint logic regression and duplicate check) and convex/ledger/validators.ts (missing reservationId field) need attention before merging.

Important Files Changed

Filename Overview
convex/ledger/postEntry.ts New 9-step pipeline — core of the PR. Contains a duplicate causedBy check that causes misleading error ordering, a dropped source.actor enforcement regression in constraintCorrection, and inconsistent use of getPostedBalance vs getAvailableBalance for seller min-fraction checks.
convex/ledger/validators.ts postEntryArgsValidator is missing the reservationId field that exists in PostEntryInput, causing the field to be silently stripped by Convex when calling postEntryDirect.
convex/ledger/mutations.ts Significantly simplified — old inline validation logic replaced with delegation to postEntry(). postEntry public mutation replaced with postEntryDirect internalMutation. Tier 2 mutations unchanged in logic. Looks correct.
convex/ledger/types.ts New file extracting EntryType, AccountType, EventSource, and ENTRY_TYPE_ACCOUNT_MAP. Clean, well-structured, no issues found.
convex/ledger/constants.ts Renamed UNITS_PER_MORTGAGETOTAL_SUPPLY and MIN_POSITION_UNITSMIN_FRACTION. Added AUDIT_ONLY_ENTRY_TYPES set. All renames propagated correctly.
convex/ledger/tests/postEntry.test.ts 23 new tests covering all 9 entry types and key rejection codes. Well-structured with proper helpers. The ACCOUNT_NOT_FOUND test only checks rejects.toThrow() without asserting the specific error code — weaker coverage than other tests.
convex/ledger/tests/ledger.test.ts Existing tests updated: postEntry public mutation calls replaced with postEntryDirect internal mutation calls; error message matchers updated to new structured error codes. Debit/credit swap in two CORRECTION tests with comments explaining the intentional change to avoid INSUFFICIENT_BALANCE ordering.
convex/demo/ledger.ts Updated to use TOTAL_SUPPLY constant rename. No logic changes.

Sequence Diagram

sequenceDiagram
    participant Caller as Caller (mutation)
    participant PE as postEntry()
    participant DB as Convex DB

    Caller->>PE: postEntry(ctx, args)
    PE->>PE: Step 1: validateInput()<br/>(INVALID_AMOUNT, SAME_ACCOUNT)
    PE->>DB: Step 2: checkIdempotency()<br/>query by_idempotency
    alt existing entry found
        DB-->>PE: existing Doc
        PE-->>Caller: return existing (no-op)
    end
    DB-->>PE: null
    PE->>DB: Step 3: resolveAccounts()<br/>ctx.db.get(debitId, creditId)
    DB-->>PE: debitAccount, creditAccount
    PE->>PE: Step 4: typeCheck()<br/>(TYPE_MISMATCH, MORTGAGE_MISMATCH,<br/>CORRECTION_REQUIRES_CAUSED_BY*)
    PE->>PE: Step 5: balanceCheck()<br/>(INSUFFICIENT_BALANCE)<br/>skipped for WORLD & AUDIT_ONLY
    PE->>PE: Step 6: constraintCheck()<br/>per-entry-type strategy<br/>(INVALID_MINT_AMOUNT, MIN_FRACTION_VIOLATED,<br/>CORRECTION_REQUIRES_ADMIN, etc.)
    PE->>DB: Step 7: getNextSequenceNumber()
    DB-->>PE: sequenceNumber (bigint)
    PE->>DB: Step 8: persist()<br/>patch cumulativeDebits/Credits<br/>(skipped for AUDIT_ONLY)<br/>insert ledger_journal_entries
    DB-->>PE: entryId
    PE->>DB: ctx.db.get(entryId)
    DB-->>PE: Doc
    PE->>PE: Step 9: nudge() [no-op]
    PE-->>Caller: Doc<"ledger_journal_entries">
Loading

Comments Outside Diff (1)

  1. convex/ledger/validators.ts, line 40-52 (link)

    reservationId missing from postEntryArgsValidator

    PostEntryInput declares reservationId?: Id<"ledger_reservations"> and the persist step writes reservationId: args.reservationId to the journal entry. However, postEntryArgsValidator (used by postEntryDirect) does not include this field.

    Convex strips undeclared fields at the runtime boundary, so any caller passing a reservationId through postEntryDirect will silently lose it — the persisted journal entry will always have reservationId as undefined.

    Add reservationId: v.optional(v.id("ledger_reservations")) to postEntryArgsValidator to align the validator with PostEntryInput.

Last reviewed commit: 575cfc6

Comment thread convex/ledger/postEntry.ts Outdated
Comment thread convex/ledger/postEntry.ts Outdated
Comment thread convex/ledger/postEntry.ts
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new centralized postEntry pipeline for the mortgage ownership ledger, refactors existing ledger mutations to route through that pipeline, and expands/updates tests to cover the new behavior and error model.

Changes:

  • Added convex/ledger/postEntry.ts implementing a 9-step posting pipeline (validation, idempotency, balance/constraints, sequencing, persistence).
  • Refactored convex/ledger/mutations.ts to remove the public postEntry mutation and expose postEntryDirect as an internalMutation, with convenience mutations calling the shared pipeline.
  • Updated constants/types/validators and added/updated tests to reflect the new pipeline and error handling.

Reviewed changes

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

Show a summary per file
File Description
specs/ENG-27/tasks.md Adds ENG-27 master task checklist for the pipeline/refactor/test work.
specs/ENG-27/chunks/manifest.md Adds chunk manifest for ENG-27 work breakdown.
specs/ENG-27/chunks/chunk-01-postentry-pipeline/tasks.md Adds chunk 01 task checklist for the postEntry pipeline.
specs/ENG-27/chunks/chunk-01-postentry-pipeline/context.md Adds detailed design/context for the 9-step pipeline and error codes.
specs/ENG-27/chunks/chunk-02-mutations-refactor/tasks.md Adds chunk 02 task checklist for mutations refactor.
specs/ENG-27/chunks/chunk-02-mutations-refactor/context.md Adds refactor plan/context for removing old helpers and wiring new pipeline.
specs/ENG-27/chunks/chunk-03-tests/tasks.md Adds chunk 03 task checklist for test updates/new tests.
specs/ENG-27/chunks/chunk-03-tests/context.md Adds testing guidance for internal mutation usage and ConvexError assertions.
convex/ledger/constants.ts Renames supply/min constants and introduces AUDIT_ONLY_ENTRY_TYPES.
convex/ledger/types.ts Introduces typed entry/account/source enums plus ENTRY_TYPE_ACCOUNT_MAP.
convex/ledger/validators.ts Adds reservation/correction/reservation-phase validators.
convex/ledger/validation.ts Updates supply invariant to use TOTAL_SUPPLY.
convex/ledger/postEntry.ts New core pipeline implementation for posting ledger entries.
convex/ledger/mutations.ts Refactors to call postEntry pipeline; adds postEntryDirect internal mutation; migrates errors to ConvexError.
convex/ledger/tests/postEntry.test.ts Adds comprehensive pipeline-focused tests (happy paths, audit-only behavior, error codes, idempotency/sequence cases).
convex/ledger/tests/ledger.test.ts Updates existing tests to call postEntryDirect and adjusts assertions for new error messages.
convex/ledger/tests/accounts.test.ts Initializes sequence counter before minting in the affected test.
convex/demo/ledger.ts Updates demo code to use TOTAL_SUPPLY.
convex/auth/tests/resourceChecks.test.ts Fixes ledger sequence counter table naming in comments/describe blocks.

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

Comment thread convex/ledger/postEntry.ts
Comment thread convex/ledger/postEntry.ts
Comment thread convex/ledger/__tests__/ledger.test.ts
Comment thread convex/ledger/__tests__/ledger.test.ts Outdated
Comment thread convex/ledger/__tests__/postEntry.test.ts
Comment thread specs/ENG-27/chunks/chunk-03-tests/tasks.md
Comment thread specs/ENG-27/chunks/chunk-01-postentry-pipeline/tasks.md
Comment thread specs/ENG-27/chunks/chunk-02-mutations-refactor/tasks.md
Comment thread specs/ENG-27/chunks/manifest.md
Connorbelez and others added 3 commits March 16, 2026 20:51
…path

Implement the core postEntry pipeline in convex/ledger/postEntry.ts — the
only code path that modifies accounts or inserts journal entries. Refactor
mutations.ts to delegate all validation to the extracted pipeline.

- Create 9-step pipeline: validate → idempotency → resolve → type check →
  balance check → constraint check → sequence → persist → nudge
- All errors now structured ConvexError with specific codes
- AUDIT_ONLY entry types (SHARES_RESERVED, SHARES_VOIDED) skip cumulative
  balance updates
- Balance checks use available balance (posted - pendingCredits)
- WORLD account exempt from balance constraints
- Replace public postEntry with postEntryDirect internalMutation
- 23 new pipeline tests covering all 9 entry types and rejection rules

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add source.actor check for CORRECTION (regression from old code)
- Use getAvailableBalance for seller in transferShares constraint
- Validate args.mortgageId matches account mortgageIds in typeCheck
- Add explicit INSUFFICIENT_BALANCE check in SHARES_RESERVED before
  min position check
- Keep CORRECTION preconditions (admin/causedBy/reason) in typeCheck
  to fire before balanceCheck, with correct ordering
- Fix test regexes to match ConvexError message format
- Add try/catch to getConvexErrorCode JSON.parse

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

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
convex/ledger/constants.ts (1)

23-32: LGTM with optional type improvement.

The AUDIT_ONLY distinction correctly separates reservation tracking (journal entries only) from actual balance movements. SHARES_RESERVED and SHARES_VOIDED create audit trails without affecting cumulative balances, while SHARES_COMMITTED finalizes the transfer.

💡 Optional: Consider using EntryType for stronger type safety

Using ReadonlySet<string> allows any string. For stricter compile-time checking:

+import type { EntryType } from "./types";
+
-export const AUDIT_ONLY_ENTRY_TYPES: ReadonlySet<string> = new Set([
+export const AUDIT_ONLY_ENTRY_TYPES: ReadonlySet<EntryType> = new Set<EntryType>([
 	"SHARES_RESERVED",
 	"SHARES_VOIDED",
-]);
+] as const);

This would catch typos at compile time if EntryType changes.

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

In `@convex/ledger/constants.ts` around lines 23 - 32, AUDIT_ONLY_ENTRY_TYPES is
currently typed as ReadonlySet<string>, which permits arbitrary strings; change
its type to ReadonlySet<EntryType> to enforce compile-time safety (use the
existing EntryType union/type in this module or import it if declared
elsewhere), update the new Set initializer to be typed as ReadonlySet<EntryType>
(or use a const assertion if needed) and ensure any usages still accept
EntryType values; reference AUDIT_ONLY_ENTRY_TYPES and EntryType when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@convex/ledger/__tests__/postEntry.test.ts`:
- Around line 45-59: The function getConvexErrorCode should guard against
malformed JSON in e.data: wrap the JSON.parse(e.data) call in a try/catch inside
the branch that checks typeof data === "string" (within getConvexErrorCode) and
return an empty string on parse failure (or otherwise fall through to the
existing object/empty return) so a bad string doesn't throw and obscure test
failures; keep the existing ConvexError instanceof check and the subsequent
object branch unchanged.

---

Nitpick comments:
In `@convex/ledger/constants.ts`:
- Around line 23-32: AUDIT_ONLY_ENTRY_TYPES is currently typed as
ReadonlySet<string>, which permits arbitrary strings; change its type to
ReadonlySet<EntryType> to enforce compile-time safety (use the existing
EntryType union/type in this module or import it if declared elsewhere), update
the new Set initializer to be typed as ReadonlySet<EntryType> (or use a const
assertion if needed) and ensure any usages still accept EntryType values;
reference AUDIT_ONLY_ENTRY_TYPES and EntryType when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9abd916-307f-44a6-8aae-f9800a5c39fc

📥 Commits

Reviewing files that changed from the base of the PR and between cd8e064 and 2bc4fbd.

📒 Files selected for processing (14)
  • convex/ledger/__tests__/accounts.test.ts
  • convex/ledger/__tests__/ledger.test.ts
  • convex/ledger/__tests__/postEntry.test.ts
  • convex/ledger/constants.ts
  • convex/ledger/mutations.ts
  • convex/ledger/postEntry.ts
  • specs/ENG-27/chunks/chunk-01-postentry-pipeline/context.md
  • specs/ENG-27/chunks/chunk-01-postentry-pipeline/tasks.md
  • specs/ENG-27/chunks/chunk-02-mutations-refactor/context.md
  • specs/ENG-27/chunks/chunk-02-mutations-refactor/tasks.md
  • specs/ENG-27/chunks/chunk-03-tests/context.md
  • specs/ENG-27/chunks/chunk-03-tests/tasks.md
  • specs/ENG-27/chunks/manifest.md
  • specs/ENG-27/tasks.md

Comment on lines +45 to +59
function getConvexErrorCode(e: unknown): string {
expect(e).toBeInstanceOf(ConvexError);
if (!(e instanceof ConvexError)) {
throw new Error("Expected ConvexError");
}
const data = e.data;
if (typeof data === "string") {
const parsed = JSON.parse(data) as { code?: string };
return parsed.code ?? "";
}
if (typeof data === "object" && data !== null) {
return (data as { code?: string }).code ?? "";
}
return "";
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing try/catch around JSON.parse in getConvexErrorCode.

The PR description mentions "adds try/catch around JSON.parse for getConvexErrorCode", but the implementation lacks error handling. If e.data is a malformed string, JSON.parse will throw and obscure the actual test failure.

🛡️ Proposed fix to add error handling
 function getConvexErrorCode(e: unknown): string {
 	expect(e).toBeInstanceOf(ConvexError);
 	if (!(e instanceof ConvexError)) {
 		throw new Error("Expected ConvexError");
 	}
 	const data = e.data;
 	if (typeof data === "string") {
-		const parsed = JSON.parse(data) as { code?: string };
-		return parsed.code ?? "";
+		try {
+			const parsed = JSON.parse(data) as { code?: string };
+			return parsed.code ?? "";
+		} catch {
+			return "";
+		}
 	}
 	if (typeof data === "object" && data !== null) {
 		return (data as { code?: string }).code ?? "";
 	}
 	return "";
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getConvexErrorCode(e: unknown): string {
expect(e).toBeInstanceOf(ConvexError);
if (!(e instanceof ConvexError)) {
throw new Error("Expected ConvexError");
}
const data = e.data;
if (typeof data === "string") {
const parsed = JSON.parse(data) as { code?: string };
return parsed.code ?? "";
}
if (typeof data === "object" && data !== null) {
return (data as { code?: string }).code ?? "";
}
return "";
}
function getConvexErrorCode(e: unknown): string {
expect(e).toBeInstanceOf(ConvexError);
if (!(e instanceof ConvexError)) {
throw new Error("Expected ConvexError");
}
const data = e.data;
if (typeof data === "string") {
try {
const parsed = JSON.parse(data) as { code?: string };
return parsed.code ?? "";
} catch {
return "";
}
}
if (typeof data === "object" && data !== null) {
return (data as { code?: string }).code ?? "";
}
return "";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/ledger/__tests__/postEntry.test.ts` around lines 45 - 59, The function
getConvexErrorCode should guard against malformed JSON in e.data: wrap the
JSON.parse(e.data) call in a try/catch inside the branch that checks typeof data
=== "string" (within getConvexErrorCode) and return an empty string on parse
failure (or otherwise fall through to the existing object/empty return) so a bad
string doesn't throw and obscure test failures; keep the existing ConvexError
instanceof check and the subsequent object branch unchanged.

Copy link
Copy Markdown
Owner Author

Connorbelez commented Mar 17, 2026

Merge activity

  • Mar 17, 2:31 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Mar 17, 2:31 AM UTC: @Connorbelez merged this pull request with Graphite.

@Connorbelez Connorbelez merged commit 421a544 into main Mar 17, 2026
1 of 3 checks passed
Connorbelez added a commit that referenced this pull request Apr 20, 2026
specs

ft. ENG-27: Extract postEntry 9-step pipeline as single ledger write path

Implement the core postEntry pipeline in convex/ledger/postEntry.ts — the
only code path that modifies accounts or inserts journal entries. Refactor
mutations.ts to delegate all validation to the extracted pipeline.

- Create 9-step pipeline: validate → idempotency → resolve → type check →
  balance check → constraint check → sequence → persist → nudge
- All errors now structured ConvexError with specific codes
- AUDIT_ONLY entry types (SHARES_RESERVED, SHARES_VOIDED) skip cumulative
  balance updates
- Balance checks use available balance (posted - pendingCredits)
- WORLD account exempt from balance constraints
- Replace public postEntry with postEntryDirect internalMutation
- 23 new pipeline tests covering all 9 entry types and rejection rules

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

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

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Added audit-only entry types (SHARES_RESERVED, SHARES_VOIDED) that create journal entries without affecting cumulative balance calculations.
  * Enhanced ledger entry validation with specific error codes for improved error handling.

* **Tests**
  * Expanded test coverage for ledger entry operations, including validation scenarios, idempotency checks, and error handling cases.

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