13 KiB
13 KiB
工程规范(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. 优先级与冲突仲裁
规则从高到低,下位不得覆盖上位:
- 可复核的代码事实
docs/charter.md的项目事实与安全不变量- 本文件的质量规则
- 文档表述
- 任何 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 行就必须是重试上限;行号漂移而描述跟着跑,比行号写错更危险。
- 校验器只能证明「该行号存在且非空」,不能证明该行内容符合描述。 内容级核对必须人工读。
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. 变更流程
- 确认
docs/charter.md的安全不变量未被违反。 - 实现 + 测试。
- 跑门禁,全部通过。
- 跑文档校验器,全部通过。
- 同轮同步文档(代码 ↔ 测试 ↔ 文档 ↔ 前端 / 网关)。
- 提交。
不得:
- 为了过门禁而删测试、跳校验、放宽级别、删访问控制、缩小检查范围。
- 把未验证的东西写进文档(「已通过」「已实现」「已验证」一律需本轮证据)。
- 在文档或注释里引用不存在的依赖、函数、类型、键、路径或行号。
- 把「计划做」写成「已完成」;把「推测」写成「结论」。
- 只改代码不改文档,或只改文档不改代码。