* chore: unify product name display to OpenDesign repo-wide Rename the user-facing product name from "Open Design" to "OpenDesign" across documentation, web/desktop UI strings, i18n locales, prompts, plugin/skill/design-system content, changelogs, and specs, matching the landing-page rename already in flight. Deliberately excluded so installed-app identity stays untouched: - packages/release PRODUCT_NAME and everything derived from it (app bundle names, installer/registry identity, updater feeds) - tools/pack, tools/release, apps/packaged, landing page (PR #6983) - mocks/ recorded traces and LICENSE - test fixtures and comments that encode real on-disk install paths (Open Design.app, Program Files\Open Design, AppData paths) Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host test runs over the renamed strings. * fix(web): keep two-letter author monogram for CamelCase brand names authorInitials split words on whitespace only, so the renamed single-token author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper CamelCase boundaries as word breaks so the avatar fallback stays "OD". * fix(release): restore spaced Windows source artifact names in win.ps1 tools/pack still derives builder outputs from the unchanged PRODUCT_NAME ("Open Design"), so the asset script must look for the spaced filenames. Matches the mac/mac-intel/linux asset scripts, which were already kept on the original names. --------- Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
28 KiB
Run Analytics v4 测试与端到端验收方案
1. 文档信息
| 项目 | 内容 |
|---|---|
| 测试对象 | run_created、run_finished、恢复动作曝光/点击、启动前拦截、Task 成功率查询 |
| 目标 Provider | amr、claude_code、codex_cli |
| Schema | event_schema_version = 4;旧扁平参数在兼容窗口继续双写 |
| 主要角色 | OpenDesign QA、研发、数据分析 |
| 核心目标 | 验证事件能发出、参数语义正确、跨 Run 能归并、PostHog 中能正确查询 |
最新 main 已经使用 v3 表达 MCP/Plugin 来源归因,因此本次代码落地使用 v4;本文中的 v4 对应需求文档里原规划的聚合与任务成功率方案。
2. 测试结论必须回答的问题
- AMR、Claude Code、Codex 的首轮执行、失败和恢复 Run 是否都能正常上报。
- 首次失败、后续重试成功时,多个 Run 是否共享同一个
task_execution_id。 - Task 成功率是否按“一次用户意图”计算,而不是把第一次失败永久算作失败。
failure_category、failure_reason、failure_stage是否只描述最终失败 Run,并且可稳定分组。- 新的聚合对象是否能在 PostHog 中按点号路径查询,数值是否保留为数值类型。
- 旧扁平字段是否继续发送,旧版本事件和旧看板是否仍然可用。
- HTML、媒体、辅助资源和多轮微调是否遵守新的 Artifact 口径。
- 关闭数据上报后是否不发送产品分析事件,敏感内容是否不会进入事件。
3. 核心不变量
下列条件任一不满足,本次改造不能发布:
- 每个实际创建的 Run 有唯一
run_id,正常终态只有一条run_finished。 - 首 Run 满足
initial_run_id = run_id、task_run_index = 0。 - 手动重试、Resume、授权后重试、切模型、切 Runtime 和澄清回答复用原
task_execution_id,新 Run 的task_run_index加一。 - 同一 Run 内的自动重试不改变
run_id,也不增加task_run_index。 - 任一关联 Run 有效成功后,Task 状态不可逆地成为
success。 result = success但clarification_requested = true的 Run 不是有效成功 Run。run_activity.artifacts.changed_file_count只解释文件变化规模,不能直接决定 Task 是否成功。primary_artifact_change在 Ask、Plan、澄清 Run 和 Design System Run 中省略。- 未采集到的值省略;不能为了字段完整而伪造为
0。 - v4 事件继续携带兼容期要求的旧字段;旧版本继续保持自身的 v2/v3 Schema,不改写历史数据。
4. 测试分层
flowchart LR
A["契约与纯函数测试"] --> B["Daemon / Web 集成测试"]
B --> C["本地真实线协议测试\nMock CLI + 假 PostHog"]
C --> D["Playwright 用户链路\n失败 → 引导 → 恢复"]
D --> E["PostHog 测试项目\n落库、嵌套查询、Task 统计"]
E --> F["灰度双写对账\n旧字段 vs v4 字段"]
| 层级 | 验证内容 | 是否依赖外部系统 |
|---|---|---|
| L1 契约/单元 | 类型、聚合映射、Token 和 Artifact 口径、Task ID 继承 | 否 |
| L2 应用集成 | 消息持久化、恢复回调、Daemon 终态补偿、启动拦截 | 否 |
| L3 线协议 E2E | 实际 Daemon 发送的 PostHog batch、压缩、公共参数、隐私 | 否,使用本地接收器 |
| L4 UI E2E | 用户看到恢复动作、点击、创建目标 Run、最终成功 | 否,使用 Mock CLI |
| L5 PostHog 验收 | JSON 对象真实落库、点号查询、HogQL、事件延迟 | 是,使用独立测试项目 |
| L6 灰度对账 | 新旧字段覆盖率和线上真实 Provider 差异 | 是,使用 beta/prerelease 数据 |
5. 测试数据准备
5.1 基础条件
- 使用本分支构建的应用,确认
event_schema_version = 4。 - 使用隔离的开发命名空间,并按仓库的 Daemon data directory contract 提供隔离数据根。
- 正向用例开启 Metrics;反向用例明确关闭 Metrics。
- 每个测试创建独立
project_id、conversation_id和安装身份,避免与其他验证数据混淆。 - Provider 行为优先使用
mocks/中的匿名真实 Trace 回放,避免消耗额度并保证失败可复现。 - 发布前再对当前真实 AMR、Claude Code、Codex CLI 各做一次最小 Smoke,用于发现 CLI 版本或流格式漂移。
5.2 黄金任务集
AMR、Claude Code、Codex 每个 Provider 分别执行以下 4 个 Task:
| Task | Run 序列 | 最终 Task 状态 |
|---|---|---|
| T1 | 首 Run 直接成功 | success |
| T2 | 首 Run 系统失败,不恢复 | system_failed |
| T3 | 首 Run 失败 → 手动重试成功 | success |
| T4 | 首 Run 请求澄清 → 用户回答 → 新 Run 成功 | success |
每个 Provider 的固定预期:
- Task 数:4。
- Task 成功数:3。
- Task 系统失败数:1。
- Task 成功率:75%。
- Run 数:6。
- 有效成功 Run:3。
- 系统失败 Run:2。
- 澄清 Run:1,不进入有效成功/失败分母。
- 有效 Run 成功率:
3 / (3 + 2) = 60%。
这组数据是上线验收基线。如果结果仍显示 Task 成功率 60%,说明查询仍在错误地按 Run 统计。
6. 测试用例矩阵
A. Task 与恢复链路
| ID | 优先级 | 测试场景 | 前置条件与步骤 | 预期结果 |
|---|---|---|---|---|
| A01 | P0 | 三个 Provider 首 Run 成功 | 分别选择 AMR、Claude Code、Codex,发送可稳定完成的 Design 任务 | 各产生一对 run_created/run_finished;Task 字段完整;result=success |
| A02 | P0 | 首 Run 最终失败 | Mock CLI 返回可分类的终态错误,不点击恢复 | run_finished.result=failed;失败分类、原因和阶段存在;Task 为 system_failed |
| A03 | P0 | 手动重试挽回 | 首 Run 失败,点击 Retry,第二个 Run 成功 | 两个 Run ID 不同、Task ID 相同;第二个 task_run_index=1、source_run_id 指向首 Run;Task 只算一次成功 |
| A04 | P0 | Resume 挽回 | 制造可恢复的中断,点击 Continue | 目标 Run 使用 recovery_action_type=resume_run;曝光、点击、创建和完成共享 recovery_action_instance_id |
| A05 | P0 | 切 Runtime 挽回 | Claude/Codex 失败后切换到 AMR 并重试成功 | Task 归属仍是首 Run Provider;恢复分析可看到源 Provider 和目标 amr |
| A06 | P0 | 切模型挽回 | 制造模型不可用,选择其他模型重试 | 新 Run 使用实际目标模型;recovery_action_type=switch_model_retry;Task ID 不变 |
| A07 | P0 | 授权后重试 | AMR 返回登录/授权错误,完成授权后自动或手动重试 | 新 Run 使用 authorize_and_retry;重复的已登录轮询不能重复创建 Run |
| A08 | P0 | 澄清回答 | 首 Run 输出 <question-form>,用户提交回答,后续 Run 成功 |
首 Run clarification_requested=true,不算有效成功;回答 Run 与原 Task 相连并最终使 Task 成功 |
| A09 | P0 | 同 Run 自动重试 | 首次 Provider 调用遇到可自动重试错误,第二次调用成功 | run_id 和 Task 序号不变;只有一条最终 run_finished;automatic_retry.retry_count>0 且最终成功 |
| A10 | P0 | 启动前拦截后恢复 | BYOK 缺 Key/模型,点击发送被拦截;修复配置后重新发送同一 Draft | 先出现 element=run_start_blocked,当时没有 Run;恢复后首 Run 仍为 task_run_index=0 且继承原 Task ID |
| A11 | P1 | 连续两次恢复 | 首 Run 失败 → Retry 仍失败 → 切 Runtime 成功 | 三个 Run 的 task_run_index 为 0/1/2;每个 source_run_id 指向直接上一个 Run;Task 只归一为一次成功 |
| A12 | P1 | 成功后的普通继续修改 | 一个 Task 成功后,用户在 Composer 发起新的修改意图 | 新 Run 使用新的 task_execution_id 和 task_run_index=0,不能错误继承上一个成功 Task |
| A13 | P1 | 用户取消 | 用户主动停止唯一 Run | Task 归类为 user_cancelled,不进入系统失败率分母 |
| A14 | P1 | 仍在运行 | 查询时 Run 尚未终止 | Task 归类为 in_progress,不提前判为成功或失败 |
B. Schema、聚合和兼容
| ID | 优先级 | 测试场景 | 前置条件与步骤 | 预期结果 |
|---|---|---|---|---|
| B01 | P0 | run_created v4 必填字段 |
创建任意 Run 并检查原始事件 | 高频顶层字段、Task 字段、has_attachments、tokens 存在;Schema 为 4 |
| B02 | P0 | run_finished v4 必填字段 |
完成成功和失败 Run | result、clarification_requested、timing.total_duration_ms 存在;失败 Run 带失败事实 |
| B03 | P0 | 兼容期双写 | 检查同一 v4 事件的新旧字段 | entry_from/entry_source、session_mode/interaction_mode、旧 Token/新 tokens 等同时存在且事实值一致 |
| B04 | P0 | 缺失值不伪造成 0 | 使用没有 Provider Usage 的 Trace | 未观测计数省略;usage_count_source=unknown;不能出现虚假的 Provider Token 0 |
| B05 | P0 | 旧版本事件兼容 | 向测试项目发送一条 v3 Fixture,再发送 v4 事件 | 两者都可查询;只依赖旧字段的兼容查询仍能读取 v4 双写数据;历史 v3 不要求拥有 v4 对象 |
| B06 | P0 | 消息升级和重载 | 从旧数据库启动,执行一次 Run,重启后加载消息并 Retry | 增量迁移成功;taskAnalytics 持久化;重载后的 Retry 继续原 Task |
| B07 | P0 | Daemon 终态补偿 | Run 已终止但正常分析完成标记缺失,重启 Daemon | 补发一条带 v4 Task/聚合字段的 run_finished;再次重启不重复补发 |
| B08 | P1 | 数组字段 | 一个 Run 使用多个 Skill/MCP | capabilities.skill_ids、mcp_server_ids 保持数组;兼容期单值字段仍按旧规则发送 |
| B09 | P1 | Provider ID 标准化 | 分别由 AMR、Claude、Codex 触发恢复动作 | 所有 Run 和恢复事件统一使用 amr、claude_code、codex_cli,不能混入 claude/codex |
C. Token 口径
| ID | 优先级 | 测试场景 | 前置条件与步骤 | 预期结果 |
|---|---|---|---|---|
| C01 | P0 | Anthropic additive | 回放包含输入、cache read、cache write 的 Claude Trace | input_accounting_mode=additive;Provider 原始值和统一有效输入值均正确 |
| C02 | P0 | OpenAI inclusive | 回放 Codex/OpenAI 风格 Usage | input_accounting_mode=inclusive;缓存读取不被重复加到有效输入 |
| C03 | P0 | 未知 Usage | Provider 只给 total 或不给 Usage | 未知拆分省略;来源标记为 unknown 或真实的 provider_usage,不能捏造 input/output |
| C04 | P0 | 首次模型调用 | 一个 Run 内有多次模型调用 | tokens.first_model_call 来自第一次调用,不得误取最后一次或累计值 |
| C05 | P0 | 新旧 Token 对账 | 对同一批 v4 事件比较旧扁平字段和 tokens |
同口径事实值 100% 一致;可派生的比率不要求在新对象重复发送 |
| C06 | P1 | JSON 数值类型 | 在 PostHog SQL 中读取 Token 嵌套字段 | 返回 Numeric/Integer,而不是序列化后的 JSON 字符串 |
D. Artifact 口径
| ID | 优先级 | 测试场景 | 前置条件与步骤 | 预期结果 |
|---|---|---|---|---|
| D01 | P0 | 首次生成 HTML | 项目没有已生成 Artifact,Run 修改预置种子 HTML | primary_artifact_change=created,不能因路径已存在误记为 modified |
| D02 | P0 | 多轮修改 HTML | 已有生成结果,第二个 Run 修改 HTML 内容 | primary_artifact_change=modified;modified_file_count 增加 |
| D03 | P0 | 只改 CSS/JS/TSX | 已有 HTML 主 Artifact,仅改变渲染依赖 | 旧 artifact_count 不膨胀;主 Artifact 记为 modified |
| D04 | P0 | HTML 加辅助 PNG | 同一 Run 修改 HTML 并新增多张图片 | 只有一个主 Artifact 变化;图片进入 supporting_asset_files_changed_count |
| D05 | P0 | 媒体项目 | 分别生成/修改图片、视频、音频 | 对应媒体是主 Artifact;首次为 created,后续为 modified |
| D06 | P0 | 同内容重写/仅时间变化 | 重写相同内容或只改变 mtime | 旧计数允许保持兼容语义;v4 内容变化计数为 0,主 Artifact 为 none |
| D07 | P0 | Ask、Plan、澄清 | 三类 Run 均不要求设计产出 | 省略 primary_artifact_change,不能发送 none 冒充产出判断 |
| D08 | P0 | Design System Run | 新建和修改 DESIGN.md |
使用 design_system.change_type;不重复发送 primary_artifact_change |
| D09 | P1 | 失败前已写部分文件 | Agent 写入文件后最终失败 | 可记录文件变化,但 result 和 Task 仍为失败;不能因 Artifact 写入改成成功 |
E. 恢复动作事件
| ID | 优先级 | 测试场景 | 前置条件与步骤 | 预期结果 |
|---|---|---|---|---|
| E01 | P0 | CTA 曝光 | 打开带 Retry、Continue 或切 AMR 的失败卡 | 每个实际展示的 CTA 各有一条 surface_view,不能只报动作数组 |
| E02 | P0 | CTA 点击 | 点击恢复 CTA | 发送 ui_click;ID 和动作类型与对应曝光一致 |
| E03 | P0 | 点击到 Run | CTA 点击后目标 Run 创建 | recovery_action_instance_id 从点击透传到 run_created 和 run_finished |
| E04 | P0 | 切换目标 | 点击切模型或切 Runtime | Click 事件包含目标 Provider/模型;目标 Run 上报实际使用值 |
| E05 | P1 | 重复渲染去重 | 失败卡因状态更新多次重渲染 | 同一 CTA 实例不会重复发送曝光;真实再次展示的新实例可以发送 |
| E06 | P1 | 点击但未启动 | 用户点击后授权失败或关闭流程 | 有曝光/点击但没有目标 Run;不能虚构一次 Run 尝试或改变原 Task 结果 |
F. 隐私、去重和稳定性
| ID | 优先级 | 测试场景 | 前置条件与步骤 | 预期结果 |
|---|---|---|---|---|
| F01 | P0 | Metrics 关闭 | 关闭 Metrics 后完成 Run | 不发送 run_created、run_finished 和恢复 UI 产品分析事件 |
| F02 | P0 | 敏感内容扫描 | Prompt、响应、路径、Key 使用唯一哨兵字符串 | 捕获的事件序列中不包含任何哨兵字符串或绝对路径 |
| F03 | P0 | 终态去重 | 模拟正常结束、断线重连和 Daemon 重启 | 同一 Run 的有效 run_finished 只有一条,或由相同 insert ID 被 PostHog 去重 |
| F04 | P1 | PostHog 延迟 | 事件写入测试项目后轮询查询 | 在约定超时内可查到;测试使用轮询而不是固定 sleep |
| F05 | P1 | 大对象和数组 | Skill/MCP 数量达到产品允许上限 | 事件没有因 Payload 过大被丢弃;聚合对象结构完整 |
7. P0 详细执行场景
场景 1:首 Run 直接成功
测试目标: 验证三个目标 Provider 的基础 Run 事件和 v4 字段。
起始条件:
- 新项目、新对话,Metrics 已开启。
- Mock CLI 配置为一次成功并生成可识别 Artifact。
- 当前 Provider 依次为 AMR、Claude Code、Codex。
执行步骤:
- 在 Design 模式发送唯一 Prompt。
- 等待应用显示完成,并确认预览中存在结果。
- 等待 PostHog 接收
run_created和run_finished。 - 用
run_id查询两条事件并比较公共、Task 和聚合字段。
预期结果:
agent_provider_id分别为amr、claude_code、codex_cli。- 首 Run 的 Task ID 在 created/finished 中一致,
initial_run_id=run_id、task_run_index=0。 result=success,clarification_requested=false。- 新旧字段同时存在;
tokens、timing可作为 JSON 查询。
场景 2:首轮失败,手动重试成功
测试目标: 验证重试成功后 Task 不再被统计为失败。
起始条件: Mock CLI 第一条 Run 固定失败,第二条 Run 固定成功。
执行步骤:
- 发送任务并等待失败卡出现。
- 记录失败 Run ID。
- 确认 Retry CTA 曝光,点击 Retry。
- 等待第二条 Run 成功。
- 按
task_execution_id查询全部事件并执行 Task 状态查询。
预期结果:
- 首 Run
result=failed,第二 Runresult=success。 - 两个 Run 共用 Task ID;第二 Run 的
source_run_id等于首 Run ID。 - 曝光、点击、第二 Run 共享一个恢复动作实例 ID。
- 该 Task 最终只计为一个
success,不同时保留一个失败 Task。
场景 3:切 Runtime 到 AMR 后成功
测试目标: 验证 Task 主归属和恢复效果归因互不混淆。
起始条件: Claude Code 或 Codex 首 Run 固定失败;AMR 固定成功。
执行步骤:
- 使用源 Provider 发送任务并等待失败。
- 点击切换到 AMR 并重试。
- 等待 AMR Run 成功。
- 查询 Task 和恢复动作链路。
预期结果:
- Task 主看板仍归属首 Run Provider。
- 恢复点击包含源 Provider 和目标
amr。 - 目标 Run 的
agent_provider_id=amr,Task ID 不变。 - 恢复分析将成功归因给
switch_runtime_retry。
场景 4:请求澄清后回答成功
测试目标: 验证澄清 Run 不会提前把任务记为成功或失败。
起始条件: Mock CLI 首 Run 输出合法 <question-form>,回答后生成 Artifact。
执行步骤:
- 发送含歧义的任务。
- 确认 Questions 面板出现,首 Run 正常结束。
- 提交回答。
- 等待回答 Run 成功。
- 查询两条 Run 和最终 Task 状态。
预期结果:
- 首 Run
result=success且clarification_requested=true,不是有效成功。 - 回答 Run 使用
recovery_action_type=question_answer、task_run_index=1。 - 最终 Task 为 success;成功归因来自回答 Run。
场景 5:启动前拦截后恢复
测试目标: 验证尚未创建 Run 的配置拦截仍能保留 Task 身份。
起始条件: BYOK 选择已启用,但缺少 API Key 或模型。
执行步骤:
- 输入唯一 Draft 并点击发送。
- 确认跳转设置且没有创建 Run。
- 修复配置,返回并重新发送同一个 Draft。
- 等待 Run 成功。
预期结果:
- 拦截时产生
run_start_blocked,没有run_created。 - 恢复后的 Run 继承拦截事件 Task ID。
- 因此前没有 Run,恢复 Run 仍为
task_run_index=0且initial_run_id=run_id。
场景 6:自动重试成功
测试目标: 验证同 Run 自动重试不会膨胀 Run/Task 失败数。
起始条件: Mock Trace 首次返回可自动重试的临时错误,下一次成功。
执行步骤:
- 发送任务并等待自动重试完成。
- 查询所有
run_retry_attempted、run_retry_finished和最终run_finished。 - 按 Run ID 和 Task ID 计数。
预期结果:
- 只有一个 Run ID、一条最终
run_finished。 automatic_retry.retry_count > 0且outcome=success。- Task 只包含一个
task_run_index=0的 Run。
场景 7:Artifact 首次生成与后续微调
测试目标: 验证作品变化与底层文件变化分离。
起始条件: Prototype 项目带种子 HTML,后续可修改 CSS 和新增 PNG。
执行步骤:
- 首 Run 修改种子 HTML,生成第一个可用预览。
- 第二 Run 仅修改 CSS。
- 第三 Run 修改 HTML 并新增两张 PNG。
- 第四 Run 重写相同内容。
预期结果:
- 四次
primary_artifact_change依次为 created、modified、modified、none。 - CSS 修改不增加旧
artifact_count,但能修改主 Artifact。 - 两张 PNG 进入辅助资源计数,不把一个作品解释成三个主 Artifact。
- 相同内容重写不进入 v4 内容变化计数。
场景 8:Daemon 重启补偿
测试目标: 验证异常退出不丢终态,也不会重复上报。
起始条件: 运行日志中存在已终止但分析未完成的 Run 状态。
执行步骤:
- 在终态事件完成标记落盘前终止 Daemon。
- 重启 Daemon,等待终态扫描。
- 查询补发事件。
- 再重启一次并再次查询。
预期结果:
- 首次重启补发一条
terminal_reconciled=true的 v4run_finished。 - 任务字段、失败分类、Timing 和 Token 来源仍可用。
- 第二次重启不再新增同一终态事件。
8. 推荐的端到端验证方法
8.1 第一阶段:本地线协议 E2E,作为 PR 必过项
新增一个 Daemon 集成测试,结构可复用现有媒体分析测试的本地 PostHog 接收器:
- 启动本地 HTTP 接收器,接收 PostHog batch,并支持解压 gzip。
- 将
POSTHOG_HOST指向接收器,提供测试 Key,开启 Metrics。 - 启动真实 Daemon,但把 AMR、Claude、Codex CLI 替换为仓库 Mock Trace。
- 通过真实
/api/runs创建 Run,而不是直接调用聚合 Builder。 - 收集
run_created、run_finished,断言完整 Payload、公共参数和隐私。 - 对一次失败和一次恢复成功提交相同
task_execution_id,验证线协议上的任务归并。
建议新增文件:apps/daemon/tests/run-analytics-v4-e2e.test.ts。
这一层能证明“应用真的把正确 JSON 发出了”,但不能证明 PostHog 已按 JSON 类型存储、点号查询可用。
8.2 第二阶段:Playwright 用户链路,作为 PR 必过项
新增一条 UI E2E,使用真实 Web + Daemon + Mock CLI:
- 打开全新项目并发送任务。
- Mock CLI 让首 Run 失败,页面出现恢复卡。
- 点击 Retry、Continue 或切 AMR。
- 第二个 Mock Run 成功。
- 从
/api/runs请求体读取analyticsHints.taskExecutionId和恢复动作实例 ID。 - 断言两个请求的任务链路,并检查最终 UI Artifact 可用。
建议新增文件:e2e/ui/run-analytics-recovery.spec.ts。
这一层证明“用户动作确实驱动了正确的目标 Run”,避免组件单测通过但真实页面没有透传。
8.3 第三阶段:PostHog 独立测试项目,作为发布必过项
本地接收器不能替代真实 PostHog。发布前使用独立测试项目完成以下验证:
- 给测试构建配置测试项目的 Ingest Key,并使用唯一环境标记。
- 执行黄金任务集,每个 Provider 4 个 Task。
- 通过 PostHog SQL Editor 或 Query API 轮询事件,不能用固定等待时间。
- 验证以下嵌套路径可直接查询和 Breakdown:
properties.tokens.effective_input_tokensproperties.timing.total_duration_msproperties.diagnostics.runtime_timing.model_first_token_msproperties.run_activity.artifacts.changed_file_count
- 验证黄金数据的 Task 成功率为 75%,有效 Run 成功率为 60%。
- 导出少量查询结果,确认对象没有被整体序列化成字符串。
PostHog 当前支持用点号访问嵌套/JSON 属性,也支持在筛选、趋势和 Breakdown 中使用 SQL 表达式,具体见 SQL expressions。通过 Query API 查询时,需要项目 ID 和具有 Query Read 权限的个人 API Key。
8.4 第四阶段:灰度双写对账,作为停止旧字段的前置条件
beta/prerelease 上线后至少观察一个完整版本周期且不少于 14 天:
- 按
agent_provider_id × app_version × runtime_type检查 v4 覆盖率。 - 新旧同口径事实字段逐事件对账,目标为 100% 一致。
- Artifact 的新旧差异必须能由“同内容重写/mtime”和“渲染依赖”规则解释。
- 检查旧看板是否把 Schema 精确限制为 3;如有,先改为兼容 3/4 或使用版本化查询。
- 只有看板、告警、导出和查询全部切换后,才能停止旧扁平字段。
9. PostHog 验收查询
9.1 查询单个 Task 的完整链路
SELECT
timestamp,
event,
properties.run_id,
properties.task_execution_id,
properties.initial_run_id,
properties.source_run_id,
properties.task_run_index,
properties.recovery_action_type,
properties.recovery_action_instance_id,
properties.agent_provider_id,
properties.model_id,
properties.result,
properties.clarification_requested
FROM events
WHERE properties.task_execution_id = '<task-id>'
ORDER BY timestamp
验收:Run 顺序、恢复来源、动作 ID 和最终结果与 UI 操作完全一致。
9.2 验证嵌套字段真实可查
SELECT
properties.run_id,
properties.tokens.effective_input_tokens,
properties.tokens.first_model_call.cache_read_tokens,
properties.timing.total_duration_ms,
properties.diagnostics.runtime_timing.model_first_token_ms,
properties.run_activity.artifacts.changed_file_count
FROM events
WHERE event = 'run_finished'
AND properties.event_schema_version = 4
AND properties.project_id = '<e2e-project-id>'
ORDER BY timestamp
验收:字段可直接读取;数值可以参与求和、平均值和 Breakdown,而不是一整段字符串。
9.3 Task 成功率
WITH task_outcomes AS (
SELECT
properties.task_execution_id AS task_id,
max(if(
properties.result = 'success'
AND properties.clarification_requested = false,
1,
0
)) AS has_success,
max(if(properties.result = 'failed', 1, 0)) AS has_system_failure
FROM events
WHERE event = 'run_finished'
AND properties.event_schema_version = 4
AND properties.project_id = '<golden-project-id>'
GROUP BY task_id
)
SELECT
count() AS eligible_tasks,
sum(has_success) AS successful_tasks,
avg(has_success) AS task_success_rate
FROM task_outcomes
WHERE has_success = 1 OR has_system_failure = 1
黄金任务集验收:每个 Provider 单独查询均为 eligible_tasks=4、successful_tasks=3、task_success_rate=0.75。
9.4 新旧 Token 双写一致性
SELECT
count() AS finished_events,
sum(if(
properties.input_tokens_effective
= properties.tokens.effective_input_tokens,
1,
0
)) AS matching_events
FROM events
WHERE event = 'run_finished'
AND properties.event_schema_version = 4
AND properties.input_tokens_effective IS NOT NULL
AND properties.project_id = '<e2e-project-id>'
验收:同口径事实字段 matching_events = finished_events。
10. 当前自动化覆盖与缺口
已覆盖
packages/contracts/tests/analytics-run-finished-contract.test.ts:v4 类型、聚合对象和兼容字段。apps/web/tests/analytics/run-task-analytics.test.ts:Task 初始化、重载回退和跨 Run 继承。apps/web/tests/components/ChatPane.resume-failed.test.tsx:Resume CTA 曝光、点击和回调。apps/web/tests/components/ProjectView.run-isolation.test.tsx:启动前拦截。apps/web/tests/components/ProjectView.run-cleanup.test.tsx:真实 ProjectView 发送、终态和重载主链路。e2e/ui/amr-run-failure-recovery.test.ts:真实 UI 失败卡、Retry、目标 Run 和成功内容恢复链路。apps/daemon/tests/run-analytics-observability.test.ts:Provider Usage、缓存和首次调用 Token。apps/daemon/tests/run-artifact-fs.test.ts:HTML/媒体、内容变化、CSS 依赖和种子 HTML。apps/daemon/tests/db-message-events.test.ts:Task analytics 消息持久化。apps/daemon/tests/runtimes/run-terminal-reconciliation.test.ts:Daemon 重启补偿。
尚未覆盖,建议补齐后再合并
- 尚无一条测试通过真实
/api/runs把本次 v4run_created/run_finished发到本地 PostHog 接收器。 - 尚未在真实 PostHog 测试项目验证嵌套字段的点号查询、Breakdown 和导出。
- 尚未执行 beta/prerelease 的新旧字段生产样本对账。
11. 发布门槛
| 阶段 | 必须满足 |
|---|---|
| PR 合并前 | 当前单元/集成测试和现有 UI 恢复链路通过;新增本地线协议 E2E;类型检查和 guard 通过 |
| Beta 前 | PostHog 测试项目完成黄金任务集;三个 Provider 均有当前 CLI 真实 Smoke |
| Prerelease 前 | Task 成功率和 Run 成功率查询经产品、数据、研发三方核对;错误分布可按三层分类拆分 |
| 停止旧字段前 | 双写至少一个完整版本周期且不少于 14 天;新旧同口径字段 100% 对账;所有看板与导出已迁移 |
12. 最终建议
端到端验收应以“黄金任务集查询结果”为最终判定,而不是以“Network 面板里看到了事件”为判定。推荐形成下面的固定闭环:
- Mock Trace 产生确定性的成功、失败、澄清和恢复序列。
- 真实 UI 驱动这些序列,验证用户动作和 Task ID 继承。
- Daemon 通过真实 SDK 发送事件,验证线协议与隐私。
- PostHog 测试项目执行相同 Task 查询,验证最终 75% Task 成功率。
- Beta 数据继续做新旧字段双写对账,确认无 Provider/版本覆盖盲区。
只有五层都通过,才能说明本次埋点改造不仅“代码正确”,而且“统计结果可信”。