Available stock counts drift from reality

Done

/home/devdi/devswarm/evals/tasks/hard_inventory_orders/repo · 2026-11-09 17:07:02 UTC · took 52.6s

  1. Plan (done)
  2. Code (done)
  3. Test (done)
  4. Review (done)
  5. Finish (done)
Cost
$0.0078
Tests
Passed
Iterations
1
Re-plans
0
LLM calls
12
Prompt read from cache
70%

Usage by agent

Cost per agent
planner: $0.0013 planner planner, Cost: $0.0013 $0.0013 coder: $0.0040 coder coder, Cost: $0.0040 $0.0040 tester: $0.0013 tester tester, Cost: $0.0013 $0.0013 reviewer: $0.0012 reviewer reviewer, Cost: $0.0012 $0.0012
Tokens per agent
  • Cache read
  • Cache write
  • Input
  • Output
planner: 10.1K planner planner, Cache read: 6,188 planner, Cache write: 1,835 planner, Output: 2,102 10.1K coder: 42.1K coder coder, Cache read: 27.6K coder, Cache write: 9,454 coder, Output: 5,047 42.1K tester: 14.1K tester tester, Cache read: 8,045 tester, Cache write: 4,715 tester, Output: 1,310 14.1K reviewer: 13.1K reviewer reviewer, Cache read: 7,189 reviewer, Cache write: 4,942 reviewer, Output: 966 13.1K
View as table
AgentCostCache readCache writeInputOutput
planner$0.00136,1881,83582,102
coder$0.004027,5599,454125,047
tester$0.00138,0454,71561,310
reviewer$0.00127,1894,9426966

Timeline

  1. planner iteration 1 Finished 3 calls · 10.1K tokens · $0.0013 · 11.7s

    Loading…

  2. coder iteration 1 Finished 5 calls · 42.1K tokens · $0.0040 · 23.7s

    Loading…

  3. tester iteration 1 Finished 2 calls · 14.1K tokens · $0.0013 · 8.3s

    Loading…

  4. verify iteration 1 Passed 0.5s
    ...........                                                              [100%]
    11 passed in 0.02s
    
  5. reviewer iteration 1 Finished 2 calls · 13.1K tokens · $0.0012 · 7.7s

    Loading…

Final diff

diff --git a/orders.py b/orders.py
index 3c6e298..640b4d4 100644
--- a/orders.py
+++ b/orders.py
@@ -1,4 +1,4 @@
-from stock import Stock
+from stock import OutOfStock, Stock
 
 
 class OrderError(Exception):
@@ -11,7 +11,17 @@ class Orders:
         self._orders: dict[int, dict] = {}
         self._next_id = 1
 
+    def _get(self, order_id: int) -> dict:
+        if order_id not in self._orders:
+            raise OrderError(f"unknown order id {order_id}")
+        return self._orders[order_id]
+
     def place(self, lines: dict[str, int]) -> int:
+        # Check every line before reserving anything so a failed order
+        # leaves stock untouched.
+        for sku, qty in lines.items():
+            if self.stock.available(sku) < qty:
+                raise OutOfStock(sku)
         for sku, qty in lines.items():
             self.stock.reserve(sku, qty)
         order_id = self._next_id
@@ -20,10 +30,19 @@ class Orders:
         return order_id
 
     def ship(self, order_id: int) -> None:
-        self._orders[order_id]["status"] = "shipped"
+        order = self._get(order_id)
+        if order["status"] == "cancelled":
+            raise OrderError(f"order {order_id} is cancelled and cannot be shipped")
+        if order["status"] == "shipped":
+            raise OrderError(f"order {order_id} has already been shipped")
+        order["status"] = "shipped"
 
     def cancel(self, order_id: int) -> None:
-        order = self._orders[order_id]
+        order = self._get(order_id)
+        if order["status"] == "cancelled":
+            return
+        if order["status"] == "shipped":
+            raise OrderError(f"order {order_id} has already shipped and cannot be cancelled")
         for sku, qty in order["lines"].items():
             self.stock.release(sku, qty)
         order["status"] = "cancelled"
diff --git a/test_orders.py b/test_orders.py
index 149497c..00f4f7c 100644
--- a/test_orders.py
+++ b/test_orders.py
@@ -1,5 +1,7 @@
-from orders import Orders
-from stock import Stock
+import pytest
+
+from orders import OrderError, Orders
+from stock import OutOfStock, Stock
 
 
 def test_place_reserves():
@@ -13,3 +15,75 @@ def test_cancel_releases():
     orders = Orders(stock)
     orders.cancel(orders.place({"a": 2}))
     assert stock.available("a") == 5
+
+
+def test_double_cancel_releases_once():
+    stock = Stock({"a": 5})
+    orders = Orders(stock)
+    oid = orders.place({"a": 2})
+    orders.cancel(oid)
+    orders.cancel(oid)
+    assert stock.available("a") == 5
+
+
+def test_failed_place_leaves_stock_unchanged():
+    stock = Stock({"a": 5, "b": 1})
+    orders = Orders(stock)
+    with pytest.raises(OutOfStock):
+        orders.place({"a": 2, "b": 3})
+    assert stock.available("a") == 5
+    assert stock.available("b") == 1
+
+
+def test_failed_place_does_not_consume_order_id():
+    stock = Stock({"a": 5, "b": 1})
+    orders = Orders(stock)
+    with pytest.raises(OutOfStock):
+        orders.place({"a": 2, "b": 3})
+    assert orders.place({"a": 1}) == 1
+
+
+def test_place_unknown_sku_raises_out_of_stock():
+    stock = Stock({"a": 5})
+    with pytest.raises(OutOfStock):
+        Orders(stock).place({"a": 1, "zzz": 1})
+    assert stock.available("a") == 5
+
+
+def test_cancel_shipped_raises_and_changes_nothing():
+    stock = Stock({"a": 5})
+    orders = Orders(stock)
+    oid = orders.place({"a": 2})
+    orders.ship(oid)
+    with pytest.raises(OrderError):
+        orders.cancel(oid)
+    assert stock.available("a") == 3
+    assert orders._orders[oid]["status"] == "shipped"
+
+
+def test_ship_cancelled_raises_and_stays_cancelled():
+    stock = Stock({"a": 5})
+    orders = Orders(stock)
+    oid = orders.place({"a": 2})
+    orders.cancel(oid)
+    with pytest.raises(OrderError):
+        orders.ship(oid)
+    assert orders._orders[oid]["status"] == "cancelled"
+    assert stock.available("a") == 5
+
+
+def test_ship_twice_raises():
+    stock = Stock({"a": 5})
+    orders = Orders(stock)
+    oid = orders.place({"a": 2})
+    orders.ship(oid)
+    with pytest.raises(OrderError):
+        orders.ship(oid)
+    assert orders._orders[oid]["status"] == "shipped"
+
+
+@pytest.mark.parametrize("method", ["ship", "cancel"])
+def test_unknown_order_id_raises_order_error(method):
+    orders = Orders(Stock({"a": 5}))
+    with pytest.raises(OrderError):
+        getattr(orders, method)(999)