mcpField
C
create-spec

create-spec

github.com/bastani-inc开发870172 次安装1 天前更新
代码仓库

关于 create-spec

面向 Claude 的技能,可将研究文档整理为带日期的实现规格说明。它引导开发者澄清需求、比较设计方案、定义入口并规划测试,但不实施变更。

主要功能

  1. 01基于研究撰写规格说明
  2. 02需求模糊时提出澄清问题
  3. 03评估兼容性立场
  4. 04设计入口与契约
  5. 05比较方案并规划 RGR 测试

适用场景

  1. 01根据 research/ 文档撰写功能规格
  2. 02规格撰写前澄清不确定需求
  3. 03记录 API 入口、契约与失败行为
  4. 04规划纵向测试驱动实施切片

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bastani-inc/atomic --skill create-spec

安装后在 Claude Code 中输入/create-spec确认已加载。

详细介绍

你的任务是利用 $ARGUMENTS 路径中的现有研究,为代码库中的新功能或系统变更编写一份规范。如果未指定研究路径,请使用整个 research/ 目录。重要提示:研究文档位于 research/ 目录中,不要在 specs/ 目录中查找研究。请使用以下模板,根据 research/ 中相关研究文档的发现,编写一份完整的规范,并将其输出到 specs/ 文件夹。规范文件必须采用 YYYY-MM-DD-topic.md 格式命名(例如 specs/2026-03-26-my-feature.md),其中日期为当前日期,主题为 kebab-case 格式的摘要。提示:建议使用 codebase-research-locator 和 codebase-research-analyzer Agent,帮助你查找和分析 research/ 目录中的研究文档。也强烈建议在规范中引用相关研究,以提供更多背景信息。

开始前先提出澄清问题

  • 如果用户的请求含糊不清或缺少必要细节,请先提出澄清问题,在开始编写规范前收集更多信息。这有助于确保规范内容完整,并符合用户需求。
确定兼容性要求
  • 分解规范编写请求之前,先确认项目是否需要为真实的下游用户保留向后兼容性。
  • 如果用户明确允许破坏性变更、公共 API 变更、清理,或表示没有真实用户/下游依赖,则允许破坏性变更。
  • 如果用户提到生产环境用户、已发布的 API、下游使用方、迁移安全性或兼容性要求,则不允许破坏性变更。
  • 如果无法从请求中推断兼容性要求,请在继续前询问用户一次;如可行,请使用现有的结构化提问工具。
  • 在规范编写计划、最终规范的 frontmatter,以及最终规范中的 ## 向后兼容性 部分中体现这一要求。
  • 如果允许破坏性变更,应将现有的旧版行为、兼容性垫片、可选标志和公共 API 记录为当前状态,而不是视为未来规范必须保留的约束,除非用户明确要求保留。
  • 如果不允许破坏性变更,应记录公共 API、对兼容性敏感的接口、下游调用方、迁移限制,以及未来工作必须保留的行为。
起草前选择工作路径

首先检查已有上下文:对话内容、指定的研究路径、research/ 文档、本地文档和代码库。选择符合实际已知信息的路径:

  • 路径 A — 将上下文转化为规范: 当现有对话、研究、文档或代码提供了足够背景,可用于描述问题、约束、受影响的代码和验收标准时,使用此路径。
  • 路径 B — 先深入询问: 当用户希望编写规范,但问题、约束、设计方向、受影响的代码或验收标准尚不明确时,使用此路径。不要臆造架构决策。

如果代码库可以回答某个问题,请先检查代码库,而不是询问用户。对于路径 B,暂时不要编写完整规范:说明缺少哪些上下文,然后使用现有的 ask_user_question 和对比式澄清规则(每次提出一个问题或一组逻辑相关的问题,并给出推荐答案及具体权衡)。待用户回答且仓库证据提供了足够上下文后,再执行路径 A。

路径 A 的工作方法

