# sub2api-cn-relay-manager Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** 构建一个完全独立于宿主 `sub2api` 的外部伴生控制面，在不修改宿主代码的前提下，通过安装 `model_pack`、批量导入国产模型 key、自动创建宿主资源并完成用户访问闭环，让普通用户直接通过 `sub2api` 标准 API 使用国产模型。

**Architecture:** 采用“外部控制面 + `model_pack` + 宿主 HTTP API 适配器”架构。控制面只通过 `sub2api` 公开管理 API 和标准 API 工作，负责模型包安装、宿主能力探测、资源编排、导入回滚、访问闭环验证和持续对账；宿主继续承担标准 API 网关职责，不做任何源码、数据库或运行时注入修改。

**Tech Stack:** Go 1.24、Chi、`database/sql` + SQLite 驱动、PostgreSQL 兼容预留、OpenAPI 3.1、Go `testing` + `httptest`、前端首版后置，MVP 先提供 HTTP API、CLI 和 Docker 交付物。

---

## 1. 实施边界

### 1.1 必须满足

- 不修改宿主 `sub2api` 源码
- 不 fork 宿主并运行自定义二进制
- 不直接写宿主数据库
- 不向宿主目录写入运行时代码、插件文件或配置补丁
- 只通过宿主现有管理 API 和标准 API 工作
- 首版必须完成从“导入 key”到“普通用户可实际调用”的闭环

### 1.2 首版不做

- 宿主原生插件中心
- 任意后端代码插件加载
- 代替用户签发宿主最终 API key
- 多宿主编排联邦
- 自定义计费、结算或审计系统

## 2. 目标目录结构

```text
sub2api-cn-relay-manager/
  cmd/
    server/main.go
    cli/main.go
  internal/
    app/
      app.go
      bootstrap.go
    config/
      config.go
    domain/
      host.go
      pack.go
      provider.go
      resource.go
      import_batch.go
      reconcile.go
      access_closure.go
    host/
      sub2api/
        client.go
        capability_probe.go
        groups.go
        channels.go
        plans.go
        accounts.go
        subscriptions.go
        gateway_probe.go
    pack/
      manifest.go
      provider_manifest.go
      checksum.go
      validator.go
      loader.go
    provision/
      preview_service.go
      import_service.go
      rollback_service.go
      naming.go
    access/
      planner.go
      subscription_service.go
      self_service_checker.go
    reconcile/
      runner.go
      drift_checker.go
      probe_runner.go
    store/
      migrations/
      sqlite/
        db.go
        hosts_repo.go
        host_capability_snapshots_repo.go
        packs_repo.go
        providers_repo.go
        installs_repo.go
        import_batches_repo.go
        resources_repo.go
        reconcile_runs_repo.go
        probe_results_repo.go
        access_closure_records_repo.go
    api/
      http/
        router.go
        middleware.go
        hosts_handler.go
        packs_handler.go
        providers_handler.go
        imports_handler.go
        access_handler.go
        reconcile_handler.go
        dto/
          hosts.go
          packs.go
          providers.go
          imports.go
          access.go
          reconcile.go
    worker/
      scheduler.go
      jobs.go
  docs/
    api/openapi.yaml
    plans/
      2026-05-12-sub2api-cn-relay-manager-implementation-plan.md
  packs/
    openai-cn-pack/
      pack.json.example
      providers/
        deepseek.json.example
  tests/
    integration/
      host_stub_test.go
      install_pack_test.go
      import_keys_test.go
      access_closure_test.go
      reconcile_test.go
```

## 3. 核心接口与契约

### 3.1 控制面对外 API

#### 宿主管理

- `POST /api/hosts`
- `GET /api/hosts`
- `GET /api/hosts/{host_id}`
- `POST /api/hosts/{host_id}/probe`

#### 模型包管理

- `POST /api/packs/install`
- `GET /api/packs`
- `GET /api/packs/{pack_id}`
- `GET /api/packs/{pack_id}/providers`

#### Provider 导入

- `POST /api/providers/{provider_id}/preview-import`
- `POST /api/providers/{provider_id}/import`
- `GET /api/providers/{provider_id}/import-batches`
- `GET /api/import-batches/{batch_id}`
- `POST /api/import-batches/{batch_id}/rollback`

#### 访问闭环

