Document repeated host acceptance failures
This commit is contained in:
@@ -86,8 +86,8 @@
|
||||
- `internal/access`: `80.5%`
|
||||
- `internal/host/sub2api`: `78.1%`
|
||||
- `internal/pack`: `73.9%`
|
||||
- `internal/provision`: `76.3%`
|
||||
- `internal/store/sqlite`: `61.4%`
|
||||
- `internal/provision`: `79.4%`
|
||||
- `internal/store/sqlite`: `77.9%`
|
||||
- `go test ./tests/integration/... -count=1` ✅
|
||||
- `bash ./scripts/test_real_host_scripts.sh` ✅
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
| Integration | ✅ PASS | `go test ./tests/integration/... -count=1` |
|
||||
| Static Analysis | ✅ PASS | `go vet ./...` |
|
||||
| Formatting | ✅ PASS | `gofmt -l .` 空输出 |
|
||||
| Core Coverage | ✅ PASS | `go test -cover ./internal/...`;access 80.5%, host/sub2api 78.1%, pack 73.9%, provision 76.3%(sqlite 61.4% 仅作信息项) |
|
||||
| Core Coverage | ✅ PASS | `go test -cover ./internal/...`;access 84.8%, host/sub2api 78.5%, pack 73.9%, provision 79.4%(sqlite 77.9% 仅作信息项) |
|
||||
| 控制面 API 计划缺口 | ✅ CLOSED | 已补 `/api/hosts/{hostID}/probe`、`/api/providers/{providerID}/import-batches`、`/api/import-batches/{batchID}/rollback` |
|
||||
| 状态一致性 | ✅ CLOSED | rollback-by-batch 回写 `rolled_back/failed`;assign-subscriptions 同步 `import_batches.access_status` |
|
||||
| provider 消歧 | ✅ CLOSED | pack 维度精确解析,避免同名 provider 跨 pack 误命中 |
|
||||
@@ -78,6 +78,11 @@
|
||||
- patched fresh-host rerun 已证明“当前 completion-gated 语义已在 fresh host 上生效”
|
||||
- 当前 `subscription` 与 `self_service` 主链路都已在真实 fresh-host 上验收通过,达到 PRD 首版放行要求
|
||||
|
||||
6. 2026-05-23 追加质量收口
|
||||
- `internal/app`、`internal/provision`、`internal/reconcile`、`internal/store/sqlite` 的热点回归分支已继续补测
|
||||
- 最新覆盖率已提升到:`internal/provision=79.4%`、`internal/store/sqlite=77.9%`
|
||||
- Kimi A7M capability 误判、stale CRM 进程/`host_base_url` 串台、`models-only ready` 假阳性,这三类反复出错点的最终根因与排查顺序已写入 `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md`
|
||||
|
||||
## 剩余项(P2 / 运营前置,不阻塞按 PRD 首版范围上线)
|
||||
|
||||
### 运营前置
|
||||
|
||||
@@ -230,6 +230,104 @@
|
||||
- CRM 的 gateway probe 是否错误使用了 `x-api-key` 而不是 `Authorization: Bearer`
|
||||
- 当前 online CRM 进程是否真的已经切到包含该修复的新二进制
|
||||
|
||||
## 2026-05-22 ~ 2026-05-23 多次反复出错后的最终收敛记录
|
||||
|
||||
这一节专门记录“不是一次性修掉,而是经过多轮误判、切环境、换宿主版本、补控制面自愈后才真正收口”的问题。后续再遇到相似现象,优先回看这里,不要重复从零推理。
|
||||
|
||||
### 1. Kimi A7M `/v1/models` 正常但 `/v1/chat/completions` 长期失败
|
||||
|
||||
最终确认这不是单一问题,而是两层问题叠加。
|
||||
|
||||
1. 第一层是宿主把 OpenAI-compatible API key account 默认判成可走 `/responses`
|
||||
- 表象:
|
||||
- upstream 直打 `/v1/chat/completions` = `200`
|
||||
- 经宿主转发后 `host /v1/chat/completions` = `502`
|
||||
- body 常见为 `Upstream access forbidden` 或 `service temporarily unavailable`
|
||||
- 真正根因:
|
||||
- 宿主把 `openai_responses_supported=true` 错写到 account capability
|
||||
- managed chat 请求被错误走成 Responses 兼容分支
|
||||
- 对 A7M 这类只稳定支持 raw chat-completions 的链路,会直接被上游拒绝
|
||||
|
||||
2. 第二层是宿主升级后 capability 误判会再次出现
|
||||
- 即使手工在宿主里把 `openai_responses_supported=false` 调对,后续异步 probe 或宿主升级仍可能覆写回错误值
|
||||
- 所以“只修宿主代码”不够稳,控制面必须有自愈
|
||||
|
||||
3. CRM 侧最终收口策略
|
||||
- access closure 首次确认时,如果看到:
|
||||
- account probe = `API returned 403: Forbidden`
|
||||
- host completion = `502 upstream_error`
|
||||
- body 含 `service temporarily unavailable` 或 `no available accounts`
|
||||
- CRM 会自动把对应 account 的 `openai_responses_supported=false` 写回宿主,然后立即重试一次 completion
|
||||
- 后台 reconcile 也复用同一逻辑,所以宿主升级后再次漂移,下一轮 confirm/reconcile 还能拉回正确状态
|
||||
|
||||
4. 已固化的回归层
|
||||
- `internal/access`:capability repair 判定与重试
|
||||
- `internal/provision`:首次安装后确认自愈
|
||||
- `internal/reconcile`:宿主升级后的后台持续自愈
|
||||
- 因此以后若再看到 “A7M `/models` 200 但 completion 502”,应先确认自愈逻辑是否触发,而不是先怀疑 pack 或 subscription 链路
|
||||
|
||||
### 2. `host_base_url` / stale CRM 进程 / fresh-host 容器串台 导致的假回归
|
||||
|
||||
这类问题在这轮里反复出现多次,而且表面上都像“代码修了但线上还是老问题”,实际是环境指向错了。
|
||||
|
||||
1. stale CRM 进程
|
||||
- 典型表象:
|
||||
- 仓库代码已经包含修复,但 live artifact 仍表现为旧逻辑
|
||||
- 最典型的是 “MiniMax channel 有 `model_mapping` 但 `model_pricing=[]`”
|
||||
- 真相:
|
||||
- 旧 artifact 反映的是旧进程创建的 channel
|
||||
- 新代码只有在新进程真的启动并重新跑过 import/update 后,宿主数据才会被纠偏
|
||||
|
||||
2. `host_base_url` 用成 operator 侧概念地址
|
||||
- 典型表象:
|
||||
- `stat pack path ... no such file or directory`
|
||||
- host 注册/导入看似失败,但实际上是 CRM 进程所在机器根本读不到该路径或访问不到该宿主地址
|
||||
- 真相:
|
||||
- `host_base_url` 和 `PACK_PATH` 都必须以 CRM 进程本机视角解释
|
||||
- 不能混用 operator 机器、remote43 主机、fresh-host 容器内部这三种地址空间
|
||||
|
||||
3. fresh-host Postgres/Redis 指到了旧容器
|
||||
- 典型表象:
|
||||
- managed user / subscription / key 状态看起来全部缺失
|
||||
- 或者 reconcile / group state 结果和当前验收宿主不一致
|
||||
- 真相:
|
||||
- harness 查的不是目标 fresh-host 的数据库,而是旧 relaymgr 或别的 fresh-host
|
||||
|
||||
4. 最终经验
|
||||
- 在判定“当前代码是否失效”前,必须先确认:
|
||||
1) CRM 在线进程启动时间
|
||||
2) CRM 实际提交版本
|
||||
3) `PACK_PATH` 是否对 CRM 本机可读
|
||||
4) `CRM_HOST_BASE` 是否真的是 CRM 到宿主的地址
|
||||
5) Postgres/Redis 容器是否属于目标 fresh-host
|
||||
|
||||
### 3. `models-only ready` 假阳性已经关闭,后续不能再按旧经验验收
|
||||
|
||||
这条误判在前几轮里也反复出现,必须明确写死。
|
||||
|
||||
1. 旧误判方式
|
||||
- 只要宿主 `/v1/models` 命中 `smoke_test_model`,就把 access 状态记成 ready
|
||||
- 这会把“普通用户 key / group / subscription 前置已完成”与“真实 completion 可用”混为一谈
|
||||
|
||||
2. 真实问题
|
||||
- `/v1/models = 200` 只能证明访问链路和宿主前置成立
|
||||
- 不证明上游 completion 一定可用
|
||||
- 在 DeepSeek、Kimi、MiniMax 的真实验收里,这一点都出现过
|
||||
|
||||
3. 当前收口后的真相
|
||||
- access ready 必须同时满足:
|
||||
- `/v1/models` 命中 `smoke_test_model`
|
||||
- 最小 `POST /v1/chat/completions` smoke 成功
|
||||
- access closure、import runtime artifact、reconcile rerun payload 现在都会持久化 completion 结果
|
||||
- 因此后续任何人看到 `latest_access_status=ready`,都可以默认它已经经过 completion 层验证
|
||||
|
||||
4. 回归建议
|
||||
- 若以后再改宿主 gateway 适配或第三方 provider capability,不要只验 `/v1/models`
|
||||
- 至少要一起看:
|
||||
- host `/v1/models`
|
||||
- host `/v1/chat/completions`
|
||||
- access closure details 里的 `completion_*` 字段
|
||||
|
||||
### 进一步缩圈:DeepSeek `chat/completions` 当前更像宿主兼容层问题,而不是 key 失效
|
||||
|
||||
2026-05-21 新增的直接证据链:
|
||||
|
||||
@@ -2,6 +2,7 @@ package provision
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sub2api-cn-relay-manager/internal/store/sqlite"
|
||||
@@ -284,7 +285,7 @@ func TestProviderStatusServiceFailsOnInvalidReconcileSummary(t *testing.T) {
|
||||
}
|
||||
|
||||
_, err = NewProviderStatusService(store).GetStatus(ctx, ProviderQuery{ProviderID: "deepseek", HostID: "host-1"})
|
||||
if err == nil || err.Error() == "" {
|
||||
t.Fatal("GetStatus() error = nil, want decode reconcile summary failure")
|
||||
if err == nil || !strings.Contains(err.Error(), "decode reconcile summary") {
|
||||
t.Fatalf("GetStatus() error = %v, want decode reconcile summary failure", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user