# 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 已达到低误删风险的生产标准。
