桌上

这一次

第 2 节 · 先学会,再选做 90 分钟挑战

你点了关闭。
对面可能已经写上了。

客户端抛出 TimeoutError,只说明你没拿到回执。不说明工单还开着。

先讲

关工单是一次有副作用的写。同一笔生意要有一个从进门就固定的 operation_id。重试循环里 new 一个号,等于另开一笔生意。

超时有两侧。请求在提交前断了:库里还没有这一笔,可以沿同一个号再试。请求在提交后断了:库里可能已经关上,再发一次就会关两次。两侧的第一件事相同:拿原来的号去问。问完才决定要不要写。

审批绑的是参数摘要,不是心情。签的是「重复件,关闭」,发出前改成「误报,关闭」,摘要对不上,写工具一次都别叫。

先修只需要:能运行 Python 函数、读懂异常和测试断言。Receipt 是写入回执,lookup 是按操作号查询回执,幂等是同一意图重复提交不会再产生一次业务写入。分不清这些词,先走下面的浏览器推演,不开计时器。

本实验的 Ledger 是同步的内存账本,查不到就表示它尚未提交。在真实系统里,查询可能落后或请求仍在处理;不能把一次空查询当成失败。需要服务端持久化幂等键及明确的处理中状态,仍未知就等待或交给人工。页面把两种真实状态展示给你学习;客户端自身并不知道超时发生在哪一侧。

例:同一号走完一次提交后超时
  1. 发出关闭,operation_id = op_close_2044
  2. 对面已提交,回执在路上丢了
  3. 客户端只看到 TimeoutError。状态:未知
  4. 拿 op_close_2044 去问,账本里有回执
  5. 不再写。把同一张回执给出去。提交次数仍是 1
若第 4 步换号再发,提交次数变成 2。客户只点了一次。

你来做

城市设施 · 重复投诉

CASE-2044

要做的事
关掉这张工单
已经签过
note = 重复件,关闭
这一次的号
op_close_2044

你发出关闭。客户端抛出 TimeoutError。

超时发生在哪一侧
工单OPEN
已提交0
客户端TimeoutError
回执无

下一行代码做什么

    先拿到能运行的实验

    下载实验包 ZIP,解压后得到 ticket-write-lab 文件夹。在终端进入这个文件夹。包里已有源码、测试、独立参考答案;不需要模型 API、云账号或真实工单。

    需要 Python 3.11 或更新版本。先运行 python3 --version。下面命令适用于 macOS / Linux;Windows 可用 py -m venv .venv,然后在 PowerShell 用 .venv\Scripts\Activate.ps1 激活。

    cd ticket-write-lab
    python3 -m venv .venv
    source .venv/bin/activate
    python -m pip install -e '.[dev]'
    python -m pytest -q tests

    首次应是 3 failed, 2 passed:提交后超时、提交前超时、审批后改参数三条红;正常关闭和重复请求两条绿。这是挑战起点。只改 src/ticket_write/service.py,不改账本或测试断言。

    1. 先读失败断言:要求提交数 1,却看见 2,说明哪一分支又写了一次?
    2. 调用审批校验;捕获超时后查询原操作号,已有回执就返回,没有才沿原号有界重试。不要依赖测试替身的 after_commit 提示做决定。
    3. 重跑同一组测试,保留修改前后输出,并解释每一条为什么变绿。五条绿只覆盖这五个合成场景。
    卡住时:按错误找下一步

    No module named ticket_write:确认当前目录有 pyproject.toml,且在已激活的环境执行过安装命令。No module named pytest:安装命令里的 '.[dev]' 不能省略。找不到文件:先进入解压后的 ticket-write-lab,而不是网页目录。

    三条指定断言失败是练习;下载依赖失败、导入失败不是练习结果。先处理安装错误再比较测试。想对答案时看参考解法与边界说明,不要把参考文件覆盖回挑战后就当作自己完成。

    审批有效期是单独扩展

    基础版只签操作号和参数摘要,没有有效期。扩展要求把 expires_at 一起签名,并在每次写入前用可信时钟检查;时间恰等于过期点也拒绝。改到期时间不能绕过签名。示范与测试见expiry.py和参考测试。

    python -m pytest -q reference/tests

    参考测试应全部通过,和基础挑战的三条红分开运行。它包含到期边界、篡改到期时间,以及第一次尝试后过期、第二次写入被拦截的情况。

    选做:从空目录写接口

    修完且能解释后,再开 90 分钟计时器。FastAPI / Pydantic 接口是额外挑战,实验包本身是普通 Python;如果没写过 HTTP 接口,先停在已验证的函数,不把框架安装当成关单逻辑。

    1. 类型落地

      operation_id 进门时就有。多一个键就拒。

    2. GET 能跑,POST 过审批

      摘要对不上,写工具一次都别叫。

    3. 超时先问

      找到回执就结束。没写上,沿同一个号再试。

    4. 弄坏一次

      再加:只有 OPEN 才能关。

    接口挑战的交付物是接口代码、请求与响应样例、五条同等断言。再补“非 OPEN 不得新增关闭”的测试。这个规则目前不在基础五条里,也不涵盖并发竞争;不能仅凭基础测试绿就宣称生产可用。完整实验说明与文件入口。

    留下:提交次数为 1 的测试,以及超时之后你走了哪一分支。下一节把同一条规则收进 Agent 的写口。