**Verdict: blocking issues** — a negative transfer amount bypasses the funds check and moves money in the wrong direction, and the statement's "Closing balance" is wrong whenever the account has history before the range start.

## Findings (most serious first)

### 1. Negative/zero transfer amounts are accepted and reverse the transfer, bypassing the overdraft check
`~/project/src/ledger/accounts.py:40-45`
`transfer` never validates `amount`. For a negative amount, `amount > source.balance` is False, so the check passes; then `source.post(day, -amount)` credits the source and `target.post(day, amount)` debits the target. Any caller can pull funds out of any account regardless of its balance.
Trigger: `l = Ledger(); l.open("alice"); l.open("bob"); l.transfer("alice", "bob", -5_000, date(2026,10,1))` → no exception, alice +5000, bob -5000. `amount=0` also silently appends two zero-cent transactions to both histories.
Fix: as the first statement in `transfer`, add `if amount <= 0: raise ValueError(f"transfer amount must be positive, got {amount}")`.

### 2. Statement "Closing balance" ignores transactions before `start`
`~/project/src/ledger/report.py:9-14`
`running` starts at 0 and only accumulates in-range transactions, so the printed closing balance is the net of the period, not the account balance at `end`. The docstring (line 7) promises "the closing balance".
Trigger: alice opened 2026-09-01 with 10_000; `transfer("alice","bob",2_500, date(2026,10,1))`; `statement(alice, date(2026,10,1), date(2026,10,31))` prints `Closing balance: -25.00` while `alice.balance` is 7_500.
Fix: seed the running total with prior history, e.g. `running = sum(t.amount for t in account.history if t.day < start)`, and add an `Opening balance:` line after the header so the report is self-consistent. (Transactions after `end` are correctly excluded.)

### 3. `Ledger.open` stamps the opening balance with `date.today()`, so backdated transfers precede it and the balance check is not date-aware
`~/project/src/ledger/accounts.py:33-36` and `:42`
The opening balance has no caller-supplied date, which makes results nondeterministic and lets a transfer dated before the opening pass the funds check (the check uses the all-time total on line 42, not the balance as of `day`).
Trigger: the shipped example in `~/project/src/ledger/__main__.py:6-9` — alice's opening is dated today (2026-10-12), the transfer is dated 2026-10-01. `statement(alice, date(2026,10,1), date(2026,10,11))` prints `Closing balance: -25.00` for an account that was never overdrawn by intent. Run the example again next month and the alice statement changes.
Fix: change the signature to `open(self, name: str, day: date, opening_cents: int = 0)` and post with `day`; update `__main__.py:6-7` to pass a date. Also reject `opening_cents < 0` with `ValueError`. If backdated transfers are intended, the overdraft check in `transfer` should compute the balance as of `day` (`sum(t.amount for t in source.history if t.day <= day)`); otherwise document that only the current balance is checked.

### 4. `Ledger.open` silently overwrites an existing account
`~/project/src/ledger/accounts.py:37`
`self.accounts[name] = account` replaces any existing entry; its history is lost and any `Account` object previously returned to a caller is orphaned.
Trigger: `l.open("alice", 10_000); l.open("alice")` → `l.accounts["alice"].balance == 0`, no error.
Fix: before line 34, `if name in self.accounts: raise ValueError(f"account {name!r} already exists")`.

### 5. Transfer to self is allowed
`~/project/src/ledger/accounts.py:40-45`
`transfer("alice", "alice", 100, day)` passes the funds check and appends a -100 and +100 pair to the same history. Net zero, but it pollutes statements with phantom lines.
Fix: `if src == dst: raise ValueError("cannot transfer an account to itself")` alongside the amount check.

### 6. Unknown account surfaces as a bare `KeyError`
`~/project/src/ledger/accounts.py:41`
`self.accounts[src]` on a missing name raises `KeyError('carol')` with no context. It is raised before any post, so no partial state (good), but callers cannot distinguish it from other `KeyError`s and the message is unhelpful.
Trigger: `l.transfer("alice", "carol", 100, date(2026,10,1))`.
Fix: add `class UnknownAccount(KeyError)` next to `InsufficientFunds`, look up with a helper that raises `UnknownAccount(f"no account named {name!r}")`, and export it from `~/project/src/ledger/__init__.py:3,6`.

### 7. `statement` does not reject `start > end`
`~/project/src/ledger/report.py:6-11`
An inverted range silently produces an empty statement with `Closing balance: 0.00`.
Trigger: `statement(bob, date(2026,10,31), date(2026,10,1))`.
Fix: `if start > end: raise ValueError(f"start {start} is after end {end}")` at the top of the function.

### 8. Cent formatting via float division is duplicated and should be a single integer-only helper
`~/project/src/ledger/report.py:13-14`
`t.amount / 100` and `running / 100` are display-only, which the money-handling rule permits, and `:.2f` rounds correctly for realistic magnitudes. But the logic is repeated in two places with different width specs, and a pure-integer helper removes all doubt. Note the obvious integer rewrite `f"{c // 100}.{c % 100:02d}"` is wrong for negatives (`-5` → `-1.95`); the sign must be split out.
Fix: add to `report.py`:
```python
def fmt_cents(cents: int) -> str:
    sign = "-" if cents < 0 else ""
    dollars, rem = divmod(abs(cents), 100)
    return f"{sign}{dollars}.{rem:02d}"
```
and use `f"{t.day.isoformat()}  {fmt_cents(t.amount):>10}  {t.memo}"` on line 13 and `f"Closing balance: {fmt_cents(running)}"` on line 14.

### 9. `Account.post` is public and unchecked
`~/project/src/ledger/accounts.py:25-26`
Any caller can append arbitrary transactions and bypass every `Ledger` invariant; it is also exported via `Account` in `__init__.py`. Minor for an in-memory toy, but worth renaming to `_post` or documenting as internal once the validations in findings 1, 3 and 5 exist, so they cannot be sidestepped.

## Missing tests (none exist; README confirms)
Highest-value coverage, in order:
- `transfer` rejects `amount <= 0`, `src == dst`, unknown `src`/`dst`, and `amount > balance`; allows `amount == balance`; after any rejection both histories are unchanged.
- `statement` range boundaries: a transaction on `start` and one on `end` are included, one on `start - 1 day` and `end + 1 day` are excluded.
- `statement` closing balance equals `account.balance` when `end` is the latest date, and includes pre-`start` history.
- Formatting of `-5`, `5`, `-2500`, `123456789` cents (expect `-0.05`, `0.05`, `-25.00`, `1234567.89`).
- `open` rejects a duplicate name and a negative opening balance; the opening transaction carries the supplied date.
- Sign convention: after a transfer, `source.balance + target.balance` is unchanged.

## What looks right
- `~/project/src/ledger/accounts.py:41-43`: both account lookups and the funds check happen before either `post`, so a failed transfer leaves no partial state.
- `accounts.py:42`: a transfer equal to the full balance is allowed (`>` not `>=`), which is the sensible boundary.
- `accounts.py:12,44-45`: the documented sign convention (positive = credit, negative = debit) is applied consistently in `transfer`.
- `accounts.py:23`: balances are integer sums; no float enters the ledger state anywhere — floats appear only in `report.py` display strings.
- `~/project/src/ledger/report.py:11`: the date range is inclusive on both ends as the docstring says; `sorted` is stable so same-day transactions keep insertion order.
- `~/project/src/ledger/__init__.py`: exports match the definitions and `__all__` is consistent.
- `~/project/src/ledger/__main__.py`: the example runs end-to-end and the printed bob statement is correct (bob has no pre-range history, which is why finding 2 is masked there).