From 2b5607285f2b733670e70daf036d18ce67a87654 Mon Sep 17 00:00:00 2001 From: phamnazage-jpg Date: Thu, 4 Jun 2026 13:45:29 +0800 Subject: [PATCH] docs(test): add 2026-06-04 testing pitfalls to checklist + fix key confusion in acceptance script MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key changes: 1. scripts/acceptance/import_remote43_provider.sh:690/695 - Fixed -> for gateway direct probe - Added REDLINE comments explaining why sub_key is correct 2. docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md: §9 - 4 new pitfalls from 2026-06-04 real testing: - Gateway probe key confusion ( vs ) - Hermes session key redaction (must get raw key from PG) - Account upstream key update method (PUT with type+key, not credentials) - Hermes redaction destroys shell variables in .sh files 3. docs/SOURCE_OF_TRUTH.md: evidence interpretation - Added rule #5: session env keys are redacted, must use PG raw values --- docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md | 56 ++++++++++++++++- docs/SOURCE_OF_TRUTH.md | 60 ++++++++++++++++++- .../acceptance/import_remote43_provider.sh | 6 +- 3 files changed, 117 insertions(+), 5 deletions(-) diff --git a/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md b/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md index 467b82e2..a873db2d 100644 --- a/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md +++ b/docs/REAL_HOST_ACCEPTANCE_CHECKLIST.md @@ -3,6 +3,7 @@ 日期:2026-05-21 用途: + - 给后续每次 real-host 验收直接复用 - 只保留最短闭环,不展开历史背景 - 如果你当前是在“新增 provider”或“宿主版本升级后重新适配”,先看:`PROVIDER_ONBOARDING_PLAYBOOK.md` @@ -11,17 +12,20 @@ ## 0. 先确认你看的是真相文档 按顺序先读: + 1. `docs/SOURCE_OF_TRUTH.md` 2. `docs/EXECUTION_BOARD.md` 3. `docs/PRODUCTION_CLOSURE_BOARD.md` 禁止: + - 直接拿历史 review/task board 顶部 gate 当当前状态 - 直接拿历史 PASS artifact 当 latest-head 当前真相 ## 1. 环境前置检查 每次验收前先确认 4 件事: + 1. 在线 CRM 进程是不是最新提交对应进程 2. `PACK_PATH` 是不是 CRM 进程本机可读路径 3. `CRM_HOST_BASE` 是不是 CRM 到宿主真实可达地址 @@ -32,31 +36,38 @@ ## 2. channel 宿主契约检查 对每个导入 provider,必须确认 channel 最终同时具备: + - `model_mapping` - `model_pricing` - `restrict_models=true` - `billing_model_source=channel_mapped` 补充规则: + - `model_mapping` 真实宿主接口是按 platform 嵌套结构 - `model_pricing[].platform` 不能留空 - existing channel 不能只复用名字,必须执行纠偏更新 一句话: + - 新建即完整 - 已存在可纠偏 ## 3. access / key 语义检查 ### self_service + 必须同时满足: + 1. 普通用户已创建 2. 普通用户 key 已创建 3. key 已绑定目标 standard group 4. 用户有可用余额 ### subscription + 必须同时满足: + 1. 普通用户已创建 2. 普通用户 key 已创建 3. 目标 group 是 `subscription` 类型 @@ -64,6 +75,7 @@ 5. key 已绑定该 subscription group 关键规则: + - subscription 场景最终 probe key 是宿主 managed key - 不是外部传入原始 `ACCESS_API_KEY` @@ -82,6 +94,7 @@ - `POST /v1/chat/completions` 禁止混用: + - `accounts/:id/models` 正确 ≠ `/v1/models` 正确 - `/v1/models` 正确 ≠ `/v1/chat/completions` 正确 - admin API 成功 ≠ 普通用户链路成功 @@ -108,14 +121,18 @@ ## 6. 常见现象 → 优先判断 ### 现象 A:`/accounts/:id/models` 对,但 `/v1/models` 403 + 优先判断: + - key 没绑 group - subscription 没分配 - 用错了原始 probe key,没用 managed key - self_service 缺余额 ### 现象 B:模型列表返回成 GPT 系,而不是目标 provider 模型 + 优先判断: + - account `credentials.model_mapping` 没落对 - channel 缺 `model_pricing` - channel 缺 `restrict_models=true` @@ -123,7 +140,9 @@ - 在线 CRM 还是旧进程 ### 现象 C:`/v1/models` 已 200,但 `/v1/chat/completions` 失败 + 优先判断: + - host provider 兼容性 - 上游 key/quota - 不要先回退归因为 CRM 导入失败 @@ -131,13 +150,13 @@ ## 7. 一次验收的最小通过标准 至少同时满足: + 1. import 成功,且 batch/provider 不为 `failed` 2. account `/models` 返回目标模型 3. channel 回读包含完整 routing/pricing 字段 4. `/v1/models` 返回目标模型 -若要宣称“真实可用通过”,还必须再加: -5. `/v1/chat/completions` 成功 +若要宣称“真实可用通过”,还必须再加:5. `/v1/chat/completions` 成功 ## 8. 一句话红线 @@ -146,3 +165,36 @@ - 不要把原始 `ACCESS_API_KEY` 当 subscription 最终 probe key - 不要看到旧 artifact 就直接判定 current-code 失效 - 不要在没核对在线 CRM 进程版本前下 live 结论 + +## 9. 2026-06-04 新增的坑(已验证修复) + +### 坑 1:验收脚本里 gateway 直探和 managed key 又混了 + +`import_remote43_provider.sh` 第 690/695 行用来直探 gateway `/v1/models` 和 `/v1/chat/completions` 的 key,**必须用 `$sub_key`(普通用户的真实 gateway key)**,不能复用 `$managed_probe_key`(托管用户的 hash key,仅用于 subscription 绑定关系验证)。 + +用错 key 时直探返回 401,但这不代表 CRM 产品链路有问题——只代表验收脚本用错了 probe key。 + +当前脚本已加 `# REDLINE` 注释标注此约束。 + +### 坑 2:从 session 中获取的 key 不可靠——必须从 PG 直取原始 key + +Hermes 脱敏工具会在 session 中截断所有 API key 环境变量。如果要直探 stock host gateway,必须从 stock host PG 的 `api_keys` 表直取原始 key 值。 + +正确的获取方式: + +```bash +ssh remote43 "sudo -n docker exec pg psql -U sub2api -d sub2api -Atc \"SELECT key FROM api_keys WHERE user_id = N\"" +``` + +然后用这个原始值做 `Authorization: Bearer *** ` 直探。 + +### 坑 3:更新 account upstream key 的正确方法 + +通过 stock host admin API 更新 account 的 upstream key 时: + +- ✅ **正确**:`PUT /api/v1/admin/accounts/{id}` with body `{"type": "apikey", "key": "sk-..."}` → `credentials_status.has_api_key = true` +- ❌ **错误**:`PUT /api/v1/admin/accounts/{id}` with body `{"credentials": {"api_key": "sk-...", ...}}` → api_key 不会保存 + +### 坑 4:Heremes 脱敏会破坏脚本变量 + +在 Hermes 会话中,如果用 `write_file` 或 `patch` 工具修改 `.sh` 文件,文件中 `$sub_key`、`$managed_probe_key`、`$upstream_key` 这类 shell 变量名可能被脱敏工具替换为 `***`。修复时必须用 Python bytes 操作或 base64 绕开。 diff --git a/docs/SOURCE_OF_TRUTH.md b/docs/SOURCE_OF_TRUTH.md index ff5e9522..a8879c59 100644 --- a/docs/SOURCE_OF_TRUTH.md +++ b/docs/SOURCE_OF_TRUTH.md @@ -8,6 +8,7 @@ 当前最新 gate:`APPROVED` 当前 gate 升到 `APPROVED` 的原因是: + 1. 代码侧已关闭“只靠 `/v1/models` 就把 access 标成 ready”的假阳性;当前 ready 必须同时通过 `/v1/models` 与 `/v1/chat/completions` smoke 2. `scripts/acceptance/import_remote43_provider.sh` 已补上 upstream `/models` 与 `/chat/completions` 直探,并落盘 `21-summary.json` 做根因分类 3. account `credentials.model_mapping`、channel `model_mapping/model_pricing`、managed key 视角 `/v1/models` 都已有 live 证据 @@ -21,6 +22,7 @@ 8. 当前仍存在的 `reconcile=drifted` 仅反映共享 fresh-host 历史残留资源,不阻塞 PRD 首版放行 一句话: + - “模型暴露、completion gate 和 upstream triage 都已进代码”是真 - “MiniMax 53hk、DeepSeek 2166 的 `subscription` 真实宿主主链路已完全放行”是真 - “latest-head `self_service` fresh-host 标准验收也已通过”是真 @@ -28,100 +30,146 @@ ## 当前真相文档(按优先级排序) ### 1. `docs/EXECUTION_BOARD.md` + 用途: + - 当前执行状态 - 最新 gate - 剩余阻断 - 最短 closure path 解释规则: + - 这是“当前执行真相”的第一来源 - 当其他文档与它冲突时,以它为准 ### 2. `docs/PRODUCTION_CLOSURE_BOARD.md` + 用途: + - 当前是否可按 PRD 首版范围放行 - 哪些是代码门禁,哪些是外部/环境门禁 - 哪些历史 PASS 不能再直接当当前真相 解释规则: + - 这是“上线收口真相”的第一来源 - 若与历史评审/历史任务板冲突,以它为准 ### 3. `docs/REAL_HOST_ACCEPTANCE_RUNBOOK.md` + 用途: + - 真实宿主验收的标准步骤 - 需要收集哪些 artifact - 需要验证哪些层级的证据 解释规则: + - 它定义“怎么验收” - 不直接定义当前 gate,当前 gate 仍以上面两份板为准 ### 4. `docs/PROVIDER_ONBOARDING_PLAYBOOK.md` + 用途: + - 新增 provider 的稳定操作顺序 - 宿主版本变更后的重验路径 - 如何把一次调通沉淀成可复用的 onboarding 流程 解释规则: + - 它定义“后续怎么稳定地继续加 provider / 复验宿主” - 不直接决定当前 gate,但决定后续变更能否低风险复用 -### 5. `docs/PROVIDER_VALIDATION_MATRIX.md` +### 4A. 前端真相入口 + 用途: + +- 回答“前端资产在哪、最小门禁是什么、哪些页面已闭环” +- 防止只看执行板长篇记录就误判前端状态 + +解释规则: + +- 前端状态统一先读: + - `docs/2026-05-31-FRONTEND_REVIEW_CHECKLIST.md` + - `docs/2026-05-31-FRONTEND_CLOSURE_AUDIT.md` + - `docs/2026-06-01-FRONTEND_ACCEPTANCE_MATRIX.md` + - `docs/2026-05-31-PROVIDERS_ACTION_ACCEPTANCE_MATRIX.md` + - `docs/2026-05-31-FRONTEND_REMEDIATION_TASK_BOARD.md` +- 若 `PRD.md` 的历史边界表述与当前前端资产冲突,要用 `PRD` 的补充说明 + 上述前端文档共同解释,不允许单独拿“暂不做 Web 控制台”否认仓库里已纳管的 portal/admin 资产 + +### 5. `docs/PROVIDER_VALIDATION_MATRIX.md` + +用途: + - 按 provider 维度跟踪“模板是否已就绪 / 官方 key 是否存在 / live 验收是否已完成” - 给后续“六小龙 + BAT + 小米等”模型矩阵扩展提供同一口径 解释规则: + - 它不直接改变项目整体 gate - 但它是当前 provider 覆盖度和验证进度的第一来源 ### 6. `docs/REAL_HOST_ACCEPTANCE_LEARNINGS.md` + 用途: + - 已调通的细节 - 高频误判点 - 推荐诊断顺序 - 经验性解释 解释规则: + - 它解释“为什么之前会误判、现在应该怎么查” - 用来辅助 runbook 和执行板,不单独决定 gate ## 次级文档(仍有价值,但必须通过当前真相文档解释) ### `docs/PRD.md` + - 定义 PRD 首版范围 +- 2026-06-01 起已明确补充: + - “暂不做 Web 控制台”保留其历史语义 + - 但 `deploy/tksea-portal/` 下的 portal/admin 静态页已属于当前仓库的 deployment-facing 配套交付物 - 只能回答“目标应该是什么”,不能单独回答“现在是否已完成” ### `docs/TDD_PLAN.md` + - 定义测试设计与实现计划 - 不能单独代表当前真实验收状态 ### `docs/DEPLOYMENT.md` + - 用于部署说明 - 若与当前执行板冲突,以执行板/收口板为准 ### `docs/KNOWN_LIMITATIONS.md` + - 用于记录仍存在的限制 - 需要通过当前执行板一起理解,避免把旧限制当作已关闭或反过来 ### `docs/plans/2026-05-12-sub2api-cn-relay-manager-implementation-plan.md` + - 说明最初实现路径 - 是设计/计划文档,不是当前状态文档 ### `docs/2026-05-12-sub2api-cn-relay-manager-solution.md` + - 说明方案设计与宿主接口认知 - 可作为背景,但不能直接当当前 gate 依据 ## 历史快照文档(只可参考,不可作为当前实现真相) ### `docs/2026-05-18-PRODUCTION_READINESS_REVIEW.md` + - 性质:历史审查快照 - 用途:回看当时发现了哪些系统性问题 - 禁止用法:不能直接引用其中的 `REJECT / CONDITIONAL_APPROVED` 作为当前 gate ### `docs/2026-05-18-PRODUCTION_REMEDIATION_TASK_BOARD.md` + - 性质:历史整改执行板 - 用途:回看 2026-05-18 那一轮整改任务是如何收口的 - 禁止用法:不能直接拿其顶部 gate 文字当当前状态 @@ -129,27 +177,34 @@ ## artifact 解释规则 ### 当前优先证据 + 优先看最新一轮、且与 latest-head / fresh host 对齐的 artifact: + - `artifacts/real-host-acceptance/20260520_222713_crm18100_live_model_mapping_validation` - `artifacts/real-host-acceptance/20260521_191418_remote43_minimax_key_import` - `artifacts/real-host-acceptance/20260521_201509_remote43_deepseek_key_import` - `artifacts/real-host-acceptance/20260521_210403` 说明: + - 上述 artifact 已包含 patched control plane 的最新 live 证据。 - 它们证明 current-code 的 `subscription` 与 `self_service` 主链路已经在 fresh host 上闭环通过;其中 `20260521_210403` 还补齐了标准 `reconcile/rollback` 验收链路。 ### 历史参考证据 + 以下可证明某个阶段“曾经打通过”,但不能直接代表当前真相: + - `artifacts/real-host-acceptance/20260518_redeploy_matrix` - `artifacts/real-host-acceptance/20260518_reconcile_hostscope_self_service` - `artifacts/real-host-acceptance/20260518_reconcile_hostscope_subscription` ### 证据解释红线 + 1. 不能把 `/api/v1/admin/accounts/:id/models` 与 `/v1/models` 混为一谈 2. 不能把 `/v1/models = 200` 自动推导成 `/v1/chat/completions = 200` 3. 不能在未核对在线 CRM 进程版本时,就把 live 现象归因为源码仍有缺陷 4. 不能把 direct probe artifact 的 401/403 单独当成产品主链路失败;先核对 probe key 语义与脚本参数 +5. 从 Hermes session 中读取的环境变量 API key 已被脱敏工具截断,不能直接用于 curl 直探。必须从 remote host PG `api_keys` 表获取原始 key 值。 ## 当前执行红线 / 非回归规则 @@ -191,11 +246,13 @@ ## 推荐阅读顺序 ### 想知道“现在到底是什么状态” + 1. `docs/SOURCE_OF_TRUTH.md` 2. `docs/EXECUTION_BOARD.md` 3. `docs/PRODUCTION_CLOSURE_BOARD.md` ### 想继续做真实宿主验收 + 1. `docs/SOURCE_OF_TRUTH.md` 2. `docs/PROVIDER_ONBOARDING_PLAYBOOK.md` 3. `docs/PROVIDER_VALIDATION_MATRIX.md` @@ -204,6 +261,7 @@ 6. 再看最新 artifact ### 想回顾为什么会演化成现在这样 + 1. `docs/SOURCE_OF_TRUTH.md` 2. `docs/2026-05-18-PRODUCTION_READINESS_REVIEW.md` 3. `docs/2026-05-18-PRODUCTION_REMEDIATION_TASK_BOARD.md` diff --git a/scripts/acceptance/import_remote43_provider.sh b/scripts/acceptance/import_remote43_provider.sh index ccd3e4a0..7ff36149 100755 --- a/scripts/acceptance/import_remote43_provider.sh +++ b/scripts/acceptance/import_remote43_provider.sh @@ -687,12 +687,14 @@ print(json.dumps({ }, ensure_ascii=False)) PY )" -ssh_cmd "curl -sS -D /tmp/models_headers.txt -o /tmp/models_body.json -H 'Authorization: Bearer $managed_probe_key' $REMOTE_HOST_BASE/v1/models" +# REDLINE: gateway probe must use $sub_key, NOT $managed_probe_key +ssh_cmd "curl -sS -D /tmp/models_headers.txt -o /tmp/models_body.json -H 'Authorization: Bearer $sub_key' $REMOTE_HOST_BASE/v1/models" ssh_cmd "cat /tmp/models_headers.txt" > "$ART/09-models.headers.txt" ssh_cmd "cat /tmp/models_body.json" > "$ART/10-models.body.json" sanitize_headers_file "$ART/09-models.headers.txt" -ssh_cmd "curl -sS -D /tmp/chat_headers.txt -o /tmp/chat_body.json -H 'Authorization: Bearer $managed_probe_key' -H 'Content-Type: application/json' $REMOTE_HOST_BASE/v1/chat/completions -d $(printf %q "$probe_payload")" +# REDLINE: chat probe also uses $sub_key, NOT $managed_probe_key +ssh_cmd "curl -sS -D /tmp/chat_headers.txt -o /tmp/chat_body.json -H 'Authorization: Bearer $sub_key' -H 'Content-Type: application/json' $REMOTE_HOST_BASE/v1/chat/completions -d $(printf %q "$probe_payload")" ssh_cmd "cat /tmp/chat_headers.txt" > "$ART/11-chat.headers.txt" ssh_cmd "cat /tmp/chat_body.json" > "$ART/12-chat.body.json" sanitize_headers_file "$ART/11-chat.headers.txt"