MessageWeave/AGENTS.md

14 KiB
Raw Blame History

工程规范(Engineering Norms)

本文件是语言无关的通用工程规范:适用于本仓库中任何语言的源代码、脚本、前端资源与文档。 它不描述任何具体项目的事实。项目目标、技术选型、安全不变量、禁止事项、测试验收清单、 稳定 ID 注册表在 docs/charter.md。

两者冲突时:项目事实与安全不变量以 docs/charter.md 为准,质量规则以本文件为准。

版本基线 填写本仓库的基线 commit:<<代码行号锚点基线>(例如 b70d188)|<文档章节引用基线>(例如 e0882c7)>,由持有门禁结果的提交者在合并该批改动时更新。

校验命令的具体可复制版本写在 docs/charter.md §6.2(GATE-P0)与 docs/charter.md §6.3(GATE-DOCS)。


1. 优先级与冲突仲裁

规则从高到低,下位不得覆盖上位:

  1. 可复核的代码事实
  2. docs/charter.md 的项目事实与安全不变量
  3. 本文件的质量规则
  4. 文档表述
  5. 任何 AI 输出的推测
  • 文档与代码冲突时以代码为准,并在同一改动内修正文档。
  • 两份文档互相矛盾时,以权威来源声明更明确的一方为准;若均无声明,以更靠近可执行事实 (代码、测试、校验器)的一方为准。
  • 未验证不得断言。 任何声称「已经实现 / 已经修好 / 门禁已通过」的句子,必须来自本轮亲自执行的 命令输出或代码阅读。禁止把历史结论、推测、"应该是"写成事实。
  • 无法核实的部分必须明确写「未验证」,并说明缺什么(凭据、环境、依赖版本、外部服务)。

2. 代码编写规范

2.1 文件规模

  • 单文件规模控制在 500 行以内(软上限)。
  • 接近 400 行必须评估拆分。确需超过 500 行时,文件头部必须有一行以 SPLIT-EVAL: 开头的注释, 写明拆分评估结论与当前不拆的理由(例:内聚性优先——A 与 B 共享 C,拆分会引入 N 处重复)。
  • 未带 SPLIT-EVAL: 标记的超限文件视为待办项。 超限不自动合规。
  • 不得为凑合规而机械拆分,也不得为压低行数把逻辑塞进不可读的长函数。

2.2 注释与代码一致

  • 注释描述行为,不描述意图、计划或愿望。
  • 禁止在注释里写「未来会改为 X」「计划支持 Y」这类未实现的东西。
  • 占位实现必须显式标注为占位,并说明「真实实现需要什么环境 / 凭据」。
  • 注释里出现的标识符必须真实存在。 函数名、类型名、键名、环境变量名、文件路径, 每个都要能被检索到定义。检索零命中即为幻影标识符,属阻塞缺陷——它比一个 bug 更坏, 因为它会诱导人去做不存在的实现,或误以为某个保护已经存在。

2.3 禁止死代码与全局抑制

  • 不得引入死代码:定义但无任何调用点的函数、常量、导出、字段,一律删除。
  • 禁止全局抑制警告(整文件 / 整模块级别的 warning allow)。
  • 确需保留未使用符号时,用局部、点名、带理由的方式标注,写清保留原因与预期何时删除。 理由写不出来的,删掉。

2.4 依赖诚实

  • 不得在文档、注释或代码中引用没有实际引入的依赖、模块或 API。
  • 引入新依赖前写清它解决的唯一问题;已有实现能解决的不引入。
  • 依赖版本以锁文件为准。描述某依赖的能力时,以锁文件里的实际版本 API 为准, 不得照抄设计文档或凭记忆。
  • 文档写「已引入 / 未引入」时必须逐字对照依赖清单文件。

2.5 引用必须真实存在

  • 文档、注释、表格中的文件路径必须真实存在。表格里的模块路径不要靠 grep 推断—— 逐个用文件系统存在性检查验证。
  • 行号锚点必须指向对应内容,不能只指向「文件里那一行」。文档写「某文件第 120 行是 重试上限」,第 120 行就必须是重试上限;行号漂移而描述跟着跑,比行号写错更危险。
  • 校验器只能证明「该行号存在且非空」,不能证明该行内容符合描述。 内容级核对必须人工读。
  • 文档引用代码一律用符号名,不写行号。 唯一例外是 docs/deployment.md(运维 runbook, 事故中按行跳转)。行号随任何无关改动重排,而符号名稳定;docs/reference.md 曾有 56 个 notify.rs 锚点,其中约 40 个落在闭合括号、注释行或错误的符号上,而门禁报 0 错, 2026-09-29 决定彻底不维护行号。
  • 需要定位到行时用 grep 符号名,不要用文档里的行号做判断依据。
  • scripts/docs_check/audit_anchor_semantics.py 是独立脚本,没接进 run_all.sh, 恒 exit 0,只打印候选。它抓「锚点指向的行跟所声称的符号毫无关系」这类漂移,但抓不到 一行以内的 ±1/±2 off-by-one——那仍然要靠人读。候选数下降说明文档在好转,不等于锚点全对。