- `POST /api/providers/{provider_id}/access/preview`
- `POST /api/providers/{provider_id}/access/assign-subscriptions`
- `GET /api/providers/{provider_id}/access/status`

#### 对账与状态

- `POST /api/providers/{provider_id}/reconcile`
- `GET /api/providers/{provider_id}/status`
- `GET /api/providers/{provider_id}/resources`

### 3.2 宿主适配器接口

`internal/host/sub2api/client.go` 中定义单一适配接口：

```go
type HostAdapter interface {
    GetHostVersion(ctx context.Context) (string, error)
    ProbeCapabilities(ctx context.Context) (HostCapabilities, error)
    CreateGroup(ctx context.Context, req CreateGroupRequest) (GroupRef, error)
    CreateChannel(ctx context.Context, req CreateChannelRequest) (ChannelRef, error)
    CreatePlan(ctx context.Context, req CreatePlanRequest) (PlanRef, error)
    CreateAccount(ctx context.Context, req CreateAccountRequest) (AccountRef, error)
    BatchCreateAccounts(ctx context.Context, req BatchCreateAccountsRequest) ([]AccountRef, error)
    TestAccount(ctx context.Context, accountID string) (ProbeResult, error)
    GetAccountModels(ctx context.Context, accountID string) ([]string, error)
    AssignSubscription(ctx context.Context, req AssignSubscriptionRequest) (SubscriptionRef, error)
    CheckGatewayAccess(ctx context.Context, req GatewayAccessCheckRequest) (GatewayAccessResult, error)
    DeleteGroup(ctx context.Context, groupID string) error
    DeleteChannel(ctx context.Context, channelID string) error
    DeletePlan(ctx context.Context, planID string) error
    DeleteAccount(ctx context.Context, accountID string) error
    ListManagedResources(ctx context.Context, req ListManagedResourcesRequest) (ManagedResourceSnapshot, error)
}
```

约束：

- 所有宿主调用都必须通过该接口进入
- 任何绕过该接口的 HTTP 调用都视为实现缺陷
- 所有宿主 API 路径、请求体和响应体在 `internal/host/sub2api/` 内部封装，不向业务层泄漏

## 4. 状态机

### 4.1 Provider 安装状态

```text
discovered -> validated -> installed -> active
                           |            |
                           v            v
                         failed <-> degraded <-> drifted
                                      |
                                      v
                                   disabled
```

状态定义：

- `discovered`：模型包已上传，尚未完成结构校验
- `validated`：包结构、校验和、版本兼容、宿主能力探测通过
- `installed`：provider 元数据和模板已写入控制面状态库
- `active`：宿主资源存在，账号探测成功，至少一种用户访问模式已验证可用
- `degraded`：核心资源存在但 smoke test、模型探测或访问闭环部分失败
- `drifted`：宿主资源与控制面记录不一致，例如资源被人工改动或删除
- `failed`：安装、导入、回滚、对账任一关键步骤失败
- `disabled`：provider 被显式停用，不再调度对账任务

### 4.2 导入批次状态

```text
pending -> previewed -> running -> succeeded
                         |  |         |
                         |  |         v
                         |  -> partially_succeeded
                         v
                       failed -> rollback_running -> rolled_back
```

### 4.3 访问闭环状态

- `not_configured`
- `subscription_ready`
- `self_service_ready`
- `fully_ready`
- `broken`

判定规则：

- 仅 `subscription` 模式可用：`subscription_ready`
- 仅 `self-service` 模式可用：`self_service_ready`
- 两者都可用：`fully_ready`
- 两者都不可用：`broken`

## 5. 校验流程

### 5.1 宿主接入校验

请求：`POST /api/hosts`

流程：

1. 校验 `base_url`、认证信息格式
2. 拉取宿主版本
3. 探测宿主管理 API 能力：
   - group 创建
   - channel 创建
   - plan 创建
   - account 创建 / 测试 / 模型列举
   - subscription 分配
4. 生成能力矩阵并落库
5. 若任一关键能力缺失，宿主状态标记为 `unsupported`

通过条件：

- 宿主版本在 `pack.json` 兼容区间内
- 所有首版硬依赖 API 均可调用

### 5.2 模型包装载校验

请求：`POST /api/packs/install`

流程：

