5.1 KiB
5.1 KiB
Ledger Correctness Boundaries - Implementation Checklist
✅ Completed
-
SQL migration files created
001_ledger_idempotency.sql- Unique constraint002_dual_ledger_outbox.sql- Outbox table003_outbox_state_machine.sql- State transitions004_balance_constraints.sql- Balance integrity005_post_ledger_entry.sql- Atomic posting function
-
Prisma schema updated
dual_ledger_outboxmodel added with correct mappings
-
Worker service created
DualLedgerOutboxWorkerwith retry/backoffrun-dual-ledger-outbox.tsrunner
-
GSS Master Ledger service refactored
- DBIS-first posting
- Outbox pattern integration
- Transactional guarantees
-
Ledger posting module created
- Guarded access enforcement
- SQL function wrapper
🔄 Next Steps (Deployment)
1. Verify Database Column Names
CRITICAL: Before running migrations, verify your database uses snake_case or camelCase:
-- 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:
-- 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
# 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
npx prisma generate
5. Deploy Worker
# 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:
// 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():
// 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:
-- 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
// 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
// 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:
- Stop worker process
- Rollback migrations (see
LEDGER_CORRECTNESS_BOUNDARIES.md) - Revert code changes
- Investigate and fix issues
- Re-apply after fixes