Files
Jyotisha/docs/tasks/TASK-consult-prep-parallel-20261013.md
T
Jesse_ChenandClaude Opus 5.5 2994588ad4 docs(tasks): fold product decisions into audit briefs (BUG-1344..1346)
- dasha-unify: fix Ashtottari birth balance (BUG-1344)
- rect-scoring-perf: server-side candidate cap 241 (BUG-1345)
- node-runtime-resources: enable user-side session cookie cache, 300 s (BUG-1346)
- consult-prep-parallel: classifier/reserve concurrency declined
- answer-effort-ab: model key supplied by product at acceptance
- drop stricter-than-test line-count red lines (AGENTS.md section 6)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ad9rqAB8pXXvyzh7f4X8v
2026-10-14 00:41:02 +08:00

28 KiB
Raw Blame History

TASK · 普通对话发问前的准备步骤改为并行(issue #待定)

  • 日期:2026-10-13
  • 来源:「2026-10-13 全仓屎山与性能排查」P2 · 咨询前置并行 / 多领域并发 / 报告章节并发
  • 执行方:Grok(Windows + Docker),分支 codex/consult-prep-parallel-20261013,只推这个分支
  • 验收:Claude(Linux)
  • BUG 编号:BUG-1337~1339(开工时核对 docs/BUG_HISTORY.md 最大号;写作时最大号 BUG-1317,1335/1336 已被其它单占用)(另:TASK-upstream-sync7-20261014 占 BUG-1318~1322,本批号段整体从 BUG-1323 起)
  • 开工前置:无(第一批)。本单之后还有两单改同一批文件,必须串行:answer-effort-ab(改 consultation-tools.ts)→ consult-route-split(搬 route.ts)。本单不要顺手搬 route.ts 的其它段落。

1. 基线

  • 基线 commit:origin/staging 77057a819
  • 涉及文件:
    • frontend/src/app/api/consult/route.ts::POST(1,840 行文件,POST 从第 296 行起)
    • frontend/src/lib/model-catalog.ts::readVersion / resolveSessionLanguageModel / invalidateLanguageModelCatalog
    • frontend/src/lib/moderation/index.ts::moderate
    • frontend/src/mastra/consultation-tools.ts::createConsultationTools(多领域循环)
    • frontend/src/lib/personal-report-generation.ts::generateSectionedPersonalReport(只核实,不改,见 §4 决策 4)

2. 事故实证

2.1 开流前的串行等待(route.ts::POST)

开流(streamFirstResponse,第 1821 行)之前,下面每一步都是 await 完上一步才开始:

顺序 行(77057a819) 步骤 依赖
1 301–316 createServerSupabaseClient + supabase.auth.getUser() —
2 318 refuseWhenAccountDeletionPending(user.id)(一次 service client RPC) 只要 user.id
3 321 request.json() + zod 无
4 331 读 chat_sessions(含整段 messages) 只要 user.id + 请求体的 sessionId
5 365 resolveSessionLanguageModel(model_id, model_config_version) 第 4 步
6 424 moderate(visibleQuestion, "input"):本地词表恒跑;MODERATION_PROVIDER=aliyun 时再远程,上限 MODERATION_REMOTE_TIMEOUT_MS = 1_500 第 4、5 步通过

第 2 步与第 3~5 步互不依赖;第 6 步排在首帧之前,开了阿里云时首帧最多晚 1.5 s(BUG-1074 的「先建流」只把第 444 行以后搬进了流里)。

流内(runTurn)还有两处串行:

  • 第 674 行 classifyConsultationTurn(SMALLTALK_TIMEOUT_MS = 3_000)排在 prepareConsultationRoute(资料/时区/候选区间读取 + reserve_consultation_usage)与 append_consultation_question 之后。
  • 第 741 行分类完成后才再读一次 chat_sessions.title(只读,不依赖分类结果)。

2.2 readVersion 每轮查库 + 解密 + DNS(model-catalog.ts)

resolveSessionLanguageModel 在会话绑定了版本号时(普通对话都绑定)走 readVersion → queryCatalog(一次联表 SQL)→ resolveRow:decryptModelProviderApiKey + 对 openai-compatible 供应商 assertAllowedModelProviderUrl(dns.lookup 全部地址并逐个判公网)。没有缓存;同文件的 loadLanguageModelCatalog 对同样的解析结果(含同一个 DNS 校验)已经缓存 15 s(cacheTtlMs = 15_000)。

核实「版本不可变」:只对了一半。

  • 不可变:已发布/已退役版本所用供应商的 code、provider_type、base_url(触发器 prevent_published_model_provider_runtime_mutation,frontend/supabase/migrations/20260808010000_model_provider_emergency_disable.sql;管理端报 model_provider_runtime_immutable)。
  • 可变:model_providers.enabled(紧急停用)、model_config_versions.enabled、供应商密钥(重新启用时可换 key)。readVersion 的 where 条件里有 v.enabled and p.enabled,所以缓存必须有 TTL 且能被管理端操作清掉。

核实 SSRF(AGENTS.md §8.3):assertAllowedModelProviderUrl 是解析模型配置时的公网地址校验;之后真正发模型请求的是 Mastra 的 createOpenAICompatible(baseURL 是主机名,fetch 会重新解析 DNS,不是 pinnedAddressLookup 固定地址)。所以今天这道 DNS 校验也不是逐请求绑定,目录路径已经把它缓存 15 s。结论:按(模型, 版本)缓存、TTL 与目录相同(15 s)、管理端变更时一并清除,不削弱现有防护;TTL 更长或缓存失败结果则会削弱,不做。

2.3 多领域逐个 await(consultation-tools.ts::createConsultationTools)

第 1230–1280 行附近 for (let index = 0; index < domains.length; …) 逐个 await runConsultationWorkflow(POST /api/consultation_workflow)。MAX_CONSULTATION_DOMAINS = floor((110_000 − 45_000) / 31_000) = 2。/api/consultation_workflow 在 scripts/api_heavy_compute_gate.py::HEAVY_COMPUTE_PATHS 里,JYOTISH_HEAVY_COMPUTE_CONCURRENCY 默认 2,满了直接 429;前端 consultation-workflow.ts::httpFailureCode 把 429 记成 workflow_rate_limited,不退避重试,整次计算失败。

本机实测(起草时,77057a819,系统 python3.13,无 vedastro SDK,本地起 ThreadingHTTPServer,Steve Jobs 公开 AA 盘,事业 + 婚恋两领域,每组 3 次取中位数):

情形 串行 两路并发
盘已缓存 0.47 s 0.60 s(更慢)
盘未缓存(每轮换出生分钟) 2.25 s(第一域约 2.0 s,第二域吃到排盘缓存 0.29 s) 2.03 s(两路各自冷算约 2.0 s)

复现:scratchpad/bench-prep/bench2.py、bench3.py(起草会话临时目录,执行方按 §6 T5 重写进 frontend/scripts/research/)。

解读:Python 服务是 ThreadingHTTPServer,排盘吃 GIL;串行时第二个领域复用第一个领域的排盘缓存,并发时两路各算一遍。本机并发只省约 10%,CPU 翻倍、一次占满两个重计算名额。 生产容器装了 vedastro SDK,每域约 1.66 s 里有 1.33 s 是 BUG-301 保留的前台网络等待(consult-latency-audit 10-05 记录),网络等待会释放 GIL,所以生产上的收益可能比本机大——要靠 A/B 实测,不能推断。

另:同一盘、同一分钟,「串行时第二个领域(命中缓存)」与「冷启动单算该领域」的响应逐字段比对,除计时/缓存字段(consult_card_facts.timing.*、chart.runtime_cache.*)外,还有 module_execution_audit[*].module_id 与 consultation_native_layers.d9_summary.vargottama 两处列表顺序不同(集合遍历顺序,ERR-111:需 PYTHONHASHSEED=0)。所以 §6 T5 的差分脚本必须固定 PYTHONHASHSEED=0 并列出忽略字段,不能直接 cmp。

2.4 报告章节并发:目标代码已退役

generateSectionedPersonalReport(personal-report-generation.ts 第 3510 行)确实逐章串行,但它只在 PERSONAL_REPORT_WRITER_ENABLED 为真时被 worker 调用;frontend/src/lib/personal-report-writer-flag.ts 写死 false(注释:「Writer narrative pipeline is retired for new reports. Keep the code; do not call it.」,cfcd369d4)。新报告走 generateLongformReport(Python 完整数据版模板渲染,不逐章调模型)。/api/reports 的 resolveReportCreate 有 jobs 时只入队。改它对线上零影响,见 §4 决策 4。

3. 根因

  • 2.1:不是回归。各步按当时目的串行写成;BUG-1074 只把「会等模型或数据库的大头」搬进流里,开流前那几步与流内的读标题没有人回头看能否并行;输入审核 09-30 加在开流前。
  • 2.2:从未设计缓存。目录缓存(15 s)只服务「没绑版本」的旧路径,绑版本的 readVersion 是后加的。
  • 2.3:BUG-257 的防复发明确写了「任何领域级并行提议必须先证明后端能承接并发,判据是 Python 侧的进程模型、GIL 约束与有界队列」。本单只做开关和证明,不默认开启。

关联记录(均已读完并核对防复发仍在):

BUG 与本单的关系 必须保留的防线
BUG-1074 先建流、首帧是确定性进度句 开流前只留鉴权/请求体/会话/会话模型(consult-first-frame-and-pacing-20260928.test.tsx 两张清单);流内拒绝必须能表达成 request_rejected
BUG-1075 放字节奏 不碰
BUG-976 / 977 寒暄分类 「reserve 仍在分类前,零点无订阅仍被拦」——分类器不得早于预留
BUG-1142 答题钟 300 s / 工具钟 110 s / 领域预算 65 s、2 个 领域上限公式不变,并发不得改 MAX_CONSULTATION_DOMAINS
BUG-257 / 945 领域上限由时钟推导;计划长度与执行长度两个常数 并发仍按 executableDomainPlan 截断、仍披露 omitted_domains、仍降级
BUG-723 引擎 429 单独成档 并发版本对 429 不得压平;并发导致的 429 必须在 A/B 里计数
BUG-1231 / 1232 分段计时(modelSteps、answer.reasoning_ms、分类耗时进用量) 不删字段;本单新增的准备阶段计时走同一个 logAgentObservability
BUG-148 DNS 固定地址请求 requestAllowedModelProvider 不动
BUG-496 报告分章失败不得中止其余章 本单不改报告

4. 决策记录

产品授权:2026-10-13 产品负责人同意按排查结论全部立单交 Grok 执行;P1/P2 改动必须做 A/B、做成开关,默认值按 A/B 定。

  1. 开流前的独立读取并行(删号检查 ∥ 读请求体 + 读会话 → 解析模型):做。
  2. 输入审核:仍在任何模型调用和预留之前生效,但不再挡首帧——开流后与资料读取并行,在 reserve 之前等它的结论;被拦时仍不扣点、不进模型;报错优先级与今天一致(见 T2)。不推翻 BUG-1074 的开流前清单(审核本来就不在那张清单里)。
  3. readVersion 缓存:做,TTL 与目录缓存相同(15 s),不缓存失败和 null,管理端模型/供应商变更时清除。不延长 TTL。
  4. 报告章节并发:本单不做。目标函数已退役(§2.4),改了不会让任何用户的报告变快;排查报告里这一条作废。若产品以后重新启用 writer 管线,另立单。
  5. 分类器与预留并发:产品负责人 2026-10-14 决定不做。 它会让零点用户的问题在预留失败前先送进模型(违反 BUG-976 边界「reserve 仍在分类前」),且分类调用产生供应商成本。可选折中「预留成功后,分类与 append_consultation_question 并行;append 失败则丢弃分类结果并取消」也涉及计费顺序,同样留待拍板。
  6. 多领域并发:做成开关,默认关(串行),A/B 达标才改默认。不改领域上限、不改时钟。
  7. 不推翻任何既有红线。

5. 硬红线

  1. 被审核拦下的问题:不扣点(不得调用 reserve_consultation_usage)、不进任何模型(分类器、Agent、标题生成都不得先于审核结论)。
  2. 开流前的 HTTP 状态集合与先后顺序不变:401 → 423 → 400(请求体)→ 503(读会话失败)→ 404 → 409(session_not_consultation)→ 409(会话模型已变)→ 503(模型不可用)→ 409(旧校正入口)→ 400(blocksPromptExtraction)。并行后按这个顺序判定返回,不得因为谁先回来就先报谁。
  3. 删号冻结(423)必须在任何扣点、写库之前判定(account-deletion.test.tsx 的 if (frozen) return frozen;)。
  4. readVersion 缓存:只缓存成功解析的结果;TTL ≤ 15 s;invalidateLanguageModelCatalog() 同时清它;不缓存 DNS 失败或非公网地址的结果;不改 assertAllowedModelProviderUrl 与 requestAllowedModelProvider。
  5. 多领域并发开关打开时:executions 顺序必须等于计划顺序(不得按完成先后);任一领域失败的处理与串行一致(整次计算失败、错误码不变);429 不得被吞或改码;MAX_CONSULTATION_DOMAINS、domainFitsRunBudget 的公式与常量不变。
  6. 不动表结构、不加迁移;不改 scripts/jyotish_api_server.py;不碰 frontend/src/app/(app)/page.tsx;不改 .gitea/workflows/**;不升级依赖;不顺手修 warning。
  7. 改既有断言写「原值 / 新值 / 原因」三栏;不得为让测试过而弱化断言。开工先 grep(AGENTS.md §7.8):
    grep -rn "await moderate(visibleQuestion\|await resolveSessionLanguageModel\|return await readVersion\|refuseWhenAccountDeletionPending\|const runTurn = async\|for (let index = 0; index < domains.length" frontend/tests tests
    
    已知会受影响:frontend/tests/content-moderation.test.ts(约 242–243 行:await moderate(visibleQuestion, "input" 存在且在 reserve_consultation_usage 之前)、frontend/tests/model-catalog.test.ts(约 48–49 行:return await readVersion(...)、await resolveSessionLanguageModel()、frontend/tests/application-billing-contract.test.ts(约 160 行)、frontend/tests/consult-first-frame-and-pacing-20260928.test.tsx(256–289 行两张清单)、frontend/tests/account-deletion.test.tsx(约 111–122 行)、frontend/tests/consultation-agentic-runtime.test.ts(领域循环相关用例)。
  8. 输出逐字节一致:开关全关时,/api/consult 的 NDJSON 事件序列与 77057a819 相同;开关全开时,除新增的 consultation-prep-v1 可观测日志外,响应体与落库内容与全关相同(T7 差分验证)。
  9. 公开仓库:A/B 记录、PROGRESS 只写虚构盘或公开名人盘;不写真实用户资料、key、日志原文。

6. 任务分解

所有新开关在 frontend/src/lib/consult/prep-flags.ts(新建目录 frontend/src/lib/consult/,后续 consult-route-split 单沿用)集中读取,每个请求读一次 process.env,非法值按默认处理并 console.warn 一次。

开关 取值 本单代码默认 A/B 后的默认
CONSULT_PREP_PARALLEL on / off off 按 T7 判定
MODEL_VERSION_CACHE_TTL_MS 0~15000 的整数,0 关 0 按 T7 判定(开则 15000)
CONSULT_DOMAIN_CONCURRENCY 1 / 2 1 按 T7 判定

默认值的改动(若 A/B 达标)只改 prep-flags.ts 里的常量,单独一个提交,便于回滚。

T1 · 开流前并行(BUG-1337 的一半)(Grok 自跑 / Claude 验收)

  • 新建 frontend/src/lib/consult/turn-prep.ts,导出 loadConsultTurnHead(deps):输入为注入的依赖(getUser、readPendingDeletion、readBody、readSession、resolveModel),返回 { ok: false, response } 或 { ok: true, user, parsed, chatSession, sessionModel }。route.ts::POST 用它替换第 301–377 行的判定段,NextResponse.json(...) 的文案、状态码、code 原样搬过去(逐字)。
  • CONSULT_PREP_PARALLEL=on:拿到 user 后同时启动「删号检查」与「读请求体 → 读会话 → 解析模型」两条链;然后按 §5.2 的顺序依次检查结果再返回。off:保持今天的串行顺序(同一个函数里两条代码路径,便于 A/B)。
  • 注意:并行时请求体非法、会话不存在等情形下仍会多跑一次删号检查(只读),可接受;但不得在冻结账户上调用任何写操作。
  • 验收:新增 frontend/tests/consult-turn-prep-20261013.test.ts:用假依赖(每个依赖带可控延迟和返回值)覆盖 §5.2 每一种返回,并断言两种开关下状态码与 body 逐字段相同;on 时总耗时 ≈ max(删号, 会话 + 模型) 而非相加(用假时钟或延迟 50/80/80 ms 断言 < 230 ms);冻结账户 + 非法请求体时返回 423。
  • 命令:./node_modules/.bin/tsx --test tests/consult-turn-prep-20261013.test.ts

T2 · 输入审核不挡首帧,仍在预留前生效(BUG-1337 的另一半)(Grok 自跑 / Claude 验收)

  • CONSULT_PREP_PARALLEL=on:在第 424 行原位置(所有开流前检查都通过之后)启动 moderate(...) 但不 await,得到 inputModerationPending;进入 runTurn 后:
    • prepareConsultationRoute 的 beforeReserve 回调第一行 await inputModerationPending,非 pass 时 recordModerationEvent(只记一次),block 时抛专用错误 ConsultationInputBlockedError;
    • prepareConsultationRoute 的 catch 里先看审核结论:若为 block,无论 prepare 抛的是什么,都返回今天那个 400(code: "content_blocked"、inputBlockedCopy(...))——保持「审核拦截优先于资料错误」的旧优先级;
    • legacy 运行时(shouldUseAgenticRuntime 为假)同样走这段,仍是直接 HTTP 400。
  • off:维持第 424 行 await 原样。
  • 首帧之后被拦的请求,客户端收到 run.failed / request_rejected / status: 400 / reason: "content_blocked",use-consultation-run.ts 的 4xx 回滚已有,不改客户端。自伤类拦截文案(MODERATION_SELF_HARM_COPY)必须原样到达用户(断言事件 message)。
  • 验收:
    • 新测试:审核挂起 1.5 s 时首帧已写出(参照 consult-first-frame-and-pacing-20260928.test.tsx 的 onFirstFrame 用例);审核 block 时 reserve 假函数调用 0 次、模型假函数调用 0 次;审核 block + 资料读取抛 ConsultationProfileTruthError 时返回 content_blocked;自伤文案逐字到达。
    • content-moderation.test.ts 约 242–243 行按三栏改:原值「源码含 await moderate(visibleQuestion, "input" 且位置早于 reserve_consultation_usage」→ 新值「beforeReserve 内 await inputModerationPending 早于 input.reserve()(consultation-route-service.ts 里 beforeReserve 先于 reserve 已有保证)+ 行为测试:block 时 reserve 0 次」。原因:审核从开流前挪到流内、预留前。新值必须比原值更强(有行为断言)。

T3 · readVersion 缓存(BUG-1338)(Grok 自跑 / Claude 验收)

  • model-catalog.ts:新增模块级 Map<"modelId:version", { expiresAt, model }>(挂 globalThis,同 jyotishaModelCatalogCache 写法)。resolveSessionLanguageModel 绑版本分支:TTL > 0 且命中未过期 → 直接返回;否则 readVersion,只有非 null 成功结果才写缓存。invalidateLanguageModelCatalog() 同时清空它。
  • 不缓存:readVersion 抛错(return null 路径)、resolveRow 返回 null(解不开 key)、assertAllowedModelProviderUrl 失败。
  • 验收:frontend/tests/model-catalog.test.ts 加行为测试(注入假 queryCatalog 与假 DNS):TTL 内第二次不查库不解析 DNS;过期后重查;invalidateLanguageModelCatalog 后重查;失败不缓存;TTL=0 每次都查。约 48 行 return await readVersion(modelId, configVersion) 按三栏改(原值 / 新值 readVersion 结果经缓存写入 / 原因)。管理端 model-mutation-handler.ts 的 invalidateCatalog 调用点(保存供应商、发布、回滚)不变,断言它们仍在。
  • 说明 SSRF 结论(§2.2)写进 PROGRESS 与 BUG-1338「防复发」:TTL 不得超过目录缓存。

T4 · 流内读标题与分类并行(BUG-1337)(Grok 自跑 / Claude 验收)

  • 第 741 行读 chat_sessions.title 改为在 append_consultation_question 成功后立即启动,与 classifyConsultationTurn 并行,在原使用点 await。分类本身的位置不动(仍在预留和 append 之后,§4 决策 5)。
  • 验收:源码断言读标题在 append 之后启动;行为不变(session-title-persist-contract.test.ts、consultation-smalltalk.test.ts 全绿)。

T5 · 多领域并发开关(BUG-1339)(Grok 自跑 / Claude 验收)

  • createConsultationTools 的领域循环:CONSULT_DOMAIN_CONCURRENCY=2 时,对 executableDomainPlan 截断后的领域同时发起 runWorkflow,用 Promise.all 收齐后按计划顺序组装 executions;chart-calculation 活动句按今天的 chartCalculationProgressLabel(index + 1, domains.length) 在发起时依次写出。任一失败 → 与串行一样整次失败、错误码不变。=1 时代码路径与今天逐字相同。
  • 并发时 domainFitsRunBudget 不再逐个判断(所有领域同时开始),但领域数仍受 MAX_CONSULTATION_DOMAINS 截断;在 PROGRESS 里写明这一点。
  • 验收:consultation-agentic-runtime.test.ts 加用例:假 runWorkflow 让第二个领域先完成,executions / 证据卡 / workflowReceipt 与串行逐字段相同;一个领域 429 时错误码为 workflow_rate_limited(与串行同);开关为 1 时调用顺序严格串行。
  • 写 frontend/scripts/research/consult-domain-concurrency-ab.py(或 .mts):本地起 Python API(PYTHONHASHSEED=0,带 vedastro SDK 的 Docker 镜像一组、不带一组),公开 AA 盘 3 张 × 领域对 2 组(career,marriage、parents,health)× 冷/热缓存,串行 vs 两路并发,每格 3 次取中位数;另跑「两个用户同时问两领域」的争用场景,统计 429 次数。差分:同盘同分钟两种方式的领域响应,忽略字段清单 = consult_card_facts.timing.*、chart.runtime_cache.*,并对 module_execution_audit、vargottama 等集合派生列表排序后比较,要求其余逐字节一致。

T6 · 准备阶段计时(Grok 自跑 / Claude 验收)

  • 在 route.ts 现有 logAgentObservability 体系里加一个事件 agentVersion: "consultation-prep-v1",contractPhases 记:prep.auth、prep.deletion、prep.session、prep.model、prep.moderation、prep.subject(prepare 到 beforeReserve)、prep.reserve、prep.append、prep.classify、prep.title,以及开关取值(写进 phase 名或已有可用字段,不新增 schema 字段)。只记毫秒和状态,不含问题原文、出生资料、用户标识以外的任何字段(用户标识沿用现有规则)。
  • 验收:测试断言事件通过 agentObservabilityEventSchema,且植入的问题原文不出现在日志里(参照 BUG-1232 的写法)。

T7 · A/B 验证(Grok 跑能跑的;需要 staging 的由 Claude)

记录写 docs/testing/consult-prep-parallel-ab-20261013.md。

项 A B 输入 度量 判定门槛(默认开启的条件)
开流前并行 + 审核挪后(CONSULT_PREP_PARALLEL) off on 进程内基准:用 T1/T2 的假依赖,延迟取真实中位数(Grok 在本机 Docker 里对本地 Postgres 量 getUser/删号 RPC/读会话/readVersion 各 30 次取中位数;阿里云审核无 key 时按 300 ms 与 1,500 ms 两档模拟);每组 30 次 首帧毫秒(onFirstFrame)、准备总毫秒、各段毫秒 首帧中位数降 ≥ 20% 且 §5.2 全部返回逐字段一致、被拦请求 reserve 0 次 → 默认 on;否则保持 off
readVersion 缓存 TTL 0 TTL 15000 同上进程内基准 + 本地 Postgres 真查;每组 30 次 prep.model 毫秒、SQL 次数、DNS 解析次数 prep.model 中位数降 ≥ 50%、TTL 内 SQL 与 DNS 次数为 0、失效/停用 15 s 内生效(测试证明)→ 默认 15000
多领域并发(CONSULT_DOMAIN_CONCURRENCY) 1 2 T5 的脚本 两领域墙钟中位数、Python 进程 CPU 秒、峰值内存、争用场景 429 次数、差分结果 带 vedastro SDK 的镜像里墙钟中位数降 ≥ 25%,且争用场景 429 次数不多于 A,且差分一致 → 默认 2;任何一条不满足保持 1。本机起草实测只降约 10%(§2.3),预计不达标,如实记录即可
staging 实测(Claude 验收时) 部署前 7 天 consultation-stream-first-v1 first_byte 中位数 部署后同口径 + consultation-prep-v1 各段 真实流量或产品负责人用测试账号 10 轮 first_byte、prep.* 只做核对,不改默认;数字写进同一份记录

每组至少重复 3 次取中位数(上表已写更多次的按表);正确性部分全部要求差分一致,不是「看起来一样」。

T8 · 记录(Grok)

  • docs/BUG_HISTORY.md:
    • BUG-1337:普通对话开流前与流内的准备步骤无依赖却串行(删号检查、读会话、解析模型、开了阿里云时审核挡首帧最多 1.5 s、读标题)。
    • BUG-1338:会话模型按版本解析每轮查库 + 解密 + DNS,无缓存(目录路径早有 15 s 缓存)。写清「版本不可变只对了一半」与 SSRF 结论。
    • BUG-1339:多领域串行;并发开关 + A/B 结论(若不达标,状态写 closed_by_design 或 investigating 并附数字,不写 resolved)。
  • docs/tasks/PROGRESS-consult-prep-parallel-20261013.md:每个 T 的结果、三栏改断言表、A/B 数字、开关最终默认值及依据。
  • CHANGELOG.md:若有默认开启的开关,写一句「发问后更快出现第一行」之类用户可感知变化;全部默认关则不写。
  • frontend/DESIGN.md:不改(无 UI 变化)。
  • BLOCKED.md:缺阿里云 key、缺 staging 实测时各记一条。

7. 让步顺序

时间不够时按此顺序砍(先砍最后的):T5(多领域并发,预计不达标)→ T4 → T6 的细分段(保留 prep.moderation 与 prep.model)。T1、T2、T3、T7 中对应 A/B 与 T8 不可砍。

8. 验收命令分工

谁 命令
Grok 自跑 cd frontend && ./node_modules/.bin/tsc --noEmit && npm run lint;单文件:./node_modules/.bin/tsx --test tests/consult-turn-prep-20261013.test.ts tests/content-moderation.test.ts tests/model-catalog.test.ts tests/application-billing-contract.test.ts tests/consult-first-frame-and-pacing-20260928.test.tsx tests/account-deletion.test.tsx tests/consultation-agentic-runtime.test.ts tests/session-title-persist-contract.test.ts tests/consultation-smalltalk.test.ts(看 # fail 与 # cancelled 都为 0、退出码 0);T7 进程内基准与 T5 脚本。前端全量、Python 快速门、整站构建不跑
Claude 验收 上述全部 + 前端全量 npm test 与基线逐名比对 + next build(/ 仍 Static,首屏 gzip ±2%)+ 复跑 T7 基准与 T5 脚本 + staging 部署后 first_byte 对比
test:db 不需要(不动表、不改数据库访问层查询语句);若 T3 改了 queryCatalog 的 SQL 文本则必须跑并按 tools/agent-loop/windows-known-failures.txt 比对

9. 开工前置命令

git fetch origin --prune
git worktree add -b codex/consult-prep-parallel-20261013 .worktrees/consult-prep-parallel-20261013 origin/staging
cd .worktrees/consult-prep-parallel-20261013
git log -1 --format=%h                       # 应为 77057a819 或其后代
grep -n "^## BUG-" docs/BUG_HISTORY.md | tail -3   # 核对最大号,本单用 1337~1339
cat frontend/src/lib/personal-report-writer-flag.ts  # 确认仍为 false(决策 4 的前提)
grep -rn "await moderate(visibleQuestion\|await resolveSessionLanguageModel\|return await readVersion\|refuseWhenAccountDeletionPending\|const runTurn = async\|for (let index = 0; index < domains.length" frontend/tests tests

10. BUG 编号起点

BUG-1337(写作时最大号 BUG-1317;1335、1336 已分配给其它单)。开工时若 1337~1339 已被占用,顺延并在 PROGRESS 写明。

11. 交付物清单

  • 代码:frontend/src/lib/consult/prep-flags.ts、frontend/src/lib/consult/turn-prep.ts(新)、route.ts、model-catalog.ts、consultation-tools.ts
  • 测试:frontend/tests/consult-turn-prep-20261013.test.ts(新)及 §5.7 列出文件的三栏修改
  • 脚本:frontend/scripts/research/consult-domain-concurrency-ab.*、T7 进程内基准脚本(同目录)
  • 文档:docs/testing/consult-prep-parallel-ab-20261013.md、docs/tasks/PROGRESS-consult-prep-parallel-20261013.md、docs/BUG_HISTORY.md(BUG-1337~1339)、视结果 CHANGELOG.md、BLOCKED.md
  • 不改:frontend/DESIGN.md、数据库迁移、scripts/jyotish_api_server.py

12. 环境缺口(预告)

  • 阿里云审核真实延迟需 key,无 key 用 300 / 1,500 ms 模拟,写进 BLOCKED.md。
  • staging 前后对比只能在部署后由 Claude 做。
  • 多领域并发的生产收益取决于 vedastro SDK 的网络等待,Grok 的 Docker 镜像要与生产同版本才算数。