# Database Migrations

This directory contains SQL migrations that enforce ledger correctness boundaries.

## Migration Order

Run migrations in this order:

1. `001_ledger_idempotency.sql` - Add unique constraint for idempotency
2. `002_dual_ledger_outbox.sql` - Create outbox table
3. `003_outbox_state_machine.sql` - Enforce state machine constraints
4. `004_balance_constraints.sql` - Enforce balance integrity (apply after data cleanup)
5. `005_post_ledger_entry.sql` - Create atomic posting function
6. `006_sal_positions_fees.sql` - SAL extension: positions (asset x chain), fees, reconciliation snapshots

## Running Migrations

### Option 1: Direct SQL execution

```bash
# Set your database connection
export DATABASE_URL="postgresql://user:password@host:port/database"

# Run migrations in order
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
psql $DATABASE_URL -f db/migrations/005_post_ledger_entry.sql
psql $DATABASE_URL -f db/migrations/006_sal_positions_fees.sql
```

### Option 2: Prisma migrate (if using Prisma migrations)

These SQL files can be added to a Prisma migration:

```bash
npx prisma migrate dev --name add_ledger_correctness_boundaries
```

Then copy the SQL into the generated migration file.

## Important Notes

### Column Naming

These migrations assume **snake_case** column names in the database (Prisma default).

If your database uses camelCase, adjust the SQL accordingly:
- `ledger_id` → `ledgerId`
- `debit_account_id` → `debitAccountId`
- etc.

### Balance Constraints

The balance constraints in `004_balance_constraints.sql` will fail if you have existing inconsistent data. 

**Before applying:**
1. Audit existing balances
2. Fix any inconsistencies
3. Then apply the constraints

### Testing

After applying migrations, verify:

```sql
-- Check idempotency constraint exists
SELECT constraint_name 
FROM information_schema.table_constraints 
WHERE table_name = 'ledger_entries' 
  AND constraint_name LIKE '%reference%';

-- Check outbox table exists
SELECT COUNT(*) FROM dual_ledger_outbox;

-- Test posting function
SELECT * FROM post_ledger_entry(
  'Test'::TEXT,
  'account1'::TEXT,
  'account2'::TEXT,
  100::NUMERIC,
  'USD'::TEXT,
  'fiat'::TEXT,
  'Type_A'::TEXT,
  'test-ref-123'::TEXT,
  NULL::NUMERIC,
  NULL::JSONB
);
```

## Rollback

These migrations are designed to be additive. To rollback:

1. Drop the function: `DROP FUNCTION IF EXISTS post_ledger_entry(...);`
2. Drop the outbox table: `DROP TABLE IF EXISTS dual_ledger_outbox CASCADE;`
3. Remove constraints: `ALTER TABLE ledger_entries DROP CONSTRAINT IF EXISTS ledger_entries_unique_ledger_reference;`
4. Remove balance constraints: `ALTER TABLE bank_accounts DROP CONSTRAINT IF EXISTS ...;`