1. 解压上传包到临时目录
2. 校验 `pack.json` 存在且字段完整
3. 校验 `providers/*.json` 至少存在一个 provider
4. 校验 `checksums.txt`
5. 校验 provider schema
6. 校验模型包声明的宿主版本兼容性
7. 将包元数据、provider 元数据写入状态库

拒绝条件：

- 缺少 `pack.json`
- provider 文件重复或 `provider_id` 冲突
- `base_url` 非 HTTPS
- `default_models` 为空
- `smoke_test_model` 不在 `default_models` 内

### 5.3 导入预检

请求：`POST /api/providers/{provider_id}/preview-import`

流程：

1. 规范化 key 列表：去空白、去重、去 BOM、过滤空行
2. 校验导入模式是否允许：`strict` 或 `partial`
3. 生成建议资源名
4. 检查宿主是否已有同名 group / channel / plan
5. 检查当前 provider 是否已有历史导入资源
6. 计算执行计划：
   - create
   - reuse
   - conflict
7. 输出预检报告，不落宿主资源

### 5.4 导入执行

请求：`POST /api/providers/{provider_id}/import`

流程：

1. 创建批次记录，状态 `pending`
2. 执行预检，成功后切换为 `previewed`
3. 创建或复用 group
4. 创建或复用 channel
5. 按访问模式决定是否创建或复用 plan
6. 批量创建 accounts
7. 对每个 account 执行：
   - 宿主 `/test`
   - 宿主 `/models`
   - 关键模型 smoke probe
8. 若访问模式为 `subscription`：
   - 预览待分配用户列表
   - 调用宿主分配接口
9. 执行网关访问探测
10. 写入资源映射、探测结果、访问闭环状态
11. 根据模式决定成功、部分成功或回滚

### 5.5 回滚校验

触发条件：

- `strict` 模式下任一关键步骤失败
- 管理员主动回滚批次

流程：

1. 读取批次关联资源
2. 按依赖逆序删除：
   - subscriptions
   - accounts
   - channel
   - plan
   - group
3. 删除成功后标记 `rolled_back`
4. 若部分删除失败，批次状态标记 `failed`，provider 状态标记 `drifted`

### 5.6 对账校验

请求：`POST /api/providers/{provider_id}/reconcile`

流程：

1. 读取控制面记录的宿主资源快照
2. 拉取宿主实际资源
3. 核对 group / channel / plan / account 是否仍存在
4. 对 active account 重新执行 `/test` 和 `/models`
5. 重新执行至少一种用户访问模式探测
6. 汇总为：
   - `active`
   - `degraded`
   - `drifted`
   - `failed`

## 6. API 请求与响应最小契约

### 6.1 `POST /api/hosts`

请求体：

```json
{
  "name": "prod-sub2api",
  "base_url": "https://sub2api.example.com",
  "auth": {
    "type": "bearer",
    "token": "xxxx"
  }
}
```

成功响应：

```json
{
  "host_id": "host_01",
  "version": "0.1.126",
  "status": "supported",
  "capabilities": {
    "groups": true,
    "channels": true,
    "plans": true,
    "accounts": true,
    "account_test": true,
    "account_models": true,
    "subscriptions": true
  }
}
```

### 6.2 `POST /api/providers/{provider_id}/import`

请求体：

```json
{
  "host_id": "host_01",
  "mode": "partial",
  "access_mode": "subscription",
  "keys": [
    "sk-1",
    "sk-2"
  ],
  "subscription_targets": [
    {
      "user_id": "user_01",
      "duration_days": 30
    }
  ]
}
```

成功响应：

```json
{
  "batch_id": "batch_01",
  "status": "succeeded",
  "provider_status": "active",
  "access_closure_status": "subscription_ready",
  "created": {
    "group_id": "g_01",
    "channel_id": "c_01",
    "plan_id": "p_01",
    "account_ids": ["a_01", "a_02"]
  }
}
```

## 7. 数据库与迁移计划

### 7.1 必建表

- `hosts`
- `host_capability_snapshots`
- `packs`
- `providers`
- `provider_installs`
- `import_batches`
- `import_batch_items`
- `managed_resources`
- `probe_results`
- `access_closure_records`
- `reconcile_runs`

### 7.2 约束

