Dubai to Paris  |  38,000 ft  |  No Sacred Cows

Core Banking
First Principles

Takeoff was delayed by an hour. I used the time for prep work - first-principles design specs, a focus group of assumptions to challenge, and mapping out the repo. Once we were finally airborne, all seven layers shipped through Claude Code. From event-sourced to double-entry. Never any inherited assumptions.

Layers
7 / 7
Tests
106
Decisions
41
Lines
~6,000
Stack
TS
Altitude
38,001 ft
Simulated Focus Group
Challenging Every Assumption

Before writing a single line of code, we ran a simulated focus group - not with real people, but with AI-generated profiles representing the voices that would normally be in the room when a core banking system gets designed. Each profile brought a different lens, a different set of biases, and a different definition of "good enough."

We put seven inherited semi-sacred beliefs in front of them - the kind every core banking system carries like excess luggage full of winter clothes on a Maldives trip. We listed the things the industry takes for granted about how a bank's core should work, then asked a simple question for each one: is this fundamentally true, or is it just how we have always done it? The principles that survived became our architecture, everything else got canned.

The Simulated Profiles
The CRO
Thinks in risk-weighted assets and capital adequacy. Every design choice is a question about what breaks under stress. The voice of "what's your worst-case scenario?"
The CTO
Sitting on 47 legacy integrations. Has seen every migration promise. Defends proven patterns and challenges anything that hasn't survived production at scale. The voice of "we tried that in 2014."
Head of Transformation
Has watched three core migrations fail. Knows the gap between architecture diagrams and reality. The voice of "the last team said the same thing."
Head of Procurement
Manages vendor relationships and contract risk. Knows what it costs to exit a platform. The voice of "what's the total cost of ownership and who owns the IP?"
Islamic Banking SVP
Needs Sharia-compliant structures: Murabaha, Ijara, Wakala. Interest is not a field - it's a compliance violation. The voice of "does this architecture support profit-sharing natively?"
COO, Digital Subsidiary
Runs a lean digital bank inside a traditional one. Ships fast, thinks in APIs. Wants pluggable, composable, cloud-native. The voice of "why can't we just swap it out?"
The Treasury Head
Lives in settlement cycles and liquidity positions. Needs T+0 visibility. Does not care about code elegance - cares about where the money is right now.
The Skeptic
Does not believe you can build this on a flight. Asks the hardest questions. Expects you to fail and wants to know what happens when you do. The voice of "show me."

This exercise shaped every major decision in the system. It is the reason we event-source instead of storing state, the reason reconciliation is continuous instead of nightly, and the reason settlement is a pluggable adapter rather than a monolith bolted to the side.