2.6 分层与依赖方向

  • 领域逻辑不得依赖任何传输层 / 渠道层 / SDK 类型;渠道 SDK 只能出现在渠道适配层。
  • 依赖只能指向更下层,不得反向引用。
  • 领域层对外暴露纯函数,使业务规则可以被不依赖任何外部服务的测试直接调用。

3. 配置与密钥规范

3.1 配置分层

  • 进程配置(连接地址、监听端口、密钥)只来自环境变量;必填项缺失时快速失败。
  • 业务配置(用户可调的行为参数)只存在于持久化状态存储中,由受保护的管理接口写入。
  • 两者不得混用;不得为了让本地开发方便而让业务配置静默回退到环境变量。
  • 不得引入通用配置框架。 配置解析用最小必要代码,手写解析可以接受。
  • 配置热加载必须显式(由受保护接口触发),进程不得轮询或监听配置文件。

3.2 密钥

  • 不得在代码、文档示例、日志或错误响应中回显密钥、令牌或密码。 示例一律用占位符。
  • 凭据只在内存中使用:不写本地文件、不进入提交记录、不出现在 URL 查询串。
  • 比较凭据必须用常量时间比较,避免时序侧信道。
  • 信任根单一。 管理凭据、会话凭据、外部服务令牌各自独立,不得互相推导; 不得用一个凭据(如存储连接密码)兼任另一个角色(如用户界面认证)—— 无密码的部署形态会让被兼任的角色永久不可用。

3.3 环境构建规范

  • 构建与测试必须在最小发行版中执行,不得在功能完整的环境里通过门禁。
  • 门禁命令必须在文档中给出完整可复制版本(含用户、挂载、环境变量、工作目录)。
  • 构建产物必须是静态链接、非 root、只读文件系统友好。
  • 依赖缓存目录不得进入版本控制,也不得进入构建镜像。
  • 门禁范围不得缩小。 不得为了让门禁变绿而删除测试、跳过校验、放宽检查级别或缩小检查范围。

4. 生产运行约束

  • 禁止数据库:不得引入关系型或文档型数据库。
  • 禁止本地文件写入:一切状态只存在于外部托管的状态存储。
  • 禁止本地文件状态:不得用本地文件持久化业务状态;进程重启必须可以完全无损。
  • 禁止非 stdout 日志:日志只写 stdout(错误信息可写 stderr),不得写日志文件。
  • 禁止长连接:不得引入 WebSocket / SSE / 长轮询;所有通信走短请求-响应。
  • 禁止有状态恢复流程:实例崩溃后不得依赖本地或内存状态「续做」未完成工作; 恢复必须来自外部状态存储中的可重放记录。
  • fail-closed 是默认姿态:依赖不可用、配置缺失、校验失败时返回可诊断的失败, 不得降级成「看起来成功」。
  • 错误响应结构全仓统一(统一 envelope + 可关联的请求 ID);可重试的失败必须告知重试时机。

5. 安全与鉴权规范

5.1 默认拒绝

  • 所有外部入口默认拒绝,白名单放行。
  • 白名单必须穷举,禁止通配(无 *、无前缀通配)。

5.2 鉴权分离

  • 不同调用方不得共享同一认证凭据。 用户界面会话、外部服务推送、机器对机器任务、 负载均衡转发必须各自持有独立凭据。
  • 会话必须是随机的、有短 TTL 的、可撤销的。
  • 凭据摘要可以落库,凭据本体不得落库。

5.3 诊断面

  • 诊断 / 联调面必须默认绝对关闭,且关闭态必须做到「路由不存在」(落到通用 404), 而不是「路由存在但拒绝」。
  • 开启必须要求两个独立因子同时满足(一个启动期显式开关 + 一个密钥环境变量)。
  • 诊断面不得暴露在任何公共网关 / CDN 白名单内,只能直连后端 origin 访问。
  • 诊断面必须是只读或纯函数式探测:不得写入外部系统状态,不得修改配置。
  • 若含测试性写操作,必须要求显式目标标识;空目标必须视为拒绝而非放行—— 「空白名单 = 放行一切」是必须显式校验的坑。

