重置项目上下文与规范文档
This commit is contained in:
289
docs/engineering/工程约束.md
Normal file
289
docs/engineering/工程约束.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# 工程约束
|
||||
|
||||
本文件只记录从当前代码、配置、迁移、构建或可复现运行结果证明的长期工程规则。业务行为属于 `openspec/specs/`,第三方协议属于 `docs/integrations/`。自动化测试当前为 N/A(用户决策)。
|
||||
|
||||
## ENG-ARCH-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:`internal/domain/**`
|
||||
- **规则**:Domain MUST NOT import Fiber、GORM、Redis、Asynq 或具体第三方 SDK。
|
||||
- **理由**:领域不变量必须能脱离传输和基础设施独立演化。
|
||||
- **最小正例**:Domain 只依赖标准库、领域类型和 Port。
|
||||
- **最小反例**:Domain 直接调用 GORM 或 Redis。
|
||||
- **机械检查/人工原因**:`! grep -RIlE 'gofiber|gorm.io|go-redis|hibiken/asynq' internal/domain --include='*.go'`,当前通过。
|
||||
- **例外条件**:既有兼容行为仍须先通过独立 Change 才能调整边界。
|
||||
- **Owner**:架构负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:Domain 依赖或分层策略变化
|
||||
|
||||
## ENG-ARCH-002
|
||||
- **状态**:生效
|
||||
- **适用范围**:新增或修改完整用例
|
||||
- **规则**:复杂写 MUST 使用 Application→Domain→Port;简单写 MUST 使用 Application 事务脚本;读取 MUST 使用 Query 或既有只读 Service,且不得修改状态。
|
||||
- **理由**:按用例选择最小边界,避免半迁移和形式化 DDD。
|
||||
- **最小正例**:订单资金状态机收口 Domain;列表查询直接 DTO 投影。
|
||||
- **最小反例**:只为单表 CRUD 创建聚合,或 Query 内更新状态。
|
||||
- **机械检查/人工原因**:暂为人工:从 Route 追踪到用例和持久化,确认一个完整用例只有一个规则归属。
|
||||
- **例外条件**:未触碰的旧 `internal/service` 保持现状。
|
||||
- **Owner**:架构负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增用例或触碰旧复杂写
|
||||
|
||||
## ENG-ERR-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:到 HTTP 边界的错误链
|
||||
- **规则**:到 Handler/HTTP 边界前 MUST 转换为 `pkg/errors` 稳定错误;Handler 参数校验 MUST NOT 拼接或返回底层 `err.Error()`。
|
||||
- **理由**:全局 ErrorHandler 只能安全映射稳定错误,底层文本可能泄密。
|
||||
- **最小正例**:`errors.Wrap(code, err)` 后返回给全局 ErrorHandler。
|
||||
- **最小反例**:`return c.JSON(...err.Error())`。
|
||||
- **机械检查/人工原因**:审查变更中的 Handler 返回点;运行 `go build ./cmd/api`。全仓 `fmt.Errorf` 只作候选,不能直接判错。
|
||||
- **例外条件**:内部不可见、不会越过接口边界的诊断错误允许 `fmt.Errorf`。
|
||||
- **Owner**:API 负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:错误系统或 ErrorHandler 变化
|
||||
|
||||
## ENG-RESP-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:HTTP Handler
|
||||
- **规则**:成功响应 MUST 使用 `pkg/response`;错误 MUST 返回给 `internal/middleware.ErrorHandler`,不得自造响应壳。
|
||||
- **理由**:统一 `{code,msg,data,timestamp}` 和 HTTP 映射。
|
||||
- **最小正例**:`return response.Success(c, data)`。
|
||||
- **最小反例**:Handler 直接 `c.JSON` 返回另一套结构。
|
||||
- **机械检查/人工原因**:审查新增 Handler;`rg "response\.(Success|Error)" internal/handler internal/routes`;生成 OpenAPI 人工核对。
|
||||
- **例外条件**:第三方回调必须返回渠道要求的字面协议时可使用专用响应。
|
||||
- **Owner**:API 负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:响应协议或第三方回调契约变化
|
||||
|
||||
## ENG-DTO-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:进入 OpenAPI 的请求/响应 DTO
|
||||
- **规则**:公开字段 MUST 有中文 description;枚举描述 MUST 与 `pkg/constants` 当前值一致;状态响应按现有契约提供名称字段。
|
||||
- **理由**:前端和生成文档依赖字段与枚举精确一致。
|
||||
- **最小正例**:从 constants 原文复制枚举值并生成 OpenAPI 核对。
|
||||
- **最小反例**:凭记忆写 description 或遗漏状态名称。
|
||||
- **机械检查/人工原因**:`go run cmd/gendocs/main.go` 后核对 OpenAPI;暂为人工比对枚举与 constants,单纯 grep 不足以证明完整。
|
||||
- **例外条件**:仅内部、不进入接口契约的结构。
|
||||
- **Owner**:API 负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:DTO、枚举或 OpenAPI 生成变化
|
||||
|
||||
## ENG-MODEL-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:新增/修改 `internal/model` 与当前根迁移
|
||||
- **规则**:关联 MUST 保存 ID 并显式查询;MUST NOT 新增 GORM `foreignKey`/`hasMany`/`belongsTo` 标签或数据库外键。
|
||||
- **理由**:当前隔离与渐进迁移依赖显式关联。
|
||||
- **最小正例**:保存 `ShopID`,由 Store 显式查询。
|
||||
- **最小反例**:新增关联切片和 `foreignKey` 标签。
|
||||
- **机械检查/人工原因**:只检查变更新增行:`git diff -- internal/model migrations | grep -E "^\+.*(foreignKey|hasMany|belongsTo|REFERENCES)"`,期望空。
|
||||
- **例外条件**:`migrations/archive` 的历史外键不作为新增违规。
|
||||
- **Owner**:数据负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:模型或关联策略变化
|
||||
|
||||
## ENG-DB-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:生产 Go 数据访问
|
||||
- **规则**:生产数据访问 MUST 使用 GORM,MUST NOT 新增 `database/sql` 直接访问。
|
||||
- **理由**:统一连接、事务、Callback 数据范围和错误处理。
|
||||
- **最小正例**:通过 `*gorm.DB` 或 Store 查询。
|
||||
- **最小反例**:业务 Service 新建 `sql.DB`。
|
||||
- **机械检查/人工原因**:检查变更新增 import:`git diff -U0 -- "*.go" | grep -E "^\+.*database/sql"`,期望空。
|
||||
- **例外条件**:第三方库内部实现不受本规则约束。
|
||||
- **Owner**:数据负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:数据访问基础设施变化
|
||||
|
||||
## ENG-MIG-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:`migrations/` 当前根目录
|
||||
- **规则**:新迁移 MUST 使用新顺序号并提供 `.up.sql`/`.down.sql` 配对;MUST NOT 修改已发布迁移。
|
||||
- **理由**:保证 Schema 可重放和回滚。
|
||||
- **最小正例**:新增同号 up/down 文件并在隔离库执行。
|
||||
- **最小反例**:改旧迁移或只提供 up。
|
||||
- **机械检查/人工原因**:按 `migrations/*.up.sql` 与 `*.down.sql` basename 配对;当前根目录 89/89。隔离库使用 `scripts/migrate.sh` 执行 up/down/up。
|
||||
- **例外条件**:不可逆数据清理须在 Change 说明恢复方式;archive 历史资产不要求补配。
|
||||
- **Owner**:数据负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:Schema 或迁移工具变化
|
||||
|
||||
## ENG-ROUTE-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:新增或修改 Handler/路由
|
||||
- **规则**:路由 MUST 经 `internal/routes.Register` 注册 RouteSpec;新增 Handler MUST 同步 `cmd/api/docs.go` 与 `cmd/gendocs/main.go` 的占位装配。
|
||||
- **理由**:运行路由与生成文档共享元数据,但两套 composition root 仍需一致。
|
||||
- **最小正例**:路由文件注册并在两处 docs Handlers 增加字段。
|
||||
- **最小反例**:只在 Fiber app 上挂载 Handler。
|
||||
- **机械检查/人工原因**:`go run cmd/gendocs/main.go`;对比两处 `bootstrap.Handlers` 字段;再对照真实 `internal/bootstrap` 装配。
|
||||
- **例外条件**:健康/就绪路由在总入口内定义,但仍使用 RouteSpec。
|
||||
- **Owner**:API 负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增 Handler 或文档生成方式变化
|
||||
|
||||
## ENG-COMMENT-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:Go 生产代码
|
||||
- **规则**:导出符号 MUST 有中文文档注释;复杂逻辑说明原因;Handler 注释 MUST 包含 HTTP 方法和路径。
|
||||
- **理由**:保持 godoc、评审和真实路由可核对。
|
||||
- **最小正例**:导出 Handler 注释写职责及 `POST /api/...`。
|
||||
- **最小反例**:注释复述赋值或路径过时。
|
||||
- **机械检查/人工原因**:`go vet` 只做基础检查;中文、原因和路径由变更 diff 人工核对真实路由。
|
||||
- **例外条件**:显而易见且少于 15 行的未导出函数可无注释。
|
||||
- **Owner**:后端负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增导出 API 或路由变化
|
||||
|
||||
## ENG-JSON-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:新增 JSON 编解码代码
|
||||
- **规则**:新增业务 JSON MUST 优先 sonic;新增 `encoding/json` import MUST 记录兼容原因。
|
||||
- **理由**:Fiber 已配置 sonic,但现有 SDK/OpenAPI/协议有标准库兼容需求。
|
||||
- **最小正例**:业务载荷使用 `sonic.Marshal`。
|
||||
- **最小反例**:无理由新增 `encoding/json`。
|
||||
- **机械检查/人工原因**:只审查 diff 中新增 import;当前仓库已有多处兼容使用,不能以全仓 grep 直接失败。
|
||||
- **例外条件**:标准库接口、第三方 SDK 或协议兼容明确要求。
|
||||
- **Owner**:后端负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:JSON 库或兼容协议变化
|
||||
|
||||
## ENG-QUEUE-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:`queueClient.EnqueueTask` 调用
|
||||
- **规则**:payload MUST 是 struct 或 map,MUST NOT 传预序列化 `[]byte`。
|
||||
- **理由**:封装会统一校验并 sonic.Marshal,字节切片会二次编码。
|
||||
- **最小正例**:`EnqueueTask(ctx, typ, Payload{ID:id})`。
|
||||
- **最小反例**:Marshal 后把 `[]byte` 交给 EnqueueTask。
|
||||
- **机械检查/人工原因**:核对全部 EnqueueTask 第三实参及 `pkg/queue/client.go` 的 ValidatePayload;运行 `go build ./cmd/worker`。
|
||||
- **例外条件**:直接调用 `asynq.NewTask` 时由调用方序列化。
|
||||
- **Owner**:异步任务负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:队列封装或载荷协议变化
|
||||
|
||||
## ENG-PAGE-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:列表 API
|
||||
- **规则**:列表 MUST 分页并执行各接口当前上限;默认 20/最大 100 仅适用于已明确采用该契约的列表。
|
||||
- **理由**:避免无界查询,同时不把不同接口的既有分页强行合并。
|
||||
- **最小正例**:Handler/Query 对缺省值与上限显式归一化。
|
||||
- **最小反例**:只写 OpenAPI tag,不在运行时限制。
|
||||
- **机械检查/人工原因**:从 DTO→Handler→Query 逐链核对 Limit;暂为人工,不以 tag 单独证明行为。
|
||||
- **例外条件**:固定且有界的小型枚举集合。
|
||||
- **Owner**:API 负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:分页协议或查询性能变化
|
||||
|
||||
## ENG-STATE-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:新增或变更状态/类型字段
|
||||
- **规则**:新增生命周期状态 SHOULD 使用 int,类型/方式 SHOULD 使用 string;启停新增语义使用 0=禁用、1=启用。既有不一致 MUST 按 As-Is 保留,未经 Change 不得统一。
|
||||
- **理由**:避免继续扩大状态语义漂移,同时保护兼容行为。
|
||||
- **最小正例**:新 Status int;新 PaymentMethod string。
|
||||
- **最小反例**:基线任务顺手改既有状态值。
|
||||
- **机械检查/人工原因**:比对 constants、DTO、模型与迁移;既有例外必须在 Spec 记录。
|
||||
- **例外条件**:第三方原始字段在 Adapter 边界转换;既有兼容状态保留。
|
||||
- **Owner**:领域负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增状态机或兼容映射
|
||||
|
||||
## ENG-AUTHZ-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:资源读取与写入
|
||||
- **规则**:资源操作 MUST 在业务边界校验店铺/企业/个人客户数据范围;MUST NOT 只依赖路由角色中间件。无权限与不存在不得形成可枚举差异。
|
||||
- **理由**:路由只做粗粒度身份,资源归属随数据变化。
|
||||
- **最小正例**:Application/Service 在读写前调用当前数据范围检查。
|
||||
- **最小反例**:知道资源 ID 即可更新。
|
||||
- **机械检查/人工原因**:从 Handler 追踪 Application/Service 的资源校验及 GORM Callback;暂为人工抽查权限失败响应。
|
||||
- **例外条件**:公开健康、回调等不以资源归属认证,但必须执行自身信任校验。
|
||||
- **Owner**:安全负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增资源入口或数据范围变化
|
||||
|
||||
## ENG-CONC-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:状态机、余额、库存与并发领取
|
||||
- **规则**:状态流转 MUST 使用 expected-status 条件更新;余额 MUST 使用 version/锁定策略;MUST NOT 读后无条件写关键事实。
|
||||
- **理由**:防止重复处理、负余额和并发覆盖。
|
||||
- **最小正例**:`WHERE status=expected` 并检查 RowsAffected。
|
||||
- **最小反例**:先读状态再无条件 Updates。
|
||||
- **机械检查/人工原因**:核对 `application/wallet`、`agentrecharge` 等现有模式;变更时人工检查条件、版本和 RowsAffected。
|
||||
- **例外条件**:只读投影不适用。
|
||||
- **Owner**:领域负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:并发写或状态机变化
|
||||
|
||||
## ENG-OUTBOX-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:需要提交后可靠副作用的用例
|
||||
- **规则**:业务事实与可靠异步副作用 MUST 在同一事务写 Outbox;MUST NOT 用裸 goroutine 代替可靠投递;消费者 MUST 容忍重复。
|
||||
- **理由**:进程崩溃和至少一次投递会造成丢失或重复。
|
||||
- **最小正例**:事务写业务事实与 Outbox,消费者条件更新。
|
||||
- **最小反例**:提交后启动 goroutine 调第三方。
|
||||
- **机械检查/人工原因**:追踪 producer→Outbox→Relay→consumer;检查事件唯一键、租约和终态条件。
|
||||
- **例外条件**:允许丢失的低价值遥测可不用 Outbox,但须明确说明。
|
||||
- **Owner**:可靠性负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增外部副作用或事件
|
||||
|
||||
## ENG-TX-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:资金、状态与成功审计
|
||||
- **规则**:资金/关键状态事实和要求成功必达的审计 MUST 在同一 GORM 事务;事务内 MUST NOT 持有不可回滚的长外部 I/O。
|
||||
- **理由**:保证事实与审计一致,并控制锁时间。
|
||||
- **最小正例**:事务写事实和 Audit Writer;提交后由 Outbox 外发。
|
||||
- **最小反例**:事务中等待第三方网络后再提交。
|
||||
- **机械检查/人工原因**:逐用例人工核对 Transaction 闭包、Audit Writer 和外部调用位置。
|
||||
- **例外条件**:业务回滚后的 failed/denied 审计使用独立短事务。
|
||||
- **Owner**:架构与审计负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:高风险写或外部调用变化
|
||||
|
||||
## ENG-AUDIT-001
|
||||
- **状态**:机械检查阻塞
|
||||
- **适用范围**:状态变更、资金、权限、关键配置、敏感读取与关键拒绝
|
||||
- **规则**:用例 MUST 明确 Audit Event、Domain Ledger、Integration Log、Outbox 的使用决定或 N/A 理由;四类事实不得互相替代。
|
||||
- **理由**:Access Log 不能证明业务事实,Integration Log 不能替代资金流水。
|
||||
- **最小正例**:状态成功写 Audit;外部尝试写 Integration Log。
|
||||
- **最小反例**:只记录 access.log 后宣称已审计。
|
||||
- **机械检查/人工原因**:当前 `cmd/audit-coverage` 写入已删除 `.scratch` 路径,不能作为通过门禁;修复前逐调用链人工核对并在仓库外记录证据。
|
||||
- **例外条件**:低风险用例可登记 N/A 理由。
|
||||
- **Owner**:审计负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:审计 CLI 输出路径修复或新增高风险用例
|
||||
|
||||
## ENG-LOG-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:运行日志、审计、资金事实和外部交互
|
||||
- **规则**:Access Log、Audit Event、Domain Ledger、Integration Log 与 Outbox MUST 分责;日志 MUST 使用结构化 Zap 且不得记录密钥或完整敏感体。
|
||||
- **理由**:不同保留期、查询维度与一致性要求不可混用。
|
||||
- **最小正例**:外部失败写脱敏 Integration Log,HTTP 请求写 Access Log。
|
||||
- **最小反例**:把完整凭证写 app.log。
|
||||
- **机械检查/人工原因**:人工核对 Logger 字段、Sanitizer 与事实存储;本地按 request_id/correlation_id 查询日志。
|
||||
- **例外条件**:第三方要求回显的非敏感渠道码可保留。
|
||||
- **Owner**:审计与安全负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:日志字段、保留策略或外部集成变化
|
||||
|
||||
## ENG-SECRET-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:配置、代码、文档、日志和示例
|
||||
- **规则**:密钥、Token、证书和真实凭证 MUST 由生效配置/环境注入,MUST NOT 新增到仓库或日志;示例只用占位符。
|
||||
- **理由**:仓库历史和日志传播范围不可控。
|
||||
- **最小正例**:文档使用 `<TOKEN>`,运行环境注入 Secret。
|
||||
- **最小反例**:提交可用 AppSecret。
|
||||
- **机械检查/人工原因**:对 git diff 做敏感模式人工扫描;核对 `pkg/config` 绑定和日志字段。当前历史凭证属于待决策存量,不得复制。
|
||||
- **例外条件**:明确不可用的假值可用于文档占位;当前无测试夹具。
|
||||
- **Owner**:安全负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:配置字段、凭证或集成变化
|
||||
|
||||
## ENG-CONFIG-001
|
||||
- **状态**:生效
|
||||
- **适用范围**:运行配置和依赖版本
|
||||
- **规则**:依赖版本 MUST 以 `go.mod` 为准;运行参数 MUST 经 `pkg/config` 加载/校验;业务代码 MUST NOT 散落读取环境变量。
|
||||
- **理由**:保证默认值、环境覆盖和启动校验只有一个入口。
|
||||
- **最小正例**:在 Config 增字段并由 loader 绑定。
|
||||
- **最小反例**:业务 Service 直接 `os.Getenv`。
|
||||
- **机械检查/人工原因**:检查变更新增 `os.Getenv`;运行 `go build` 并核对 `pkg/config/config.go` 校验。
|
||||
- **例外条件**:启动脚本读取环境变量用于组装进程配置。
|
||||
- **Owner**:基础设施负责人
|
||||
- **最后验证日期**:2026-08-07
|
||||
- **更新触发条件**:新增配置或依赖升级
|
||||
Reference in New Issue
Block a user