Index: docs/P3-UseCaseModel/UseCase0004.md
===================================================================
--- docs/P3-UseCaseModel/UseCase0004.md	(revision df058387ebe7d07864d874fd1376f15512418581)
+++ docs/P3-UseCaseModel/UseCase0004.md	(revision 9e6d8a2e95a15f67178a3f9b0a89aafa9a1d7f59)
@@ -37,13 +37,18 @@
    BEGIN;
 
+   -- (a) record intent — no trade has happened yet.
    INSERT INTO project.orders
-       (user_id, market_id, side, type, status, quantity, price, executed_at)
+       (user_id, market_id, side, type, status, quantity, price)
    VALUES
-       ($user_id, $market_id, 'buy', 'market', 'executed', $qty, $price, now())
+       ($user_id, $market_id, 'buy', 'market', 'open', $qty, $price)
    RETURNING id;  -- captured as $order_id
 
+   -- (b) lock and check the user balance
    SELECT available_balance FROM project.users WHERE id = $user_id FOR UPDATE;
    -- abort if available_balance < notional
 
+   -- (c) move cash from available to invested. A buy never reserves crypto
+   --     the way a sell does — it only ever adds to the position, so there
+   --     is nothing on the holdings side to commit before settling.
    UPDATE project.users
       SET available_balance = available_balance - $notional,
@@ -52,5 +57,5 @@
     WHERE id = $user_id;
 
-   -- Upsert holding with running weighted-average price:
+   -- (d) upsert holding with running weighted-average price:
    SELECT quantity, avg_price
      FROM project.holdings
@@ -61,4 +66,5 @@
    -- new_avg = (old_qty*old_avg + $qty*$price) / (old_qty + $qty)
 
+   -- (e) ledger entry
    INSERT INTO project.transactions
        (user_id, type, amount, currency, related_order, description)
@@ -66,8 +72,14 @@
        ($user_id, 'buy', -$notional, 'USD', $order_id, 'Market buy ...');
 
+   -- (f) record the resulting market trade
    INSERT INTO project.market_trades
        (market_id, executed_at, price, quantity, side, source)
    VALUES
        ($market_id, now(), $price, $qty, 'buy', 'user');
+
+   -- (g) settle the order itself — it has now actually been filled.
+   UPDATE project.orders
+      SET status = 'executed', executed_at = now()
+    WHERE id = $order_id;
 
    COMMIT;
Index: docs/P3-UseCaseModel/UseCase0005.md
===================================================================
--- docs/P3-UseCaseModel/UseCase0005.md	(revision df058387ebe7d07864d874fd1376f15512418581)
+++ docs/P3-UseCaseModel/UseCase0005.md	(revision 9e6d8a2e95a15f67178a3f9b0a89aafa9a1d7f59)
@@ -6,4 +6,20 @@
 
 A Trader sells part or all of a holding at the current market price. Cost basis is preserved so realised P/L can be reconstructed from the ledger.
+
+## Reserve, then settle
+
+The crypto being sold is **reserved** (`holdings.reserved_quantity`) before it
+is actually removed from the position, so the check a second sell order makes
+is always against what is truly still free (`quantity - reserved_quantity`),
+not against the raw `quantity`, which would also count crypto already
+promised to this order. Because only market orders are implemented, an order
+settles in the same database transaction it is placed in, so reserve and
+settle below are two statements inside one commit rather than two separate
+ones — the existing all-or-nothing guarantee (see
+[PrototypeImplementation](../P4-Prototype/PrototypeImplementation.md)) is
+kept. They stay logically distinct so that a future limit-order matcher —
+where an order really would sit `open` for a while before a *later*
+transaction settles it — needs only a second transaction where today there is
+one, not a schema change.
 
 ## Scenario
@@ -18,22 +34,34 @@
    BEGIN;
 
+   -- (a) record intent — no trade has happened yet.
    INSERT INTO project.orders
-       (user_id, market_id, side, type, status, quantity, price, executed_at)
+       (user_id, market_id, side, type, status, quantity, price)
    VALUES
-       ($user_id, $market_id, 'sell', 'market', 'executed', $qty, $price, now())
+       ($user_id, $market_id, 'sell', 'market', 'open', $qty, $price)
    RETURNING id;   -- $order_id
 
-   SELECT quantity, avg_price
+   -- (b) lock the holding and check what is actually free to sell.
+   SELECT quantity, reserved_quantity, avg_price
      FROM project.holdings
     WHERE user_id = $user_id AND crypto_id = $crypto_id
     FOR UPDATE;
-   -- abort if row missing or quantity < $qty
+   -- available := quantity - reserved_quantity
+   -- abort if row missing or available < $qty
    ```
-6. If the holding check passes, system reduces the holding, credits cash and debits invested, and appends a ledger and a market trade:
+
+6. If the check passes, system reserves the crypto, then — since this is a market order — settles it immediately, all inside the same transaction:
 
    ```sql