选定路径 A 后,按以下顺序开展工作,并将结果填入下方编号文档标题对应的部分:

  1. 加载标准和本地上下文。 检查本地词汇、模块布局、领域概念、错误、适配器、可观测性、运行时模式和测试风格。在引入模式、库、适配器、架构风格或测试策略之前,先查阅既有做法;并在 §2.1 和各个入口名称中说明依据。
  2. 提取设计问题。 在 §2、§3 和 §9 中记录当前状态、用户和调用方、痛点、目标、非目标、约束、不变量、受影响的系统、可能的入口、运维方面的考虑、风险和待解决问题。未知事项应保留为待解决问题。
  3. 在确定推荐方案前,探索实质上不同的替代方案。 比较接口形式、接缝位置、所有权、调用栈、运行时拓扑和模块边界,而不只是名称。将比较结果记录在 §6 中,即使 §6 在文档中位于推荐设计之后。
  4. 指定类型化契约。 在 §5.1–§5.3 中定义推荐的入口、类型、API、明确命名的失败情况和拒绝情形,同时保留入口评估标准。
  5. 指定调用栈和数据流。 按照下方的可视化格式,在 §5.4 中列出当前和建议的路径,以及可到达位置的失败行为、重试、取消和幂等性。
  6. 映射文件和模块。 在 §4 或 §5 下列出新增、更改、删除、测试和配置文件,并说明各自负责的内容。
  7. 规划纵向 RGR TDD 切片。 在 §8 中,让每个重要的公共入口或接缝依次经历红灯行为测试、最小的绿灯实现,以及能保留该行为的重构;不要制定先写完所有测试的横向计划。
  8. 产出仅包含设计的规范。 编写 specs/YYYY-MM-DD-topic.md;不要在此技能中实现变更。

设计理念:规范是对其入口的理论阐述

一个程序的入口点合在一起,构成了该程序对自身用途的理论阐述。边界内的一切都是机制——即 如何实现。只有在边界处,代码才会以意义来表达——即 是什么 和 为什么。因此,这份规范最重要的定义不是系统内部的机制,而是系统保留的入口集合:接收不可信输入并触发不可逆副作用的函数、路由和 RPC 方法。

这一主张包含两项工作,而好的规范会兼顾二者。一项是发现入口——通过研究 research/ 中的资料,了解软件所服务的实际世界中哪些内容真正重要,从而找出领域现有的接缝。另一项是塑造入口——为每个入口命名并设计其形式,使其如实反映其背后的内容。应将入口设计视为规范的主干:评审者只读入口集合,就应能在阅读任何实现细节之前,重建系统的用途。

对规范引入或更改的每个入口应用以下五项原则,并逐一按照评估标准进行检查。

五项原则
  1. 为领域关节命名,而不是为工具命名。 一个领域有其分界——现实本就划分成有意义单元的地方(验证用户身份、结算付款、撤销访问权限、发布草稿)。这些单元在你的代码诞生之前就已存在。每个入口都应以这样的关节命名,绝不要以其背后的机制命名(run the query、call the service、update the row)。以工具命名的入口,只会让读者了解它如何运作,却始终不知道它的用途是什么——这是本体层面的错位,再干净的机制也无法弥补。倾听领域(以及研究),而不是代码。

  2. 如实压缩,否则就不要压缩。 入口的价值大致取决于隐藏的机制与暴露的表面之间的比例——但前提是名称准确承诺了主体实际提供的内容。 不可少于实际内容(否则会掩盖危险或不完整之处),也不可多于实际内容(否则会暗示它并不具备的保证)。有时会悄无声息地不保存的 save()、执行软删除的 delete()、会修改数据的 validate()、会创建用户的 getUser()——这些都是边界上的谎言;每个谎言都会影响所有根据名称进行推理的调用方,层层累积。在词汇中体现成本和风险(低成本借用 vs 分配 vs 消耗;read vs read_exact;在名称中标明 panic 风险)。

  3. 意图体现在入口所拒绝的事物中。 边界传达的信息,既来自它允许什么,也来自它禁止什么。入口集合的形状——让什么变得容易、让什么变得不可能——直接体现了设计者视为神圣的东西。与其只在运行时检查规则,不如优先让非法状态在结构上无法表示:检查规则的入口需要信任调用方;让规则在结构上成为必要条件的入口则无需信任任何人。用新类型(AccountId、OrderId)代替基本类型,用和类型代替独立布尔值,并使用携带能力的类型(AdminSession、AuthorizedCharge),且只能由有权授予这些能力的入口创建。

  4. 为跨越时间的陌生人而写。 你设计入口的对象不是机器,而是一位能力出众的陌生人:多年后才来到这里,从未见过你,却必须在敢于修改系统之前理解其目的。核心检验标准是:他们能否仅凭入口重构出系统的用途,而不阅读任何主体? 如果他们必须阅读实现才能了解系统的含义,说明意图已经从入口泄漏到了机制中。

  5. 让危险入口少而诚实。 一个系统的成熟度,体现在守护不可逆操作的入口有多么少,以及这些入口的命名有多么准确。每当涉及资金流转、授予访问权限、销毁数据、生成密钥或广播消息时,都应将每项操作导向一个名称诚实的控制点,让守护它的承诺只有一个归属。若危险散布在许多零散且未命名的路径中(每个能执行 chargeCard 的处理程序、可执行 DROP TABLE 的宽泛数据库授权、临时的 os.system(...)、默认公开的存储),就没有人——甚至作者自己——说得清系统的重担由何处承担。