- `providers.provider_id` 在同一 `pack_id` 下唯一
- `managed_resources` 以 `host_id + provider_id + resource_type + logical_key` 唯一
- `import_batch_items.raw_key_hash` 唯一约束只用于同一批次内去重
- 密钥明文不落库，只存掩码、哈希和宿主侧 account ID

## 8. 测试策略

### 8.1 单元测试

- `internal/pack/validator.go`
- `internal/provision/naming.go`
- `internal/access/planner.go`
- `internal/reconcile/drift_checker.go`

### 8.2 宿主适配器契约测试

- 使用 `httptest.Server` 模拟 `sub2api` 管理 API
- 覆盖成功、鉴权失败、字段缺失、版本不兼容、接口漂移

### 8.3 集成测试

- 安装模型包
- 多 key 导入
- `strict` 失败回滚
- `partial` 部分成功
- `subscription` 访问闭环
- 对账发现漂移

### 8.4 验收测试

- 真实连接一套测试版 `sub2api`
- 导入 `deepseek` 两个 key
- 为测试用户分配 subscription
- 使用该用户实际标准 API key 发起一次 `/v1/chat/completions`
- 验证请求成功进入目标 provider

## 9. 分阶段里程碑

### Milestone 1：项目骨架与状态库

验收标准：

- 控制面可启动
- SQLite 可自动迁移
- `POST /api/hosts`、`GET /api/hosts` 可用
- 宿主能力探测结果可持久化

### Milestone 2：模型包安装与校验

验收标准：

- 可安装 `openai-cn-pack`
- 可列出 provider
- 版本兼容和 checksum 校验有效
- 非法 provider 包会被拒绝

### Milestone 3：多 key 导入与资源编排

验收标准：

- 可对单个 provider 执行预检
- 可批量导入多个 key
- 可创建 group / channel / plan / account
- `strict` / `partial` 两种模式行为正确

### Milestone 4：访问闭环

验收标准：

- `subscription` 模式打通
- 至少一个测试用户可通过宿主标准 API 调用目标国产模型
- 控制面可返回 `subscription_ready` 或 `fully_ready`

### Milestone 5：对账、漂移与回滚

验收标准：

- 定时对账可运行
- 宿主资源被人工删除时，provider 状态变为 `drifted`
- 可对批次执行回滚
- 回滚后状态库与宿主资源一致

### Milestone 6：独立交付与部署文档

验收标准：

- 可生成单二进制和 Docker 镜像
- 提供 `.env.example` 和最小部署说明
- 可通过 CLI 完成宿主接入、模型包装载和导入
- 文档明确宿主零改动边界和支持矩阵

## 10. 逐任务实施清单

### Task 1: 建立项目骨架与配置加载

**Files:**
- Create: `cmd/server/main.go`
- Create: `cmd/cli/main.go`
- Create: `internal/app/app.go`
- Create: `internal/app/bootstrap.go`
- Create: `internal/config/config.go`

**Step 1: 写配置加载单元测试**

- Test: `tests/integration/config_bootstrap_test.go`
- 覆盖环境变量、默认值、缺失必填项

**Step 2: 运行测试验证失败**

Run: `go test ./tests/integration -run TestConfigBootstrap -v`
Expected: FAIL，提示配置加载尚未实现

**Step 3: 写最小实现**

- 完成配置结构、启动参数、默认 SQLite DSN

**Step 4: 运行测试验证通过**

Run: `go test ./tests/integration -run TestConfigBootstrap -v`
Expected: PASS

**Step 5: Commit**

```bash
git add cmd internal tests
git commit -m "feat: bootstrap control plane app skeleton"
```

### Task 2: 建立状态库与迁移

**Files:**
- Create: `internal/store/migrations/0001_init.sql`
- Create: `internal/store/sqlite/db.go`
- Create: `internal/store/sqlite/hosts_repo.go`
- Create: `internal/store/sqlite/packs_repo.go`
- Create: `internal/store/sqlite/providers_repo.go`

**Step 1: 写迁移与仓储测试**

- Test: `tests/integration/store_init_test.go`
- 覆盖建表、唯一约束、事务回滚

**Step 2: 跑失败测试**

Run: `go test ./tests/integration -run TestStoreInit -v`
Expected: FAIL，表不存在

**Step 3: 实现最小迁移与仓储**