+   -- (c) reserve: committed to this order, not yet removed from the position.
    UPDATE project.holdings
-      SET quantity   = quantity - $qty,
-          updated_at = now()
+      SET reserved_quantity = reserved_quantity + $qty,
+          updated_at        = now()
+    WHERE user_id = $user_id AND crypto_id = $crypto_id;
+
+   -- (d) settle: release the reservation and remove the asset in one step.
+   UPDATE project.holdings
+      SET quantity          = quantity - $qty,
+          reserved_quantity = reserved_quantity - $qty,
+          updated_at        = now()
     WHERE user_id = $user_id AND crypto_id = $crypto_id;
 
@@ -54,11 +82,36 @@
        ($market_id, now(), $price, $qty, 'sell', 'user');
 
+   -- (e) settle the order itself — it has now actually been filled.
+   UPDATE project.orders
+      SET status = 'executed', executed_at = now()
+    WHERE id = $order_id;
+
    COMMIT;
    ```
+
 7. System confirms: `Order executed: sell 0.5000 ETH @ 3520.000000 (notional 1760.0000 USD)`.
 
 ### Alternate flow 5a — insufficient holding
 
-If the `SELECT ... FOR UPDATE` returns no row, or the held quantity is smaller than the sell quantity, the entire transaction rolls back and system shows "Insufficient holding: trying to sell X, hold Y."
+If the holding row is missing, or `quantity - reserved_quantity < $qty`, the
+entire transaction rolls back — including the `open` order from step 5, which
+was never committed — and system shows:
+`"Insufficient holding: trying to sell X, available Y (of Z held, W reserved)."`
+
+### Worked example — the case this fixes
+
+Alice holds 2 BTC, `reserved_quantity = 0`, and places `sell 0.5 BTC`:
+
+| | quantity | reserved_quantity | available |
+|---|---|---|---|
+| before | 2.0000 | 0.0000 | 2.0000 |
+| after step (c) — reserved | 2.0000 | 0.5000 | 1.5000 |
+| after step (d) — settled | 1.5000 | 0.0000 | 1.5000 |
+
+If a second sell for more than 1.5 BTC is placed concurrently, its own
+`SELECT … FOR UPDATE` in step 5b blocks until the first transaction commits,
+then sees the reduced `quantity` and correctly reports insufficient holding —
+proven under real concurrency in
+[UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md).
 
 ### Realised P/L (post-scenario)
Index: docs/P3-UseCaseModel/UseCase0006.md
===================================================================
--- docs/P3-UseCaseModel/UseCase0006.md	(revision df058387ebe7d07864d874fd1376f15512418581)
+++ docs/P3-UseCaseModel/UseCase0006.md	(revision 9e6d8a2e95a15f67178a3f9b0a89aafa9a1d7f59)
@@ -17,4 +17,6 @@
    SELECT symbol,
           quantity,
+          COALESCE(reserved_quantity,  0),
+          COALESCE(available_quantity, quantity),
           COALESCE(avg_price,      0),
           COALESCE(current_price,  0),
@@ -26,4 +28,8 @@
     ORDER BY symbol;
    ```
+
+   `reserved_quantity` is the amount committed to the Trader's own open sell
+   orders (see [UseCase0005](UseCase0005.md)); `available_quantity` is what is
+   actually free to sell right now.
 3. System displays the rows and a computed summary:
 
@@ -54,4 +60,6 @@
        c.symbol,
        h.quantity,
+       h.reserved_quantity,
+       (h.quantity - h.reserved_quantity)      AS available_quantity,
        h.avg_price,
        lp.price                                AS current_price,
Index: docs/P3-UseCaseModel/UseCaseModel.md
===================================================================
--- docs/P3-UseCaseModel/UseCaseModel.md	(revision df058387ebe7d07864d874fd1376f15512418581)
+++ docs/P3-UseCaseModel/UseCaseModel.md	(revision 9e6d8a2e95a15f67178a3f9b0a89aafa9a1d7f59)
@@ -38,5 +38,5 @@
  * [https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0005.md UC0005] –
    '''Place market SELL order''' – Trader sells part or all of a holding at the current market
-   price, which credits cash and preserves the cost basis.
+   price, which reserves the crypto being sold, credits cash and preserves the cost basis.
  * [https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0006.md UC0006] –
    '''View portfolio and transaction history''' – Trader inspects current holdings, unrealised
@@ -88,5 +88,5 @@
 ||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0003.md UC0003 – Deposit]||High||Shows a multi-row transaction: `UPDATE users` plus `INSERT INTO transactions`.||
 ||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0004.md UC0004 – Buy]||Very high||Core of the exchange: `INSERT orders`, `UPDATE users`, upsert `holdings`, ledger entry, market trade.||