6. 测试与门禁规范

6.1 变更验证闭环

实现 → 校验 → 修文档 必须同一改动内完成,不得只改一边,也不得留到「以后补」。完整判据见 docs/charter.md §6.1。

6.2 测试要求

  • 每个行为变更都要有测试覆盖;纯重构不得改变测试总数。
  • 每条硬性安全不变量都要有一条测试或断言能证明它成立,而不是只写在文档里。
  • 门禁必须全部通过才算交付:格式化检查、依赖锁校验、静态检查(warning 当错误)、测试。
  • 禁止删除测试、跳过测试、放宽检查级别、删除访问控制来通过门禁。
  • 不得提交未验证的代码。 门禁结果必须来自本轮实际执行,不得沿用历史结论。

6.3 测试与实现同路径

  • 探针、测试、健康检查等「验证某依赖可用」的代码,必须与真实调用走同一条路径 (同样的认证、同样的 URL 归一化、同样的协议细节)。
  • 简化版验证代码会误报不可用,或更糟——误报可用。误报不可用会让入口层停发流量, 后果是全站不可用。

6.4 外部依赖测试

  • 无外部服务凭据时,测试用 mock 或本地回环服务。
  • 真实环境集成测试必须显式标注为可忽略,且缺凭据时静默跳过(不泄露凭据、不判失败)。
  • 不得声称已通过真实环境验证,除非本轮真的连过。

6.5 跨层改动

  • 改公共 API / 状态存储键与 TTL / 配置项 / 错误码 / 用户可见文案时, 文档、后端、前端或网关侧必须同轮同步。
  • 网关白名单改动必须同步路由文档与白名单测试。
  • 文案是产品行为的一部分。 面向用户的提示若与后端实际行为不符,就是一个 bug—— 填错的用户无法自助排障。

7. 文档治理规范

7.1 文档分工与权威

  • 每个文档只回答一个问题;不得跨文档复制表格、清单或数值。
  • 必须明确声明哪个文档是哪类事实的唯一权威来源。
  • 其他文档引用事实时给稳定锚点(文件 + 稳定 ID),不复制数值本身。

7.2 同轮同步

  • 改公共 API、状态存储键名与 TTL、配置项、错误码、用户可见文案时,同轮更新相关文档。
  • 中英双语文档必须信息对等(覆盖相同的事实集,不要求逐句对应)。

7.3 稳定 ID 与引用

  • 项目用稳定 ID 做跨文档引用;注册表属于项目文档,不属于通用规范。
  • 跨文档禁止用 §x.y 章节号做引用(章节号随编辑漂移);用稳定 ID。
  • 同文档内允许 §x.y 引用。
  • 新增稳定 ID 必须先登记到注册表。 未登记 ID 不得出现在注释、文档或标识符中。

7.4 自动化校验

  • 文档必须经过仓库内的文档校验器校验,通过才算完成。
  • 校验器只能证明「可达」:行号存在、引用可解析、表格列数一致。 它不能证明语义正确。
  • 报告校验结果时必须同时说明这个区分,不得把「0 error」说成「内容已验证」。
  • 新增文档必须加入校验器的文档清单,否则它永远不会被校验——这是静默失效。
  • 文档不得出现不存在的文件路径——audit_paths 逐个检查反引号内的路径型 token 是否真实存在, docs/retired.md 同样在检查范围内。曾被讨论但从未落地的路径只以文字描述其形态,不逐字写出。 已删除但 git 历史可证的文件(如 docs/todo.md)可以引用;无法证明存在过的不能。

8. 变更流程

  1. 确认 docs/charter.md 的安全不变量未被违反。
  2. 实现 + 测试。
  3. 跑门禁,全部通过。
  4. 跑文档校验器,全部通过。
  5. 同轮同步文档(代码 ↔ 测试 ↔ 文档 ↔ 前端 / 网关)。
  6. 提交。

不得:

  • 为了过门禁而删测试、跳校验、放宽级别、删访问控制、缩小检查范围。
  • 把未验证的东西写进文档(「已通过」「已实现」「已验证」一律需本轮证据)。
  • 在文档或注释里引用不存在的依赖、函数、类型、键、路径或行号。
  • 把「计划做」写成「已完成」;把「推测」写成「结论」。
  • 只改代码不改文档,或只改文档不改代码。