- 先实现 `hosts`、`packs`、`providers`

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestStoreInit -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/store tests
git commit -m "feat: add state store migrations and repositories"
```

### Task 3: 实现宿主适配器与能力探测

**Files:**
- Create: `internal/host/sub2api/client.go`
- Create: `internal/host/sub2api/capability_probe.go`
- Create: `internal/host/sub2api/groups.go`
- Create: `internal/host/sub2api/channels.go`
- Create: `internal/host/sub2api/plans.go`
- Create: `internal/host/sub2api/accounts.go`
- Create: `internal/host/sub2api/subscriptions.go`

**Step 1: 写 `httptest` 宿主桩和适配器测试**

- Test: `tests/integration/host_stub_test.go`
- 覆盖版本获取、能力探测、错误映射

**Step 2: 先跑失败**

Run: `go test ./tests/integration -run TestSub2APIHostAdapter -v`
Expected: FAIL，适配器未实现

**Step 3: 写最小适配器实现**

- 只支持首版必需 API

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestSub2APIHostAdapter -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/host tests
git commit -m "feat: add sub2api host adapter and capability probe"
```

### Task 4: 实现模型包协议与安装校验

**Files:**
- Create: `internal/pack/manifest.go`
- Create: `internal/pack/provider_manifest.go`
- Create: `internal/pack/checksum.go`
- Create: `internal/pack/validator.go`
- Create: `internal/pack/loader.go`

**Step 1: 写模型包校验测试**

- Test: `tests/integration/install_pack_test.go`
- 覆盖合法包、缺失字段、坏 checksum、版本不兼容

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestInstallPack -v`
Expected: FAIL，包解析和校验未实现

**Step 3: 实现最小装载器**

- 支持从 zip 和目录读取

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestInstallPack -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/pack tests
git commit -m "feat: add model pack loader and validator"
```

### Task 5: 实现导入预检与命名策略

**Files:**
- Create: `internal/provision/preview_service.go`
- Create: `internal/provision/naming.go`
- Create: `internal/domain/import_batch.go`

**Step 1: 写预检测试**

- Test: `tests/integration/import_preview_test.go`
- 覆盖 key 规范化、冲突判定、create/reuse/conflict 输出

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestImportPreview -v`
Expected: FAIL

**Step 3: 实现预检逻辑**

- 只做只读分析，不写宿主

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestImportPreview -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/provision internal/domain tests
git commit -m "feat: add provider import preview flow"
```

### Task 6: 实现多 key 导入、探测与回滚

**Files:**
- Create: `internal/provision/import_service.go`
- Create: `internal/provision/rollback_service.go`
- Create: `internal/store/sqlite/import_batches_repo.go`
- Create: `internal/store/sqlite/resources_repo.go`
- Create: `internal/store/sqlite/probe_results_repo.go`

**Step 1: 写导入测试**

- Test: `tests/integration/import_keys_test.go`
- 覆盖 `strict` 成功、`strict` 回滚、`partial` 部分成功

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestImportKeys -v`
Expected: FAIL

**Step 3: 实现导入与回滚服务**

- 创建宿主资源
- 记录资源映射
- 运行账号测试和模型探测

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestImportKeys -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/provision internal/store tests
git commit -m "feat: add multi-key import and rollback flow"
```

### Task 7: 实现访问闭环

**Files:**
- Create: `internal/access/planner.go`
- Create: `internal/access/subscription_service.go`
- Create: `internal/access/self_service_checker.go`
- Create: `internal/domain/access_closure.go`

**Step 1: 写访问闭环测试**

- Test: `tests/integration/access_closure_test.go`
- 覆盖 `subscription_ready`、`self_service_ready`、`broken`

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestAccessClosure -v`
Expected: FAIL

**Step 3: 实现最小闭环服务**

- 首版先保证 `subscription` 模式打通
- `self-service` 只做绑定状态检查，不代用户签发 key

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestAccessClosure -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/access internal/domain tests
git commit -m "feat: add access closure services"
```

### Task 8: 实现对账与漂移检测

**Files:**
- Create: `internal/reconcile/runner.go`
- Create: `internal/reconcile/drift_checker.go`
- Create: `internal/reconcile/probe_runner.go`
- Create: `internal/store/sqlite/reconcile_runs_repo.go`

**Step 1: 写对账测试**

