# Ledger Correctness Boundaries - Implementation Checklist ## โœ… Completed - [x] SQL migration files created - [x] `001_ledger_idempotency.sql` - Unique constraint - [x] `002_dual_ledger_outbox.sql` - Outbox table - [x] `003_outbox_state_machine.sql` - State transitions - [x] `004_balance_constraints.sql` - Balance integrity - [x] `005_post_ledger_entry.sql` - Atomic posting function - [x] Prisma schema updated - [x] `dual_ledger_outbox` model added with correct mappings - [x] Worker service created - [x] `DualLedgerOutboxWorker` with retry/backoff - [x] `run-dual-ledger-outbox.ts` runner - [x] GSS Master Ledger service refactored - [x] DBIS-first posting - [x] Outbox pattern integration - [x] Transactional guarantees - [x] Ledger posting module created - [x] Guarded access enforcement - [x] SQL function wrapper ## ๐Ÿ”„ Next Steps (Deployment) ### 1. Verify Database Column Names **CRITICAL**: Before running migrations, verify your database uses snake_case or camelCase: ```sql -- Check actual column names SELECT column_name FROM information_schema.columns WHERE table_name = 'ledger_entries' AND column_name IN ('ledger_id', 'ledgerId', 'reference_id', 'referenceId') ORDER BY column_name; ``` If columns are camelCase, update SQL migrations accordingly. ### 2. Audit Existing Data Before applying balance constraints: ```sql -- Check for inconsistent balances SELECT id, balance, available_balance, reserved_balance FROM bank_accounts WHERE available_balance < 0 OR reserved_balance < 0 OR available_balance > balance OR (available_balance + reserved_balance) > balance; ``` Fix any inconsistencies before applying `004_balance_constraints.sql`. ### 3. Run Migrations ```bash # Set database URL export DATABASE_URL="postgresql://user:password@host:port/database" # Run in order cd dbis_core psql $DATABASE_URL -f db/migrations/001_ledger_idempotency.sql psql $DATABASE_URL -f db/migrations/002_dual_ledger_outbox.sql psql $DATABASE_URL -f db/migrations/003_outbox_state_machine.sql psql $DATABASE_URL -f db/migrations/004_balance_constraints.sql # After data cleanup psql $DATABASE_URL -f db/migrations/005_post_ledger_entry.sql ``` ### 4. Generate Prisma Client ```bash npx prisma generate ``` ### 5. Deploy Worker ```bash # Add to package.json scripts "worker:dual-ledger-outbox": "ts-node src/workers/run-dual-ledger-outbox.ts" # Run worker npm run worker:dual-ledger-outbox # Or use PM2 pm2 start src/workers/run-dual-ledger-outbox.ts --name dual-ledger-outbox ``` ### 6. Implement SCB API Client Update `DualLedgerOutboxWorker.callScbLedgerApi()` with real HTTP client: ```typescript // Replace placeholder with actual SCB API call const response = await fetch(`${SCB_API_BASE_URL}/${sovereignBankId}/ledger/post`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey, // CRITICAL }, body: JSON.stringify({ ledgerId, ...payload, }), }); ``` ### 7. Update Existing Code Replace direct `ledgerService.postDoubleEntry()` calls with `ledgerPostingModule.postEntry()`: ```typescript // OLD (banned) await ledgerService.postDoubleEntry(...); // NEW (required) await ledgerPostingModule.postEntry({ ledgerId: 'Master', debitAccountId: '...', creditAccountId: '...', amount: '100.00', currencyCode: 'USD', assetType: 'fiat', transactionType: 'Type_A', referenceId: 'unique-ref-id', }); ``` ### 8. Add Monitoring Monitor outbox queue: ```sql -- Queue depth SELECT status, COUNT(*) FROM dual_ledger_outbox GROUP BY status; -- Failed jobs needing attention SELECT outbox_id, attempts, last_error, last_attempt_at FROM dual_ledger_outbox WHERE status = 'FAILED' ORDER BY last_attempt_at DESC LIMIT 10; ``` ## ๐Ÿงช Testing ### Test Atomic Posting ```typescript // Should succeed await ledgerPostingModule.postEntry({ ledgerId: 'Test', debitAccountId: 'account1', creditAccountId: 'account2', amount: '100.00', currencyCode: 'USD', assetType: 'fiat', transactionType: 'Type_A', referenceId: 'test-1', }); // Should fail (duplicate reference_id) await ledgerPostingModule.postEntry({ // ... same params with same referenceId }); ``` ### Test Outbox Pattern ```typescript // Post to master ledger const result = await gssMasterLedgerService.postToMasterLedger({ nodeId: 'SSN-1', sourceBankId: 'SCB-1', destinationBankId: 'SCB-2', amount: '1000.00', currencyCode: 'USD', assetType: 'fiat', }, 'test-ref-123'); // Check outbox was created const outbox = await prisma.dual_ledger_outbox.findFirst({ where: { referenceId: 'test-ref-123' }, }); console.log(outbox.status); // Should be 'QUEUED' ``` ## ๐Ÿ“‹ Verification Checklist - [ ] Migrations applied successfully - [ ] Prisma client regenerated - [ ] Worker process running - [ ] SCB API client implemented - [ ] Existing code updated to use `ledgerPostingModule` - [ ] Monitoring in place - [ ] Tests passing - [ ] Documentation updated ## ๐Ÿšจ Rollback Plan If issues occur: 1. Stop worker process 2. Rollback migrations (see `LEDGER_CORRECTNESS_BOUNDARIES.md`) 3. Revert code changes 4. Investigate and fix issues 5. Re-apply after fixes