重置项目上下文与规范文档

This commit is contained in:
2026-08-07 16:18:07 +08:00
parent 6611ca5226
commit 79e2d9ff92
1900 changed files with 1552 additions and 348365 deletions

View 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 使用 GORMMUST 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 或 mapMUST 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 在同一事务写 OutboxMUST 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 LogHTTP 请求写 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
- **更新触发条件**:新增配置或依赖升级