-||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0005.md UC0005 – Sell]||Very high||Dual of Buy; demonstrates row-level `FOR UPDATE` locking and cost-basis bookkeeping.||
+||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0005.md UC0005 – Sell]||Very high||Dual of Buy; demonstrates row-level `FOR UPDATE` locking, reservation of committed crypto (`holdings.reserved_quantity`) and cost-basis bookkeeping.||
 ||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0006.md UC0006 – Portfolio]||High||Demonstrates joins over `holdings`, `markets` and `crypto`, and the `v_portfolio` view.||
 ||[https://github.com/StefanTrsunov/bp/blob/main/docs/P3-UseCaseModel/UseCase0007.md UC0007 – Watchlist]||Medium||Demonstrates N–M relation handling and `ON CONFLICT` upsert semantics.||
@@ -104,10 +104,15 @@
    kept in one place. Direct links:
    [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-1--2026-04-21 Session 1 – 2026-04-21],
-   [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-2--2026-08-06--2026-08-07 Session 2 – 2026-08-06/07].
+   [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-2--2026-08-06--2026-08-07 Session 2 – 2026-08-06/07],
+   [https://github.com/StefanTrsunov/bp/blob/main/docs/P1-ConceptualModel/ERModelAIUsage.md#session-3--2026-09-16 Session 3 – 2026-09-16].
 
 '''Service:''' Claude Code (Anthropic), https://claude.com/claude-code – Claude subscription,
-model Claude Opus 4.7 (1M context).
+model Claude Opus 4.7 (1M context) in sessions 1–2, Claude Sonnet 5 in session 3.
 
 '''In short:''' the AI proposed the actor taxonomy and drafted the seven use cases with their SQL
 in session 1. In session 2 the use-case model itself was '''not''' changed – the only work was
 re-executing every scenario, including the failure paths, against a live PostgreSQL 16 database.
+In session 3, UC0004 and UC0005 were revised to reserve the resource an order commits (crypto on
+a sell) before settling it, closing a gap where nothing stopped a second sell order from being
+granted crypto already promised to a first one; see
+[UseCaseModelAIUsage](UseCaseModelAIUsage.md#session-3--2026-09-16).
Index: docs/P3-UseCaseModel/UseCaseModelAIUsage.md
===================================================================
--- docs/P3-UseCaseModel/UseCaseModelAIUsage.md	(revision df058387ebe7d07864d874fd1376f15512418581)
+++ docs/P3-UseCaseModel/UseCaseModelAIUsage.md	(revision 9e6d8a2e95a15f67178a3f9b0a89aafa9a1d7f59)
@@ -55,2 +55,40 @@
 duplicate registration, wrong password). The results are documented per use case
 on the `UseCaseXXXXImplementation` pages.
+
+### Session 3 — 2026-09-16
+
+Driven by the design review logged in full in
+[ERModelAIUsage](../P1-ConceptualModel/ERModelAIUsage.md#session-3--2026-09-16):
+placing a sell order checked `holdings.quantity` directly, with no way to
+record that part of a position was already promised to another, unsettled
+order.
+
+**What changed:**
+
+- [UseCase0005](UseCase0005.md) — the scenario now reserves the crypto
+  (`holdings.reserved_quantity`) before removing it from the position, checks
+  `quantity - reserved_quantity` rather than raw `quantity`, and adds a
+  worked example and a note on why the reserve and settle steps stay inside
+  one transaction rather than two (only market orders are implemented, and
+  splitting into two commits would risk an order stuck `open` with no cancel
+  use case to recover it).
+- [UseCase0004](UseCase0004.md) — no change to the balance logic, but the
+  order insert now goes through `status='open'` before a final
+  `UPDATE ... SET status='executed'`, matching the sell side, so `Orders`
+  genuinely has the lifecycle [ERModel](../P1-ConceptualModel/ERModel.md)
+  describes for it rather than a status column that is only ever written
+  once.
+- [UseCase0006](UseCase0006.md) — the `v_portfolio` reference and its query
+  gained `reserved_quantity`/`available_quantity`, since the portfolio screen
+  is where a Trader would actually see the new field.
+- The use-case importance table and UC0005's one-line description in
+  [UseCaseModel](UseCaseModel.md) were reworded to mention the reservation.
+
+Every changed scenario's SQL was re-run against the live database, including a
+two-concurrent-sells test that reproduces the exact bug being fixed: see
+[UseCase0005Implementation](../P4-Prototype/UseCase0005Implementation.md).
+
+**What I decided:** to keep this a revision of the existing UC0004/UC0005
+pages rather than a new use case (e.g. "cancel order") — `cancelled` remains
+an unused status, same as before, since nothing in the prototype produces it
+and inventing a cancel flow was not what the review asked for.
