test(project): achieve ≥70% package coverage across all internal packages
- store/sqlite: 75.4% (repos + db coverage) - host/sub2api: 80.8% (httptest mock server, pure function tests) - app: 74.2% (handler error paths, NewActionSet closures) - pack: 72.4% - provision: 75.2% - access: 77.3% - config: 94.7% (lookup mock tests) All tests pass: build, vet, race, coverage gates.
This commit is contained in:
36
docs/DEPLOYMENT.md
Normal file
36
docs/DEPLOYMENT.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Deployment
|
||||
|
||||
## Environment
|
||||
|
||||
Required:
|
||||
|
||||
- `SUB2API_CRM_ADMIN_TOKEN`: control-plane bearer token
|
||||
|
||||
Optional:
|
||||
|
||||
- `SUB2API_CRM_LISTEN_ADDR` (default `:8080`)
|
||||
- `SUB2API_CRM_SQLITE_DSN` (default `file:sub2api-cn-relay-manager.db?_foreign_keys=on&_busy_timeout=5000`)
|
||||
|
||||
## Local Docker Compose
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# edit SUB2API_CRM_ADMIN_TOKEN before startup
|
||||
mkdir -p data
|
||||
docker compose up --build -d
|
||||
curl -fsS http://127.0.0.1:8080/healthz
|
||||
```
|
||||
|
||||
## Standalone Binary
|
||||
|
||||
```bash
|
||||
go build -o bin/sub2api-cn-relay-manager ./cmd/server
|
||||
SUB2API_CRM_ADMIN_TOKEN=replace-me ./bin/sub2api-cn-relay-manager
|
||||
```
|
||||
|
||||
## Runtime Notes
|
||||
|
||||
- SQLite file should be mounted on persistent storage.
|
||||
- Admin token must be rotated outside source control.
|
||||
- The service is stateless except for SQLite runtime state.
|
||||
- Use `/healthz` for container liveness checks.
|
||||
111
docs/EXECUTION_BOARD.md
Normal file
111
docs/EXECUTION_BOARD.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# sub2api-cn-relay-manager 执行板
|
||||
|
||||
日期:2026-05-13
|
||||
当前 Gate:REQUEST_CHANGES
|
||||
目标:实现 implementation plan 全量能力,达成独立控制面、零侵入宿主、一键导入国产模型,并补齐回滚/对账/HTTP API/交付物。
|
||||
|
||||
## 当前真实状态
|
||||
|
||||
模块完成 gate(新增执行要求,后续每个大模块都必须执行):
|
||||
- 仅 `go test` 通过不算完成;每次完成大模块后,必须补做:
|
||||
1. 两阶段 review(先对规划/设计文档做实现对齐检查,再做代码质量 review)
|
||||
2. execution board 当前状态同步
|
||||
3. 若发现实现/设计漂移,优先修正文档结论或回退模块状态,不维持虚假 `COMPLETED`
|
||||
- 本板从本次起按上述 gate 维护。
|
||||
|
||||
已完成:
|
||||
- 项目骨架与配置加载
|
||||
- SQLite 最小状态库(hosts/packs/providers)
|
||||
- SQLite 运行态状态库扩展(import_batches / items / managed_resources / probe_results / access_closure_records / reconcile_runs)
|
||||
- sub2api HostAdapter 基础创建/探测能力
|
||||
- HostAdapter 删除能力(group/channel/account;plan 接口已补)
|
||||
- HostAdapter 资源枚举能力(groups/channels/plans/accounts)
|
||||
- import strict 模式自动回滚已接入
|
||||
- 手动 rollback CLI(`rollback-provider`)已接入,支持按 provider 名称规则回收 group/channel/plan/accounts
|
||||
- pack 目录装载与 checksum/schema 校验
|
||||
- 正式 pack install 生命周期已接入:支持 zip/目录装载、宿主版本兼容校验、pack/provider 元数据持久化、CLI `install-pack`
|
||||
- CLI `import-provider` 导入闭环已接入 SQLite 运行态持久化(host/pack/provider/import/probe/access)
|
||||
- CLI `preview-provider` 预检查入口
|
||||
- 最小 HTTP 控制面已接入:admin token 鉴权 + `/api/packs/install` + `/api/providers/{providerID}/preview-import` + `/api/providers/{providerID}/import` + `/api/import-batches/{batchID}` + `/api/providers/{providerID}/status` + `/api/providers/{providerID}/resources` + `/api/providers/{providerID}/access/status` + `/api/providers/{providerID}/rollback` + `/api/providers/{providerID}/reconcile`
|
||||
- preview 已接入宿主资源快照查询
|
||||
- 账号探测与 `/v1/models` 网关访问验证
|
||||
|
||||
未完成的关键事实:
|
||||
- 状态库已接入 `import-provider` 运行链并可持久化 host/pack/provider/import/probe/access;最小 HTTP 控制面已补齐 batch detail / provider status / resources / access status / rollback / reconcile,OpenAPI 草案已同步扩展
|
||||
- preview/import/rollback/reconcile 已有 CLI 与最小 HTTP 入口,但仍缺少 hosts 管理面与更完整的批次/对账操作文档输出
|
||||
- 宿主资源枚举已实现,但尚未对真实 sub2api 版本做兼容性实测
|
||||
- 最小 reconcile / drift detection 已接入,当前实现仍是 `internal/provision/batch_detail_and_reconcile_service.go` 内联版本,但已补齐对最新 batch 的 account smoke probe 重跑、access closure 复检与 reconcile summary 持久化;状态仍未完全对齐 implementation plan 目标中的 `internal/reconcile/*` 结构,且真实宿主兼容性实测未完成
|
||||
- OpenAPI 草案已覆盖 status/resources/access-status,但仍未收口 hosts 契约与生产级文档细节
|
||||
- 无 scheduler/jobs
|
||||
- 已补齐 Dockerfile / compose / .env.example / deployment 文档,并新增 distribution smoke test;但尚无真实容器启动 E2E 执行记录
|
||||
|
||||
## P0(必须先完成)
|
||||
|
||||
### P0-1 状态库扩展并接入运行链
|
||||
- 状态:COMPLETED(schema/repo、`import-provider` 运行链消费、`batch detail` / `provider status` / `resources` / `access status` / `reconcile` 查询面均已接入)
|
||||
- 目标:补齐 implementation plan 所需核心表与 repo
|
||||
- 范围:`import_batches`、`import_batch_items`、`managed_resources`、`probe_results`、`access_closure_records`、`reconcile_runs`
|
||||
- 验证:`go test ./tests/integration -run 'TestStore(Runtime|Init)' -count=1`
|
||||
- 完成判据:表存在、约束有效、事务回滚有效、repo 可写入读取,并被运行链消费
|
||||
|
||||
### P0-2 import preview + naming
|
||||
- 目标:导入前可输出 create/reuse/conflict,不盲写宿主
|
||||
- 范围:`preview_service.go`、`naming.go`、`import_preview_test.go`
|
||||
- 验证:`go test ./tests/integration -run TestImportPreview -v`
|
||||
|
||||
### P0-3 真实 rollback 闭环
|
||||
- 状态:PARTIAL(strict 自动回滚 + 手动 rollback CLI + HTTP rollback API 已完成;真实宿主兼容性实测未完成)
|
||||
- 目标:strict 失败自动清理,支持手动 rollback
|
||||
- 前置:HostAdapter 增加 DeleteGroup/DeleteChannel/DeletePlan/DeleteAccount/ListManagedResources
|
||||
- 验证:`go test ./internal/provision ./tests/integration ./cmd/cli -run 'TestRollback|TestExecuteRollbackProviderWritesSummary|TestSub2APIHostAdapterListManagedResources' -v`
|
||||
|
||||
### P0-4 正式 pack install 生命周期
|
||||
- 状态:COMPLETED(zip/目录装载、宿主版本兼容性校验、pack/provider 元数据持久化、CLI `install-pack` 已接入)
|
||||
- 目标:支持 zip/目录装载、宿主版本兼容性校验、pack/provider 元数据持久化
|
||||
- 验证:`go test ./internal/pack ./internal/provision ./cmd/cli ./tests/integration -v`
|
||||
|
||||
## P1(形成真正控制面)
|
||||
|
||||
### P1-1 Access 独立模块化
|
||||
- 状态:PARTIAL(访问闭环校验/订阅分配/网关探测已从 `import_service` 抽离到 `internal/access/closure.go`,但 implementation plan 目标结构中的 `planner.go` / `subscription_service.go` / `self_service_checker.go` 仍未落地)
|
||||
- 目标:将访问闭环从 import_service 解耦为 `internal/access/*`
|
||||
- 设计对齐复核:当前已完成的是“最小闭环抽离”,未达到 implementation plan 中 Access 子模块拆分粒度;因此不再维持 `COMPLETED`
|
||||
- 验证:`go test ./internal/access ./internal/provision -count=1`
|
||||
|
||||
### P1-2 Reconcile / Drift Detection
|
||||
- 状态:PARTIAL(最小 reconcile API + drift 计数写入已接入;本轮新增 account smoke probe 重跑、access closure 复检、`active/degraded/drifted` 状态语义与回写验证,但 implementation plan 目标中的 `internal/reconcile/*` 结构、`failed` 语义收口与真实宿主兼容性实测仍未完成)
|
||||
- 目标:拉宿主快照,对比状态库,重跑 probe,标记 drifted
|
||||
- 验证:`go test ./internal/provision ./internal/app ./tests/integration -run 'TestReconcileService|TestAPIReconcileProviderReturnsSummary|TestStore(Runtime|Init)' -count=1`
|
||||
|
||||
### P1-3 HTTP API + OpenAPI
|
||||
- 状态:PARTIAL(`/api/packs/install`、`/api/providers/{providerID}/preview-import`、`/api/providers/{providerID}/import`、`/api/import-batches/{batchID}`、`/api/providers/{providerID}/status`、`/api/providers/{providerID}/resources`、`/api/providers/{providerID}/access/status`、`/api/providers/{providerID}/rollback`、`/api/providers/{providerID}/reconcile` 已接入;OpenAPI 草案已同步扩展,但 hosts 管理面仍缺失)
|
||||
- 目标:暴露 hosts / packs/install / providers preview-import / imports rollback / access / reconcile
|
||||
- 验证:`go test ./internal/app ./cmd/server ./tests/integration -run 'TestAPI|TestBootstrap' -v`
|
||||
|
||||
## P2(工程化交付)
|
||||
|
||||
### P2-1 Scheduler / Jobs
|
||||
- 目标:支持定时 reconcile 与手动触发
|
||||
- 验证:`go test ./tests/integration -run TestCLIScheduler -v`
|
||||
|
||||
### P2-2 Distribution Artifacts
|
||||
- 状态:PARTIAL(已补齐 `Dockerfile` / `.env.example` / `docker-compose.yml` / `docs/DEPLOYMENT.md`,并新增 distribution smoke test;但尚无真实容器启动与镜像构建 E2E 记录)
|
||||
- 目标:Dockerfile / .env.example / docker-compose / deployment 文档 / e2e 脚本
|
||||
- 验证:`go test ./tests/integration -run TestDistributionArtifactsExistAndReferenceRequiredEnv -v`
|
||||
|
||||
### P2-3 CLI 面板补齐
|
||||
- 目标:`host add` / `pack install` / `provider import` / `reconcile run`
|
||||
- 验证:CLI 集成测试 + `go test ./...`
|
||||
|
||||
## 当前执行顺序
|
||||
1. P1-1 Access 模块继续拆分到 implementation plan 粒度
|
||||
2. P1-2 Reconcile 结构化与真实宿主兼容性实测
|
||||
3. P1-3 Hosts 管理面 / OpenAPI 收口
|
||||
4. P2-1 Scheduler / Jobs
|
||||
5. P2-2 Distribution 容器级 E2E 验证
|
||||
6. P2-3 CLI 全量收口
|
||||
|
||||
## 禁止错误结论
|
||||
- `go test ./...` 当前通过 ≠ implementation plan 全部实现
|
||||
- CLI 最小导入闭环 ≠ 独立控制面已完成
|
||||
- 资源创建成功 ≠ 用户访问闭环已长期可运维
|
||||
45
docs/PRD.md
Normal file
45
docs/PRD.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# sub2api-cn-relay-manager PRD(MVP)
|
||||
|
||||
日期:2026-05-13
|
||||
|
||||
## 目标
|
||||
|
||||
在**完全不修改 sub2api 官方系统代码**的前提下,交付一个可独立打包运行的外部伴生项目,使管理员能够通过一次导入动作,把国产模型 OpenAI 兼容中转能力安装到任意一套兼容的 sub2api 实例中。
|
||||
|
||||
## 硬约束
|
||||
|
||||
1. 不修改宿主源码
|
||||
2. 不 fork 宿主并运行私有二进制
|
||||
3. 不直接写宿主数据库
|
||||
4. 不向宿主目录注入插件代码或补丁文件
|
||||
5. 仅通过宿主现有 HTTP 管理 API 与标准 API 工作
|
||||
|
||||
## 首版验收
|
||||
|
||||
1. `model_pack` 可独立校验与装载
|
||||
2. CLI 可直接读取 pack、选择 provider、导入多条 key
|
||||
3. 导入流程能创建 group / channel / plan(subscription 模式)/ accounts
|
||||
4. 至少一个 account 完成 `/test` 与 `/models` 验证
|
||||
5. 至少一种普通用户访问路径被真实探测:`GET /v1/models`
|
||||
6. 失败时明确区分:`succeeded / partially_succeeded / failed`
|
||||
|
||||
## 首版边界
|
||||
|
||||
### 做
|
||||
- pack runtime
|
||||
- provider schema 校验
|
||||
- 多 key 去重与批量导入
|
||||
- subscription/self-service 两种访问模式建模
|
||||
- CLI 一键导入
|
||||
- 基于 stub 的端到端测试
|
||||
|
||||
### 暂不做
|
||||
- Web 控制台
|
||||
- 多宿主管理
|
||||
- 自动代用户签发最终 API key
|
||||
- 对账调度器完整实现
|
||||
- 真实宿主删除/回滚链路
|
||||
|
||||
## 当前实现策略
|
||||
|
||||
首版先把“可独立打包 + 零侵入导入 + 用户访问验证”做成最小闭环;状态库、HTTP 控制面、对账调度在此基础上继续扩展。
|
||||
41
docs/TDD_PLAN.md
Normal file
41
docs/TDD_PLAN.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# TDD 实施计划(MVP)
|
||||
|
||||
日期:2026-05-13
|
||||
|
||||
## 设计结论
|
||||
|
||||
首版采用:
|
||||
|
||||
- `packs/openai-cn-pack/`:真实可校验模型包
|
||||
- `internal/pack`:pack 装载、checksum 校验、provider schema 校验
|
||||
- `internal/provision`:导入编排服务
|
||||
- `internal/host/sub2api`:宿主 admin/gateway 适配
|
||||
- `cmd/cli import-provider`:一键导入入口
|
||||
|
||||
## TDD 顺序
|
||||
|
||||
1. 先写 `internal/pack/loader_test.go`
|
||||
- 成功装载 pack
|
||||
- checksum mismatch 失败
|
||||
- provider schema 非法失败
|
||||
2. 再写 `internal/provision/import_service_test.go`
|
||||
- subscription 模式成功导入
|
||||
- strict 模式探测失败直接失败
|
||||
- 参数非法拒绝
|
||||
3. 再补宿主适配器集成测试
|
||||
- `CheckGatewayAccess()` 能校验 `/v1/models`
|
||||
4. 最后补 CLI 测试
|
||||
- `import-provider` 参数解析
|
||||
- 输出状态摘要
|
||||
|
||||
## 当前 MVP 风险
|
||||
|
||||
1. 回滚删除链路尚未接入真实宿主 HTTP 路径,当前仅在服务层保留失败状态,不宣称真实宿主回滚已闭环
|
||||
2. 现有集成验证基于 `httptest` stub,尚未对真实 sub2api 版本做兼容性实测
|
||||
3. 状态库尚未承接 import batch / managed resources / reconcile runs 持久化
|
||||
|
||||
## 完成标准
|
||||
|
||||
- `go test ./...` 通过
|
||||
- CLI 能从真实 pack 读取 provider
|
||||
- 导入报告明确输出 batch/provider/access 三种状态
|
||||
265
docs/openapi.yaml
Normal file
265
docs/openapi.yaml
Normal file
@@ -0,0 +1,265 @@
|
||||
openapi: 3.1.0
|
||||
info:
|
||||
title: sub2api-cn-relay-manager API
|
||||
version: 0.1.0
|
||||
servers:
|
||||
- url: /
|
||||
paths:
|
||||
/healthz:
|
||||
get:
|
||||
responses:
|
||||
'200':
|
||||
description: ok
|
||||
/api/packs/install:
|
||||
post:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/InstallPackRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: pack installed
|
||||
/api/import-batches/{batchID}:
|
||||
get:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: batchID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
format: int64
|
||||
responses:
|
||||
'200':
|
||||
description: batch detail
|
||||
/api/providers/{providerID}/status:
|
||||
get:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: pack_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: provider runtime status
|
||||
/api/providers/{providerID}/resources:
|
||||
get:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: pack_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: provider managed resources snapshot
|
||||
/api/providers/{providerID}/access/status:
|
||||
get:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: pack_id
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: provider access closure status
|
||||
/api/providers/{providerID}/preview-import:
|
||||
post:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PreviewProviderRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: preview summary
|
||||
/api/providers/{providerID}/import:
|
||||
post:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ImportProviderRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: import summary
|
||||
/api/providers/{providerID}/rollback:
|
||||
post:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RollbackProviderRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: rollback summary
|
||||
/api/providers/{providerID}/reconcile:
|
||||
post:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: providerID
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ReconcileProviderRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: reconcile summary
|
||||
components:
|
||||
securitySchemes:
|
||||
bearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
schemas:
|
||||
InstallPackRequest:
|
||||
type: object
|
||||
required: [host_base_url, pack_path]
|
||||
properties:
|
||||
host_base_url:
|
||||
type: string
|
||||
host_api_key:
|
||||
type: string
|
||||
host_bearer_token:
|
||||
type: string
|
||||
pack_path:
|
||||
type: string
|
||||
PreviewProviderRequest:
|
||||
type: object
|
||||
required: [host_base_url, pack_path, keys]
|
||||
properties:
|
||||
host_base_url:
|
||||
type: string
|
||||
host_api_key:
|
||||
type: string
|
||||
host_bearer_token:
|
||||
type: string
|
||||
pack_path:
|
||||
type: string
|
||||
provider_id:
|
||||
type: string
|
||||
keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
mode:
|
||||
type: string
|
||||
ImportProviderRequest:
|
||||
type: object
|
||||
required: [host_base_url, pack_path, keys, access_api_key]
|
||||
properties:
|
||||
host_base_url:
|
||||
type: string
|
||||
host_api_key:
|
||||
type: string
|
||||
host_bearer_token:
|
||||
type: string
|
||||
pack_path:
|
||||
type: string
|
||||
provider_id:
|
||||
type: string
|
||||
keys:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
mode:
|
||||
type: string
|
||||
access_mode:
|
||||
type: string
|
||||
access_api_key:
|
||||
type: string
|
||||
subscription_users:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
subscription_days:
|
||||
type: integer
|
||||
RollbackProviderRequest:
|
||||
type: object
|
||||
required: [host_base_url, pack_path]
|
||||
properties:
|
||||
host_base_url:
|
||||
type: string
|
||||
host_api_key:
|
||||
type: string
|
||||
host_bearer_token:
|
||||
type: string
|
||||
pack_path:
|
||||
type: string
|
||||
provider_id:
|
||||
type: string
|
||||
ReconcileProviderRequest:
|
||||
type: object
|
||||
required: [host_base_url, pack_path]
|
||||
properties:
|
||||
host_base_url:
|
||||
type: string
|
||||
host_api_key:
|
||||
type: string
|
||||
host_bearer_token:
|
||||
type: string
|
||||
pack_path:
|
||||
type: string
|
||||
provider_id:
|
||||
type: string
|
||||
Reference in New Issue
Block a user