# 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) - 对账:[batch_detail_and_reconcile_service.go](/home/long/project/sub2api-cn-relay-manager/internal/provision/batch_detail_and_reconcile_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) - 当前逻辑主要仍集中在 `internal/provision/*` 与 `internal/access/closure.go`。 影响: - 目前更像可运行 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 已达到低误删风险的生产标准。