你对规范中的每个入口点都要执行的评审标准

对于规范引入或变更的每个非平凡入口点,按顺序逐项检查。遇到第一个无法明确回答的问题时就停下——这就是一项发现,应写入规范(通常作为 §5 中的约束,或 §9 中的开放问题)。正向检查你起草的入口点,也反向检查——思考每项义务应由哪个入口点承担——以找出系统仍然缺失的入口点。

  1. 领域关节,而非工具。 名称是否代表一个非工程人员也能理解的领域意图单元,而不是对实现机制的描述?如果只能用实现术语命名,它就是一个步骤,而非入口点。
  2. 一句话说清。 能否用一个不含 and 的陈述句说明它的保证?如果不能,它要么是融合的(拆分它),要么未定义(最危险的情况——停下来弄清它实际承诺了什么)。
  3. 名称诚实。 它是否准确承诺函数体将提供的内容——既不隐藏危险,也不暗示它无法兑现的保证?列出名称可能被理解为谎言的各种方式。
  4. 义务都已履行。 根据这句话推导出前置条件、不变量、后置条件和 never。每项义务是否都对应设计中的真实步骤,每个步骤也都对应一项义务?
  5. 每种退出方式都兑现承诺。 检查错误返回、重试、超时、部分写入、并发调用者和第二次进入。保证必须在所有这些情况下成立,而不仅仅是在顺利路径上。
  6. 拒绝确实有效。 这个入口点会让什么变得不可能?非法状态是无法表示,还是仅仅经过检查后便被信任?
  7. 信任转换明确且唯一。 如果不可信变为可信,或权限有所提升,这是否发生在此处——且只在此处?
  8. 不可逆影响必须经过单一关口。 这是控制该影响的唯一主导入口点吗?如果可以通过其他方式触发该影响,那么其他方式就是缺陷。
  9. 边界处设有气闸。 验证、授权、转换和错误边界都位于入口点,让内部可以信任自身不变量。深层代码中的防御式检查意味着边界位置不正确。
  10. 陌生人也能还原意图。 只看这个入口点本身——名称和签名,而非函数体——别人能否知道它的用途和责任?
每个边界上的领域关节都应一致

进程内的 settle_payment、REST 中的 POST /v1/payment_intents/{id}/capture,以及 gRPC 中的 Billing.SettlePayment,是同一个入口点、三种传输方式、同一个名称。如果函数、路由和 RPC 方法对领域关节的理解不一致,那么至少有一个是在给工具命名——标记出来。在网络协议上,HTTP 方法让协议免费提供了诚实性(GET 是 safe,PUT/DELETE 是 idempotent,POST 两者都不是——这正是涉及资金的入口点携带 Idempotency-Key 的原因),状态码则是入口点诚实的退出方式(201 表示已创建,204 表示已完成,202 表示已接受;409/412 是真正的拒绝)。最严重的谎言,是用 200 OK 包裹 {"error": ...}。身份验证是在边缘的一道关卡,这样其后的每个处理程序都可以信任自己正在与已知调用方通信。

