MessageWeave/AGENTS.md

270 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 工程规范(Engineering Norms)
> 本文件是**语言无关**的通用工程规范:适用于本仓库中任何语言的源代码、脚本、前端资源与文档。
> 它不描述任何具体项目的事实。项目目标、技术选型、安全不变量、禁止事项、测试验收清单、
> 稳定 ID 注册表在 [`docs/charter.md`](docs/charter.md)。
>
> 两者冲突时:**项目事实与安全不变量以 `docs/charter.md` 为准,质量规则以本文件为准。**
> **版本基线** 填写本仓库的基线 commit:<`9d4996c`(例如 `b70d188`)|`3b36fe3`(例如 `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 例外,已于 2026-10 合并入 `README.md` 并删除;
README 一律用符号名引用代码,不再保留行号锚点。)
`docs/reference.md` 的 56 个 `notify.rs` 锚点里约 40 个落在闭合括号、注释行或错误符号上,
而门禁报 0 错,2026-09-29 起全部删除、不再回补。opengaps / retired / design 里还剩 21 个
存量锚点,**不再新增、也不刻意清理**——它们改动频率低,不值得为此动文档。
- 需要定位到行时用 `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 测试要求
- 测试投入与风险匹配:影响安全不变量、API 契约、持久化、重试/幂等或关键数据流的行为变更,应增加或调整能区分正确与错误行为的测试。纯文档/文案、格式化、机械重命名,以及有充分理由确认不改变行为的局部整理,不需要新增单元测试。
- **每条硬性安全不变量都要有一条测试或断言能证明它成立**,而不是只写在文档里。
- 选最小但有判别力的验证:开发中优先跑受影响的测试;在一个关键阶段、准备提交或交付前,再运行该改动要求的完整门禁。独立且微小的编辑不要求每次重复全量测试。
- 门禁必须全部通过才算交付:适用的格式化检查、依赖锁校验、静态检查(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. 提交。
**不得**:
- 为了过门禁而删测试、跳校验、放宽级别、删访问控制、缩小检查范围。
- 把未验证的东西写进文档(「已通过」「已实现」「已验证」一律需本轮证据)。
- 在文档或注释里引用不存在的依赖、函数、类型、键、路径或行号。
- 把「计划做」写成「已完成」;把「推测」写成「结论」。
- 只改代码不改文档,或只改文档不改代码。
---
## 9. 协作与并行边界
多 agent 并行时,**同一件事只能有一个有效指令版本**。这不是礼仪,是事故来源。
2026-09-29 的实际故障:同一件事的旧版指令和新版指令同时躺在队列里,执行者按**旧版**动手,
lead 已经按**新版**收口并提交——同一份文档被两个人改,一次收口变成三轮互相覆盖。
**哪一版被执行是随机的,队列里没有「哪条有效」这个概念。**
1. **撤回必须点名。** 作废一条已发出的指令,光把新版发出去不算数——要直接告诉执行者
「那条作废、别做、保持 idle」。
2. **派活前先查状态。** 对方 `working` 时不要派活、不要改它的交付物、不要假设它收到了;
确认 `idle` 之后再派。
3. **不要发「改进版」覆盖未执行的旧版。** 要么等旧版执行完,要么先点名作废,再发新版。
4. **不要广播。** 广播会把整队打进 paused;点对点派发或靠任务派发唤醒。
5. **不撞车的机械收尾自己做。** 锚点重算、文档措辞、测试补齐这类不会互相覆盖的工作,
等别人时自己做完,不要排队——排队本身是延迟源。提交由 lead 统一收口。