- Test: `tests/integration/reconcile_test.go`
- 覆盖正常、资源丢失、账号失效、访问闭环破坏

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestReconcile -v`
Expected: FAIL

**Step 3: 实现对账器**

- 拉取宿主资源快照
- 比对状态库
- 重新执行探测

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestReconcile -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/reconcile internal/store tests
git commit -m "feat: add provider reconcile and drift detection"
```

### Task 9: 暴露 HTTP API 与 OpenAPI 文档

**Files:**
- Create: `internal/api/http/router.go`
- Create: `internal/api/http/middleware.go`
- Create: `internal/api/http/hosts_handler.go`
- Create: `internal/api/http/packs_handler.go`
- Create: `internal/api/http/providers_handler.go`
- Create: `internal/api/http/imports_handler.go`
- Create: `internal/api/http/access_handler.go`
- Create: `internal/api/http/reconcile_handler.go`
- Create: `docs/api/openapi.yaml`

**Step 1: 写 handler 测试**

- Test: `tests/integration/http_api_test.go`
- 覆盖主要成功与失败路径

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestHTTPAPI -v`
Expected: FAIL

**Step 3: 实现路由和 DTO**

- 保持 handler 只做参数解析和错误映射

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestHTTPAPI -v`
Expected: PASS

**Step 5: Commit**

```bash
git add internal/api docs/api tests
git commit -m "feat: add control plane HTTP API"
```

### Task 10: 增加调度器、CLI 和端到端验收脚本

**Files:**
- Create: `internal/worker/scheduler.go`
- Create: `internal/worker/jobs.go`
- Modify: `cmd/cli/main.go`
- Create: `scripts/e2e/verify_with_host_stub.sh`

**Step 1: 写调度与 CLI 测试**

- Test: `tests/integration/cli_scheduler_test.go`
- 覆盖手动 reconcile 和定时调度

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestCLIScheduler -v`
Expected: FAIL

**Step 3: 实现最小调度与 CLI**

- 支持 `host add`、`pack install`、`provider import`、`reconcile run`

**Step 4: 跑全量测试**

Run: `go test ./...`
Expected: PASS

**Step 5: Commit**

```bash
git add cmd internal scripts tests
git commit -m "feat: add scheduler cli and e2e verification script"
```

### Task 11: 补齐独立交付物与部署文档

**Files:**
- Create: `Dockerfile`
- Create: `.env.example`
- Create: `deploy/docker-compose.yml`
- Create: `docs/deployment.md`
- Modify: `README.md`

**Step 1: 写交付物检查测试**

- Test: `tests/integration/distribution_smoke_test.go`
- 覆盖配置样例、镜像启动参数、CLI 帮助输出

**Step 2: 跑失败**

Run: `go test ./tests/integration -run TestDistributionSmoke -v`
Expected: FAIL

**Step 3: 实现最小交付物**

- 提供可运行镜像
- 提供本地 SQLite 默认部署
- 文档明确对接宿主所需环境变量

**Step 4: 跑测试**

Run: `go test ./tests/integration -run TestDistributionSmoke -v`
Expected: PASS

**Step 5: Commit**

```bash
git add Dockerfile .env.example deploy docs README.md tests
git commit -m "docs: add distribution artifacts and deployment guide"
```

## 11. 最终验收清单

- 从空状态启动控制面
- 连接一套兼容版本的 `sub2api`
- 安装 `openai-cn-pack`
- 导入 `deepseek` 两个 key
- 成功创建宿主资源并记录映射
- 至少一个账号通过测试与模型探测
- 至少一种用户访问模式验证成功
- 使用宿主标准 API 成功调用国产模型
- 人工删除一个宿主 account 后，对账将 provider 状态标记为 `drifted`
- 执行批次回滚后，宿主残留资源清理完成

## 12. 风险与收敛策略

- 宿主管理 API 漂移风险：所有 HTTP 路径和字段通过 `HostAdapter` 封装，并为版本差异保留适配层
- 宿主权限模型不稳定：在 `POST /api/hosts` 阶段强制能力探测，不满足即拒绝接入
- 不同 provider 规则差异：统一约束到 `providers/*.json`，首版只支持 OpenAI-compatible provider
- 访问闭环误判：首版必须落真实标准 API 探测，不能只以资源存在判定成功
