Files
sub2api-cn-relay-manager/docs/EXECUTION_BOARD.md
2026-05-29 14:58:23 +08:00

1203 lines
89 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sub2api-cn-relay-manager 执行板
日期2026-05-22
当前 GateAPPROVED代码门禁已通过并且 2026-05-21 已继续收掉 account probe、gateway probe 认证语义和 latest-head `self_service` fresh-host 复验的剩余问题。最新 MiniMax 53hk fresh-host 验收 `artifacts/real-host-acceptance/20260521_191418_remote43_minimax_key_import/21-summary.json`、DeepSeek 2166 `subscription` fresh-host 验收 `artifacts/real-host-acceptance/20260521_201509_remote43_deepseek_key_import/21-summary.json`、以及 latest-head `self_service` 标准 fresh-host 验收 `artifacts/real-host-acceptance/20260521_210403/05-import.json` / `07-access-status.json` 已共同证明:`subscription``self_service` 主链路都能在真实 fresh host 上闭环到 readyhost `/v1/models``/v1/chat/completions` 也都真实返回 `HTTP 200`。当前仍存在的 `reconcile=drifted` 只反映共享 fresh-host 环境里的历史残留资源,不阻塞 PRD 首版放行)
目标:实现独立控制面、零侵入宿主、可导入国产模型并具备可运维的导入/回滚/访问闭环。
## 2026-05-22 当前真相
- 当前主目录 `artifacts/real-host-acceptance/` 已只保留最终证据;历史调试样本已迁到 `artifacts/real-host-acceptance-archive/`
- access ready 语义已经收口为:`/v1/models` 命中 `smoke_test_model`,且最小 `POST /v1/chat/completions` smoke 成功;不会再出现 models-only 假 ready
- `subscription` 主链路已通过 latest fresh-host 复验:
- MiniMax 53hk`artifacts/real-host-acceptance/20260521_191418_remote43_minimax_key_import/21-summary.json`
- DeepSeek 2166`artifacts/real-host-acceptance/20260521_201509_remote43_deepseek_key_import/21-summary.json`
- Kimi A7Mlocal host `v0.1.129``artifacts/real-host-acceptance/20260522_122706_local_v0129_kimi_a7m_subscription_freshhost/21-summary.json`
- `self_service` 主链路已通过 latest-head 标准 fresh-host 复验:
- `artifacts/real-host-acceptance/20260521_210403/05-import.json`
- `artifacts/real-host-acceptance/20260521_210403/07-access-status.json`
- 2026-05-27 已把公网用户入口从 `kimi-portal` 收口为通用多模型 portal
- 新正式地址:`https://sub.tksea.top/portal/`
- 旧地址 `https://sub.tksea.top/kimi-portal/` 当前保留为 `302` 跳转,避免历史分享链接失效
- 站点资产与 Nginx 路由不再只存在 `/tmp` 临时文件,已收口进仓库:
- `deploy/tksea-portal/index.html`
- `deploy/tksea-portal/admin-batch-import.html`
- `deploy/tksea-portal/nginx.sub.tksea.top.conf.example`
- `scripts/deploy/deploy_tksea_portal.sh`
- 新页面已补齐登录态、用户信息、可绑定分组、活跃订阅、历史 key 列表,以及“新创建 key 对应分组/模型”的即时展示
- 同轮已补最小 batch-import 管理页:
- 地址:`/portal/admin-batch-import.html`
- 直接消费 `POST /api/batch-import/runs`
- 直接消费 `GET /api/batch-import/runs/{run_id}`
- 直接消费 `GET /api/batch-import/runs/{run_id}/items`
- 用于验证 `matched_account_state / account_resolution / provision_reused`
- 2026-05-27 已继续把管理入口收成统一 `/portal/admin/` 体系:
- `https://sub.tksea.top/portal/admin/`:管理首页
- `https://sub.tksea.top/portal/admin/providers.html`provider 目录 / preview-import / import / manifest 草稿页
- `https://sub.tksea.top/portal/admin/batch-import.html`:结构化 batch-import 入口,当前跳转到 legacy `admin-batch-import.html`
- Nginx 示例与 deploy 脚本已补同域 CRM 反代 `https://sub.tksea.top/portal-admin-api/`
- 目的不是绕过鉴权,而是让浏览器可直接操作 remote43 CRM当前已继续补成“管理员用户名 / 密码登录 + HttpOnly session cookie”同时保留 Bearer admin token 兼容脚本与紧急兜底
- 2026-05-27 已继续把 provider manifest 草稿从“只存在浏览器”补成真正的服务端能力:
- 新增 `POST /api/provider-drafts`
- 新增 `GET /api/provider-drafts`
- 新增 `GET /api/provider-drafts/{draft_id}`
- 新增 `PUT /api/provider-drafts/{draft_id}`
- 新增 `DELETE /api/provider-drafts/{draft_id}`
- 数据当前落到 CRM SQLite `provider_drafts`
- `providers.html` 已可直接“保存到服务端”、回看历史草稿、以及更新 / 删除已保存草稿
- 2026-05-28 已把“草稿一键生成 pack/provider 文件并提交到仓库”的发布链路补齐:
- 新增 `POST /api/provider-drafts/{draft_id}/publish`
- 发布动作会把草稿 canonicalize 成完整 `pack.ProviderManifest`
- 服务端会原子执行:写 `providers/<provider_id>.json`、bump `pack.json` patch 版本、更新 `checksums.txt`、重跑整包校验、`git add` + `git commit`
- 运行前提新增:`SUB2API_CRM_REPO_ROOT` 必须指向**真实 Git 仓库**
- remote43 原本的 `/home/ubuntu/sub2api-cn-relay-manager` 只是普通目录,不带 `.git`
- 2026-05-28 已继续把这条路径收成正式部署约定:
- CRM 现在应统一指向 `/home/ubuntu/sub2api-cn-relay-manager-git-current`
- `scripts/deploy/setup_remote43_patched_stack.sh` 会自动生成并刷新该固定 checkout
- 这样 provider 草稿发布链不再依赖任何一次性的时间戳 repo 目录
- 公网 `providers.html` 已新增“发布到仓库”按钮与 commit message 输入框
- remote43 公网真验已通过:
- `draft_id=draft_remote43_publish_smoke_1779924243`
- `provider_id=smoke-publish-1779924243`
- `provider_path=packs/openai-cn-pack/providers/smoke-publish-1779924243.json`
- `pack_version=1.1.5 -> 1.1.6`
- `publish_mode=created`
- `commit_sha=d8d647e`
- 远端 repo `HEAD` 与 API 返回 `commit_sha` 一致,说明 create -> publish -> git commit 已完整闭环
- 线上无副作用验收已确认:
- `GET /portal/` 返回 `200`
- `GET /kimi-portal/` 返回 `302 -> /portal/`
- `GET /portal-proxy/api/v1/keys` 在无效 token 下已命中宿主真实 `INVALID_TOKEN`,说明新的同域代理已生效
- 2026-05-28 已继续把管理态“每次手贴 Bearer token”收口为正式登录流
- 新增 `GET /api/admin/session`
- 新增 `POST /api/admin/session/login`
- 新增 `POST /api/admin/session/logout`
- 管理态受保护接口现已同时接受:
- `Authorization: Bearer <SUB2API_CRM_ADMIN_TOKEN>`
- 或同域管理员 session cookie
- `providers.html``admin-batch-import.html` 现已优先走 sessiontoken 输入框仅保留为兜底
- 当前部署环境可通过以下变量显式配置管理员账号:
- `SUB2API_CRM_ADMIN_USERNAME`
- `SUB2API_CRM_ADMIN_PASSWORD`
- `SUB2API_CRM_ADMIN_SESSION_TTL`
- 2026-05-28 已继续把 `providers.html` 的 manifest 草稿表单收口成“按最近成功模板起步”的录入流:
- 草稿区首次打开且字段为空时,会优先回填最近一次成功发布的模板;没有历史时,回退到当前 pack/provider 目录里的现有 provider最后才使用静态样例
- `Provider ID` 现已按 `display_name / base_url / supported_models` 自动生成,并在与现有 provider / draft 冲突时自动补后缀避重
- 当新填的 `supported_models` 已在现有 provider 或草稿里出现时,页面会直接提示“同模型已存在”,并优先建议复用已有 `provider_id`,避免因为“官方 / 中转”重复新增同一模型定义
- `GET /api/packs/{pack_id}/providers` 现已补充返回 `base_url / smoke_test_model / supported_models`,用于前端做模板参考和模型冲突提示
- 2026-05-28 继续把这条规则下沉到服务端:`POST /api/provider-drafts``PUT /api/provider-drafts/{draft_id}``POST /api/provider-drafts/{draft_id}/publish` 现在都会做 pack 级模型冲突校验;同模型若已被其他 provider / draft 占用,会直接返回 `409 provider_model_conflict`
- 2026-05-28 已新增独立实验 pack `packs/openai-cn-pack-route-lab/`,用于验证 “同一个 group 下挂多条 GPT 路线” 的方案 B 骨架:
- 当前实验 provider 为 `gpt-asxs-route-lab``gpt-codex2api-route-lab`
- 两者共用 `group_template.name=GPT Shared 路由实验``plan_template.name=GPT Shared 路由实验套餐`
- 两者使用不同 `channel_template.name`,并先以不同公开 alias 对外:
- `gpt-5.4-asxs` / `gpt-5.4-mini-asxs`
- `gpt-5.4-codex2api` / `gpt-5.4-mini-codex2api`
- 这轮只解决 “同组多线路” 的第一阶段验证,不代表当前系统已经支持 “同公开模型名双线路 + 显式 route policy”
- `codex2api` 当前先按 `https://www.codex2api.com/v1` 作为 API 根地址假设,若 preview/import 失败,需先校正真实 API base URL
- 2026-05-28 remote43 真验结论已落地:
- `gpt-asxs-route-lab` 可成功导入artifact`artifacts/real-host-acceptance/20260528_142205_remote43_gpt-asxs-route-lab_key_import/21-summary.json`
- 导入时创建资源:`group_id=8``channel_id=7``plan_id=7``account_id=9`
- upstream `asxs``/models``/chat/completions``200`;但该轮 managed chat 仍返回 `503`,原因是脚本首轮使用了 canonical model `gpt-5.4` 探测,而当前 route-lab 对外 alias 实际是 `gpt-5.4-asxs`
- `gpt-codex2api-route-lab` 在尝试复用同一 group 时被宿主直接拒绝artifact`artifacts/real-host-acceptance/20260528_142320_remote43_gpt-codex2api-route-lab_key_import/03-import.body.json`
- 宿主返回 `409 GROUP_ALREADY_IN_CHANNEL`,错误为:`one or more groups already belong to another channel`
- 因此当前真实结论不是“同组多 channel 可继续验证路由策略”,而是 **stock / patched sub2api 当前结构上不允许同一个 group 绑定到第二个 channel**
- 2026-05-28 已进一步把宿主侧最小改造方案固化到 `docs/HOST_MULTI_CHANNEL_MINIMAL_RETROFIT.md`
- 真实最小改造不只是移除 `channel_groups(group_id)` 唯一索引
- 还必须给宿主 `account_groups` 引入 `channel_id`,并让 `gateway / scheduler / sticky session / account stats pricing` 全部从 `group -> single channel` 升级到 `group + channel` 维度
- 否则就算数据库允许同一 group 绑定多个 channel运行时账号池仍会被按 group 混跑,结构上仍不成立
- 2026-05-28 已明确 fallback 方案:不修改宿主源码,改由 relay-manager 插件层维护 `logical_group -> route -> shadow_group` 三层抽象,详见 `docs/PLUGIN_ROUTE_STICKY_DESIGN.md`
- 前端只看到一个逻辑分组
- 插件层先做 route 级 sticky再把请求稳定转发到某个宿主 shadow group
- 宿主继续只做单线路 group 内的 account sticky / 调度
- 2026-05-29 已基于上述结论新增 canonical shadow pack
- `packs/openai-cn-pack-shadow-asxs/`
- provider`gpt-asxs-shadow-lab`
- 当前约束是:
- 一个 route 对应一个独立宿主 shadow group
- 宿主 shadow group 只承载 canonical upstream model`gpt-5.4``gpt-5.4-mini`
- alias/public model 的抽象只保留在插件 `logical_group -> route -> shadow_model` 层,不再下沉到宿主 channel
- 设计与验收路径已单独沉淀到 `docs/SHADOW_PROVIDER_VALIDATION.md`
- 2026-05-29 已完成 remote43 真实宿主直连验收:
- 验收 artifact`artifacts/real-host-acceptance/20260529_123659_remote43_gpt-asxs-shadow-lab_key_import/21-summary.json`
- 通过 `gpt-asxs-shadow-lab` 成功导入 canonical shadow provider
- 导入后创建资源:`subscription_group_id=9``import_group_id=9`
- 真实 managed key 直连宿主结果:
- `/v1/models` 返回 `200`
- 模型集包含 canonical model`gpt-5.4``gpt-5.4-mini`
- `/v1/chat/completions` 返回 `200`
- upstream `asxs``/models``/chat/completions` 同样返回 `200`
- 说明 canonical shadow 设计已经绕过旧 `route-lab` 的 alias 下沉问题
- 2026-05-29 已修复 remote43 导入脚本 pack 路径使用错误:
- `scripts/acceptance/import_remote43_provider.sh` 新增 `REQUEST_PACK_PATH`
- 本地 pack 解析仍使用 `PACK_PATH`
- 发给 remote43 CRM 的导入请求改为使用远端实际可见路径,避免 CRM 在远端错误地 `stat /home/long/...`
- 2026-05-28 已新增插件整体需求盘点 `docs/PLUGIN_REQUIREMENTS_OVERVIEW_2026-05-28.md`
- 已把“增加模型、维护逻辑分组、智能路由、供应商帐号导入与停启用、普通用户前端”五大功能域统一收口
- 并明确区分 `已完成 / 待优化 / 待完成 / 未来规划`
- 2026-05-28 已继续细化闭环实施规划 `docs/PLUGIN_CLOSED_LOOP_IMPLEMENTATION_PLAN_2026-05-28.md`
- 明确当前插件数据库仍为 SQLite`SUB2API_CRM_SQLITE_DSN`
- 明确后续继续以 SQLite 作为主状态库Redis 作为智能路由运行态缓存
- 明确智能路由日志必须结构化落入插件 SQLite而不是只放 Redis 或 stdout
- 2026-05-28 已新增 Phase 1 可开工任务单 `docs/plans/2026-05-28-phase1-logical-routing-foundation-plan.md`
- 已把 `SQLite migration / logical_group-route repo+API / 路由日志写入器 / Redis sticky 抽象` 拆成可执行任务
- 已继续细化到任务级 `入场条件 / 产出清单 / 远端验证步骤 / 证据要求 / 回滚原则`
- 并明确要求:每个闭环功能完成后,都必须提交、推送、部署到 `remote43` 再验证,不能只停留在本地测试
- 当前 Phase 1 的统一真相是:
- 主状态库继续使用 SQLite
- 路由运行态使用 Redis 或 memory backend 抽象
- 智能路由日志必须最终结构化写回插件 SQLite
- 2026-05-29 已新增 Phase 2 可开工任务单 `docs/plans/2026-05-29-phase2-intelligent-routing-closure-plan.md`
- 已把 `管理页入口 / 正式数据面入口 / route 健康视图 / 真实验收矩阵` 拆成可执行任务
- 已明确当前 Phase 2 不是再证明“路由能跑”,而是把现有能力收敛成产品闭环:
- 管理员可维护 `logical_group -> route -> shadow_group`
- 插件存在正式数据面入口,而不只是实验 proxy
- route 的 sticky / failover / cooldown 状态可被运营查看
- `remote43` 真验收敛成固定矩阵,而不是零散命令
- 当前 Phase 2 的建议实施顺序是:
- `P2-T1 管理页入口`
- `P2-T2 正式数据面入口`
- `P2-T3 route 健康视图`
- `P2-T4 真实验收矩阵`
- 2026-05-29 已完成 Phase 2 / `P2-T1 管理页入口`
- 提交:`2e9b4ab9 feat(portal): add logical group admin page`
- 新增静态页:
- `deploy/tksea-portal/admin/logical-groups.html`
- 已完成 admin 导航接线:
- `deploy/tksea-portal/admin/index.html`
- `deploy/tksea-portal/admin/providers.html`
- `deploy/tksea-portal/admin-batch-import.html`
- 当前页面覆盖的最小运营流:
- `logical_group` 创建 / 更新 / 删除
- `public_model` 新增 / 删除
- `route` 创建 / 更新 / 删除
- `route model` 新增 / 查看
- 静态资产与脚本回归已通过:
- `bash ./scripts/test/test_tksea_portal_assets.sh`
- `bash ./scripts/test/test_real_host_scripts.sh`
- Go 质量门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- portal 已部署到 remote43
- 部署脚本:`scripts/deploy/deploy_tksea_portal.sh`
- 公网新页面:`https://sub.tksea.top/portal/admin/logical-groups.html`
- 管理首页已可见入口:`https://sub.tksea.top/portal/admin/`
- 公网页面回读已确认:
- `logical-groups.html` 已包含 `Logical Group / Public Models / Routes / Route Models`
- 管理首页已出现 `逻辑分组 / 路由` 导航与入口卡片
- 公网 admin API 真验已通过:
- `POST /api/admin/session/login` 建立管理员会话成功
- `POST /api/logical-groups` 创建 `logical_group_id=p2t1-lg-1780031264`
- `POST /api/logical-groups/p2t1-lg-1780031264/routes` 创建 `route_id=asxs-ui-1780031264`
- `GET /api/logical-groups/p2t1-lg-1780031264` 已回读到:
- `shadow_group_id=9`
- `shadow_host_id=proxy-real-host-1780026133`
- `upstream_base_url_hint=https://api.asxs.top/v1`
- 当前结论:
- `logical_group -> route -> shadow_group` 已有独立管理页入口
- 现有 CRM API 已足够支撑首版 UI
- `P2-T2` 可以直接在这个页面基础上继续对接正式数据面入口
- 2026-05-29 已完成 Phase 2 / `P2-T2 正式数据面入口`
- 提交:`ecdeedb1 feat(routing): add formal chat route endpoint`
- 新增正式入口:
- `POST /api/routing/chat/completions`
- 兼容策略:
-`POST /api/routing/proxy/chat/completions` 保留,继续作为实验/调试入口
- 新入口复用既有 `resolve -> sticky -> failover -> managed subscription -> forward -> route_decision_logs` 链路
- 对外返回改为正式产品语义:
- `model`
- `selected_route`
- `sticky_hit / sticky_action / fallback_used`
- `forward.upstream_status`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到:
- `repo HEAD = ecdeedb1`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- 本轮还修正了一个远端部署细节:
- 18173 活跃实例曾继续跑旧 CRM 二进制
- 原因是实例目录里的 `sub2api-cn-relay-manager-server` 未被新构建产物覆盖
- 现已通过定向替换实例二进制并按实际监听 PID 重启收口
- 公网 admin API 真验已通过:
- 先创建临时 `logical_group_id=p2t2-check-1780032198`
- 再创建临时 `route_id=asxs-check-1780032198`
- route 命中真实 canonical shadow
- `shadow_host_id=proxy-real-host-1780026133`
- `shadow_group_id=9`
- `shadow_model=gpt-5.4`
- 调用 `POST /api/routing/chat/completions`
- `request_id=req-p2t2-check-1780032198`
- `backend=redis`
- `sticky_hit=false`
- `sticky_action=bind`
- `selected_route.route_id=asxs-check-1780032198`
- `selected_route.shadow_group_id=9`
- `selected_route.shadow_model=gpt-5.4`
- `forward.ok=true`
- `forward.upstream_status=200`
- `forward.effective_gateway_key_source=managed_subscription`
- `forward.managed_user_id=36`
- `forward.content_type=text/event-stream`
- 返回 completion 内容:`content=pong`
- `GET /api/routing/logs/decisions?request_id=req-p2t2-check-1780032198&limit=5`
- 已回读到 `2` 条 decision log
- 最新一条:
- `selected_route_id=asxs-check-1780032198`
- `selected_shadow_group_id=9`
- `upstream_status=200`
- `fallback_used=false`
- 当前结论:
- 正式入口 `POST /api/routing/chat/completions` 已经可用
- canonical shadow route + managed subscription key + real host `/v1/chat/completions` 已在正式入口下再次验证为 `200`
- `P2-T3` 可以直接在这一入口之上补 route 健康视图与聚合状态
- 2026-05-29 已完成 Phase 2 / `P2-T3 route 健康视图`
- 提交:`2896e620 feat(routing): add route health admin view`
- 新增聚合状态 API
- `GET /api/routing/routes/health`
- 可选过滤:
- `logical_group_id`
- `route_id`
- `status=healthy|cooldown|failing|disabled`
- 聚合字段首版已收口:
- `route_id`
- `route_name`
- `logical_group_id`
- `shadow_host_id`
- `shadow_group_id`
- `priority`
- `runtime_status`
- `failure_count`
- `cooldown_until`
- `cooldown_reason`
- `last_error_class`
- `last_selected_at`
- `last_upstream_status`
- `recent_failover_count`
- 新增管理页:
- `https://sub.tksea.top/portal/admin/route-health.html`
- 现有管理导航已接线:
- `admin/index.html`
- `admin/logical-groups.html`
- `admin/providers.html`
- `admin-batch-import.html`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- `bash ./scripts/test/test_tksea_portal_assets.sh`
- `bash ./scripts/test/test_real_host_scripts.sh`
- remote43 已原位升级到 `repo HEAD = 2896e620`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- portal 已重新发布,公网健康页在线:
- `GET https://sub.tksea.top/portal/admin/route-health.html -> HTTP 200`
- 页面标题包含 `Route Health Admin`
- remote43 真实矩阵已通过:
- 临时 `logical_group_id = p2t3-health-1780033345`
- `primary-1780033345` 写入 cooldown`runtime_status=cooldown`
- `failing-1780033345` 写入 route failure`runtime_status=failing`
- `POST /api/routing/resolve`
- `request_id=req-p2t3-health-1780033345`
-`primary-1780033345` 处于 `active_cooldown:degraded`
- 自动切到 `fallback-1780033345`
- `resolve.route_id=fallback-1780033345`
- `fallback_used=true`
- 再次回读 `GET /api/routing/routes/health?logical_group_id=p2t3-health-1780033345`
- `primary-1780033345=cooldown`
- `fallback-1780033345=healthy`
- `failing-1780033345=failing`
- `fallback-1780033345.last_selected_at` 已写入
- `fallback-1780033345.recent_failover_count=1`
- `GET /api/routing/logs/failovers?request_id=req-p2t3-health-1780033345&limit=5`
- `from_route_id=primary-1780033345`
- `to_route_id=fallback-1780033345`
- `reason=active_cooldown:degraded`
- 当前结论:
- route 健康页已经能把 `cooldown / failing / healthy / disabled` 四态聚合出来
- 健康页与真实 `resolve` / `failover` 日志已经对齐
- `P2-T4` 可以开始把这些验证步骤收敛成标准化验收矩阵与脚本
- 2026-05-29 已完成 Phase 2 / `P2-T4 真实验收矩阵`
- 提交:
- `94913400 feat(routing): add route acceptance matrix scripts`
- `5689286f fix(acceptance): align route model list parsing`
- 新增标准化验收脚本:
- `scripts/acceptance/route_acceptance_lib.sh`
- `scripts/acceptance/verify_route_control_plane.sh`
- `scripts/acceptance/verify_route_health_ui.sh`
- `scripts/acceptance/verify_route_data_plane.sh`
- `scripts/acceptance/verify_route_acceptance_matrix.sh`
- 新增矩阵说明:
- `docs/ROUTE_ACCEPTANCE_MATRIX.md`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- `bash ./scripts/test/test_tksea_portal_assets.sh`
- `bash ./scripts/test/test_real_host_scripts.sh`
- remote43 已使用 fixed checkout
- `repo HEAD = 5689286f`
- 本轮仅新增脚本和文档,无需重启 CRM
- `http://127.0.0.1:18173/healthz` 持续返回 `ok`
- remote43 已按标准命令完成整套矩阵真验:
- `CRM_BASE=https://sub.tksea.top/portal-admin-api`
- `ROUTE_HEALTH_PAGE_URL=https://sub.tksea.top/portal/admin/route-health.html`
- `SHADOW_HOST_ID=proxy-real-host-1780026133`
- `SHADOW_GROUP_ID=9`
- `SUBSCRIPTION_USER_ID=36`
- 产物目录:
- `/tmp/phase2-routing-matrix/1780034317_route_matrix`
- control plane summary
- `group_id=p2t4-cp-1780034317`
- `route_id=primary-1780034317`
- `public_model=gpt-5.4`
- `shadow_model=gpt-5.4`
- 已验证:
- 创建 group
- 更新 group
- 创建 route
- 更新 route
- 创建 route model
- 列出 route model
- health UI / runtime summary
- `group_id=p2t4-health-1780034318`
- `request_id=req-p2t4-health-1780034318`
- `resolve_route_id=fallback-1780034318`
- `resolve_fallback_used=true`
- 状态矩阵已对齐:
- `primary-1780034318=cooldown`
- `fallback-1780034318=healthy`
- `failing-1780034318=failing`
- `fallback_recent_failover_count=1`
- data plane summary
- `group_id=p2t4-dp-1780034319`
- `route_id=primary-1780034319`
- `request_id=req-p2t4-dp-1780034319`
- `auth_mode=managed_subscription`
- `forward_upstream_status=200`
- `selected_shadow_host_id=proxy-real-host-1780026133`
- `selected_shadow_group_id=9`
- `selected_shadow_model=gpt-5.4`
- `effective_gateway_key_source=managed_subscription`
- `decision_log_count=2`
- 当前结论:
- Phase 2 的控制面、运行态、正式数据面、管理页都已被收敛进固定脚本矩阵
- 后续任何 Phase 2 回归都可以直接在 `remote43` 上复用同一套脚本,而不再依赖临时串命令
- `P2` 已具备可持续回归的最小产品闭环
- 2026-05-29 已完成 Phase 3 / `P3-T1 帐号清单与启停 API`
- 提交:
- `b5343452 feat(accounts): add provider account inventory api`
- 新增 SQLite migration
- `internal/store/migrations/0012_provider_accounts.sql`
- 新增帐号库存 repo / 回填逻辑:
- `internal/store/sqlite/provider_accounts_repo.go`
- `internal/store/sqlite/provider_accounts_sync.go`
- 新增管理 API
- `GET /api/provider-accounts`
- `POST /api/provider-accounts/{account_id}/enable`
- `POST /api/provider-accounts/{account_id}/disable`
- `POST /api/provider-accounts/{account_id}/retire`
- 当前首版设计边界已明确:
- `provider_accounts` 是插件侧帐号资产库存
- 列表接口会先按最新 `reconcilable import batch` 做一次库存回填,避免 remote43 上已有导入结果看不到
- runtime import 完成后会自动把 `managed_resources + import_batch_items` 同步进 `provider_accounts`
- `enable / disable / retire` 当前只更新插件 SQLite 库存状态,不猜测宿主 `/api/v1/admin/accounts/:id` 的写入 payload
- 为避免人工禁用状态被回填覆盖,同步层现在会保留人工 `disabled` / 人工 `deprecated`;只允许 `missing_from_latest_batch` 这种自动弃用在新 batch 命中后被恢复
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到:
- `repo HEAD = b5343452`
- CRM 实例目录:`/home/ubuntu/sub2api-kimi-patched-auto2-20260525_18169`
- 活跃监听 PID`1771174`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- remote43 真实 API 验证已通过:
- `GET /api/provider-accounts`
- `count=1`
- 样本帐号:
- `id=1`
- `provider_id=gpt-asxs-shadow-lab`
- `host_account_id=10`
- 初始 `account_status=active`
- `POST /api/provider-accounts/1/disable`
- `reason=p3_t1_remote_verify`
- 返回 `account_status=disabled`
- 再次 `GET /api/provider-accounts`
- 同一条样本仍为 `account_status=disabled`
- 说明列表回填不会把人工禁用状态刷回 `active`
- `POST /api/provider-accounts/1/enable`
- 返回 `account_status=active`
- 再次 `GET /api/provider-accounts`
- 同一条样本恢复为 `account_status=active`
- 当前结论:
- 插件侧已经具备“帐号库存列表 + 人工启停状态”的最小资产闭环
- 首版语义仍然是“插件库存状态”,不是“宿主账号状态联动”
- `P3-T2` 可以直接继续做帐号资产页和 route / shadow group 归属展示
- 2026-05-28 已完成 Phase 1 / `P1-T1 SQLite schema foundation`
- 提交:`7f75d8a6 feat(routing): add logical group schema foundation`
- 新 migration`internal/store/migrations/0010_logical_groups_and_routes.sql`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = 7f75d8a`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- remote43 实例 SQLite `/home/ubuntu/sub2api-kimi-patched-auto2-20260525_18169/sub2api-cn-relay-manager.db` 已确认包含:
- `logical_groups`
- `logical_group_models`
- `logical_group_routes`
- `logical_group_route_models`
- 这轮远端验证还顺手暴露并修正了一个部署细节:
- 若只在 `/home/ubuntu` 下直接拉起 CRM新进程会回退到默认相对 SQLite 路径 `/home/ubuntu/sub2api-cn-relay-manager.db`
- 当前已改为显式 `cd` 到实例目录并 `source .env.crm` 后再启动,确保 migration 生效在实例库而不是错误的默认库
- 2026-05-28 已完成 Phase 1 / `P1-T2 logical_group / route repo + admin API`
- 提交:`28188922 feat(routing): add logical group admin api`
- 新增 SQLite repo
- `logical_groups`
- `logical_group_models`
- `logical_group_routes`
- `logical_group_route_models`
- 新增管理 API
- `POST /api/logical-groups`
- `GET /api/logical-groups`
- `GET /api/logical-groups/{group_id}`
- `PUT /api/logical-groups/{group_id}`
- `DELETE /api/logical-groups/{group_id}`
- `POST /api/logical-groups/{group_id}/models`
- `GET /api/logical-groups/{group_id}/models`
- `DELETE /api/logical-groups/{group_id}/models/{model}`
- `POST /api/logical-groups/{group_id}/routes`
- `GET /api/logical-groups/{group_id}/routes`
- `PUT /api/logical-groups/{group_id}/routes/{route_id}`
- `DELETE /api/logical-groups/{group_id}/routes/{route_id}`
- `POST /api/logical-groups/{group_id}/routes/{route_id}/models`
- `GET /api/logical-groups/{group_id}/routes/{route_id}/models`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = 2818892`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- remote43 真实 API 验证已通过:
- `POST /api/logical-groups` 创建 `logical_group_id=p1t2-gpt-shared-1779971040`
- `GET /api/logical-groups` 返回列表,当前计数 `2`
- `GET /api/logical-groups/p1t2-gpt-shared-1779971040` 返回 `display_name=P1T2 GPT Shared`,建 route 前 `routes_count=0`
- `POST /api/logical-groups/p1t2-gpt-shared-1779971040/routes` 创建 `route_id=asxs-1779971040``shadow_group_id=p1t2-gpt-shared-1779971040__asxs`
- 2026-05-28 已完成 Phase 1 / `P1-T3 route logging repo + async writer`
- 提交:`6e0bd59e feat(routing): add route log writer and admin api`
- 新增 migration`internal/store/migrations/0011_route_logging.sql`
- 新增 SQLite repo
- `route_decision_logs`
- `route_failover_events`
- `route_sticky_audit`
- 新增异步写入器:
- `internal/routing/logwriter.go`
- 默认采用内存 channel + 定时/显式 `Flush()` 批量落 SQLite
- 队列满时退化为同步写入,避免热路径静默丢日志
- 新增管理 API
- `POST /api/routing/logs/decisions`
- `GET /api/routing/logs/decisions`
- `POST /api/routing/logs/failovers`
- `GET /api/routing/logs/failovers`
- `POST /api/routing/logs/sticky-audit`
- `GET /api/routing/logs/sticky-audit`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = 6e0bd59`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- remote43 真实公网 API 验证已通过:
- `POST /api/routing/logs/decisions` 创建 `request_id=req-p1t3-decision-1779976705`,返回 `selected_route_id=asxs-1779971040`
- `GET /api/routing/logs/decisions?request_id=req-p1t3-decision-1779976705` 回读到同一条 decision log
- `POST /api/routing/logs/failovers` 创建 `request_id=req-p1t3-failover-1779976748`,返回 `to_route_id=asxs-1779971040-fallback`
- `GET /api/routing/logs/failovers?request_id=req-p1t3-failover-1779976748` 回读到同一条 failover event
- `POST /api/routing/logs/sticky-audit` 创建 `sticky_key=sticky-p1t3-1779976750`,返回 `action=bind`
- `GET /api/routing/logs/sticky-audit?sticky_key=sticky-p1t3-1779976750` 回读到同一条 sticky audit
- 2026-05-29 已完成 Phase 1 / `P1-T4 Redis sticky store abstraction`
- 提交:`98bd619e feat(routing): add sticky runtime backends`
- 新增运行时抽象:
- `internal/routing/sticky.go`
- `internal/routing/sticky_memory.go`
- `internal/routing/sticky_redis.go`
- 新增统一 key 规则:
- `sticky:{scope}:{logical_group_id}:{public_model}:{subject_id}`
- `routefail:{route_id}`
- `routecool:{route_id}`
- 新增启动配置:
- `SUB2API_CRM_ROUTE_RUNTIME_BACKEND`
- `SUB2API_CRM_REDIS_ADDR`
- `SUB2API_CRM_REDIS_PASSWORD`
- `SUB2API_CRM_REDIS_DB`
- 新增管理 API
- `POST /api/routing/sticky/bindings`
- `GET /api/routing/sticky/bindings`
- `POST /api/routing/sticky/route-failures`
- `GET /api/routing/sticky/route-failures`
- `POST /api/routing/sticky/cooldowns`
- `GET /api/routing/sticky/cooldowns`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = 98bd619`
- `http://127.0.0.1:18173/healthz``memory` / `redis` 两种运行模式下均返回 `ok`
- remote43 `redis` 运行时目标已确认使用容器地址 `172.24.0.3:6379``127.0.0.1:6379` 不可用
- remote43 真实公网 API 验证已通过:
- `memory` 模式:
- `POST /api/routing/sticky/bindings` 创建 `subject_id=conv-p1t4-memory-1780011984`,返回 `backend=memory`
- `GET /api/routing/sticky/bindings` 回读到同一条绑定,`route_id=asxs-1779971040`,返回 `backend=memory`
- `POST /api/routing/sticky/route-failures` 创建 `route_id=route-p1t4-memory-1780011984`,返回 `failure_count=2``backend=memory`
- `GET /api/routing/sticky/route-failures` 回读到同一条故障计数,返回 `backend=memory`
- `POST /api/routing/sticky/cooldowns` 创建 `route_id=route-p1t4-memory-1780011984`,返回 `reason=degraded``backend=memory`
- `GET /api/routing/sticky/cooldowns` 回读到同一条 cooldown返回 `backend=memory`
- `redis` 模式:
- `POST /api/routing/sticky/bindings` 创建 `subject_id=conv-p1t4-redis-1780012047`,返回 `backend=redis`
- `GET /api/routing/sticky/bindings` 回读到同一条绑定,`route_id=asxs-1779971040`,返回 `backend=redis`
- `POST /api/routing/sticky/route-failures` 创建 `route_id=route-p1t4-redis-1780012047`,返回 `failure_count=3``backend=redis`
- `GET /api/routing/sticky/route-failures` 回读到同一条故障计数,返回 `backend=redis`
- `POST /api/routing/sticky/cooldowns` 创建 `route_id=route-p1t4-redis-1780012047`,返回 `reason=cooldown-active``backend=redis`
- `GET /api/routing/sticky/cooldowns` 回读到同一条 cooldown返回 `backend=redis`
- 2026-05-29 已完成基础设施闭环补充 / `route resolve + sticky hit`
- 提交:`66ad319c feat(routing): add sticky-backed route resolver`
- 新增管理 API
- `POST /api/routing/resolve`
- 行为收口:
- 首次 resolve 按 `logical_group_routes.priority` 选择可用 route
- 选择结果会同步写入 sticky store
- 同步写入 `route_decision_logs``route_sticky_audit`
- 后续同 `scope + logical_group_id + public_model + subject_id` 请求会优先命中 sticky
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = 66ad319`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- 远端实例二进制校验:
- 活跃实例目录:`/home/ubuntu/sub2api-kimi-patched-auto2-20260525_18169`
- 本地构建与远端实例二进制 `sha256` 已对齐为 `f7b8334cd992e0a3e65d3f129163f0a01f06a1c746071b67f6e8d1f6fe38ad99`
- 修正了一次“按绝对路径 `pkill` 未命中 `./sub2api-cn-relay-manager-server`”导致旧进程仍在跑的问题,之后已定向 kill 活跃 PID 并重启
- remote43 真实公网 API 验证已通过(`redis` 运行时):
- 创建临时逻辑分组 `logical_group_id=p1t5-gpt-shared-1780019458`
- 创建两条 route
- `asxs-1780019458``priority=20`
- `codex2api-1780019458``priority=10`
- 第一次 `POST /api/routing/resolve`
- `request_id=req-p1t5-first-1780019458`
- `subject_id=conv-p1t5-1780019458`
- 返回 `backend=redis`
- 返回 `route_id=codex2api-1780019458`
- 返回 `sticky_hit=false`
- 返回 `sticky_action=bind`
- 第二次 `POST /api/routing/resolve`(同 subject
- `request_id=req-p1t5-second-1780019458`
- 返回 `backend=redis`
- 返回 `route_id=codex2api-1780019458`
- 返回 `sticky_hit=true`
- 返回 `sticky_action=hit`
- `GET /api/routing/logs/decisions?sticky_key=lg:p1t5-gpt-shared-1780019458:m:gpt-5.4:conv:conv-p1t5-1780019458`
- 回读到两条 decision log
- 最新一条 `request_id=req-p1t5-second-1780019458``sticky_hit=true`
- 较早一条 `request_id=req-p1t5-first-1780019458``sticky_hit=false`
- `GET /api/routing/logs/sticky-audit?sticky_key=lg:p1t5-gpt-shared-1780019458:m:gpt-5.4:conv:conv-p1t5-1780019458`
- 回读到两条 sticky audit
- 最新一条 `action=hit`
- 较早一条 `action=bind`
- 2026-05-29 已完成基础设施闭环补充 / `route failure threshold failover`
- 提交:`eb2242ca feat(routing): add resolver failover fallback`
- 行为收口:
- `resolve` 现在会在选路时读取 `route-failures``cooldowns`
- 当高优先级 route 的 `failure_count >= logical_group.failover_threshold` 时,会自动跳过并选择下一条可用 route
- 首次 fallback 会把 `route_decision_logs.fallback_used` 置为 `true`
- 同时写入 `route_failover_events`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = eb2242c`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- 远端实例二进制已更新为 `sha256=cc177700541d9ab85a638f768e6fba045d1e864c347e6dfd895ea9e05f27c571`
- remote43 真实公网 API 验证已通过(`redis` 运行时):
- 创建临时逻辑分组 `logical_group_id=p1t5-failover-1780020305`
- 创建两条 route
- `codex2api-1780020305``priority=10`
- `asxs-1780020305``priority=20`
- `POST /api/routing/sticky/route-failures`
-`codex2api-1780020305` 写入 `failure_count=2`
- `last_error_class=timeout`
- 返回 `backend=redis`
- 第一次 `POST /api/routing/resolve`
- `request_id=req-p1t5-failover-first-1780020305`
- 返回 `route_id=asxs-1780020305`
- 返回 `sticky_hit=false`
- 返回 `sticky_action=bind`
- 说明高优先级 `codex2api-1780020305` 已因超阈值被自动跳过
- 第二次 `POST /api/routing/resolve`(同 subject
- `request_id=req-p1t5-failover-second-1780020305`
- 返回 `route_id=asxs-1780020305`
- 返回 `sticky_hit=true`
- `GET /api/routing/logs/failovers?request_id=req-p1t5-failover-first-1780020305`
- 回读到一条 failover event
- `from_route_id=codex2api-1780020305`
- `to_route_id=asxs-1780020305`
- `reason=failure_threshold_exceeded:timeout`
- `failure_count=2`
- `GET /api/routing/logs/decisions?sticky_key=lg:p1t5-failover-1780020305:m:gpt-5.4:conv:conv-p1t5-failover-1780020305`
- 第一条 resolve 对应记录 `fallback_used=true``sticky_hit=false`
- 第二条 resolve 对应记录 `fallback_used=false``sticky_hit=true`
- 2026-05-29 已完成基础设施闭环补充 / `cooldown rebinding regression matrix`
- 代码基线:沿用 `eb2242ca feat(routing): add resolver failover fallback`
- 验证目标:
- 人工设置 `cooldown`
- `resolve` 自动跳过处于 cooldown 的高优先级 route
- 写入 `route_failover_events`
- 验证旧 sticky 失效后的重新绑定,以及新 sticky 的后续命中
- remote43 真实公网 API 验证已通过(`redis` 运行时):
- 创建临时逻辑分组 `logical_group_id=p1t5-cooldown-1780020999`
- 创建两条 route
- `codex2api-1780020999``priority=10`
- `asxs-1780020999``priority=20`
- 第一次 `POST /api/routing/resolve`
- `request_id=req-p1t5-cooldown-first-1780020999`
- 正常命中主 route `codex2api-1780020999`
- 返回 `sticky_hit=false`
- 返回 `sticky_action=bind`
- `POST /api/routing/sticky/cooldowns`
-`codex2api-1780020999` 写入 `reason=degraded`
- 返回 `backend=redis`
- 第二次 `POST /api/routing/resolve`(同 subject
- `request_id=req-p1t5-cooldown-second-1780020999`
- 旧 sticky 指向的主 route 因 `cooldown` 被自动判定失效
- 返回 `route_id=asxs-1780020999`
- 返回 `sticky_hit=false`
- 返回 `sticky_action=bind`
- 说明已完成 sticky 重绑定到 fallback route
- 第三次 `POST /api/routing/resolve`(同 subject
- `request_id=req-p1t5-cooldown-third-1780020999`
- 返回 `route_id=asxs-1780020999`
- 返回 `sticky_hit=true`
- 说明新的 sticky 已稳定命中 fallback route
- `GET /api/routing/logs/failovers?request_id=req-p1t5-cooldown-second-1780020999`
- 回读到一条 failover event
- `from_route_id=codex2api-1780020999`
- `to_route_id=asxs-1780020999`
- `reason=active_cooldown:degraded`
- `failure_count=0`
- `GET /api/routing/logs/decisions?sticky_key=lg:p1t5-cooldown-1780020999:m:gpt-5.4:conv:conv-p1t5-cooldown-1780020999`
- 第一条 resolve`selected_route_id=codex2api-1780020999``fallback_used=false`
- 第二条 resolve`selected_route_id=asxs-1780020999``fallback_used=true``sticky_hit=false`
- 第三条 resolve`selected_route_id=asxs-1780020999``fallback_used=false``sticky_hit=true`
- `GET /api/routing/logs/sticky-audit?sticky_key=lg:p1t5-cooldown-1780020999:m:gpt-5.4:conv:conv-p1t5-cooldown-1780020999`
- 依次可回读到三条 sticky audit
- `action=bind``route_id=codex2api-1780020999`
- `action=bind``route_id=asxs-1780020999`
- `action=hit``route_id=asxs-1780020999`
- 2026-05-29 已完成基础设施闭环补充 / `resolve + minimal chat proxy bridge`
- 提交:`9b1c6f43 feat(routing): add minimal chat proxy bridge`
- 新增管理 API
- `POST /api/routing/proxy/chat/completions`
- 行为收口:
- 先复用 `POST /api/routing/resolve` 选出 `route_id / shadow_host_id / shadow_group_id / shadow_model`
- 再从插件 SQLite `hosts` 表读取 `shadow_host_id -> host.base_url`
- 用调用方显式提供的 `gateway_api_key` 代理转发最小 `POST /v1/chat/completions`
- 默认在未提供 `messages` 时发送最小 `ping` 消息
- 转发结果会以同一 `request_id` 追加写回 `route_decision_logs`,补齐 `upstream_status / latency_ms / error_class`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = 9b1c6f43`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- remote43 真实服务器 API 验证已通过:
- 为避免依赖现成 managed gateway key这轮在 remote43 本机临时起了 loopback stub `shadow host`
- `host_id=proxy-shadow-host-1780022254`
- `base_url=http://127.0.0.1:18095`
- 只暴露最小 `POST /v1/chat/completions`
- 要求 `Authorization: Bearer gateway-key`
- 通过真实 CRM API 创建临时逻辑分组与路由:
- `logical_group_id=p1t6-proxy-1780022254`
- `route_id=asxs-1780022254`
- `shadow_group_id=p1t6-proxy-1780022254__asxs`
- `shadow_model=gpt-5.4-asxs`
- 调用 `POST /api/routing/proxy/chat/completions`
- `request_id=req-p1t6-proxy-1780022254`
- `subject_id=conv-p1t6-proxy-1780022254`
- 返回 `route_id=asxs-1780022254`
- 返回 `shadow_host_id=proxy-shadow-host-1780022254`
- 返回 `shadow_model=gpt-5.4-asxs`
- 返回 `forward.ok=true`
- 返回 `forward.upstream_status=200`
- 返回 `forward.response.choices[0].message.content=pong-from-stub`
- stub 侧回读确认:
- 实际收到 `Authorization: Bearer gateway-key`
- 实际收到 `model=gpt-5.4-asxs`
- `GET /api/routing/logs/decisions?request_id=req-p1t6-proxy-1780022254&limit=5`
- 共回读到 `2` 条 decision log
- 最新一条 `upstream_status=200`
- 说明控制面 `resolve` 与数据面最小转发已被同一 `request_id` 串成闭环
- 2026-05-29 已完成真实宿主 managed key 供应路径补充 / `route proxy -> managed subscription -> real host chat`
- 提交:`cffe3332 feat(routing): auto-supply managed proxy keys`
- `POST /api/routing/proxy/chat/completions` 新增:
- 当请求未显式提供 `gateway_api_key` 且带 `subscription_user_id` 时,会自动调用目标宿主的 `EnsureSubscriptionAccess(...)`
- 通过 `shadow_group_id + shadow_host_id` 解析真实宿主 group再以新签发的 managed subscription key 转发到真实 host `/v1/chat/completions`
- 转发结果继续以同一 `request_id` 追加写回 `route_decision_logs`
- 本地门禁已通过:
- `gofmt -l .`
- `go vet ./...`
- `go test -cover ./internal/...`
- `go test ./tests/integration/... -count=1`
- remote43 已原位升级到 `repo HEAD = cffe3332`
- `http://127.0.0.1:18173/healthz` 返回 `ok`
- remote43 真实服务器 API 验证结果:
- 通过 remote43 本机 `host admin login` 拿到真实宿主管理员 access token
- 已确认 route-lab 真实 shadow group
- `name=GPT Shared 路由实验-subscription`
- `host_group_id=8`
- 通过真实 CRM API 创建临时 host / logical group / route
- `host_id=proxy-real-host-1780026133`
- `logical_group_id=p1t7-real-1780026133`
- `route_id=asxs-real-1780026133`
- `shadow_group_id=8`
- `shadow_model=gpt-5.4-asxs`
- 调用 `POST /api/routing/proxy/chat/completions`
- `request_id=req-p1t7-real-1780026133`
- `subscription_user_id=proxy-managed-1780026133`
- 返回 `effective_gateway_key_source=managed_subscription`
- 返回 `managed_user_id=33`
- 返回 `effective_gateway_key_fingerprint=sha256:f0fa3dec6e94348945431c9470c1faa8258c50fcee1adcb1904dac0fa42a15d6`
- 返回 `forward.ok=false`
- 返回 `forward.upstream_status=503`
- 返回 `forward.response.error.message=Service temporarily unavailable`
- `GET /api/routing/logs/decisions?request_id=req-p1t7-real-1780026133&limit=5`
- 共回读到 `2` 条 decision log
- 最新一条 `upstream_status=503`
- 当前结论:
- 插件控制面 `resolve -> shadow_host/shadow_group/shadow_model`
- managed subscription key 自动供给
- 真实宿主 `/v1/chat/completions` 转发
- 插件侧 decision log 回写
- 以上链路均已真实命中
- 但该次 upstream 仍返回 `503`,因此当前收口应表述为“真实 managed key 供应路径已打通,真实上游可用性仍待继续压实”
- 2026-05-29 已完成该 `503` 的只读根因收敛 / `managed key vs gateway vs account test vs host logs`
- 目标样本仍使用上一轮已真实命中的 managed key
- `subscription_user_id=proxy-managed-1780026133`
- `managed_key_fingerprint=sha256:f0fa3dec6e94348945431c9470c1faa8258c50fcee1adcb1904dac0fa42a15d6`
- `group_id=8`
- `group_name=GPT Shared 路由实验-subscription`
- 同一条 managed key 的宿主 gateway 对照结果:
- `GET /v1/models` 返回 `200`
- 返回模型集只包含 alias`["gpt-5.4-asxs","gpt-5.4-mini-asxs"]`
- `POST /v1/chat/completions` with `model=gpt-5.4-asxs` 返回 `503`
- `POST /v1/chat/completions` with `model=gpt-5.4` 也返回 `503`
- 同一 group 对应账号状态:
- 仅命中 `account_id=9`
- `name=gpt-asxs-route-lab-01`
- `status=active`
- `schedulable=true`
- `group_ids=[8]`
- `credentials.base_url=https://api.asxs.top/v1`
- `credentials.model_mapping={"gpt-5.4-asxs":"gpt-5.4","gpt-5.4-mini-asxs":"gpt-5.4-mini"}`
- `GET /api/v1/admin/accounts/9/models` 返回 `200`,模型集包含 `gpt-5.4-asxs` / `gpt-5.4-mini-asxs`
- `POST /api/v1/admin/accounts/9/test` with `model_id=gpt-5.4` 返回 success
- `POST /api/v1/admin/accounts/9/test` with `model_id=gpt-5.4-asxs` 也返回 success
- 同一 group 对应 channel 状态:
- `channel_id=7`
- `name=GPT Shared - asxs-subscription`
- `restrict_models=true`
- `billing_model_source=channel_mapped`
- `model_mapping.openai={"gpt-5.4-asxs":"gpt-5.4","gpt-5.4-mini-asxs":"gpt-5.4-mini"}`
- `model_pricing[0].models=["gpt-5.4-asxs","gpt-5.4-mini-asxs"]`
- 真实宿主容器日志(`weishaw/sub2api:0.1.129` / container `f2f0490d4b7f`)已明确给出拦截原因:
- `channel pricing restriction blocked request`
- `openai_chat_completions.account_select_failed`
- `error=no available accounts supporting model: gpt-5.4-asxs (channel pricing restriction)`
-`model=gpt-5.4` 也出现相同 `channel pricing restriction`
- 本轮判定:
- 这不是 managed key 失效:同一 key 的 `/v1/models` 已返回 `200`
- 这不是账号坏掉:`account_id=9` active/schedulable且 direct `account test` 成功
- 这不是 pack/provider 基础字段缺失channel 上 `model_mapping + model_pricing + restrict_models + billing_model_source=channel_mapped` 都已存在
- 当前 `503` 的直接根因是 **stock host `weishaw/sub2api:0.1.129` 在 gateway 选路阶段把该 shadow group 视为 `channel pricing restriction` 不可用**
- 工程决策:
- **优先修 route/provider 配置,不继续押注宿主兼容热修**
- 插件的 `logical_group/public_model` 仍可继续保留 alias / 路由抽象
- 但宿主 `shadow_group` 不应继续承载 alias 模型名
- 后续 shadow group 应改为只承载 canonical upstream model例如
- `gpt-5.4`
- `gpt-5.4-mini`
- alias/public model 的抽象只保留在插件 `logical_group -> route -> shadow_model` 这一层;不要再把 alias 下沉到 stock host 的 `channel_mapped + restrict_models` 组合里
- 2026-05-29 已完成上述根因的修复验证 / `canonical shadow provider -> managed subscription -> real host chat`
- 提交:
- `3c061f3d feat(routing): add canonical shadow provider pack`
- `4a38e95d fix(acceptance): separate request pack path`
- fixed checkout 已更新到 `repo HEAD = 3c061f3d`
- 真实宿主影子组改为:
- `shadow_group_id=9`
- `provider_id=gpt-asxs-shadow-lab`
- `shadow_model=gpt-5.4`
- remote43 本机经真实 CRM API 创建临时路由:
- `logical_group_id=p1t7-shadow-1780029532`
- `route_id=asxs-shadow-1780029532`
- `shadow_host_id=proxy-real-host-1780026133`
- `subscription_user_id=proxy-shadow-managed-1780029532`
- 调用 `POST /api/routing/proxy/chat/completions` 的真实结果:
- `request_id=req-p1t7-shadow-1780029532`
- `effective_gateway_key_source=managed_subscription`
- `managed_user_id=35`
- `forward.ok=true`
- `forward.upstream_status=200`
- `forward.shadow_group_id=9`
- `forward.shadow_model=gpt-5.4`
- `forward.response.content_type=text/event-stream`
- 返回内容已回读到正常 completion`content=pong`
- `GET /api/routing/logs/decisions?request_id=req-p1t7-shadow-1780029532&limit=5`
- 共回读到 `2` 条 decision log
- 最新一条 `upstream_status=200`
- 当前闭环结论:
-`503` 的根因已经固定为 **alias/public model 下沉到 stock host shadow group 后触发 `channel pricing restriction`**
- 把宿主 shadow group 收回 canonical upstream model 后,真实 managed key `/v1/chat/completions` 已恢复 `200`
- 插件控制面 `resolve`
- managed subscription key 自动供给
- 真实宿主 `/v1/chat/completions`
- 插件侧 decision log 回写
- 以上链路现已全部真实跑通
- 2026-05-26 已把“最终用户 -> 公网域名 -> OpenClaw”这一跳补进正式验证口径
- 公网根地址当前统一为 `https://sub.tksea.top`
- OpenClaw 本地 `MiniMax` 运行时故障已定位为 `pi-ai/openai-node` 未继承系统 `HTTP(S)_PROXY`,不是 allowlist 或模型名大小写问题
- 操作者本机已新增升级后自检与提醒链路:
- `~/.openclaw/bin/apply-openclaw-minimax-proxy-fix.sh`
- `~/.openclaw/bin/openclaw-minimax-post-upgrade-check.sh`
- `~/.openclaw/bin/openclaw-minimax-proxy-reminder.sh`
- `~/.openclaw/bin/install-openclaw-minimax-reminder-cron.sh`
- 当前 OpenClaw 真实验证基线已收口为:
- `tksea-gpt/gpt-5.4`PASS
- `tksea-gpt/gpt-5.4-mini`PASS
- `tksea-gpt/gpt-5.5`:当前 upstream `503`
- `tksea-minimax/MiniMax-M2.5-highspeed`PASS
- `tksea-minimax/MiniMax-M2.7-highspeed`PASS
- `deepseek-official/deepseek-chat`PASS2026-05-27 已补齐本机 auth profileone-shot 返回 `OK`
- 这部分测试用例与执行过程已沉淀到 `docs/OPENCLAW_EXTERNAL_VALIDATION.md`
- 2026-05-26 remote43 patched host 最新 provider 扩展验收:
- `openai-zhongzhuan``artifacts/real-host-acceptance/20260526_155548_remote43_openai_zhongzhuan_multi_model_rootprep/21-summary.json` 已确认 `batch_status=succeeded``provider_status_from_import=active``direct_chat_status=200`
- `minimax-53hk``artifacts/real-host-acceptance/20260526_155705_remote43_minimax53hk_multi_model_rootprep/21-summary.json` 已确认 `batch_status=succeeded``provider_status_from_import=active``direct_chat_status=200``upstream_chat_status=200`
- `deepseek-chat-official`
- 旧 artifact `artifacts/real-host-acceptance/20260526_155810_remote43_deepseek_chat_official_multi_model_rootprep/21-summary.json` 停在 `partially_succeeded/degraded`
- 2026-05-27 rerun `artifacts/real-host-acceptance/20260527_051655_remote43_deepseek-chat-official_key_import/21-summary.json` 已确认本机经 remote43 patched host 的真实数据面恢复:`direct_models_http200=true``direct_models=["deepseek-chat"]``direct_chat_status=200``latest_access_status=subscription_ready``upstream_chat_status=200`
- 剩余 `partially_succeeded/degraded` 的唯一原因已定位为宿主 account probe 返回裸 `API returned 404:`,而后续 gateway `/v1/models` + `/v1/chat/completions` 实际都已通过HEAD 现已把该 404 视为 advisory不再阻塞最终状态收敛
- 同轮还补上 remote43 scripted stack 的真实脚本缺陷:`.env.crm` 里的 SQLite DSN 含 `&_busy_timeout=5000` 时,旧版未做 shell escape`source .env.crm` 会吞掉 `SUB2API_CRM_SQLITE_DSN`,导致 remote CRM 实际退回默认 DB 路径;`scripts/deploy/remote43_patched_stack_lib.sh` 已修复并有回归测试覆盖
- latest-head relay-manager 已新增宿主 capability 自愈:
- 当第三方 OpenAI-compatible upstream 因宿主把 `openai_responses_supported` 误判成 `true` 而导致 host `/v1/chat/completions` 返回 `502 upstream_error`access closure 与后台 reconcile 会自动把相关 account 修正到 raw `/chat/completions` 路径后再重试
- 但这条控制面自愈当前仍不足以单独收敛本地 stock `weishaw/sub2api:0.1.129` + `kimi-a7m` 场景;`artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_scheme_c_stockhost_rerun/21-summary.json` 已再次证明在不改宿主源码的前提下managed `/v1/models` 虽然命中 `kimi-k2.6``/v1/chat/completions` 仍会落到 `502 upstream_error`,所以该 case 仍需宿主运行时兼容补丁或 shim
- 2026-05-23 remote43 线上验收脚本已继续收口:
- `scripts/acceptance/import_remote43_provider.sh` 现已明确拆分 `CRM_HOST_BASE``REMOTE_HOST_BASE`
- 远端 Postgres / Redis 容器已改成按目标宿主端口自动解析,不再硬编码落到 `sub2api-relaymgr-pg/redis`
- 远端 managed probe `/v1/models``/v1/chat/completions` 已改成只走 `REMOTE_HOST_BASE`
- provider status / access status / access preview 末尾查询已补 `host_id`,避免本地 CRM 有多宿主历史时被 `provider exists on multiple hosts` 截断
- 2026-05-25 已把 Hermes 里可复用的 `a7m-kimi` 正式并入主 pack
- 新增 `packs/openai-cn-pack/providers/kimi-a7m.json`
- `openai-cn-pack` 版本现为 `1.1.3`
- 当前主仓不再需要依赖历史临时 pack `openai-cn-pack-kimi-a7m`
- `kimi-a7m` provider manifest 现在也开始承载 `host_overlays` 元数据;本地已把 `sub2api v0.1.129` 的 Kimi A7M runtime overlay 说明与 `.patch` 资产纳入 `packs/openai-cn-pack/overlays/`
- 新增 `go run ./cmd/cli apply-host-overlay` 最小执行器;当前 pack 内命中的 overlay 已可直接生成 patched 宿主构建目录,不再只是 preview/import 阶段的提示信息
- 2026-05-25 已继续把路线 A 推进到运行态层面:
-`/tmp/sub2api-clean` 的 clean worktree `HEAD` 导出 stock 源码,再用 `go run ./cmd/cli apply-host-overlay --provider-id kimi-a7m --host-version 0.1.129` 生成全新 patched 源码树
- 基于该 patched 源码树重建 `localhost/sub2api:patched-overlay-20260525-clean`,并在独立 Podman 网络里启动新的 Postgres / Redis / App fresh-host
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_patched_overlay_image_freshhost_clean/21-summary.json` 已确认:`import_batch_status=succeeded``provider_status=active``latest_access_status=subscription_ready``completion_ok=true``completion_status=200`
- 同目录 `07-access-status.json` 与 patched host 运行日志已共同证明 managed subscription key 真实打通 `/v1/models``POST /v1/chat/completions`
- 注意:该 fresh-host 使用的镜像基底仍是 `weishaw/sub2api:0.1.129`,但宿主管理 API 当前自报 `host_version=0.1.126`;后续读 artifact 时应以日期和证据链为准,不要只依赖版本字段
- 2026-05-25 已把同一条 patched overlay 路线放到 remote43 做线上验收:
- remote43 侧单独拉起了 `sub2api-kimi-patched-20260525-{app,pg,redis}`patched host 暴露 `127.0.0.1:18139`
- 临时 CRM 也切到 remote43 本机运行,再通过 SSH 隧道映射回本地 `127.0.0.1:18143`,避免“本地 CRM 透过隧道探远端 host”导致的 `get host version` 超时噪音
- `artifacts/real-host-acceptance/20260525_remote43_kimi_a7m_patched_overlay_freshhost_remotecrm/21-summary.json` 已确认:`batch_status=succeeded``access_status_from_import=subscription_ready``provider_status_from_import=active``direct_models_http200=true``direct_chat_http200=true``upstream_chat_status=200`
- 同目录 `14-access-status.json` 已确认 `effective_probe_key_source=managed_subscription``completion_status=200`
- remote43 宿主日志也已落到真实 `GET /v1/models = 200``POST /v1/chat/completions = 200`,说明这条 patched overlay 路线不只在本地 fresh-host 成功,也已在远端真实环境收敛到 ready
- 这轮还顺手修掉了 `scripts/acceptance/import_remote43_provider.sh` 的一个真实脚本缺陷:查找未分配 `relay-sub-*` 用户时,`NOT EXISTS` 子查询错误引用了无 alias 的 `users.id`,在 PostgreSQL 上会报 `invalid reference to FROM-clause entry for table "users"`
- 2026-05-25 继续把这套 remote43 patched-host / remote CRM 的启动流程脚本化:
- 新增 `scripts/deploy/setup_remote43_patched_stack.sh`,把 pack 镜像、二进制上传、remote43 上的 PG/Redis/patched host/临时 CRM 拉起、以及本地 operator env / SSH 隧道提示收口为一个固定入口
- 新增 `scripts/deploy/remote43_patched_stack_lib.sh`,把远端 host env / CRM env / bootstrap script 渲染逻辑抽成可测试 helper
- `scripts/test/test_real_host_scripts.sh` 已新增对应回归,避免以后再回退到手工 `/tmp/*.sh` 拼装
- 脚本首轮真实演练暴露出一个运行态细节patched `sub2api` 二进制实际监听 `8080`,不能沿用旧临时脚本里的 `127.0.0.1:$HOST_PORT:3000` 端口映射;当前 `setup_remote43_patched_stack.sh` 已新增 `HOST_CONTAINER_PORT=8080` 默认值并完成 remote43 二次实跑验证
- 用修复后的固定脚本在 remote43 新起的 `sub2api-kimi-patched-auto2-20260525` 栈上,`kimi-a7m` 再次完成真实导入主链路:`artifacts/real-host-acceptance/20260525_remote43_kimi_a7m_patched_overlay_scripted_stack/03-import.body.json` 已确认 `batch_status=succeeded``access_status=subscription_ready``provider_status=active`,同目录 `10-models.body.json` / `12-chat.body.json` / `18-upstream-models.body.json` / `20-upstream-chat.body.txt` 也已再次证明 managed 与 upstream 双侧都回到 `HTTP 200`
- 2026-05-26 继续把 scripted stack 的末尾状态查询收口为稳定契约:`scripts/acceptance/import_remote43_provider.sh` 末尾不再只传 `host_id`,而是显式拼上 `pack_id=openai-cn-pack&host_id=<create-host 返回值>`;修复原因是 remote43 上同一个 provider 可能存在多个 pack 版本,缺 `pack_id``/api/providers/{providerID}/status` 会返回 `400 provider exists in multiple packs; pack_id is required`
- 修复后,`artifacts/real-host-acceptance/20260526_remote43_kimi_a7m_patched_overlay_scripted_stack_rerun2/13-provider-status.json``14-access-status.json``21-summary.json` 已全部自动补齐;其中 `21-summary.json` 已再次确认 `batch_status=succeeded``provider_status_from_import=active``latest_access_status=subscription_ready``direct_chat_status=200``upstream_chat_status=200`
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_from_hermes/21-summary.json` 已证明:
- Hermes 本机 `A7M_KIMI_API_KEY` 直探 upstream `/v1/models``/v1/chat/completions` 均为 `200`
- latest-head relay-manager + 本地 `weishaw/sub2api:0.1.129` fresh-host 下import-time gateway `/v1/models` 命中 `kimi-k2.6`
- 但 completion 仍落到 `502 upstream_error`,手工 managed key 再探 `/v1/chat/completions` 也返回 `503`
- 管理员 account 视角 `/api/v1/admin/accounts/1/models` 正确,但手工 managed key `/v1/models` 仍会回到 GPT 默认集合,当前应继续归类为宿主运行时 gap / drift而不是 Hermes key 失效
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_from_hermes/22-patched-host-validation.json` 已证明:
- 问题根因是宿主把 Kimi A7M 这类 custom upstream 误走到 `Responses` 路径,而不是 Hermes key 或 relay-manager pack 失效
-`/tmp/sub2api-clean` 的宿主补丁下,旁路容器 `sub2api-patched` 已恢复 `managed key /v1/models=200``managed key /v1/chat/completions=200``admin accounts/:id/test=true`
- fallback 日志与账号 `extra.openai_responses_supported=false` 持久化已同时出现,说明这条链路已经从 stock host 的 `partially_succeeded / broken` 收敛到 patched host 的 `ready`
- `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_scheme_c_stockhost_rerun/21-summary.json` 已证明:
- 仅启用 relay-manager 侧方案 C预先 `force_disable_openai_responses_api` + import/access/reconcile capability repair但保持宿主仍是未打补丁的 stock `weishaw/sub2api:0.1.129`
- import-time gateway `/v1/models` 仍能命中 `kimi-k2.6`
- 但 import-time gateway `/v1/chat/completions` 依旧返回 `502 upstream_error``access_status` 仍是 `broken``provider_status_latest` 仍是 `partially_succeeded`
- 因此当前最新真相不是“方案 C 已替代宿主补丁”,而是“方案 C 缩小了控制面误判范围,但这条 Kimi A7M / stock v0.1.129 链路仍需要宿主运行时兼容修复”
- 2026-05-25 已继续补齐方案 C控制面侧 capability repair
- `internal/host/sub2api` 新增 `ClearTempUnschedulable`
- access / reconcile 的 capability repair 现在会同时写 `extra.openai_responses_supported=false` 并清理账号 `temp_unschedulable`
- `packs/openai-cn-pack/providers/kimi-a7m.json` 新增 `force_disable_openai_responses_api=true`,导入后会在 gateway closure 前预先把该账号切到 raw `/v1/chat/completions`
- `artifacts/real-host-acceptance/20260523_144937_remote43_kimi-a7m_key_import` 已证明:
- 这轮线上 `kimi-a7m` 不再复现“错库取 key 导致统一 401”或“模型列表串成 GPT 默认集合”
- import 已返回 `gateway.status_code=200``models=["kimi-k2.6"]``has_expected_model=true`
- upstream `/models``/chat/completions` 都是 `200`
- 未改宿主的真实阻塞已收缩为 host `/v1/chat/completions` 仍返回 `503/502`,不再是插件脚本的数据面问题
- `artifacts/real-host-acceptance/20260523_145531_remote43_kimi-a7m_key_import` 说明另一类运行时噪音:
- 当本地 SSH 隧道端口存活但链路已失活时,`POST /api/hosts` 阶段会在 `get host version` 处超时
- 这类现象应优先解释为 tunnel/runtime 故障,而不是 provider 导入逻辑回退
- 官方 provider 验证矩阵当前仍保留一条非阻塞事实:
- `artifacts/real-host-acceptance/20260521_222212_remote43_minimax-m2-7-official_key_import/21-summary.json` 已证明 official MiniMax 模板链路是通的,但该验证 key 当前命中 upstream `429`
- `reconcile=drifted` 仍可能在 shared fresh-host 上出现,但当前解释是“历史残留资源噪音”,不阻塞 PRD 首版放行
- 调通细节与诊断经验已沉淀到:
- `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md`
- `docs/REAL_HOST_ARTIFACT_RETENTION.md`
- 2026-05-24 本地代码门禁修复已继续收口三类非回归点:
- `go test -race ./... -count=1` 现已再次真实通过;根因不是业务逻辑,而是多个测试包并行 `sqlite.Open()` 时与 `modernc.org/sqlite` 初始化路径的 race 噪音。当前已把 `internal/app``internal/provision``internal/reconcile` 的测试 SQLite 打开路径收口到串行 helper关闭这类假红灯同时保持 sqlite 包内测试不引入导入环。
- `DELETE /api/hosts/{hostID}` 不再默认放行危险级联删除;`hosts` repo 现在会先统计 `import_batches / managed_resources / reconcile_runs` 三类运行态依赖,有残留时返回 `409 host_in_use`,避免误删状态库里的回滚/对账真相。
- 控制面 JSON 请求体现在统一受 `MaxBytesReader` 限制;超限请求会明确返回 `413 request_too_large`,不再允许无界 body 直接进入解码路径。
## 本轮已完成
1. 宿主身份模型统一
- host 注册时持久化 `auth_type/auth_token`
- import / reconcile / rollback-provider / access 运行时链路切换为 `host_id` 主键
- provider status / resources / access status / import-batches 支持 `host_id` 查询维度
2. managed_resources 宿主维度收口
- 新增迁移 `0004_host_identity_and_managed_resources.sql`
- `managed_resources` 唯一键提升为 `(host_id, resource_type, host_resource_id)`
- 仓储与服务查询切换为 host-scoped 语义
3. reconcile run 结果按批次收口
- 新增迁移 `0006_reconcile_runs_batch_scope.sql`
- `reconcile_runs` 补充 `batch_id`batch detail 仅返回本批次 reconcile 记录
4. capability probe 收敛为无副作用探测
- 不再对真实创建接口发送空 `POST`
5. rollback-provider 风险收敛
- 改为优先按已记录批次资源 `RollbackStoredResources()` 回滚
- 缺少已记录资源时拒绝危险删除
6. 文档真相同步
- 新增 `docs/2026-05-18-PRODUCTION_REMEDIATION_TASK_BOARD.md`
- 下调 `DEPLOYMENT.md` 中未实现的 `/metrics` / 限流 / 监控承诺
7. current-code remote43 导入链路已补齐 tunnel-aware 验证能力
- `scripts/acceptance/import_remote43_provider.sh` 新增 `CRM_HOST_BASE`允许把“operator 访问 host 地址”和“CRM 进程访问 host 地址”分离
- 历史 live model-mapping 关键证据保留在:`artifacts/real-host-acceptance/20260520_222713_crm18100_live_model_mapping_validation`
8. current-code remote43 access gate 根因修正已落地
- subscription access 改为宿主侧闭环CRM 不再依赖外部预先给定的宿主普通用户 key而是按 `subscription_users` selector 在宿主创建/查找托管普通用户、登录创建托管 key、回写 allowed_groups / balance、再执行订阅分配
- account 创建请求现在同步写入 `credentials.model_mapping`,修正 `/v1/models` 读取 account model whitelist 时回退到 GPT 默认集合的问题
- 新增/更新测试覆盖:`internal/access``internal/provision``internal/host/sub2api`
9. current-code access ready 语义已提升到 completion 层
- `/v1/models` 不再单独决定 `subscription_ready/self_service_ready`
- 只有 `/v1/models` 命中 `smoke_test_model``/v1/chat/completions` smoke 成功,控制面才会把 access 状态记成 ready
- access closure / import runtime artifact / reconcile rerun payload 都会持久化 `completion_ok/completion_status/completion_type/completion_preview`
10. current-code remote43 验收脚本已补 upstream API 证据层
- `scripts/acceptance/import_remote43_provider.sh` 会直探 provider `base_url` 对应的 upstream `/models``/chat/completions`
- 新增 `21-summary.json`,用于把 completion 失败自动分流成 `host_compatibility_gap``upstream_key_quota_issue`
- 2026-05-27 已把 V2 batch-import reuse runtime 真正接到 live action
- `internal/app/batch_runtime.go` 现已接入 `InspectReuse`
- runtime reuse 查询优先命中既有 `import_run_items`,再回退到 legacy `import_batches / import_batch_items / managed_resources / providers`
- 兼容 V2 短指纹与 legacy 完整 sha256 指纹
- live run 现在可真实产出 `matched_account_state / account_resolution / provision_reused`
- 2026-05-27 继续用 `/portal/admin-batch-import.html` 做真实页面操作验证,抓到了一个 live reuse 兼容缺口并已在本地修正:
- real remote43 样本 `https://api.53hk.cn/v1 + sk-4175...d776 + host=remote43-kimi-patched-auto2-18169` 首轮返回 `TOKEN_EXPIRED`,根因是 CRM 中持久化的宿主 bearer 已过期;刷新 host auth 后item 已能恢复到 `access_status=active`
- 旧版 runtime 仍把同一条历史账号判成 `matched_account_state=none / account_resolution=created`,根因是 live runtime 的 normalized `provider_id`(如 `api-53hk-42797c06`)与 legacy pack provider id`minimax-53hk`不一致时legacy reuse fallback 只按 `provider_id` 精确匹配
- 当前已补 `base_url` fallback + `ProviderMatched` 策略信号legacy lookup 会补查相同 `base_url` 的 provider且“同 base_url + 同 key + family covered”现在可以真实收敛到 `reused/reactivated`
- 定向回归已通过:`go test ./internal/app -run 'TestBatchImportHTTP/(create run action reuses matched legacy account|create run action reuses legacy account when pack provider id differs from normalized runtime id)$' -count=1``go test ./internal/batch -run TestDecideReuse -count=1``go test ./internal/store/sqlite -run 'TestProvidersRepoListBy(BaseURL|BaseURLEmpty)$' -count=1`
- remote43 二次复验现已补证:更新后的 CRM 二进制已替换到 `18173` 控制面,真实 rerun `run_1779882868037300268` 已确认 item 从 `account_resolution=created` 收敛为 `account_resolution=reused`,并且 `provision_reused=true``access_status=active`
- 当前剩余的细节是:该 rerun item 的 `matched_account_state` 仍为 `none`说明“reuse 命中后是否补出 active/disabled/deprecated state badge”仍可继续优化但这不影响本轮要验证的 `created -> reused` 结果成立
11. patched CRM external validation 已完成
- patched CRM 实例下DeepSeek 与 MiniMax 都已验证“completion smoke 通过时能落成 succeeded/active失败时不会误记成 ready”
- `20260521_191418_remote43_minimax_key_import``20260521_201509_remote43_deepseek_key_import` 已同时证明当前 `subscription` provider 链路可真实闭环
- `20260521_210403` 已证明 latest-head `self_service` 标准 fresh-host 验收也可闭环到 `self_service_ready / fully_ready`
12. artifact 保留策略已收口
- 主目录 `artifacts/real-host-acceptance/` 当前只保留最终证据
- 历史失败/半成功/试错样本已迁到 `artifacts/real-host-acceptance-archive/`
- 分类规则见:`docs/REAL_HOST_ARTIFACT_RETENTION.md`
13. relay-manager latest-head 已收口 Kimi A7M 两段竞态
- account test 首次 `403 Forbidden` 已降级为 advisory warning只要 `/models` 已命中 `smoke_test_model`,不会再把 batch 误判为 blocking failure
- access closure 对导入后瞬时 `503 / no available accounts` 增加短暂 completion retry避免宿主异步 probe / account warm-up 窗口把真实可用链路误记成 `broken`
- `20260522_122706_local_v0129_kimi_a7m_subscription_freshhost` 已证明:在修复后的 relay-manager + patched host 组合下,`kimi-a7m / kimi-k2.6` 可落到 `batch_status=succeeded``provider_status=active``latest_access_status=subscription_ready`
14. relay-manager latest-head 已补宿主升级后的 capability 自愈
-`API returned 403: Forbidden` 这类 `/responses` 误判 advisory控制面现在会在 access closure 与 reconcile rerun 中把目标 account 的 `openai_responses_supported` 修正为 `false`,随后重试 gateway `/v1/chat/completions`
- 这样即使宿主升级或异步 probe 把 capability 标记覆写错,控制面也能在“安装后确认”与“后台持续对账”两个环节重新拉回可用状态
15. 2026-05-24 本地质量门禁补丁已完成
- 新增 repo 级删除保护:`internal/store/sqlite/hosts_repo.go` 引入 `RuntimeDependencyCountsByHostID``HostDeleteBlocker`
- 新增回归测试:`TestHostsRepoDeleteByHostIDBlocksWhenRuntimeStateExists``TestBatchImportRejectsOversizedJSONBody``TestDecodeJSON/rejects oversized request body`
- `internal/app/http_api.go` 现已统一限制 JSON request body 大小,并把 host 删除占用态映射为 `host_in_use`
- `internal/app` / `internal/provision` / `internal/reconcile` 测试 SQLite 打开路径已改为串行 helper当前 `go test -race ./... -count=1` 重新恢复为绿
## 已验证门禁
- `gofmt -l .` ✅ 空输出
- `go vet ./...`
- `go test ./...`
- `go test -race ./...`
- `go test -cover ./internal/...`
- `internal/access`: `80.5%`
- `internal/host/sub2api`: `78.1%`
- `internal/pack`: `73.9%`
- `internal/provision`: `79.4%`
- `internal/store/sqlite`: `77.9%`
- `go test ./tests/integration/... -count=1`
- `bash ./scripts/test/test_real_host_scripts.sh`
## 当前保留的最终证据
1. `artifacts/real-host-acceptance/20260520_222713_crm18100_live_model_mapping_validation`
- 证明 account `credentials.model_mapping` 与 live runtime 对齐
2. `artifacts/real-host-acceptance/20260521_142211_crm18100_deepseek_completion_split`
- 证明 host completion 失败与 upstream completion 成功可以分离
- 是 completion 分流逻辑的关键根因证据
3. `artifacts/real-host-acceptance/20260521_191418_remote43_minimax_key_import`
- MiniMax 53hk `subscription` 最终成功样本
- `21-summary.json` 已到 `batch_status=succeeded``provider_status=active`
4. `artifacts/real-host-acceptance/20260521_201509_remote43_deepseek_key_import`
- DeepSeek 2166 `subscription` 最终成功样本
- `21-summary.json` 已到 `batch_status=succeeded``provider_status=active`
5. `artifacts/real-host-acceptance/20260521_210403`
- latest-head `self_service` 标准 fresh-host 验收最终成功样本
- `05-import.json` = `succeeded/self_service_ready/active`
- `07-access-status.json` = `latest_access_status=fully_ready`
6. `artifacts/real-host-acceptance/20260521_222212_remote43_minimax-m2-7-official_key_import`
- official MiniMax 模板 live 样本
- 模板链路打通,但当前验证 key 命中 upstream `429`
7. `artifacts/real-host-acceptance/20260522_122706_local_v0129_kimi_a7m_subscription_freshhost`
- latest-head relay-manager 对 patched host `v0.1.129` 的 Kimi A7M `subscription` 最终成功样本
- `21-summary.json` 已到 `batch_status=succeeded``provider_status=active`
- `account_probe_summary` 明确记录 `probe_advisory=true``validation_status=warning`,证明 403 probe race 已被 relay-manager 正确降级
8. `artifacts/real-host-acceptance/20260523_144937_remote43_kimi-a7m_key_import`
- remote43 未改宿主 + 修正后的 latest-head 验收脚本样本
- 已证明脚本层的“错库取 key / 错地址 / 多 host 历史查询”问题被收掉
- 仍保留的真实阻塞是宿主 completion 路径 `502/503`
9. `artifacts/real-host-acceptance/20260525_local_v0129_kimi_a7m_from_hermes`
- 当前主 pack `1.1.1` 正式纳入 `kimi-a7m` 后的本地 fresh-host 验收样本
- `21-summary.json` 保留了 stock host `v0.1.129` 的原始失败快照,证明 Hermes A7M upstream key 当前在线可用,阻塞不在 key 本身
- `22-patched-host-validation.json``23-sub2api-host-patch-notes.md` 已补齐 patched host 的真实收敛证据:问题是宿主 runtime 的 `Responses -> raw chat` 兼容缺口,补丁后链路已回到 ready
## 剩余项P2 / 运营前置,不阻塞按 PRD 首版范围上线)
1. 运营前置
- 真实宿主初始化不会自动创建普通用户;上线前必须显式创建普通用户并留存可复用凭据
- `self_service` 需要普通用户 key 绑定目标标准 group且通常还需要可用余额
- `subscription` 需要 subscription 类型 group + 普通用户订阅分配 + key/group 绑定
- 若启用持续后台 reconcileSQLite 状态库将持久化最新 access probe 元数据,部署时必须按 secret 级别保护数据库文件
2. 部署与环境限制
- 标准多阶段 Dockerfile 在受限网络环境下仍不稳
- 当前推荐 `scripts/deploy/build_local_image.sh` + `Dockerfile.local`
3. official provider 验证矩阵
- official MiniMax 当前 live 样本已证明模板链路可用,但验证 key 命中 upstream `429`
- Qwen / GLM / Kimi / Step 等官方 provider 是否通过 live 验收,仍取决于后续官方 key 与 quota
## 当前最短后续路径
1. 若继续扩大 provider 覆盖面,优先按 `docs/PROVIDER_VALIDATION_MATRIX.md` 补官方 key再做 official live 验收
2. 若继续优化 shared fresh-host 信噪比,对历史残留资源做一次环境清理,降低 `reconcile=drifted` 噪音
3. 若继续产品化,优先扩大 official provider live 验收覆盖面,并基于新 create-run 入口补充真实宿主 acceptance artifact
## v2 规划Batch Auto-ImportURL + Key
**当前阶段**:✅ 已按基线计划恢复实现T1~T13 已落地create-run entry wiring 已补齐,最新全量验证通过)
**文档**`docs/2026-05-21-BATCH_AUTO_IMPORT_SPEC.md`(需求规格)
**TDD 计划**`docs/2026-05-21-BATCH_AUTO_IMPORT_TDD_PLAN.md`(实现路径,已确认开放问题)
**技术架构**`docs/2026-05-22-BATCH_AUTO_IMPORT_V2_ARCHITECTURE.md`运行态状态库、结果页、API、页面字段布局
**Migration 草案**`docs/2026-05-22-BATCH_AUTO_IMPORT_V2_MIGRATION_DRAFT.md`SQLite 新表、索引、lease/retry 字段、legacy link
**API Schema 细稿**`docs/2026-05-22-BATCH_AUTO_IMPORT_V2_API_SCHEMAS.md`run/item 响应结构、筛选参数、badge 文案、错误语义)
**本轮设计收敛**
- 已把真实验收中的三类高频问题写入 v2 方案:
- 添加模型时的模型名归一化与纠错
- 第三方国产模型的兼容能力画像(`/responses``/chat/completions`、Anthropic compatible、stream/tools
- 添加账号后的异步确认窗口(首次 `403` probe race、首次 `503 no available accounts` warm-up
- 已补充两类产品化能力到 v2
- run / item 状态持久化、retry 轨迹、控制面重启后的历史结果查看
- 批次列表页 / 批次详情页用于查看模型纠错结果、账号状态、provider warning 与最终 access 状态
- 当前 v2 的目标已从“同步导入成功”升级为“导入 + 异步确认 + 最终闭环验真”
- 已按 review 收口最关键的 4 个冲突:
- 统一 canonical contract`run_id/item_id/provider_id/run.state/confirmation_status/access_status`
- 补齐 `subscription` / `self_service` 的输入契约
- 明确 V2 唯一状态源为 `import_runs/import_run_items/import_run_item_events`
- 明确 `ConfirmationWorker + lease + next_retry_at` 的异步确认执行机制
- 其余 review 问题也已同步收口:
- capability 从 upstream 总画像升级为 transport + model profiles
- 结果页字段、状态库存储字段、retry/event trail 已统一
- run 级请求上下文已持久化到 `import_runs`,控制面重启后 validate 能继续使用 `host_id / subscription_users / subscription_days / probe_api_key`
- OpenAPI 已补齐 `/api/batch-import/runs*`legacy `/api/import-batches/*` 降级为 v1/legacy
- run/item 列表 API 已补齐 `cursor/next_cursor`run 列表 `q` 可命中 `run_id / provider_id / base_url`
- 已补充重复导入自动复用策略:按 `provider_id + api_key_fingerprint + canonical_model_family` 判断 `reused / patch_only / replace`
- 已补充同模型别名归一化契约:例如 `kimi 2.6 / kimi-2.6 / kimi-k2.6` 可归并到同一模型家族并快速复用
- 已补充多账号重复导入与弃用账号再启用策略active 账号提示“重复已启用”disabled/deprecated 账号显示原状态并走 `reactivated` 快速启用路径
- 已修正 access closure artifact 的 probe key 语义漂移:`subscription` 现在持久化 `requested_probe_api_key``effective_probe_key_source``effective_probe_key_fingerprint`,不再把外部 raw key 伪装成 `probe_api_key``probe_api_key` 仅继续用于 `self_service` 向后兼容
- 最新干净本地 fresh-host 验收 `artifacts/real-host-acceptance/20260523_local_clean_minimax_subscription_probe_semantics` 已验证:
- `subscription` closure 会正确区分 `requested_probe_api_key``managed_subscription` 实际 probe 来源
- 同一轮 raw key 直打宿主仍返回 `403 not assigned to any group`
- provider completion 仍受 MiniMax 官方 upstream `429 rate_limit_error` 影响,但这已不再会被 artifact 误读成 raw key 可用
- 同一 fresh-host 上继续补的 MiniMax `M2.5` 缩圈验证已证明:
- `artifacts/real-host-acceptance/20260523_local_clean_minimax_m25_only_probe`:单独只打 `M2.5` 时,宿主会选中真实账号并命中 upstream `429`
- `artifacts/real-host-acceptance/20260523_local_clean_minimax_m25_repeated_probe`:连续第 1 次 `M2.5``429`,后续第 2/3 次退化成 `503 Service temporarily unavailable`
- 对应宿主日志中,第一次有 `account_id=1``upstream_status=429`,后续只有 `account_select_failed error=\"no available accounts\"`
- 当前 MiniMax live 阻断要按两层解释:第一次是 upstream quota/rate-limit后续 `503` 是唯一账号进入临时不可调度窗口后的宿主侧结果
**本轮实现状态T1 ~ T13**
- [x] `internal/batch` canonical types / reuse policy / service / confirmation / validation / projection 已落地
- [x] `internal/probe` models / alias / capability / smoke completion 已落地
- [x] `internal/store/sqlite` run/item/event runtime state repo 与 migration 已落地
- [x] `/api/batch-import/runs*` 路由、projection 读取、CLI `batch-import` 命令、集成测试与设计还原审计已落地
- [x] `go test ./... -count=1`
- [x] `go test ./tests/integration/... -count=1`
- [x] `go test -cover ./internal/... -count=1`
- [x] `go vet ./...`
- [x] `gofmt -l .`
**T13 审计结论**
- `docs/2026-05-22-BATCH_AUTO_IMPORT_V2_RESTORATION_CHECKLIST.md` 已完成
- latest-head 已补齐 `internal/app/http_batch_import.go` -> `internal/app/batch_runtime.go` 的 create-run 入口 wiring
- API 与 CLI create-run 现在都会真实驱动 `BatchImportService + ConfirmationWorker + ValidationService`
- 控制面 server 启动后会自动运行 batch-import background scheduler`running` run 在重启后可继续推进
- 最新一轮验证结果保持全绿:`go test ./... -count=1``go test ./tests/integration/... -count=1``go test -cover ./internal/... -count=1``go vet ./...``gofmt -l .`
**真实 Gate**:✅ 文档、状态机、投影、测试、审计与 create-run 入口已经对齐,**V2 设计已按基线计划交付**
---
## 禁止错误结论
- ❌ 历史失败 artifact ≠ 当前 latest-head 仍失败
- ❌ capability probe 无副作用 ≠ 所有宿主版本都已真实兼容
- ❌ rollback-provider 已改安全路径 ≠ 历史脏资源自动消失
-`HTTP 200` ≠ 宿主初始化会自动准备普通用户/订阅/余额;这些仍是显式运营前置