<EXTREMELY_IMPORTANT>

  • 请使用你的 ask_user_question 工具,以丰富的交互界面向用户提问。
  • 此阶段请勿实现任何内容,只需按下文要求创建全面的规格说明。
  • 编写规格说明时,请勿包含具体日期或时间线信息(例如 # 分钟、小时、天、周等),而应采用明确的阶段划分(例如阶段 1、阶段 2 等)。
  • 规格说明必须将入口集合视为一等产物。第 5 节围绕 doors 构建(具有类型签名、具名失败,以及通过类型表达的拒绝),并且必须通过上述评审标准。不得让意图渗入机制:评审者应仅凭 §4.4 和 §5.1 就能还原系统的目的。
  • 如果规格说明针对的工作流包含评审关卡和最终操作(例如创建 PR/MR/评审、发布标签、部署或发布),请明确区分实现/评审验收与这些审批后的最终操作。工作流设计应告知评审者/归约器:当实现和验证标准已得到证明,且只剩一项经明确授权的最终操作时,应批准并停止实现循环;将剩余工作作为下一步操作/最终操作字段记录,而不是再进行一轮实现。
  • 如果规格说明针对的是工作流或工作流提示词重构,则必须要求阶段/评审者/归约器提示词在本地明确、以行动为导向。模型阶段只能看到自己的提示词、产物、工具和读取内容——无法看到工作流图的名称或周边实现细节,除非明确提供了这些信息。因此,应优先使用“评审当前代码变更”或“创建/更新评审请求”等指令,而不是依赖实现专用的工作流昵称,除非该名称是面向用户的上下文或会实质性改变行为。
  • 生成规格说明后,请逐个或按逻辑分组提问:
    • 参考“## 9. Open Questions / Unresolved Issues”一节,逐个处理每个问题,并使用对比式澄清(提供 2–3 个具体选项及其权衡),而不是开放式提问。这意味着应给出诸如“(A)选项 X——权衡 Y”和“(B)选项 Z——权衡 W”的解读,而不是询问“你对 X 有什么看法?”。
    • 在逐个讨论问题并根据用户回答更新规格说明的过程中,持续深入询问此计划/规格说明的每个方面,直到双方达成共识。沿着设计树逐步梳理每个分支,并逐一解决决策之间的依赖关系。特别关注 doors:对于联合点是什么、door 承诺什么、拒绝什么,或危险效果应通过何处进行约束,任何分歧都值得与用户进一步确认。
    • 如果某个问题可以通过探索代码库来回答,请改为探索代码库,并向用户确认这是你推断出的意图。
  • 最后,在规格说明生成完毕且开放问题都已回答后,向用户提供规格说明的执行摘要,包括 specs/ 目录中生成的规格说明文档路径。
    • 在摘要中仅按名称列出 door 集合(从陌生读者视角出发),并指出哪些 door 会约束不可逆操作。
    • 鼓励用户评审规格说明,以获得最佳效果,并提供反馈或提出后续问题。

</EXTREMELY_IMPORTANT>

以形状优先的视觉语言

规范保留为 Markdown,但其中的视觉内容应采用能清楚表达重点的最小视图。省去前言,精简文字,并将每个视觉内容放在它所支持的简短文字旁边。根据需要使用以下一种或多种形式;不必每次都全部使用:

  • 用缩进的 text 伪代码展示逻辑或算法,不要只用文字描述。
  • 用缩进的调用树展示运行时控制流。
  • 用 tsx 组件树展示 UI 结构,包括重要的状态和模块边界。
  • 用浅层 text 文件树展示文件职责或大范围重构。
  • 用 Mermaid 展示组件交互、控制流或数据流。
  • 如果周围的结构已存在,且重点在于变更内容,则使用 diff。根据主题选择相应的 diff:组件树、文件树、调用树或状态/控制流。
  • 如果大部分内容都是新增的、被省略的上下文会掩盖归属关系或顺序,或者读者需要可复制的目标结构,则展示完整代码块。

对于视觉 UI、布局、状态对比,或过于复杂、不适合用 Mermaid 展示的概念,可以创建一个聚焦的 show-me-{description}.html 产物(图表、信息图或简短幻灯片)。保持与产品的颜色、字体、间距、组件、标签和数据一致;同时支持桌面和移动设备。规范仍以 Markdown 形式保存在 specs/ 中;仅当页面本身就是重点时,才将 HTML 作为可选补充。使用 Atomic 的 bash 工具和可移植的打开方式打开它:

if [ "$(uname -s)" = "Darwin" ] && command -v open >/dev/null 2>&1; then
  open "path/to/show-me-{description}.html"
elif [ "$(uname -s)" = "Linux" ] && command -v xdg-open >/dev/null 2>&1; then
  xdg-open "path/to/show-me-{description}.html"
else
  printf 'Open this file: %s\n' "path/to/show-me-{description}.html"
fi

[Project Name] 技术设计文档 / RFC

文档元数据详细信息
作者!git config user.name
状态草稿(WIP)/ 审核中(RFC)/ 已批准 / 已实现 / 已弃用 / 已拒绝
团队 / 负责人
创建时间 / 最近更新时间

1. 执行摘要

说明:用“TL;DR”概述本文档。假设读者是 VP,或来自其他团队、只有 2 分钟时间的工程师。概述背景(问题)、解决方案(提案)和影响(价值)。指出这项变更的核心一两个关口。控制在 200 字以内。

示例: 本 RFC 提议用事件驱动架构替换当前的夜间批处理计费系统。目前,计费延迟导致客户支持工单增加 5%。提议的解决方案引入两个资金关口——authorize_charge(可撤销的冻结)和 settle_payment(不可撤销的扣款)——作为所有对外资金流转的唯一控制点,将计费延迟从 24 小时缩短至 <5 分钟,同时从结构上杜绝重复扣款。

2. 背景与动机

说明:我们为什么要做这件事?为什么是现在?请链接到产品需求文档(PRD),并引用相关的 research/ 文档。

2.1 当前状态

说明:描述现有架构,并如实指出缺陷——包括哪些现有入口存在泄漏(以工具命名、压缩内容不实、危险四处分散)。选择能清楚呈现当前状态的最小视图:用于说明职责归属的浅层文件树、用于说明运行时流程的调用树、用于说明 UI 结构的组件树,或用于说明交互/数据流的 Mermaid 图。将图示放在简短说明旁边;如果文字更清晰,就不要强行加入图示。

  • **架构:**目前,Service A 和 Service B 通过共享 SQL 数据库通信。
  • **局限:**这会造成紧密耦合;当 Service A 锁定表时,Service B 会超时。
  • **当前存在泄漏的入口:**例如,chargeCard(token, cents) 可从结账流程、重试任务和管理面板调用——没有人负责确保“恰好扣款一次”。processPayment(...) -> bool 将银行卡被拒、网络故障和重复提交都压缩成同一个 false。
2.2 问题

说明:具体痛点是什么?

  • **用户影响:**客户在夜间批处理时段无法下载收据。
  • **业务影响:**由于计费错误,我们每月因客户流失损失 $X。
  • **技术债务:**危险四处分散;边界位置不当,防御性代码藏在核心深处,而非入口处。

3. 目标与非目标

说明:这是契约/成功定义。请务必准确。

3.1 功能目标
  • 用户必须能够以 CSV 格式导出数据。
  • 系统必须支持多租户数据隔离。
3.2 非目标(不在范围内)

说明:明确说明你不会做什么。请记住:意图体现在入口拒绝什么——你刻意不构建的入口,与实际构建的入口同样能体现目的。这有助于防止范围蔓延。

  • 此版本不会支持 PDF 导出(仅支持 CSV)。
  • 我们不会迁移超过 3 年的数据。
  • 我们不会开放第二条资金转移路径;settle_payment 仍将是唯一的关口。

4. 提议的解决方案(高层设计)

说明:“整体概览”。从上述以结构为先的可视化语言中,选择最合适的最小视图;只有在每个视图都能回答不同问题时,才使用多个视图。类型、树、差异或 Mermaid 应定义结构,简短文字说明原因。不要使用复杂的样式化图示;如果更简单的视图就能清楚表达边界,应优先使用简单视图。

4.1 系统架构图

说明:展示系统边界并标出气闸(不可信输入转变为可信请求的唯一边界)。使用 Mermaid 展示组件交互或数据流,使用浅层文件树说明模块职责,使用调用树展示运行时控制流,或使用组件树说明 UI 结构。如果大部分内容都是新设计,或省略上下文会导致职责归属或顺序不清,就展示完整结构。

flowchart TB
    User((User)) -->|untrusted request| Gateway["Gateway<br/>auth · validate · authorize<br/>airlock"]
    Gateway -->|trusted request| API["Core service<br/>trusts its invariants"]
    API --> DB[(Primary DB)]
    API -.-> Worker[Worker]
    Worker -.-> Ext["External provider<br/>irreversible effect"]
4.2 架构模式

说明:指出所采用的模式(例如,“事件溯源”、“BFF——面向前端的后端”、“发布-订阅”)。

  • 我们将采用发布-订阅模式:Order Service 发布 OrderCreated 事件,Billing Service 异步消费这些事件。
4.3 关键组件
组件职责技术栈理由
Ingestion Service验证传入的 WebhookGo、Gin Framework需要高并发性能。
Event Bus解耦服务Kafka持久化日志,支持重放。
Projections DB读优化视图MongoDB灵活的架构可适应多种收据格式。

说明:列出实现该设计的文件和模块。列出每个新增、修改、删除、测试和配置文件,并说明各自负责的内容。若目录结构是关键点,请使用浅层文件树;否则请使用此简明映射。

路径操作负责内容
src/feature/door.ts修改对外的门及边界契约
test/feature/door.test.ts新增贯穿整个门的纵向行为切片
4.4 门一览(跨时间的陌生人视角)

说明:仅列出入口名称,不写签名或函数体。一个能力出众的陌生人应该能从此列表中重建系统的用途。如果做不到,说明意图已泄漏到机制中;请返回 §5 并重命名,直到做到为止。用 ⚠ 标记每个守护不可逆效果的门。

示例: register_account、authenticate、authorize_charge、settle_payment ⚠、grant_access ⚠、revoke_access、publish_draft。仅阅读这些名称,就能看出系统允许谁进入、资金分两步且仅分两步流动、谁可以授予访问权限,以及让工作正式上线意味着什么。

5. 详细设计

说明:本文档的“核心”部分。提供足够的细节,让工程师能够开始编写代码。先介绍门——它们是规范中承重的部分——然后描述其背后的机制。

5.1 门(入口契约)

说明:对每个非平凡入口,给出类型化签名(可使用类型化伪代码——关注类型,而非语法)、一句话保证(不能含有“并且”)、明确列出的失败情况,以及它在类型系统中强制执行的拒绝条件。然后记录评审标准的结果。应让非法状态无法表示,而不只是进行检查。引用能证明每个关节的 research/ 文档。如果该门大部分是新建的,或读者需要可复制的目标形态,请使用完整代码块;如果是在修改现有门,请使用与主题匹配的 diff。用调用树展示进出该门的运行时路径,并在相关时用组件树展示 UI/模块边界。

// — Money. Two doors, and there is no third way to move a cent. —

authorize_charge(
  account: AccountId,            // newtype: cannot be confused with any other id
  amount: Money,                 // currency-typed: USD and JPY will not add
  idempotency_key: IdempotencyKey,
): Result<AuthorizedCharge, ChargeError>
// Guarantee: places a reversible hold and returns proof an authorization exists.
// ChargeError = InsufficientFunds | CardDeclined | NetworkError | DuplicateKey

settle_payment(
  authorized: AuthorizedCharge,  // ← can ONLY be produced by authorize_charge
  idempotency_key: IdempotencyKey,
): Result<Settlement, SettlementError>
// Guarantee: captures the held funds. IRREVERSIBLE. The single chokepoint for outbound money.
// You cannot settle a charge you did not authorize — not because a check forbids it,
// but because there is no way to CONSTRUCT an AuthorizedCharge except by calling
// authorize_charge. The illegal state is unrepresentable. The idempotency key makes
// the retry, the double-click, and the at-least-once queue converge on ONE settlement.

逐门审计(执行评审标准):

门(1)关节(2)一句话,不含“并且”(3)名称如实(5)所有出口(6)拒绝真实有效(7)信任转换(8)单一瓶颈
authorize_charge✅ 业务动词✅ “放置可撤销的预授权”✅重试 → DuplicateKey;超时 → NetworkError货币不匹配无法表示不适用可撤销,并非瓶颈
settle_payment ⚠✅ 业务动词✅ “扣取已预留的资金”✅ 文档和类型中均说明不可逆性重放会通过密钥收敛无法结算未授权的费用(类型限制)不适用✅ 唯一的对外资金流出门
5.2 API 接口 — 线上的同一扇门

说明:Web 服务的真实边界是它的传输接口。URL 命名关节,HTTP 动词声明其安全类别,状态码则是这扇门诚实的出口。绝不能用 200 OK 包装错误。线上接口必须与其进程内对应接口使用相同名称(§5.1)。

# Identity — the one trust transition, at the edge
POST   /v1/sessions                       201 Created      # = authenticate; 401 on bad credentials
DELETE /v1/sessions/current               204 No Content   # = log out

# Money — two doors, one chokepoint, idempotent under retry
POST   /v1/payment_intents                201   Idempotency-Key: <key>   # = authorize_charge (reversible)
POST   /v1/payment_intents/{id}/capture   200   Idempotency-Key: <key>   # = settle_payment (IRREVERSIBLE)
#   409 Conflict if the key is replayed with a different body
#   422 Unprocessable if the intent was never authorized

# Access — authority demanded by the route, destructive door made idempotent
POST   /v1/accounts/{id}/grants           201   (admin scope required)            # = grant_access
DELETE /v1/grants/{id}                     204   (204 even if already revoked)     # = revoke_access

# Publishing — the domain's own verb, refusing to clobber a concurrent edit
POST   /v1/drafts/{id}/publish            200   If-Match: <etag>                   # = publish_draft
#   412 Precondition Failed if the draft moved under you — the wire's --force-with-lease

如果使用 gRPC,请在 .proto 中定义相同的关节;类型化请求消息本身就构成了气闸。使用准确的状态码(INVALID_ARGUMENT、PERMISSION_DENIED、NOT_FOUND、ALREADY_EXISTS、FAILED_PRECONDITION,以及可重试的 ABORTED/UNAVAILABLE)——绝不能只返回一个携带错误字段的 OK。

5.3 数据模型 / 架构

说明:提供 ERD 或 JSON 架构。讨论规范化与反规范化。优先选择能让非法状态无法表示的架构(使用和类型状态列,而不是互相独立的布尔标志)。

表: invoices(PostgreSQL)

列类型约束说明
idUUIDPK
user_idUUIDFK -> Users分区键
statusENUM'DRAFT','LOCKED','PROCESSING','PAID'和类型,而非三个布尔值
5.4 算法与状态管理

说明:描述复杂逻辑、状态机或一致性模型。将每次状态转换与执行它的接口关联起来,并选择能清晰说明行为的最小视图。算法使用缩进的 text 伪代码,运行时控制流使用调用树,交互或数据流使用 Mermaid,现有状态或控制流结构的变更则使用与主题匹配的 diff。只要存在可能路径,就应说明失败、重试、取消、幂等性和并发行为;未知事项留给 §9,不要凭空编造。

on(settle_payment)
  if request is a replay
    return the recorded settlement
  validate the authorized charge
  if provider call fails
    return named retryable error
  persist settlement
  return settlement
publishDraft
  authenticate
  loadDraft
  checkVersion
  persistPublication
  notifySubscribers
  • 状态机: 发票从 DRAFT → LOCKED → PROCESSING → PAID;只有通过 settle_payment 才能完成 PROCESSING → PAID 转换。
  • 并发: 对 version 列使用乐观锁;在线上表现为 If-Match/412。
  • 数据流: 当这些阶段可达时,请在 Mermaid 图或简洁的箭头链中展示边界输入、解析、规范域输入、服务接口、适配器调用、类型化结果/错误、投影和序列化输出。

6. 考虑过的替代方案

说明:证明你考虑过权衡取舍,包括不同的门集合(例如,一个万能端点还是各自独立的关节)。在确定建议方案之前,探索存在实质差异的替代方案,然后记录为何选定的边界更好。比较接口形态、接缝位置、所有权、调用栈、运行时拓扑和模块边界,而不只是名称。

选项优点缺点拒绝理由
选项 A:单个 POST /execute {action}一个路由,灵活万能门;意图隐藏在负载中;危险操作未通过专用通道不符合“关节,而非工具”和“危险门应尽可能少”的原则。
选项 B:一步完成的 chargeCard()调用次数最少没有可撤销的预授权;重试会导致重复扣款无法从根本上杜绝重复扣款。
选项 C:authorize + settle(已选定)可撤销的预授权;单一关口;幂等比单次调用多一步**已选定:**对应两个真正的关节,且不可逆操作只通过一个关口。

7. 跨领域关注点

7.1 安全与隐私

说明:在这里,“让危险的门尽可能少且明确”和“在边界处设置气闸”将落到实处。

  • **信任转换是唯一的:**不受信任的调用方只有在 POST /v1/sessions / 网关处才会变为受信任。没有其他门会将匿名调用方提升为受信任方。(评分标准 #7。)
  • **权限由类型承载:**破坏性或特权门要求具备仅由 authenticate 创建的能力(AdminSession)——由于不存在缺少此检查的调用点,因此调用处不可能漏掉权限检查。(评分标准 #6。)
  • **不可逆操作通过单一关口:**资金操作通过 settle_payment,删除操作通过唯一受保护的门;必须明确请求才能执行灾难性操作。(评分标准 #8。)
  • **数据保护:**PII(姓名、电子邮件)在静态存储时使用 AES-256 加密;Password 是一种新类型,无法被意外记录、打印或比较。
  • **威胁模型:**主要威胁是 API 密钥遭到泄露;补救措施是快速轮换密钥并限制速率。

8. 测试计划

说明:测试各个入口兑现承诺和拒绝请求的情况,而不只是正常路径。评估标准 #5 中的每个出口都应有对应测试。通过公共入口和接缝规划纵向的红—绿—重构(RGR)切片:每个切片都从一个失败的行为测试开始,加入能使测试通过的最小实现,然后在不改变行为的前提下重构。不要先把所有测试作为一批横向任务写完。交互式验证可让人类或其他 Agent 无须阅读实现主体就能确认功能正确——这就是“跨越时间的陌生人”测试,并将其变成可执行测试。

  • 单元测试: 每个入口的具名失败变体;以及拒绝请求的测试(例如,通过类型或构造测试证明 settle_payment 只能接受 AuthorizedCharge)。
  • 端到端测试: 通过 §5.2 中真实的线路入口驱动联合定义的完整领域流程(注册 → 身份验证 → 授权 → 结算)。
  • 集成测试: 重放时的幂等性(相同密钥 → 一次结算);并发编辑时的 412;信任转换(除了 authenticate,任何入口都不得提升匿名调用者的权限)。
  • 模糊测试 / 属性测试: 向各个入口(气闸)输入格式错误和对抗性数据(气闸);边界必须拒绝类型所禁止的一切输入,且绝不能导致核心崩溃。对随机输入断言不变量(例如,无论重试如何交错,settle_payment 都会收敛为一次结算;除非经过瓶颈,否则任何输入序列都无法触发资金转移)。
  • 交互式验证: 提供一份可运行的清单或脚本,供人类或其他 Agent 执行,以确认功能已正确实现——每一步都要指定一个入口,提供一个输入,并说明预期的如实出口(状态码 / 具名错误 / 结果状态),让人仅凭边界就能观察到正确性。列出要运行的确切命令或请求,以及每项的通过 / 失败条件。

9. 待解问题 / 未解决事项

说明:列出已知的未知事项。将文档标记为“已批准”之前,必须解决这些问题。如果某个入口的评估标准无法明确作答,也必须列出——尤其是未定义的保证(评估标准 #2,最危险的情况),以及尚未汇入单一瓶颈的任何不可逆效果(评估标准 #8)。通过对比式澄清与用户一起解决这些问题。

  • publish_draft 是将草稿发布上线的唯一入口吗?还是管理面板也可以发布?(如果是后者,该效果尚未汇入单一瓶颈——评估标准 #8。)
  • 部分供应商服务中断时,authorize_charge 的具体承诺是什么?这项保证是否有明确规定?(评估标准 #2。)
  • 法务团队会批准用于生成 PDF 的第三方库吗?
  • 当前 VPC 对等连接是否允许连接到旧版大型机?

评论

支持 Markdown