摘要
PRD V1.4 §5.4 FR-3.5-02 验收要点(原文):「译文抽检合格率与失败重试成功率跟踪(基线立项定);AI 服务接入启动与扩量严格按 C2 门槛执行(接入时点见 DEC-1)。」 需求本体:商品、商品分类等业务内容在发布后自动进行中日英三语 AI 翻译,前台按用户语言展示对应译文——商家只发一种语言,全球买家均可以母语浏览商品信息。 本条底座是三语检索(FR-3.3-02)与新零售跨语运营的底座;当前翻译范围限商品与商品分类的结构化内容(标题、描述等)。
两态口径(不得混同,承 PRD V1.4):①底座已建成——翻译参数配置页(密钥加密存储)、失败自动重试队列、状态提示、自动化测试均在;
②AI 服务未接入——生产未配置任何 AI 服务(2026-10-21 部署权威口径,风险 C1 已更正为「已确认未接入」)。
代码层基线:平台唯一真实 AI 通路为 backend/src/modules/translation/client.ts(586 行,OpenAI 兼容客户端,SSRF 三层防护 + 60s 超时),
dev 环境已接内网 LLM(TRANSLATION_AI_BASE_URL=http://192.168.66.225:3030/v1),生产 .env.template 为占位值。
因此本 TDD 基线=增强现有翻译管道,非从零新建。
全文以两段式组织(强制拆分):管道增强段(P-①~⑤,无门控、零外部依赖,pilot 波立即可开工)与 服务接入段(生产接入步骤链、category 预检对齐、专属单测、抽检挂接点,DEC-1/C2 门控)。 接入顺序不可颠倒:先关 C2 合规条款(AI-3.9-01 专项条款发布)→ DEC-1 定接入时点 → DEC-2 定扩量节奏。
ai_call_log 从零新建(usage 审计,闭环 NFR-3.9-03);translation_config 仅加 1 列;translation_job 零变更关键概念
isAIConfigured 入口预检(探测自身异常时返回 true 保 C6 永不阻断);防线二:retry worker 内 markSkippedNoConfig 兜底终态化——NFR-3.8-01 强制范式。claimed_at 原子占位 + 指数退避 2^retry_count 分钟 + MAX_TRANSLATION_RETRY=5 + 全局并发信号量 3 + 5 分钟 cron 轮询。enc:v1:+base64(iv‖tag‖ciphertext)。现状密文无独立版本标记层、解密对无前缀旧明文透明返回——轮换密钥即存量密文不可解(P-③ 以 enc:v2: 双密钥「读旧写新」根除)。usage 字段从不读取(rg 0 命中)——即 NFR-3.9-03「调用量与成本统计尚无」在代码层的丢弃点。{source}/{target} 占位替换+空模板回退内置指令(client.ts:295-306 复核精确)。场景化 prompt 路由与术语注入属 AI-3.5-03 范围,本卡仅注明现状。方案设计
两段式总览(承两态口径的强制拆分)
| 分段 | 门控 | 内容 | 开工条件 |
|---|---|---|---|
| 管道增强段 | 无门控 零外部依赖 | P-① 白名单参数化 / P-② 状态查询 API / P-③ 加密强制+密钥轮换 / P-④ usage 审计采集 / P-⑤ 吞吐背压指标位 | 立即可开工(pilot 波) |
| 服务接入段 | DEC-1 + C2 门控 | 生产接入步骤链、category 预检对齐、run-translate-category 专属单测、译文抽检挂接点 | 前置决策点 DEC-1/C2 关闭后方可开工(顺序不可颠倒) |
设计红线(AGENTS.md)
- Workflow 承载业务逻辑——本卡全部数据变更走 Workflow/step(加密守卫在 step 内实现,路由层不改);
- 不修改上游源码——
@medusajs/translation与@mercurjs/*仅经官方扩展点;本卡对 client.ts/config/job 均为本地模块内扩展,不触碰上游包; - 后端是单一数据源——队列状态、配置、审计均以后端为准,admin 仅消费;
- 动态 SQL 参数化绑定——现有 q ILIKE 已示范参数化,本卡无新增裸 SQL。
管道增强段(无门控,立即可开工)
P-① 字段白名单参数化(缺口②)
现状:可翻译字段白名单写死在 step 工具内(collect-product-input.ts,卡内 :16-44 / 白名单判定实际 :36-60)。
| 层级 | 现状硬编码白名单 | 回写条件 |
|---|---|---|
| 商品级字段 | title / subtitle / description | 值非空即纳入 |
| option 级字段 | options[].title | option 有 id(拿不到 id 无法回写译文,降级跳过不 fail) |
| option value 级字段 | options[].values[].value | value 有 id |
| 分类(另一工具内) | name / description(run-translate-category.ts buildFields :87-93,description 空则丢弃) | 值非空 |
| 候选 | 说明 | 评估 |
|---|---|---|
A. translation_config 表新增 translatable_fields JSONB 列 |
与现有「DB 优先+env 回退」配置模型(service.ts:22-32)同构;admin 现有配置页可直接扩展 | ✅ 选定 admin 可视化维护零新增界面骨架;单行 key="default" 结构天然承载;沿用 maskApiKey 同页管理 |
| B. 常量模块+环境变量 | 零 migration,但改白名单需发版+env 泛滥 | ❌ 运营不可自助,与「配置页可视化」产品方向相悖 |
translation_config.translatable_fields:jsonb NULL。NULL=回退内置默认集(向后兼容,存量行为零变化);- JSON schema(zod 校验,键白名单闭合——只允许代码已知且能回写译文的字段,防止配置进未支持字段后 AI 产出无处落):
{
"product": {
"fields": ["title", "subtitle", "description"],
"options": { "title": true },
"option_values": { "value": true }
},
"product_category": { "fields": ["name", "description"] }
}
- 内置默认集(
backend/src/utils/translation-field-whitelist.ts新常量)=现状硬编码清单的参数化等价物,保证「配置为空 → 行为与今天逐字节相同」; - 读取链:
collectProductInput(product, sourceLocale, whitelist?)增加可选参——由run-translate-productstep 从getEffectiveConfig()取translatable_fields ?? DEFAULT_TRANSLATABLE_FIELDS传入;category 路径同理读product_category键; - 前端:
translation-config-form.tsx增加白名单编辑区(字段多选+非法键禁用),提交并入现有POST /admin/translation-config(validators.ts zod schema 扩展,服务端二次校验键闭合)。
白名单变更仅影响后续触发的翻译,不对存量商品自动全量重译(避免误操作触发大规模 AI 调用;如需重译走 admin 手动批量通道)。
P-② 翻译状态查询 API + banner 升级(缺口③)
新增路由:GET /admin/translations/status(新建 backend/src/api/admin/translations-status/route.ts——如与上游 @medusajs/translation 的 /admin/translations/* 路由命名冲突,回退名 GET /admin/translation-jobs/status;以任务卡指定名为准并留冲突注记)。
- 权限域:admin 侧(与
/admin/translation-config同级,走/admin全局认证中间件;query 校验用validateAndTransformQuery); - 聚合来源:
translation_job模块(本地模块,:157注册isQueryable: true)——不改上游@medusajs/translation。
{
"queue": {
"pending": 0, // status='pending' 计数
"processing": 0, // status='processing'
"done": 0, // status='done'(近 7 天窗口,query 参数 window_days 默认 7)
"failed": 0, // status='failed'(达 MAX_TRANSLATION_RETRY=5 上限)
"retry_due_now": 0, // pending 且 next_retry_at <= now()
"oldest_pending_at": null // 最老 pending 的 created_at(积压时长感知)
},
"by_reference": {
"product": { "pending": 0, "processing": 0, "failed": 0 },
"product_category": { "pending": 0, "processing": 0, "failed": 0 }
},
"failure_top_errors": [ // 近窗口 last_error 聚合 top 5(截断展示)
{ "last_error": "AI translation config not set", "count": 12 }
],
"retry_success_rate": null, // done/(done+failed) 窗口内;样本不足为 null——映射「失败重试成功率跟踪」
"ai_config": {
"configured": true, // 复用 isAIConfigured 语义(utils/translation-config.ts:49-66)
"encryption_enabled": true // isEncryptionConfigured()(P-③ 状态透出)
},
"generated_at": "2026-10-25T00:00:00Z"
}
- 错误码:401 未认证 / 403 非 admin / 400
window_days非法(1-90)/ 500 聚合失败(读路径不写库,无事务); - 前端升级:
translation-config-status-banner.tsx(:40-57 现状仅 isFullyConfigured 二态 Alert)升级为「配置态+队列态」复合横幅:新增admin-panel/src/hooks/api/translation-status.ts(React Query,60s 轮询),展示 pending/failed 计数与最老积压;AI 未配置时保持现有 not-configured 文案(横幅只是可观测性,不产生任何阻断行为)。
P-③ 加密强制 + 密钥轮换(RK-02 / R-encryption-fallback 缓解,缺口⑥)
现状缺陷(两处):
- 明文降级无守卫:
src/workflows/translation-config/steps/upsert-translation-config.ts:52-56——isEncryptionConfigured() ? encryptSecret(...) : input.api_key,生产未设TRANSLATION_ENCRYPTION_KEY时 api_key 明文落库(无 NODE_ENV=production 守卫); - 密文无版本标记层:
backend/src/utils/crypto/secret-crypto.ts——decryptSecret(:92)对无前缀旧明文透明返回(:89-95),且解密仅认单一 v1 形态——轮换密钥即存量密文全部不可解。
设计 1:生产加密守卫(两阶段灰度)
- 阶段 1(告警):step 内检测
NODE_ENV=production && !isEncryptionConfigured() && input.api_key 非空→ logger.error 告警但仍落库(灰度观察期); - 阶段 2(强制):同条件改为 Workflow step 内抛
MedusaError(Types.INVALID_DATA, "TRANSLATION_ENCRYPTION_KEY is required in production"),拒绝落库不降级——守卫在 step 内实现(AGENTS.md 红线),上游 route 无需改动; - 灰度开关:env
TRANSLATION_ENCRYPTION_REQUIRED(缺省 false=阶段 1,置 true=阶段 2); - 存量明文检测与迁移:一次性 workflow
migrate-plaintext-api-keys——扫描无enc:v1:前缀的非空 api_key 行 →encryptSecret回写;幂等(有前缀跳过);dev 环境跳过。
设计 2:密钥轮换(enc:v2: 双密钥读旧写新)
secret-crypto.ts扩展:新前缀enc:v2:与KEY_SALT_V2(派生盐换版本串,隔离 KDF 域);resolveKey(version)——v1 读TRANSLATION_ENCRYPTION_KEY,v2 读TRANSLATION_ENCRYPTION_KEY_V2(未设则 v2 写操作抛错);decryptSecret按前缀路由解密密钥(enc:v2:→新钥,enc:v1:→旧钥,无前缀→明文透明返回兼容不变);读旧写新:写路径在 V2 密钥存在时一律产出enc:v2:——「密文无版本标记、轮换即存量不可解」由「前缀→密钥版本路由」根除;- 轮换 workflow:
rotate-translation-encryption-key(steps 链见 Workflow 设计表)。
轮换操作步骤(运维 SOP,随 TDD 交付):
- 配置
TRANSLATION_ENCRYPTION_KEY_V2(新钥)并重启; - 触发轮换 workflow:逐行读存量
enc:v1:密文 → 用旧钥解密 →enc:v2:重加密回写(分批+幂等+失败行留痕); - 全量行核验为
enc:v2:后,将新钥值挪到TRANSLATION_ENCRYPTION_KEY、清空_V2(或保持双变量、以 v2 为主——两形态等价,取其一并在 runbook 固化); - 旧密钥仅在「回滚窗口」内保留(见迁移与回滚)。
dev .env 未配置加密主键(明文态)不受影响;轮换属管道增强段,与 AI 服务是否接入无关。
P-④ AI 调用审计 usage 采集(闭环 NFR-3.9-03,缺口⑦)
现状:callChatCompletions(client.ts :369-400)响应的 usage(prompt_tokens/completion_tokens/total_tokens)从不读取(rg 0 命中),成功/失败调用均无留痕——PRD NFR-3.9-03 明示「AI 调用专项审计(调用量与成本统计)尚无」为待建项。
新实体 ai_call_log(新本地模块 ai-call-log,表 ai_call_log)字段级:
| 字段 | 类型 | 约束/默认 | 说明 |
|---|---|---|---|
id | text | PK,prefix aicall | |
request_id | text | nullable | 调用关联 id(每次 translateWithAI 批次生成,跨 locale 复用) |
model | text | not null | config.model |
provider_host | text | not null | base_url host:port(排障用,无 PII) |
locale | text | not null | 目标 locale(ja-JP/zh-Hans/en-US) |
reference | text | not null | product/product_category(对齐 TRANSLATION_REFERENCES :20-25) |
reference_id | text | not null | 实体 id |
status | enum(success, error) | not null, default success | 该次单 locale 调用结果 |
prompt_tokens | integer | nullable | 响应 usage.prompt_tokens;provider 未返回/解析失败为 null |
completion_tokens | integer | nullable | 同上 |
total_tokens | integer | nullable | 同上 |
duration_ms | integer | not null, default 0 | 请求起止耗时 |
error_summary | text | nullable | 失败原因(截断 500 字,复用 last_error 截断范式) |
created_at / updated_at / deleted_at | timestamptz | 标准三件套 | medusa 约定 |
- 索引:
(reference, reference_id)、(created_at)、(model)、(status);migration 走medusa db:generate; - 采集点设计(最小侵入):client.ts 为纯函数文件夹、已有 container 入参——在
translateWithAI单 locale 请求完成/抛错处补recordAiCallLog(container, entry);落库走新ai-call-log模块 service;写失败仅 logger.warn,不阻断翻译主流程(C6 同构);不修改上游@medusajs/translation(本卡全部 AI 流量走自研 client,采集覆盖完整)。
译文版本历史(译文被覆盖可回溯)属 NFR-3.9-03 的另一待建项,归 AI-3.5-04 复核工作流范围,本卡不设计(引用不展开)。
P-⑤ 吞吐/背压指标位(RK-09 缓解,缺口⑧)
现状参数:并发信号量 3(retry-translations.ts:60-68)、轮询 cron 5min(constants.ts:13)、指数退避 2^retry_count 分钟(constants.ts:16)、重试上限 MAX 5(constants.ts:10)。验收口径必含四个指标名(数值一律「基线立项定(DEC-5)」,不臆造):
| 指标 | 定义 | 现成数据源 |
|---|---|---|
| 队列积压深度 | pending 计数+最老 pending 存续时长 | P-② API queue 段 |
| 单任务端到端时延 | enqueue→done 的时长分布 | translation_job created_at/updated_at 差值(P-② 聚合扩展位) |
| 重试成功率 | 窗口内 done/(done+failed) | P-② API retry_success_rate |
| 理论吞吐上限 | 并发 3 × (单任务时长)^-1 | ai_call_log.duration_ms 聚合(P-④ 落地后可算) |
全量商品回填积压规模估算公式(运营预案用):积压规模 ≈ 商品数 × 语种数(=2) ÷ 日吞吐;日吞吐由第四项实测后回填——基线立项定(DEC-5)。
服务接入段(DEC-1/C2 门控,关闭后方可开工)
本段设计照写备妥,但任何实施动作(生产配置、触发真实 AI 调用、扩量)必须等:① C2 合规条款关闭(AI-3.9-01 专项条款发布,先关条款后接入)→ ② DEC-1 定接入时点 → ③ 扩量节奏按 DEC-2。管道增强段(P-①~⑤)不受本门控约束。
生产接入步骤链(每步可独立验证,顺序硬约束):
- C2 前置(不可跳过):隐私政策 AI 处理专项条款发布并生效(内容与双分支归属归 AI-3.9-01/法务,本卡只挂前置);验收=条款线上可见+分支归属回填(DEC-2 关联);
- 生产配置:
backend/.env按.env.template:91-94填入真实TRANSLATION_AI_BASE_URL/API_KEY/MODEL(ALLOW_PRIVATE_HOST 必须留空——模板 :88-90 注释明示生产 SSRF 防护依赖此项,且误设时 NODE_ENV=production 双重门控拦截);同时确认 P-③ 加密守卫已处强制态; - DEC-1 时点到达后灰度开闸:先分类(量小)后商品,观察 P-② 队列面板与 P-⑤ 指标;
- DEC-2 节奏扩量:扩量速率受 C2 分支归属与吞吐基线约束;全量回填前按 P-⑤ 公式预估积压。
接入段补两个缺口(缺口④⑤收口):
- category 触发链对齐:
category-created-translation.tshook 现直调translateCategoryToLocales(无预检)→ 对齐 product 范式:hook 内先isAIConfigured预检(异常返回 true 不阻断)→ enqueue 进translation_job(reference=product_category 已在白名单 :35-42,retry worker 的 retryCategory :278-284 已就绪)——category 路径全程 C6 不阻断建类; - 补
run-translate-category.unit.spec.ts专属单测:覆盖正常翻译/字段缺失跳过/AI 异常 enqueue/未配置降级四分支。
译文抽检挂接点(PRD 验收要点「译文抽检合格率」载体预留):在翻译持久化 step 与 translation.product.updated 事件之间预留 sample-review 扩展点(采样率/合格口径基线立项定(DEC-5);复核工作流本体归 AI-3.5-04,交叉引用不重复设计)。
管道时序 · 状态机 · 实体图
数据模型 / API / Workflow
数据模型设计汇总(字段级)
| 对象 | 变更 | 字段级内容 |
|---|---|---|
translation_config(现有表) |
新增 1 列 | translatable_fields jsonb NULL(P-①;NULL=内置默认集) |
ai_call_log(新表/新模块) |
新建 | 14 字段全列见 P-④ 字段表(id/request_id/model/provider_host/locale/reference/reference_id/status/prompt_tokens/completion_tokens/total_tokens/duration_ms/error_summary/时间三件套)+ 4 索引 |
translation_job(现有表) |
无变更 | P-② 仅读聚合;字段已在 models/translation-job.ts:44-68(migration Migration20260623184915.ts:6) |
| module link | 无新增 | translation_job 与译文本体(上游 Translation Module)经 reference/reference_id 弱关联(模型头注释 :5-8),维持不变 |
API 契约汇总
| 路由 | 方法 | 变更 | 权限域 | 要点 |
|---|---|---|---|---|
/admin/translations/status |
GET | 新增(P-②) | admin | 响应/错误码见 P-②;只读聚合无副作用;上游命名冲突注记→回退名 /admin/translation-jobs/status |
/admin/translation-config |
GET/POST/PUT | 行为变化(P-①③) | admin | POST/PUT 请求体新增可选 translatable_fields(zod 键闭合校验);生产未配加密钥时按灰度阶段 warn/抛错(守卫在 workflow step 内,路由层不改) |
/admin/translations、/admin/translations/batch |
GET/POST | 无改动 | admin | 上游 @medusajs/translation 提供(medusa-config.ts:239;admin hooks translations.ts:92 消费),本卡不触碰 |
Workflow 设计(业务逻辑必须在 Workflow)
| Workflow | 状态 | steps 链 | 补偿逻辑 | 事件 |
|---|---|---|---|---|
upsert-translation-config(现有) |
增强(P-①③) | upsert-translation-config-step(:40-70 已有 previous 快照返回)内嵌:加密守卫(阶段1 warn/阶段2 抛错)+ translatable_fields 校验 |
已有:StepResponse 携 {created, id, previous},compensation 回写 previous(现有结构沿用) |
无新增 |
rotate-translation-encryption-key |
新增(P-③) | ① list-v1-rows(扫描 enc:v1: 行,分批游标)→ ② re-encrypt-batch(旧钥解→v2 密回写,幂等:v2 前缀跳过)→ ③ verify-all-v2(全量核验,失败行留痕不抛)→ ④ emit-rotation-report |
② 失败批:保留原密文不动(读旧写新保证可重入);③ 不通过则报告列出失败行,不自动回写 | 完成后 event bus emit translation.encryption.rotated(直接 emit,理由同 run-translate-product :251-256) |
migrate-plaintext-api-keys |
新增(P-③) | ① find-plaintext-rows(无 enc: 前缀非空 api_key)→ ② encrypt-batch(幂等) |
单行失败跳过留痕 | 完成日志报告 |
run-translate-product / run-translate-category(现有) |
P-① 接入白名单参数 | translateProductAsync/translateCategoryToLocales 读 effective config 的 translatable_fields | 既有 catch→enqueue(runTranslationWithRetry 承载) | translation.product.updated(:254,直接 emit) |
| category 触发链改造(接入段) | hook→预检→enqueue | 见服务接入段(isAIConfigured 预检→enqueue) |
C6 try/catch 不阻断建类 | 复用 job 队列 |
前端改动面与 i18n
前端改动面(逐端)
| 端 | 改动 | 依据 |
|---|---|---|
| admin-panel | ① translation-config-status-banner.tsx:静态二态→配置态+队列态复合横幅(新增 hooks/api/translation-status.ts,60s 轮询);② translation-config-form.tsx:新增 translatable_fields 编辑区;③ translation-section/translation-editor:无逻辑改动(批量通道不变) |
P-①②;现挂载点 product-detail/category-detail/article-form 不变 |
| vendor-panel | 无改动 | rg「translation」仅命中 i18n 框架文件,无翻译业务面;商家侧译文消费走 storefront API |
| influencer / referrer panel | 无改动 | 无翻译业务触点(本卡改动收敛于 admin 与后端) |
| storefront(B2C) | 无改动 | 译文经 Medusa store API 按 x-medusa-locale 透出(storefront/src/lib/data/products.ts 已传 locale);P-② 的新 API 为 admin 域 |
| b2b / h2b storefront | 无改动 | 两端 products.ts x-medusa-locale 0 命中——「B2B 前台始终显示原语言」属 AI-3.3-02 三语检索条目范围,本卡不越界 |
i18n 三语言 key 规划(en / zhCN / ja)
改动面仅 admin-panel:admin-panel/src/i18n/translations/en.json、zhCN.json、ja.json 三文件同步新增(三语值随实现提交;遵循现有 translationConfig.* 命名空间):
| 新增 key | en(示意) | 用途 |
|---|---|---|
translationConfig.fields.title / .description / .options.title / .optionValues.value | Translatable field labels | P-① 白名单编辑区 |
translationConfig.fields.resetToDefault | Reset to default set | 回退默认集按钮 |
translationConfig.encryption.notConfiguredWarning | Encryption key not set — API key will be stored in plain text | 阶段 1 告警横幅 |
translationConfig.encryption.requiredError | Encryption key is required in production | 阶段 2 报错文案 |
translationConfig.encryption.rotationAction | Rotate encryption key | 轮换入口(运维角色可见) |
translationConfig.status.queue.pending / .failed / .oldest | pending / failed / oldest pending | P-② 队列横幅 |
translationConfig.status.retrySuccessRate | Retry success rate | P-② 指标展示 |
storefront / b2b / h2b / vendor i18n 无新增 key(无 UI 改动)。
验收与测试
验收标准(PRD 要点+技术口径)
| # | 验收标准 | 映射 |
|---|---|---|
| A1 | 失败重试成功率可在管理端观测(P-② retry_success_rate 字段),口径与数值基线立项定(DEC-5) | PRD 验收要点① |
| A2 | 译文抽检合格率:抽检机制挂接点就绪;合格率数值与采样率基线立项定(DEC-5)(本体归 AI-3.5-04) | PRD 验收要点① |
| A3 | 接入启动与扩量严格按 C2 门槛执行:接入段步骤链顺序不可颠倒可审计(条款生效时间戳 < 首次生产 AI 调用时间戳) | PRD 验收要点② |
| A4 | 生产环境 TRANSLATION_ENCRYPTION_KEY 未设时 api_key 拒绝落库(阶段 2 强制态验证);存量明文迁移后 0 行无前缀 | RK-02 缓解验收 |
| A5 | 密钥轮换演练:v1 存量 100% 轮换为 v2 后,配置读取/翻译全链路无感;轮换中任一时刻 decrypt 可用 | P-③ 验收 |
| A6 | ai_call_log 覆盖率:自研通路 AI 调用 100% 留痕(成功+失败);NFR-3.9-03 口径达标值立项定 | 缺口⑦验收 |
| A7 | 白名单参数化:改配置后新触发翻译按新白名单执行;清空配置回退默认集与现状行为逐字节一致 | 缺口②验收 |
| A8 | 吞吐/背压四指标(积压深度/端到端时延/重试成功率/理论吞吐)有采集位与展示;数值基线立项定(DEC-5) | RK-09 验收 |
| A9 | 故障演练:模拟 AI 服务异常,商品发布/分类创建主链路零阻断(NFR-3.8-01 验收要点原文) | 5.3 测试点 2 |
测试策略(分层)与 L0 门禁
本文档十要素章节齐备、关键 code anchor 字符串可 rg 命中、既有翻译域 8 个单测可回归通过(实现期复跑)。
| 层 | 范围 | 明细 |
|---|---|---|
| 既有回归(8 文件) | 现网行为守卫 | client / ssrf / translation-config.service / mark-skipped-no-config / retry-translations / translation-updated-revalidate / translation-config(utils) / run-translate-product |
| 新增单测(管道段) | P-①~④ | ① 白名单参数化:默认集等价性/非法键拒绝/配置覆盖;② status API:聚合正确性/窗口参数/权限;③ 加密守卫(两阶段)+轮换(v1→v2→读旧写新→回滚)+明文迁移幂等;④ usage 采集:成功/失败/usage 缺失三态落库、写失败不阻断 |
| 新增单测(接入段) | 缺口④⑤ | ⑤ category 预检:未配置跳过/异常回退 enqueue;⑥ run-translate-category 专属:正常/字段缺失/AI 异常 enqueue/未配置降级 |
| 降级路径测试 | NFR-3.8-01 | 双防线四测试点全量纳入(AI 未配置/故障/探测异常/重试耗尽,见风险与降级节) |
| 集成点 | 跨模块 | enqueue→claim→translate→落译文→事件→(storefront revalidate 由既有 translation-updated-revalidate 覆盖);接入段灰度演练脚本(步骤链 1-4) |
风险与降级
风险卡
TRANSLATION_ENCRYPTION_KEY 时 api_key 明文落库(upsert-translation-config.ts:52-56 无 NODE_ENV 守卫);密文无版本标记层,轮换即存量不可解。它是 AI 服务正式接入前的强制完成项(RK-02:接入前必须强制加密)。缓解:P-③ 守卫灰度→强制+enc:v2: 读旧写新轮换 runbook,建议排在接入段步骤链第 0 步。
GET /admin/translations/status 可能与上游 @medusajs/translation 的 /admin/translations/* 命名冲突。缓解:实现期冲突时回退名 GET /admin/translation-jobs/status;以任务卡指定名为准并留冲突注记。
降级与跳过路径(NFR-3.8-01 强制范式——双防线跳过)
执行时序与分支见图 2。降级链路四条(转写自源 §5.3):
- 防线一(入口预检):
isAIConfigured(utils/translation-config.ts:49-66)——未配置(base_url/api_key/model 任一缺)→ 跳过 enqueue,主流程零感知;探测自身异常 → catch 返回 true(:63-65)→ 回退 enqueue 路径(C6 永不阻断); - 防线二(兜底终态化):retry worker 内再探测(:205-215)——未配置 →
markSkippedNoConfig(job.id, SKIP_NO_CONFIG_REASON)终态化(failed+no-config 前缀,logger.info 非告警——预期行为非告警); - AI 调用失败:enqueue 幂等重试(指数退避 2^n 分钟,MAX 5,:243-248 达上限告警);
- storefront:译文缺失 → 原语言 fallback(C6 预期降级,collections/[handle]/page.tsx:42-44 同范式)。
NFR-3.8-01 范式测试点(必列,映射 PRD 验收要点「故障演练」):
- 未配置态:建商品/建分类 → 主流程成功、
translation_job0 新增或立即 markSkippedNoConfig; - 故障态:mock AI 5xx/超时 → 主流程成功、任务进重试队列、指数退避生效;
- 探测异常态:mock getEffectiveConfig 抛错 → isAIConfigured 返回 true → enqueue 兜底(不静默丢);
- 重试耗尽:MAX 5 后 failed+告警日志(:243-248 断言)。
迁移与回滚
| migration | 内容 | 兼容性 |
|---|---|---|
translation-config 新 migration | ALTER TABLE translation_config ADD COLUMN translatable_fields jsonb NULL | NULL 默认→存量行为零变化 |
ai-call-log 模块 migration | CREATE TABLE ai_call_log(全列+索引) | 新表,无存量 |
| 轮换/明文迁移 | 不用 schema migration——走 workflow(数据迁移与 schema 迁移分离,可重入幂等) | — |
pnpm db:reset / db:reset:demo 兼容:两 migration 均可空库重放;translatable_fields NULL→默认集回退使 seed 流程行为不变;ai_call_log 空表不参与 seed(seed 留空惯例一致)。
| 变更 | 回滚路径 |
|---|---|
| 白名单参数化 | admin 清空 translatable_fields(或置 NULL)→ 回退内置默认集=现状行为;代码回滚无需数据回滚 |
| 加密守卫 | 阶段 2 → 阶段 1:TRANSLATION_ENCRYPTION_REQUIRED 置 false(灰度开关即回滚开关) |
| 密钥轮换 | 读旧写新保证 v1 密文在轮换中始终可解;回滚=恢复旧钥环境变量+未轮换完的 v1 行原样保留(re-encrypt 幂等跳过已 v2 行,重跑即可续传) |
| 状态 API / ai_call_log | 只读/只追加;停写旁路 recordAiCallLog(warn 旁路设计)即回退,无数据一致性风险 |
| 新能力总开关 | featureFlags.translation(medusa-config.ts:54-56)既有开关沿用;AI 配置缺省关闭即天然关闭(isAIConfigured 探测) |
上线顺序(管道段内部建议序):P-③ 加密守卫最先(告警期先行积累观察)→ P-①②④ 并行 → P-⑤ 收口 → 接入段等 DEC-1/C2。全卡无源代码以外的仓库改动;本 TDD 为设计文档,不改任何源码/迁移/配置。
前置决策点
Blocker 二分表(阻塞什么 / 可并行做什么——禁止因阻塞点否定整条 TDD)
| Blocker | 类型 | 阻塞什么 | 可并行做什么 |
|---|---|---|---|
| D-dec1(翻译 AI 服务接入时点,PRD 2.5/8.1 C1) Blocker 待决策 | 管理层决策 | 仅阻塞服务接入段(生产配置、灰度开闸、扩量) | 管道增强段 P-①~⑤ 全部五项零阻塞可立即开工;接入段代码(category 预检对齐、专属单测)可先行开发但不上生产闸 |
| D-dec2(C2 条款时点与扩量节奏,PRD 2.5/8.1 C2) Blocker 待决策 | 管理层决策+法务 | C2 条款关闭前翻译扩量不得启动(PRD 7.5-2 顺序不可颠倒) | 条款内容起草(法务侧)与管道增强段并行;dev 环境内网 LLM 的工程验证不受限(生产数据不出域评估归 C2) |
| R-encryption-fallback(明文降级+轮换无路径) 接入前强制完成 | 代码缺陷(非外部阻塞) | 不阻塞外部工作——但它是 AI 服务正式接入前的强制完成项(RK-02) | 纳入管道增强段 P-③ 立即开工(守卫灰度→强制→轮换 runbook),建议排在接入段步骤链第 0 步 |
| R-throughput(吞吐天花板,RK-09) 容量风险 | 容量风险 | 不阻塞开发;阻塞「全量回填」运营动作的排期承诺 | 指标位(P-⑤)随管道增强段落地;数值基线立项定(DEC-5);回填前按公式预估积压 |
内部依赖与下游消费
- 内部依赖:无(本条为打样条目,无上游 TDD);
- 下游消费:AI-3.3-02(三语检索以本管道译文为索引输入)、AI-3.5-03(场景翻译复用本管道+fillPrompt 扩展位)、AI-3.5-04(复核工作流挂本卡抽检点)、AI-3.9-03(P-④ 为其 P0 商用化动作「补 AI 调用专项审计(与翻译扩展同批)」的实现载体)。
开放数值决策
- DEC-5 失败重试成功率与译文抽检合格率的口径基线与目标数值——基线立项定,本卡仅引用编号不内联臆造数值(外部决策点引用规范)。
- DEC-5 吞吐/背压四指标(积压深度/端到端时延/重试成功率/理论吞吐)的目标数值与抽检采样率——同属 DEC-5,随实现纳入。
DEC-1(接入时点)、DEC-2(C2 条款时点与扩量节奏)、DEC-5(验收基线数值)——本卡仅引用编号,不内联臆造数值。
附录:溯源表
本文档由 docs/reports/ai-module-prd/tdd/AI-3.5-02-dynamic-content-translation.md(v1.0,2026-10-25)可读化重组生成。章节映射与处理方式如下:
| 本文档章节 | 源文档章节 | 处理方式 |
|---|---|---|
| 摘要 | 文档头(元数据/depth_baseline/两态口径)+ §1 溯源与现状 | Summarized |
| 关键概念 | §2 代码现状底座+§3 缺口与目标状态 | Summarized |
| 方案设计 · 两段式总览 | §4.0 两段式总览 | Preserved |
| 方案设计 · 设计红线 | §4.0 设计红线 | Preserved |
| 方案设计 · 管道增强段 P-①~⑤ | §4.1(含字段表/JSON schema/响应 schema/轮换 SOP) | Preserved |
| 方案设计 · 服务接入段 | §4.2(步骤链+缺口④⑤收口+抽检挂接点) | Preserved |
| 数据模型 / API / Workflow | §4.3 数据模型汇总+§4.4 API 契约汇总+§4.5 Workflow 设计+§4.1 P-④ 字段表 | Preserved |
| 前端改动面与 i18n | §4.6 前端改动面+§4.7 i18n key 规划 | Preserved |
| 验收与测试 | §8.1 验收标准(A1-A9)+§8.2 测试策略与 L0 门禁 | Preserved |
| 风险与降级(风险卡/降级链路/迁移回滚) | §5.3 降级与跳过路径+§7 Blocker 二分表+§10 迁移与回滚 | Preserved |
| 前置决策点 | §6 合规挂钩+§7 前置依赖与决策点 | Preserved |
| 图 1 两段式开工条件 | §4.0 分段表+§4.2 步骤链+§9 子任务 DAG | Inferred(文字图转绘为流程图,门控关系一致) |
| 图 2 翻译管道执行时序 | §2.1④ 触发链+§5.3 降级与跳过路径 | Inferred(双防线与重试链路转绘,分支条件一致) |
| 图 3 translation_job 状态机 | §2.1③ 队列模型+constants 参数 | Inferred(按字段与常量转绘) |
| 图 4 翻译域实体关系 ER 图 | §2.1①②③ schema+§4.1 P-④ 字段表+§4.3 汇总 | Inferred(按源 schema 字段转绘) |
源 TDD 所有锚点在打样 run(run-64960599a217)中以 rg/sed 逐条复核;上游 findings 原始锚点与复核实际行号存在漂移处,均以「卡内锚点(复核:实际行号)」双写标注,内容实质均已验证成立。本页沿用源文档复核后的锚点表述(如 collect-product-input.ts 白名单判定实际 :36-60、markSkippedNoConfig 兜底调用实际 :205-215、secret-crypto 实际位于 src/utils/crypto/)。