docs(员工代收款): 新增路由前缀修复治理变更并勾选 AUG26-017 门禁
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 1m30s
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 1m30s
- 新增 OpenSpec Change fix-employee-collection-route-prefix,承载已落地的 ff1362d 路由前缀修复(无规格 delta,skip_specs)
- 记录根因(Register 的 basePath 只服务文档)、影响面(7 条根级残留、15 条同层抢占)、修复方式与验证方式
- AUG26-017 全局健康门禁实际通过后勾选 5.7(tasks 27/27)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
## Context
|
||||
|
||||
见 `proposal.md`。归档 Change `2026-09-11-add-employee-collection-bills`(来源提交 `ce24d56`)新增员工代收款账单闭环,其路由注册把资源前缀写进了 `Register` 的 `basePath`,导致 12 处注册落到 `/api/admin` 根上。修复提交 `ff1362d`(2026-09-11 14:40,`fix(员工代收款): 修正路由前缀注册方式,消除 /api/admin 根级 /:id 抢占`)已推送 `origin/Iteration/8-11`,但当时没有对应的 OpenSpec Change:主规格与 OpenAPI 文档描述的是 `/api/admin/employee-collection-*`,运行时却不是。本 Change 补上这条治理记录。
|
||||
|
||||
主规格侧的契约现在已显式化:证据链修复提交 `e687a26`(`补齐员工代收款账单能力证据链与可达操作索引`)在 `openspec/specs/employee-collection-bill/spec.md` 追加 `## 可达操作索引`,列出本次修复恢复的全部 12 条 `/api/admin/employee-collection-*` 路由,并同步 `docs/verification/context-reset/` 的 Requirement 证据与入口矩阵。本修复与该索引完全一致。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- 用独立 Change 承载已落地的路由前缀修复,使缺陷、影响面、修复方式与验证方式可追溯。
|
||||
- 明确本次修复不产生规格 delta 的依据,以及归档时的规格与证据链影响面。
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- 不改变业务行为、handler/application/service/domain/查询/迁移、权限可见性、OpenAPI 契约与 `Register` 语义。
|
||||
- 不承载证据链修复本身(`employee-collection-bill` 证据行、可达操作索引、入口矩阵,已由 `e687a26` 落地),也不改动任何主规格文件。
|
||||
|
||||
## Decisions
|
||||
|
||||
### 1. 根因:`basePath` 只服务文档,路由注册只用相对 path
|
||||
|
||||
`internal/routes/registry.go` 的 `Register` 第一行是 `router.Add(method, path, handler)`;`basePath` 只出现在 `doc != nil` 分支中拼接 `fullPath`(`pathParamRegex` 转换后写入文档)。缺陷版 `ce24d56` 把 admin 根路由组与相对 path 组合,`paymentMethodPath` / `billPath` / `applicationPath` 只传给了文档生成器。结果:文档路径正确,运行时路径缺少资源前缀。
|
||||
|
||||
Fiber 按注册顺序匹配且没有静态优先。最小复现:同一应用先注册 `GET /:id` 再注册 `GET /refunds`,`app.Test` 请求 `/api/admin/refunds` 命中参数路由;调换注册顺序后命中静态路由。这就是根级 `GET /api/admin/:id` 抢占后续同层静态 GET 的机制,也是 `registerEmployeeCollectionBillRoutes` 中「统计必须先于 `/:id` 注册」注释所反映的同一种顺序敏感性。
|
||||
|
||||
### 2. 影响面(离线路由表与真实路由实证)
|
||||
|
||||
离线路由表(真实 `RegisterAdminRoutes` + `openapi.BuildDocHandlers`,内存 Fiber 实例):缺陷版 356 条、修复版 358 条;差异恰为 `/api/admin` 根级减少 10 条栈条目(7 条不同 method+path)与 `/api/admin/employee-collection-*` 增加 12 条。
|
||||
|
||||
同层抢占清单(同方法下先注册的参数路由命中后注册的静态路由;原始命中 29 条,按「方法 + 被抢占静态路由」去重 15 条):
|
||||
|
||||
| 被抢占路由 | 归属能力 |
|
||||
| --- | --- |
|
||||
| `GET /api/admin/refunds`、`GET /api/admin/order-package-invalidate-tasks` | `order-refund-exchange` |
|
||||
| `GET /api/admin/system-configs` | `system-operations` |
|
||||
| `GET /api/admin/agent-recharges` | `agent-funds-commission` |
|
||||
| `GET /api/admin/agent-self-recharge-payment-methods` | `agent-funds-commission`(由在途 AUG26-017 引入) |
|
||||
| `GET /api/admin/asset-package-batch-orders` | `package-lifecycle` |
|
||||
| `GET /api/admin/wecom` | `external-integration` |
|
||||
| `PUT|DELETE /api/admin/{agent-self-recharge-payment-methods,agent-recharges,refunds,wecom}` | 与 GET 相同路径的同层写端点 |
|
||||
|
||||
真实 Fiber 路由请求复现(同样调用真实注册函数、装载真实 handler、nil 依赖):缺陷版 6 个命名 GET 全部 `400 / 1001 无效的路径ID`,`GET /api/admin/statistics` 落到账单统计处理器返回 `503 / 2004`,`GET /api/admin/employee-collection-bills` 也被根级 `/:id` 抢走返回 `400 / 1001`;修复版这些路径命中各自处理器(权限中间件在无会话上下文时返回 `403 / 1005`),`GET /api/admin/statistics` 返回 `404 / 1006`,员工代收款端点命中自身处理器。
|
||||
|
||||
### 3. 修复方式:资源前缀交给 `router.Group`
|
||||
|
||||
三个注册函数各 `router.Group("/<资源前缀>")`,12 处 `Register` 首参改为该 group,`basePath` 拼接保持原样(文档路径不变)。这是仓库既有写法(`internal/routes` 内 50 处 `router.Group(`),不存在第二种约定,因此不引入新的抽象或兼容层。
|
||||
|
||||
### 4. 为什么独立成 Change 而非并入 AUG26-017
|
||||
|
||||
- 归属:缺陷由 AUG26-001(员工代收款账单闭环)引入;AUG26-017(代理自充收款方式)只是在同层注册了被抢占的端点。并入会把根因错误归到代理自充能力。
|
||||
- 契约面:本修复不改 `agent-funds-commission` 的任何行为、Schema、审批语义或配置,只恢复路由;AUG26-017 的 proposal/design 已限定其影响面与非目标,混入路由修复会让该 Change 的验收与回滚边界失真。
|
||||
- 发布边界:`ff1362d` 已独立落地并推送,AUG26-017 仍在途且工作区未提交;合并会使已发布的干净提交与未完成改动耦合。
|
||||
- 治理:并入会让本次修复继续缺少独立的 OpenSpec 记录,而它的价值正是让「实现回到已归档规格」这件事可追溯。
|
||||
|
||||
### 5. 规格 delta 决策:无 delta(`skip_specs: true`)
|
||||
|
||||
结论:本 Change 不产生任何 delta,`.openspec.yaml` 标记 `skip_specs: true`。
|
||||
|
||||
依据:
|
||||
|
||||
1. `openspec/specs/employee-collection-bill/spec.md` 只有 5 条行为 Requirement,均不描述 HTTP 路径;路由由独立的 `## 可达操作索引` 承载(`e687a26` 已补齐 12 条 `/api/admin/employee-collection-*`),`scripts/context-health.sh` 明确禁止 `### Requirement: ...接口集合`。本修复让运行时路径与该索引一致,索引文本无需改动。
|
||||
2. 规格侧唯一描述路径的产物是 `docs/admin-openapi.yaml`,缺陷版、修复版与当前工作区三处 gendocs 产物逐字节相同(md5 均为 `274f45440597aaafb1e168ac266cbb85`),文档契约从未改变。
|
||||
3. 被阻断的端点属于其它能力,其主规格本来就要求这些端点可用(例如 `GET /api/admin/refunds` 已在入口矩阵中登记为 `order-refund-exchange` 的 HTTP 入口)。修复是让实现符合既有契约,不是改变契约。
|
||||
4. 若强行写 delta,只能写出「路由注册必须使用资源前缀」这类实现约束,或把路由目录写成 Requirement;两者都违反 `AGENTS.md`「Requirement 必须描述可观察行为,不能用接口目录代替状态、权限、金额、失败与幂等语义」。
|
||||
5. OpenSpec 官方为无规格变化的 Change 提供 `skip_specs` 逃生阀(本地实测 `openspec validate --strict` 与 `openspec validate --all` 均通过,见 `tasks.md`)。归档时该 Change 不触碰任何主规格文件。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- 任何按缺陷路径写死的调用方会在修复后 404。仓库源码、注释与 OpenAPI 文档均使用 `/api/admin/employee-collection-*`,未发现此类调用方;前端如有硬编码需按文档路径核对。
|
||||
- 根因是「相对路径 + 无前缀注册」这类写法;本 Change 不修改 `Register` 语义、也不做全仓扫描修复,后续新增注册若再次省略前缀仍会复现同类缺陷。`registerEmployeeCollectionBillRoutes` 对注册顺序的依赖注释表明该模式本身脆弱。
|
||||
- 无 delta 意味着规格历史不记录「路由修复」这一事件,治理信息落在本 Change 的 proposal/design/tasks。若维护者希望把路由布局写入主规格,应通过独立 Change 在可达操作索引中登记(`e687a26` 已完成),而不是由本修复承担。
|
||||
- 归档不会改动 `docs/verification/context-reset/*.json`:本 Change 不新增 Requirement 名,也不新增可达操作索引条目,因此 Requirement 键集与 HTTP 入口集不变。归档预演(`/tmp` 全量副本):`openspec archive` 输出 `archivedAs: 2026-09-11-fix-employee-collection-route-prefix`、`specsUpdated: false`,24 个主规格文件 sha256 与 Requirement 名集合、可达路由集合全部不变;在包含本 Change 的副本上 `./scripts/context-health.sh` 输出「Context 健康检查通过」。
|
||||
Reference in New Issue
Block a user