## 决定：暂不合并

这两个问题都由实际运行提供了证据，不是输入校验或假设场景。以下行号对应所审查的 `reserve.py`。

### P1 — 并发请求能超卖，使库存为负
**位置：** `reserve.py:6–10`。**复现：** `reproduce.py` 用两个协程、同一 SQLite 连接和屏障，让两个请求都通过库存 `SELECT` 后才继续；两者各预留 1 件，初始库存为 1。**观察：** 屏障确认两个请求均通过检查，两个结果都是 `reserved`，持久库存为 `-1`，并有两条预留记录。**要求：** 库存不可为负。**影响：** 系统确认超过现有库存的订单，并留下与库存不一致的持久状态。

**最小修复方向：** 在同一事务中执行带库存下限条件的原子扣减（例如 `qty >= 请求数量`），并检查受影响行数；零行更新应返回 `insufficient`。扣减、预留记录写入必须作为一个原子事务提交或回滚。库存检查不能只依赖 await 前读到的值。

### P1 — 重放检查忽略租户和请求载荷，误报 duplicate
**位置：** `reserve.py:2,4–5`、`reserve.py:11`。表把 `request_id` 单独设为全局主键，查询也只按 `request_id` 查；命中后不比较 tenant、sku 或 qty。**两项独立复现：** 同租户同 ID、qty 从 1 改为 2，第二次仍返回 `duplicate`，而非要求的 `conflict`；跨租户使用同一 ID，第二租户也返回 `duplicate`，其库存未扣、也没有自己的记录。**影响：** 有载荷变更的调用被静默当作成功重放；另一租户的合法预留可能被吞掉。

**最小修复方向：** 将幂等身份定义为 `(tenant, request_id)`，并让查询、唯一约束及插入保持一致；命中时比较 sku/qty，相同才返回 `duplicate`，不同则返回 `conflict`。为避免与扣库存竞态，应在原子事务中完成幂等检查、条件扣减和预留写入；任何失败都回滚全部相关持久状态。需为现有表准备相应迁移，而非只改查询条件。

### 事务失败控制：通过
**位置：** `reserve.py:9–11`。复现程序在实际 `INSERT` 上安装 SQLite `BEFORE INSERT` 触发器并触发 `ABORT`，确认到达错误点；失败后库存仍为 4、记录数为 0。移除触发器后，以同一 ID 重试成功，库存为 2、记录数为 1。因此在此控制下，现有 `with db` 确实回滚扣减并允许合法重试；不应把它误报为已复现的回滚缺陷。修复前两项时必须保留并重新跑此控制。

### 建议的聚焦回归
1. 同一库存 1、两个请求各要 1；用屏障确保都先读库存。断言不得出现负库存，最多一项 `reserved`，另一项为 `insufficient`，库存与预留记录一致。
2. 同租户同 ID 同 sku/qty 重放：`duplicate` 且只扣一次；同 ID 改 qty 或 sku：`conflict`，库存和原记录不变。
3. 两租户同 ID：两者均可各自预留，库存分别扣减，记录分别归属各租户。
4. 在预留 INSERT 处注入 ABORT：断言扣减与记录都回滚；移除故障后同一请求重试成功且只扣一次。

修复方向按 Google 工程实践复核：借鉴 CR1 的显式并发交错、观察副作用并保留合法控制；借鉴 CR2 对身份/作用域边界及正反向兼容案例的检验。此处采用的是审查方法，不把示例中的领域规则套用为本任务事实。按上述修复路径，屏障后的原子条件扣减会阻止超卖；事务内复查复合幂等键并比较载荷可区分重放、冲突和跨租户请求；事务失败仍整体回滚，保留已验证的重试行为。完成这些修复及回归前，不建议合并。

本地复现文件：`reserve.py`、`reproduce.py`；实际输出保存在 `reproduction.out`。未修改真实系统或对外发送信息。