409 lines
19 KiB
Markdown
409 lines
19 KiB
Markdown
# Sub2API CN Relay Manager 上线审查报告
|
||
|
||
日期:2026-05-18
|
||
审查范围:`/home/long/project/sub2api-cn-relay-manager`
|
||
审查目标:评估当前实现是否与规划设计对齐,是否达到生产上线要求,并明确阻塞项、非阻塞项和建议整改路径。
|
||
|
||
> 状态更新(2026-05-18 晚些时候):本报告识别出的 4 个系统性阻塞项已进入代码修复并已落地到当前分支;对应执行任务见 `docs/2026-05-18-PRODUCTION_REMEDIATION_TASK_BOARD.md`。本报告保留为“发现问题时的审查快照”,最新门禁结论以整改板与执行板为准。
|
||
>
|
||
> 再次状态更新(2026-05-18 最新真实宿主复验后):已在最新代码上重新生成两套真实宿主 artifact:
|
||
> - `artifacts/real-host-acceptance/20260518_self_service_reaccept_v6`
|
||
> - `artifacts/real-host-acceptance/20260518_subscription_reaccept_v6`
|
||
>
|
||
> 结果:两条链路都未形成最终通过 artifact,因此当前仍不能把项目从 `CONDITIONAL_APPROVED` 推进到最终放行。
|
||
>
|
||
> 最终状态更新(2026-05-18 fresh redeploy 复验后):`artifacts/real-host-acceptance/20260518_redeploy_matrix` 已在全新 redeploy 宿主上确认两条真实普通用户访问链路都可打通:
|
||
> - `self_service`:普通用户 key 绑定标准 group 且用户具备可用余额后,`/v1/models -> 200`
|
||
> - `subscription`:subscription 类型 group + 普通用户订阅分配 + key/group 绑定后,`/v1/models -> 200`
|
||
>
|
||
> 进一步状态更新(2026-05-18 reconcile host-scope 复验后):已在最新代码上补充两套 host-scoped acceptance artifact:
|
||
> - `artifacts/real-host-acceptance/20260518_reconcile_hostscope_self_service`
|
||
> - `artifacts/real-host-acceptance/20260518_reconcile_hostscope_subscription`
|
||
>
|
||
> 这两套新 artifact 补齐了 `status / resources / reconcile / batch detail / rollback` 的 host-scoped 证据链,进一步证明 `reconcile_runs` 带上 `host_id + batch_id` 后,batch detail 的 reconcile 视图已不再按 provider 粗暴聚合。
|
||
>
|
||
> 因此本报告中的 `REJECT / CONDITIONAL_APPROVED` 结论已成为历史快照;当前最新真相以 `docs/EXECUTION_BOARD.md`、`docs/PRODUCTION_CLOSURE_BOARD.md`、`20260518_redeploy_matrix` 与 `20260518_reconcile_hostscope_*` artifact 为准。
|
||
|
||
## 一、审查结论
|
||
|
||
本节为“首次审查时的历史结论快照”,不再代表当前最新 gate:
|
||
|
||
- 当时判断:代码层质量门禁整体通过,但真实宿主最终放行证据不足。
|
||
- 因此当时结论为“代码层 `CONDITIONAL_APPROVED`,真实宿主最终放行未完成”。
|
||
|
||
当前最新上线判定请以:
|
||
|
||
- `docs/EXECUTION_BOARD.md`
|
||
- `docs/PRODUCTION_CLOSURE_BOARD.md`
|
||
- `artifacts/real-host-acceptance/20260518_redeploy_matrix`
|
||
- `artifacts/real-host-acceptance/20260518_reconcile_hostscope_self_service`
|
||
- `artifacts/real-host-acceptance/20260518_reconcile_hostscope_subscription`
|
||
|
||
为准。
|
||
|
||
## 二、审查方法与证据
|
||
|
||
本次审查结合以下证据来源:
|
||
|
||
1. 设计与规划文档对齐
|
||
- [PRD.md](/home/long/project/sub2api-cn-relay-manager/docs/PRD.md:1)
|
||
- [TDD_PLAN.md](/home/long/project/sub2api-cn-relay-manager/docs/TDD_PLAN.md:1)
|
||
- [implementation-plan.md](/home/long/project/sub2api-cn-relay-manager/docs/plans/2026-05-12-sub2api-cn-relay-manager-implementation-plan.md:1)
|
||
- [EXECUTION_BOARD.md](/home/long/project/sub2api-cn-relay-manager/docs/EXECUTION_BOARD.md:1)
|
||
- [PRODUCTION_CLOSURE_BOARD.md](/home/long/project/sub2api-cn-relay-manager/docs/PRODUCTION_CLOSURE_BOARD.md:1)
|
||
|
||
2. 代码审查重点
|
||
- 控制面 API:[http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:1)
|
||
- 宿主适配器:[client.go](/home/long/project/sub2api-cn-relay-manager/internal/host/sub2api/client.go:1)
|
||
- 能力探测:[capability_probe.go](/home/long/project/sub2api-cn-relay-manager/internal/host/sub2api/capability_probe.go:1)
|
||
- 导入运行时:[runtime_import_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/runtime_import_service.go:1)
|
||
- 回滚:[rollback_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/rollback_service.go:1)
|
||
- 对账:[service.go](/home/long/project/sub2api-cn-relay-manager/internal/reconcile/service.go:1)
|
||
- batch detail:[batch_detail_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/batch_detail_service.go:1)
|
||
- 状态库:[db.go](/home/long/project/sub2api-cn-relay-manager/internal/store/sqlite/db.go:1)
|
||
- 资源记录:[managed_resources_repo.go](/home/long/project/sub2api-cn-relay-manager/internal/store/sqlite/managed_resources_repo.go:1)
|
||
|
||
3. 本地质量门禁复核
|
||
- `gofmt -l .`:空输出
|
||
- `go vet ./...`:通过
|
||
- `go test ./...`:通过
|
||
- `go test -race ./...`:通过
|
||
- `go test -cover ./internal/...`:通过
|
||
- `internal/access`:`77.3%`
|
||
- `internal/pack`:`72.7%`
|
||
- `internal/provision`:`76.9%`
|
||
- `internal/store/sqlite`:`68.2%`
|
||
|
||
4. 真实宿主 artifact 复核
|
||
- 历史 `self_service` 成功样例(旧证据,现仅作历史对照):
|
||
- [05-import.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260517_openai_platform_fix_retest/05-import.json:1)
|
||
- [06-access-preview.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260517_openai_platform_fix_retest/06-access-preview.json:1)
|
||
- [07-access-status.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260517_openai_platform_fix_retest/07-access-status.json:1)
|
||
- 最新 `self_service` 复验(当前真相):
|
||
- [05-import.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260518_self_service_reaccept_v6/05-import.json:1)
|
||
- [06-access-preview.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260518_self_service_reaccept_v6/06-access-preview.json:1)
|
||
- [09-reconcile.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260518_self_service_reaccept_v6/09-reconcile.json:1)
|
||
- 最新 `subscription` 复验(当前真相):
|
||
- [05-import.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260518_subscription_reaccept_v6/05-import.json:1)
|
||
- [06-access-preview.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260518_subscription_reaccept_v6/06-access-preview.json:1)
|
||
- [09-reconcile.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260518_subscription_reaccept_v6/09-reconcile.json:1)
|
||
|
||
## 三、设计对齐判断
|
||
|
||
### 已对齐部分
|
||
|
||
- 满足“零宿主代码改动”的核心约束。
|
||
- 当前所有宿主交互都通过 `internal/host/sub2api` 适配层完成,未发现直接写宿主数据库或注入宿主目录的实现。
|
||
- 满足 MVP 的主链路目标。
|
||
- `pack` 装载、provider 解析、导入编排、账号探测、网关 `GET /v1/models` 检查、状态库存证据链都已具备。
|
||
- 控制面 API 基本覆盖当前计划要求。
|
||
- 对照 implementation plan 中列出的当前 API,仓库已实现主要端点。
|
||
- 测试覆盖与门禁实现基本符合仓库自述。
|
||
|
||
### 未完全对齐部分
|
||
|
||
- implementation plan 中的“宿主管理对象”与运行时导入路径没有形成统一身份模型。
|
||
- implementation plan 默认呈现为“可管理宿主对象 + 持久化资源状态”的控制面,但当前状态模型仍偏向“单次导入任务持久化”,不足以支撑稳定的多次运维动作。
|
||
- PRD 把多宿主管理列为首版非目标,但仓库已经暴露 `hosts` 管理 API;这意味着系统外观上已像多宿主管理器,但状态语义并未真正收口。
|
||
|
||
## 四、阻塞项
|
||
|
||
以下问题阻塞“整体生产无条件上线”。
|
||
|
||
### 阻塞项 1:宿主身份模型不统一,`/api/hosts` 与导入/对账/回滚没有形成同一条资产链
|
||
|
||
严重级别:`High`
|
||
|
||
证据:
|
||
|
||
- `POST /api/hosts` 保存的是用户提供的 `name -> host_id`,[http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:53) [http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:1077)
|
||
- `POST /api/providers/{providerID}/import` 请求体没有 `host_id` 字段,只有 `host_base_url` 和临时认证信息,[http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:168)
|
||
- `RuntimeImportService.Import()` 在 `HostID` 为空时直接退回到 `HostBaseURL` 作为宿主身份,[runtime_import_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/runtime_import_service.go:45)
|
||
|
||
影响:
|
||
|
||
- 先注册的宿主记录和后续真实导入批次可能落在两条不同的 `hosts` 记录上。
|
||
- `GET /api/hosts/{hostID}`、批次查询、provider 状态、回滚定位不会共享同一条宿主身份链。
|
||
- 这会直接削弱控制面的可运维性和可审计性。
|
||
|
||
结论:
|
||
|
||
- 当前宿主对象模型只具备“登记能力”,不具备稳定的“生命周期主键能力”。
|
||
|
||
### 阻塞项 2:`managed_resources` 没有宿主维度,状态库存在跨宿主资源串扰风险
|
||
|
||
严重级别:`High`
|
||
|
||
证据:
|
||
|
||
- `managed_resources` 的唯一键是 `(resource_type, host_resource_id)`,[0002_operational_runtime.sql](/home/long/project/sub2api-cn-relay-manager/internal/store/migrations/0002_operational_runtime.sql:17)
|
||
- repo 的资源身份查询也只按这两个字段判断,[managed_resources_repo.go](/home/long/project/sub2api-cn-relay-manager/internal/store/sqlite/managed_resources_repo.go:53)
|
||
- 运行时持久化遇到“资源已存在”就直接跳过,[runtime_import_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/runtime_import_service.go:238)
|
||
|
||
影响:
|
||
|
||
- 两个不同宿主只要资源 ID 恰好相同,就会被控制面当成同一条资源。
|
||
- rollback/reconcile 的依据会被污染。
|
||
- 该风险和宿主 ID 通常为自增整数的现实形态高度相容,不能假设不会发生。
|
||
|
||
结论:
|
||
|
||
- 这不是边界体验问题,而是状态建模缺陷。
|
||
|
||
### 阻塞项 3:宿主能力探测存在副作用风险,违背“零侵入宿主”目标的工程保守性
|
||
|
||
严重级别:`High`
|
||
|
||
证据:
|
||
|
||
- `ProbeCapabilities()` 直接对真实创建接口发空 `POST`:
|
||
- `/api/v1/admin/groups`
|
||
- `/api/v1/admin/channels`
|
||
- `/api/v1/admin/payment/plans`
|
||
- `/api/v1/admin/accounts`
|
||
- `/api/v1/admin/subscriptions/assign`
|
||
- 见 [capability_probe.go](/home/long/project/sub2api-cn-relay-manager/internal/host/sub2api/capability_probe.go:10)
|
||
|
||
影响:
|
||
|
||
- 该实现假设宿主会把空请求稳定地当作“无副作用校验失败”处理。
|
||
- 一旦宿主版本行为变化、参数默认值变化或某接口宽松接受空载荷,探测可能制造脏资源。
|
||
- 这和 PRD 的“零侵入”承诺不冲突于字面,但明显冲突于生产工程保守性。
|
||
|
||
结论:
|
||
|
||
- 在真实生产宿主上,这类探测方式不可视为安全。
|
||
|
||
### 阻塞项 4:`rollback-provider` 仍按同名资源扫描删除,不是按批次记录的真实资源集删除
|
||
|
||
严重级别:`High`
|
||
|
||
证据:
|
||
|
||
- `rollback-provider` 入口虽然先找到了 pack/provider/latest batch,[http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:981)
|
||
- 但实际执行仍走 `Rollback(ctx, RollbackRequest{Provider: providerManifest})`,[http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:1005)
|
||
- `Rollback()` 的实现会按名字重新枚举宿主资源再删除,[rollback_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/rollback_service.go:40)
|
||
- 更安全的 `RollbackStoredResources()` 已存在,但未被该路径采用,[rollback_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/rollback_service.go:58)
|
||
|
||
影响:
|
||
|
||
- 在脏现场、残留现场、同名 provider 现场,会有误删风险。
|
||
- 当前真实 artifact 已经反复体现 reconcile 仍可能看到 `extra_count`,此时按名字删尤其不稳。
|
||
|
||
结论:
|
||
|
||
- 当前回滚策略还不满足“生产可放心执行”的标准。
|
||
|
||
## 五、非阻塞项
|
||
|
||
以下问题不一定阻塞受限范围上线,但会显著影响运维成熟度与文档可信度。
|
||
|
||
### 非阻塞项 1:部署文档承诺高于实际实现
|
||
|
||
证据:
|
||
|
||
- `DEPLOYMENT.md` 把 `/metrics`、限流、监控接入写进了生产清单,[DEPLOYMENT.md](/home/long/project/sub2api-cn-relay-manager/docs/DEPLOYMENT.md:90)
|
||
- 实际路由只看到 `/healthz` 和控制面 API,[http_api.go](/home/long/project/sub2api-cn-relay-manager/internal/app/http_api.go:193)
|
||
|
||
影响:
|
||
|
||
- 运维人员容易误以为仓库已内置观测与生产保护机制。
|
||
- 文档可信度低于代码可信度。
|
||
|
||
结论:
|
||
|
||
- 应补功能,或先下调文档承诺。
|
||
|
||
### 非阻塞项 2:计划结构与物理目录仍有明显漂移
|
||
|
||
证据:
|
||
|
||
- 该评审形成时,implementation plan 里期望的 `internal/reconcile/*`、`access/planner.go`、`worker/scheduler.go` 等结构仍未落地,[implementation-plan.md](/home/long/project/sub2api-cn-relay-manager/docs/plans/2026-05-12-sub2api-cn-relay-manager-implementation-plan.md:69)
|
||
- 截至 2026-05-22,这些结构项已分别落到 `internal/reconcile/*`、`internal/access/{planner,subscription,self_service,validation}.go` 与 `internal/worker/*`。
|
||
|
||
影响:
|
||
|
||
- 目前更像可运行 MVP,而不是已经完成结构收敛的生产后端。
|
||
- 长期维护和职责边界清晰度会受影响。
|
||
|
||
结论:
|
||
|
||
- 属于已知结构债务,不阻塞受限范围上线,但不应被忽略。
|
||
|
||
### 非阻塞项 3:`subscription` 模式缺少现成通过的真实闭环 artifact
|
||
|
||
证据:
|
||
|
||
- 当前看到的 `subscription` 真实宿主样例仍是:
|
||
- `batch_status=partially_succeeded`
|
||
- `provider_status=degraded`
|
||
- `access_status=broken`
|
||
- 见 [05-import.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260517_104007_subscription_after_fix/05-import.json:1)
|
||
- 访问预检也仍显示不可用,[06-access-preview.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260517_104007_subscription_after_fix/06-access-preview.json:1)
|
||
|
||
影响:
|
||
|
||
- `subscription` 不能被纳入当前上线放行范围。
|
||
|
||
结论:
|
||
|
||
- 该项虽已在执行板中被承认为剩余风险,但从上线审查角度仍需明确隔离。
|
||
|
||
### 非阻塞项 4:`reconcile` 结果在真实宿主上仍有漂移
|
||
|
||
证据:
|
||
|
||
- `self_service` 成功样例中,`09-reconcile.json` 仍是 `status=drifted` 且 `extra_count=11`,[09-reconcile.json](/home/long/project/sub2api-cn-relay-manager/artifacts/real-host-acceptance/20260517_openai_platform_fix_retest/09-reconcile.json:1)
|
||
|
||
影响:
|
||
|
||
- 说明系统主链路成功并不等于现场状态完全收敛。
|
||
- 当前更适合“可上线但需现场治理”,不适合“上线即稳定自治”。
|
||
|
||
结论:
|
||
|
||
- 该问题单独不阻塞 `self_service` 条件性放行,但阻塞“成熟运维能力”结论。
|
||
|
||
## 六、建议整改 PR 列表
|
||
|
||
以下 PR 列表按优先级排序。
|
||
|
||
### PR-1:统一宿主身份模型,导入/对账/回滚全面切换到 `host_id`
|
||
|
||
目标:
|
||
|
||
- 把宿主从“可登记对象”升级为“真实生命周期主对象”。
|
||
|
||
建议内容:
|
||
|
||
- 为导入、reconcile、rollback、assign-subscriptions 等请求增加显式 `host_id` 输入。
|
||
- 通过 `host_id` 查宿主记录并派生 `base_url`、认证策略。
|
||
- 禁止运行时再用 `HostBaseURL` 作为默认宿主主键。
|
||
- 梳理 `hosts`、`import_batches`、provider 状态接口的主键一致性。
|
||
|
||
预期收益:
|
||
|
||
- 彻底消除宿主对象分裂。
|
||
- 为后续多宿主治理和审计打基础。
|
||
|
||
### PR-2:为 `managed_resources` 增加宿主维度并修复唯一约束
|
||
|
||
目标:
|
||
|
||
- 把资源身份从“宿主外的资源 ID”升级成“宿主内资源身份”。
|
||
|
||
建议内容:
|
||
|
||
- 在 `managed_resources` 中增加 `host_id` 或等价外键。
|
||
- 唯一约束改为 `host_id + resource_type + host_resource_id`。
|
||
- `GetByResourceIdentity()`、`ListByProviderID()`、持久化逻辑同步改造。
|
||
- 增加迁移和回归测试,覆盖“两个宿主同 ID 资源不串扰”场景。
|
||
|
||
预期收益:
|
||
|
||
- 消除跨宿主资源串写与误跳过问题。
|
||
|
||
### PR-3:重写 `ProbeCapabilities`,改成无副作用探测
|
||
|
||
目标:
|
||
|
||
- 让能力探测满足生产保守性要求。
|
||
|
||
建议内容:
|
||
|
||
- 优先使用只读接口、标准版本接口或显式 capability endpoint。
|
||
- 无只读接口时,退化为“版本白名单 + 已知能力矩阵”。
|
||
- 至少不要再通过真实创建接口发空 `POST`。
|
||
- 给真实宿主兼容矩阵补测试与文档。
|
||
|
||
预期收益:
|
||
|
||
- 避免宿主现场因 probe 被污染。
|
||
|
||
### PR-4:让 `rollback-provider` 走批次资源集回滚,不再按名字重新扫描
|
||
|
||
目标:
|
||
|
||
- 让回滚动作真正基于控制面状态库,而不是宿主现场猜测。
|
||
|
||
建议内容:
|
||
|
||
- `rollback-provider` 先定位 latest batch,再读取该 batch 的 managed resources。
|
||
- 统一调用 `RollbackStoredResources()`。
|
||
- 名字扫描保留为显式“人工应急模式”,不要做默认路径。
|
||
- 增加“存在同名残留资源时不误删”的测试。
|
||
|
||
预期收益:
|
||
|
||
- 明显降低误删风险。
|
||
|
||
### PR-5:补齐最小生产运维能力,或下调部署文档口径
|
||
|
||
目标:
|
||
|
||
- 让文档承诺和代码现实一致。
|
||
|
||
建议内容:
|
||
|
||
- 二选一:
|
||
- 实装 `/metrics`、基础限流、最小审计日志
|
||
- 或把 `DEPLOYMENT.md` 中的生产清单改成“建议外挂能力”,不暗示已内建
|
||
|
||
预期收益:
|
||
|
||
- 避免上线预期与实际能力错配。
|
||
|
||
### PR-6:补 subscription 真实闭环验收,并固化 artifact
|
||
|
||
目标:
|
||
|
||
- 让 `subscription` 模式从“理论支持”转为“真实可放行”。
|
||
|
||
建议内容:
|
||
|
||
- 使用有效凭据复跑真实宿主验证。
|
||
- 固化 import / access-preview / access-status / reconcile / rollback artifact。
|
||
- 失败时把原因收敛到代码问题还是宿主现场问题。
|
||
|
||
预期收益:
|
||
|
||
- 决定 `subscription` 是否可以进入下一个放行窗口。
|
||
|
||
## 七、上线建议
|
||
|
||
### 可以上线的范围
|
||
|
||
- 单宿主
|
||
- OpenAI-compatible provider
|
||
- `self_service` 主链路
|
||
- 接受以下前提:
|
||
- reconcile 仍需现场治理
|
||
- 回滚不作为默认高频自动运维动作
|
||
- 不把 `/api/hosts` 视为稳定宿主管理源
|
||
|
||
### 不建议上线的范围
|
||
|
||
- `subscription` 模式对外承诺
|
||
- 多宿主统一治理
|
||
- 高信任自动回滚
|
||
- 把控制面当成已具备完整生产运维能力的平台使用
|
||
|
||
## 八、最终判定
|
||
|
||
综合设计对齐度、代码门禁、真实 artifact 和运维语义,本次审查给出如下正式判定:
|
||
|
||
1. 代码质量:`PASS`
|
||
2. MVP 主链路完整性:`PASS`
|
||
3. 生产运维闭环完整性:`FAIL`
|
||
4. 全量生产放行:`REJECT`
|
||
5. 单宿主 self-service 条件性放行:`CONDITIONAL_APPROVED`
|
||
|
||
## 九、附注
|
||
|
||
以下事项需要避免被错误解读:
|
||
|
||
- `go test`、`race`、`vet` 全通过,不等于已经具备完整生产运维语义。
|
||
- `self_service` 成功,不等于 `subscription` 已成功。
|
||
- 当前存在 `hosts` API,不等于宿主对象模型已经真正完成。
|
||
- 当前有 rollback 能力,不等于 rollback 已达到低误删风险的生产标准。
|