Old Assumption New Reality What It Informed
State is stored in rows Events are truth. State is derived. The entire event-sourcing architecture. Balances are never stored - they are computed from the event log on every read. The log is the system.
Decisions 09, 12, 13
Reconciliation runs overnight in batch Continuous. Real-time. Always. Layer 4 - the reconciliation engine. Any account balance is verifiable against full event history at any moment. SHA-256 hash chain detects tampering instantly.
Decision 18 (trial balance per currency)
Settlement is a separate system Settlement is native to the core. Layer 6 - the pluggable settlement bridge. Settlement adapter pattern means mock, EVM, Solana, SWIFT, or SEPA slot in without touching core logic. The interface is the product.
Decision 04
The audit trail is a log file somewhere The event log IS the audit trail. Immutable, append-only events with hash chain integrity. No separate audit system needed. Every state is derivable. Every mutation is traceable.
Decisions 12, 14
Currency means fiat. Period. Fiat, stablecoin, CBDC, token. All native. Multi-currency from day one. CBDC modeled as asset class with currency-like behavior. Per-currency trial balances. No retrofitting needed when digital dirham arrives.
Decisions 02, 05, 18
Business logic lives in middleware Logic lives at the ledger layer. Posting rules are pure projections. Overdraft policy is pluggable at the engine level. Compliance subscribes to events - core never knows it exists. No middleware dependency.
Decisions 03, 06, 15
Floats are fine for money if you round Floats are never fine for money. Ever. Decimal.js with 38-digit precision and banker's rounding throughout. Mixed-currency operations throw, never coerce. Amounts stored as string representations in the event log.
Decision 11
Download Full Focus Group Report (PDF)
Architecture
7 Layers. All Shipped.
Each layer is independently valuable. Each has passing tests and a clean commit. Built bottom-up - from atoms to interface.
L1
Event Store + Domain Model
Money value object, account aggregate, append-only event log. Branded AccountId, optimistic concurrency, per-aggregate versioning.
src/domain/money.ts
src/domain/account.ts
src/events/sqlite-event-store.ts
L2
Ledger Engine
Double-entry accounting enforced at engine level. Pure posting rules. Pluggable overdraft policy. Per-currency trial balance.
src/ledger/ledger-engine.ts
src/ledger/posting-rules.ts
src/ledger/overdraft-policy.ts
L3
Transaction Engine
Atomic transfers with saga pattern. Compensating transactions on failure. Idempotency keys. No partial states survive.
src/transactions/transaction-saga.ts
src/transactions/transaction-engine.ts
src/transactions/idempotency-store.ts
L4
Reconciliation Engine
On-demand verification against full event replay. SHA-256 hash chain for tamper evidence. Balance cross-checked across aggregate replay, ledger projection, and trial balance.
src/reconciliation/
reconciliation-engine.ts
L5
API Layer + Test Suite
Fastify REST endpoints. Zod-validated inputs. Per-account API key auth with JWT-ready interface. 19 integration tests.
src/api/
POST /accounts
POST /transfers
L6
Settlement Bridge
Pluggable adapter pattern. Mock settlement simulating T+0, T+1, T+2. On-chain adapter interface stubbed for EVM and Solana - not yet wired.
src/settlement/
settlement-adapter.ts
mock-settlement.ts
L7
Dashboard + Visualiser
Live SSE event stream. Account balances. Trial balance. Transaction forms. Reconciliation status. The system made visible.
src/dashboard/
GET / (served by Fastify)
First Principles
What does a bank actually do?
Strip everything away. A bank does exactly three things. Everything else is logic layered on top.
01
It Holds Value
An account is a claim on a balance. The balance is derived from history, not stored as a number. The event log is the source of truth.
02
It Moves Value
A transaction is a debit on one claim and a credit on another. Simultaneously. Atomically. No partial states. No intermediate moments.
03
It Records Everything
Every movement is immutable. Every state is derivable from history. The log is not a backup. The log is the system.
Technology
The Stack
Every choice made for a reason. Every reason documented in DECISIONS.md.
TypeScript
Strict mode. No any.
Decimal.js
Money arithmetic
sql.js
SQLite via WASM
Fastify
API framework
Zod
Schema validation
Vitest
106 tests
Decision Log
41 Decisions. All Documented.
Every architectural choice logged the moment it was made. Here are the headlines.
DECISION 01
Account Identity
UUID internally, IBAN-format display
DECISION 02
Multi-Currency
Currency-aware from day one
DECISION 03
Overdraft Policy
Pluggable interface, default hard reject
DECISION 06
Compliance Hooks
Event-driven. Core never knows compliance exists.
DECISION 11
Money Arithmetic
Throw on mixed currency. Never coerce silently.
DECISION 13
Projections
Pure functions. No cached state. Ever.
DECISION 14
Concurrency
Optimistic with expectedVersion
DECISION 15
Posting Rules
Pure projection. Events stay business-level.
Git Log
Commit History
One layer per commit. Clean restore points all the way down.
301885c layer 7: live dashboard with SSE event stream
ad40da5 layer 6: pluggable settlement adapter with mock rail
705d265 layer 5: REST API with Zod validation and per-account auth
626815b layer 4: hash-chained event log + reconciliation pipeline
afa9ad1 layer 3: atomic transfers, saga events, idempotency
9cba44a layer 2: double-entry ledger + pluggable overdraft
6560bb4 layer 1: event store + domain model
c8b03e0 scaffold: initial project structure
Honest Disclosure
What This Is. What It Is Not.
This system was built as a personal experiment to test a hypothesis: can you build a working core banking system from first principles on a single flight using AI? The answer is yes - with caveats. Here they are.
TAMPER EVIDENCE, NOT TAMPER PROOF
The hash chain detects tampering - it doesn't prevent it
The SHA-256 chain proves nobody modified an event without also rewriting the chain. But the chain lives in the same database as the events. A privileged attacker with write access to SQLite could rewrite both. Production would need an external witness - a Merkle root published to a separate immutable store, a blockchain, or even just a timestamped S3 object.
ON-DEMAND, NOT CONTINUOUS
Reconciliation runs when you call it, not automatically
The current implementation verifies the full event log on demand via the API. There is no background process, no event subscription, and no automatic trigger. True continuous reconciliation would subscribe to the event bus and verify incrementally on every append. The architecture supports this - the wiring is a post-flight task.
MVP SCALE ONLY
readAll() loads everything into memory
Reconciliation calls store.readAll(), which loads the entire event log into memory. That works at MVP scale. At a million events it would crash the process. Production needs streaming, incremental verification from the last checkpoint, and pagination. The single-process SQLite model also means the global hash chain serializes all writes - multi-node deployment would need per-aggregate chains or a dedicated chain writer.
INTERFACE DESIGNED, NOT WIRED
Settlement adapters are stubs, not integrations
The settlement bridge has a clean adapter interface and a working mock. The EVM and Solana adapters are interface stubs that throw "not implemented." No smart contracts were deployed. No SWIFT or SEPA integration exists. The value is in the architecture - the pluggable pattern means wiring a real rail is an integration task, not a redesign.
NOT AUDITED
106 tests, zero external review
The test suite covers core invariants: books balance, no partial states, idempotency holds, hash chain integrity. It does not include load testing, fuzz testing, mutation testing, or third-party security audit. The Decimal.js arithmetic is correct by construction but has not been verified against regulatory precision requirements for any specific jurisdiction.
EXPERIMENT, NOT PRODUCT
This is a proof of concept, not banking software
No bank should deploy this in production. It has no KYC, no AML screening, no regulatory reporting, no disaster recovery, no access controls beyond API keys, and no operational monitoring. The point was never to replace a production core banking system. The point was to prove that the core primitives - the things that actually make a bank a bank - are not as complex as the industry pretends they are.
Source Code
Open Source
The full system. Every dang line. Every dang test. And every decision.
PUBLIC REPOSITORY
core-banking-fp
A core banking system built from first principles on a flight from Dubai to Paris. Event-sourced, double-entry, 7 layers, 106 tests.
TypeScript
106 tests
~6,000 lines
Newsletter
Inside The Institution

Where banking innovation meets first-principles thinking. Subscribe on LinkedIn.

Subscribe