Explorar el Código

test: 添加代码审查回归、截图报告与数据库验证记录

main
leiyun hace 2 semanas
padre
commit
8df908a58e
Se han modificado 100 ficheros con 3220 adiciones y 0 borrados
  1. BIN
     
  2. BIN
     
  3. BIN
     
  4. BIN
     
  5. +67
    -0
      reports/review-20260905/backend-review.html
  6. +51
    -0
      reports/review-20260905/backend-review.md
  7. BIN
     
  8. +56
    -0
      reports/review-20260905/cleanup.json
  9. +143
    -0
      reports/review-20260905/database-migration.json
  10. +73
    -0
      reports/review-20260905/database-review.html
  11. +56
    -0
      reports/review-20260905/database-review.md
  12. +54
    -0
      reports/review-20260905/diagnostics/cleanup-h264-probe.json
  13. +52
    -0
      reports/review-20260905/diagnostics/cleanup-initial.json
  14. +52
    -0
      reports/review-20260905/diagnostics/cleanup-resource-wait.json
  15. BIN
     
  16. BIN
     
  17. BIN
     
  18. BIN
     
  19. +315
    -0
      reports/review-20260905/diagnostics/live-chromium-h264-unsupported.json
  20. +283
    -0
      reports/review-20260905/diagnostics/live-resource-wait-timeout.json
  21. +41
    -0
      reports/review-20260905/diagnostics/video-probe-msedge.json
  22. +36
    -0
      reports/review-20260905/diagnostics/video-probe.json
  23. +29
    -0
      reports/review-20260905/environment.json
  24. BIN
     
  25. BIN
     
  26. +67
    -0
      reports/review-20260905/frontend-e2e/README.md
  27. BIN
     
  28. BIN
     
  29. BIN
     
  30. BIN
     
  31. BIN
     
  32. BIN
     
  33. BIN
     
  34. BIN
     
  35. BIN
     
  36. BIN
     
  37. BIN
     
  38. BIN
     
  39. BIN
     
  40. BIN
     
  41. BIN
     
  42. BIN
     
  43. BIN
     
  44. +76
    -0
      reports/review-20260905/frontend-e2e/html/index.html
  45. +794
    -0
      reports/review-20260905/frontend-e2e/results.json
  46. BIN
     
  47. BIN
     
  48. BIN
     
  49. BIN
     
  50. BIN
     
  51. BIN
     
  52. BIN
     
  53. BIN
     
  54. BIN
     
  55. BIN
     
  56. BIN
     
  57. BIN
     
  58. BIN
     
  59. BIN
     
  60. BIN
     
  61. BIN
     
  62. BIN
     
  63. +4
    -0
      reports/review-20260905/frontend-e2e/test-results/.last-run.json
  64. BIN
     
  65. BIN
     
  66. BIN
     
  67. BIN
     
  68. BIN
     
  69. BIN
     
  70. BIN
     
  71. BIN
     
  72. BIN
     
  73. BIN
     
  74. BIN
     
  75. BIN
     
  76. BIN
     
  77. BIN
     
  78. BIN
     
  79. BIN
     
  80. BIN
     
  81. +100
    -0
      reports/review-20260905/frontend-review.html
  82. +68
    -0
      reports/review-20260905/frontend-review.md
  83. +295
    -0
      reports/review-20260905/index.html
  84. +119
    -0
      reports/review-20260905/live/api-evidence.json
  85. +24
    -0
      reports/review-20260905/live/cleanup-manifest.json
  86. BIN
     
  87. BIN
     
  88. BIN
     
  89. BIN
     
  90. BIN
     
  91. BIN
     
  92. BIN
     
  93. BIN
     
  94. BIN
     
  95. BIN
     
  96. +76
    -0
      reports/review-20260905/live/html/index.html
  97. +285
    -0
      reports/review-20260905/live/results.json
  98. +4
    -0
      reports/review-20260905/live/results/.last-run.json
  99. BIN
     
  100. BIN
     




+ 67
- 0
reports/review-20260905/backend-review.html Ver fichero

@@ -0,0 +1,67 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>backend-review · Code Review</title><style>*{box-sizing:border-box}body{margin:0;background:#edf2f7;color:#203248;font:16px/1.75 "Microsoft YaHei","Segoe UI",sans-serif}header{background:#0c2b47;color:white;padding:22px max(24px,calc((100vw - 1160px)/2))}header a{color:#c1e6ff;margin-right:24px}main{max-width:1200px;margin:24px auto 60px;padding:36px;background:white;border-radius:12px;box-shadow:0 6px 24px #1028400c}h1{font-size:30px;line-height:1.4;margin-top:0}h2{font-size:22px;margin:36px 0 14px;border-bottom:2px solid #dbe8f2;padding-bottom:8px}h3{font-size:18px}p{margin:13px 0}a{color:#126fba;text-decoration:none}a:hover{text-decoration:underline}table{border-collapse:collapse;width:100%;margin:18px 0;font-size:14px;table-layout:auto}th,td{padding:11px 13px;border:1px solid #dce5ed;text-align:left;vertical-align:top;overflow-wrap:anywhere}th{background:#edf5fc}tr:nth-child(even){background:#f9fbfd}code{font:13px/1.5 Consolas,monospace;background:#edf2f7;padding:2px 5px;border-radius:4px;overflow-wrap:anywhere}pre{padding:18px;overflow:auto;background:#edf2f7;border-radius:8px}pre code{padding:0}img{max-width:100%;height:auto}details{padding:12px;background:#f7f9fc;margin:12px 0}.gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}figure{margin:0;border:1px solid #dce5ed;border-radius:8px;overflow:hidden;background:#f8fafc}figure img{display:block;width:100%;height:300px;object-fit:contain;background:#e8eef3}figcaption{padding:12px;font-size:14px;color:#37526b}footer{color:#657c91;font-size:13px;margin-top:36px}li{margin:8px 0}@media(max-width:760px){main{margin:0;padding:20px;border-radius:0}.gallery{grid-template-columns:1fr}table{display:block;overflow:auto}h1{font-size:25px}header{padding:16px}}@media print{body{background:white}main{margin:0;box-shadow:none;max-width:none}header{display:none}figure{break-inside:avoid}h2{break-after:avoid}}</style></head><body><header><a href="index.html">审查总报告</a><a href="live/html/index.html">真实 E2E</a><a href="frontend-e2e/html/index.html">模拟接口回归</a></header><main><h1 id="后端增量-code-review">后端增量 Code Review</h1>
<p>日期:2026-09-05。仓库:<code>unreal_tran_api</code>;基线:<code>3307c23 → b683bb7</code>,57 个文件。审查开始时后端工作树干净;审查完成后按用户授权提交为 <code>53d860b</code>。</p>
<h2 id="资料-原型-设计-开发-测试">资料 → 原型 → 设计 → 开发 → 测试</h2>
<p>资料和原型追溯见同目录 <a href="reference-review.html">reference-review.md</a>。本轮沿用既有“三渠道共享训练定义、失败可以重试、全过程留痕”的设计,未扩展评分、提前提交或渠道分类。</p>
<p>后端对照以下正式设计审查:<code>wiki/19-训练定义渠道合同.md</code>、<code>wiki/20-教学命令幂等.md</code>、<code>wiki/21-教学步骤运行事实.md</code>,并交叉核对 <code>wiki/14-教学实施模块.md</code>、接口总览、033/034 增量和完整 DDL。审查覆盖多渠道发布预检与固定快照、教员内容写权限、三个登录身份、四类命令回执、逐步推进/失败/重试/提交门槛、历史无事实兼容、正式考核和学员访问隔离。</p>
<h2 id="已修复旧尝试的迟到证据错误完成重试">已修复:旧尝试的迟到证据错误完成重试</h2>
<p>严重度:P2。位置:<code>TeachingStepRunService.validateEvidenceTime</code>。</p>
<p>重现顺序:步骤首次开始时间为 100,失败时间为 110,重新开始时间为 120;重试后补报发生于 115 的成功事件。旧实现只与首次 <code>startedAt</code> 和最近 <code>failedAt</code> 比较,因此把重试开始前的证据计入第二次尝试,错误将步骤变为 <code>COMPLETED</code>。同类迟到失败事件也可错误增加当前尝试失败次数。</p>
<p>先添加回归用例并在旧实现运行,测试失败,错误为“预期抛出 BusinessException,但没有抛出”。证据:<a href="backend-repro.log">backend-repro.log</a>。</p>
<p>修复使用已有 <code>last_event_id</code> 指向的重试激活事件,比较其发生时间;旧尝试的 <code>MATCHED/UNMATCHED</code> 返回 HTTP 400。首次开始时间仍保留,同秒或重试之后的有效事件仍可推进。缺失或跨运行的激活证据拒绝处理。没有新增字段、接口或训练步骤,也没有回填历史事实。</p>
<p>新增测试同时验证迟到成功、迟到失败都不改变当前状态/尝试数/失败数/最近证据;重试同秒的有效成功可以完成,并保留首次开始时间。另更新四个服务测试中的依赖装配,并同步 wiki 01/14/21。</p>
<h2 id="其他审查结论">其他审查结论</h2>
<ul>
<li>多渠道配置由发布预检验证全部启用渠道;任务按渠道固化选中的 profile,运行读取固定快照。未发现需要改变产品行为的问题。</li>
<li>内容写操作增加有效教员身份检查,并保留页面权限、动作权限和数据范围校验;纯管理员身份不能绕过教员要求。</li>
<li>命令回执在运行锁内检查,同载荷回放先于旧版本校验;首次执行的运行、事件、步骤和回执共享事务。模拟服务回归覆盖重放与冲突;真实并发和回滚以主 E2E/数据库报告为准。</li>
<li>新运行按发布步骤初始化,最后一步目标进度为 100%;历史无步骤行仅兼容读取/旧提交,不在 GET 中补写。</li>
<li>学员越权、正式考核当前步骤投影、未完成提交、撤回时不删除执行事实均有现有测试覆盖。</li>
<li>主审查在隔离 KingBase schema 发现 034 的独立唯一索引不能作为该目标版本的组合外键目标,由主审查负责新增 035 和完整 DDL 向前修复;数据库执行、恢复点与复跑证据由主报告记录。本后端子任务未连接或执行数据库。</li>
</ul>
<h2 id="实际验证">实际验证</h2>
<p>环境为 Temurin Java 17.0.20、Maven 3.9.14。</p>
<table>
<thead>
<tr>
<th>验证</th>
<th>结果</th>
<th>证据</th>
</tr>
</thead>
<tbody>
<tr>
<td>修改前 <code>mvn test -B -ntp</code></td>
<td>401 项通过:Gateway 1、Auth 86、Tran 314</td>
<td>Maven 退出码 0,完成时间 11:19:45</td>
</tr>
<tr>
<td>新增回归用例运行于旧实现</td>
<td>1 项失败,成功复现缺陷</td>
<td><a href="backend-repro.log">backend-repro.log</a></td>
</tr>
<tr>
<td>四个相关服务类定向测试</td>
<td>23 项通过</td>
<td><a href="backend-targeted.log">backend-targeted.log</a></td>
</tr>
<tr>
<td>修改后全量 <code>mvn test -B -ntp</code></td>
<td>402 项通过:Gateway 1、Auth 86、Tran 315;0 失败、0 错误、0 跳过</td>
<td><a href="backend-mvn-test.log">backend-mvn-test.log</a>,11:24:52 完成</td>
</tr>
<tr>
<td><code>mvn -DskipTests package -B -ntp</code></td>
<td>三个服务打包成功,退出码 0</td>
<td><a href="backend-package.log">backend-package.log</a>,11:25:19 完成</td>
</tr>
<tr>
<td><code>git diff --check</code></td>
<td>通过,无空白错误</td>
<td>仅出现既有 CRLF 归一化提示</td>
</tr>
</tbody>
</table>
<p>测试为本地单元/服务装配验证。名称含 <code>IntegrationTest</code> 的相关测试仍使用 Mockito mapper;不能把它们作为真实数据库并发、设备接入或浏览器 E2E 已通过的证据。带截图的浏览器验证和真实 API/数据库检查由同目录主报告汇总。</p>
<p>已观察到的非阻断警告:JVM 类共享提示、现有弃用/unchecked 编译警告、测试刻意模拟 Redis 失败后的数据库回源警告。没有据此修改依赖版本。</p>
<h2 id="功能确认边界">功能确认边界</h2>
<p>本后端修复没有功能扩展;资料与现有产品的四项差异统一列于 <a href="reference-review.html">reference-review.md</a>,随主测试报告交用户确认。未放宽“全部步骤完成才可提交”,未引入自动评分或新渠道。</p><footer>2026-09-05 · 开发环境审查 · 图片可点击查看原尺寸</footer></main></body></html>

+ 51
- 0
reports/review-20260905/backend-review.md Ver fichero

@@ -0,0 +1,51 @@
# 后端增量 Code Review

日期:2026-09-05。仓库:`unreal_tran_api`;基线:`3307c23 → b683bb7`,57 个文件。审查开始时后端工作树干净;审查完成后按用户授权提交为 `53d860b`。

## 资料 → 原型 → 设计 → 开发 → 测试

资料和原型追溯见同目录 [reference-review.md](reference-review.md)。本轮沿用既有“三渠道共享训练定义、失败可以重试、全过程留痕”的设计,未扩展评分、提前提交或渠道分类。

后端对照以下正式设计审查:`wiki/19-训练定义渠道合同.md`、`wiki/20-教学命令幂等.md`、`wiki/21-教学步骤运行事实.md`,并交叉核对 `wiki/14-教学实施模块.md`、接口总览、033/034 增量和完整 DDL。审查覆盖多渠道发布预检与固定快照、教员内容写权限、三个登录身份、四类命令回执、逐步推进/失败/重试/提交门槛、历史无事实兼容、正式考核和学员访问隔离。

## 已修复:旧尝试的迟到证据错误完成重试

严重度:P2。位置:`TeachingStepRunService.validateEvidenceTime`。

重现顺序:步骤首次开始时间为 100,失败时间为 110,重新开始时间为 120;重试后补报发生于 115 的成功事件。旧实现只与首次 `startedAt` 和最近 `failedAt` 比较,因此把重试开始前的证据计入第二次尝试,错误将步骤变为 `COMPLETED`。同类迟到失败事件也可错误增加当前尝试失败次数。

先添加回归用例并在旧实现运行,测试失败,错误为“预期抛出 BusinessException,但没有抛出”。证据:[backend-repro.log](backend-repro.log)。

修复使用已有 `last_event_id` 指向的重试激活事件,比较其发生时间;旧尝试的 `MATCHED/UNMATCHED` 返回 HTTP 400。首次开始时间仍保留,同秒或重试之后的有效事件仍可推进。缺失或跨运行的激活证据拒绝处理。没有新增字段、接口或训练步骤,也没有回填历史事实。

新增测试同时验证迟到成功、迟到失败都不改变当前状态/尝试数/失败数/最近证据;重试同秒的有效成功可以完成,并保留首次开始时间。另更新四个服务测试中的依赖装配,并同步 wiki 01/14/21。

## 其他审查结论

- 多渠道配置由发布预检验证全部启用渠道;任务按渠道固化选中的 profile,运行读取固定快照。未发现需要改变产品行为的问题。
- 内容写操作增加有效教员身份检查,并保留页面权限、动作权限和数据范围校验;纯管理员身份不能绕过教员要求。
- 命令回执在运行锁内检查,同载荷回放先于旧版本校验;首次执行的运行、事件、步骤和回执共享事务。模拟服务回归覆盖重放与冲突;真实并发和回滚以主 E2E/数据库报告为准。
- 新运行按发布步骤初始化,最后一步目标进度为 100%;历史无步骤行仅兼容读取/旧提交,不在 GET 中补写。
- 学员越权、正式考核当前步骤投影、未完成提交、撤回时不删除执行事实均有现有测试覆盖。
- 主审查在隔离 KingBase schema 发现 034 的独立唯一索引不能作为该目标版本的组合外键目标,由主审查负责新增 035 和完整 DDL 向前修复;数据库执行、恢复点与复跑证据由主报告记录。本后端子任务未连接或执行数据库。

## 实际验证

环境为 Temurin Java 17.0.20、Maven 3.9.14。

| 验证 | 结果 | 证据 |
|---|---|---|
| 修改前 `mvn test -B -ntp` | 401 项通过:Gateway 1、Auth 86、Tran 314 | Maven 退出码 0,完成时间 11:19:45 |
| 新增回归用例运行于旧实现 | 1 项失败,成功复现缺陷 | [backend-repro.log](backend-repro.log) |
| 四个相关服务类定向测试 | 23 项通过 | [backend-targeted.log](backend-targeted.log) |
| 修改后全量 `mvn test -B -ntp` | 402 项通过:Gateway 1、Auth 86、Tran 315;0 失败、0 错误、0 跳过 | [backend-mvn-test.log](backend-mvn-test.log),11:24:52 完成 |
| `mvn -DskipTests package -B -ntp` | 三个服务打包成功,退出码 0 | [backend-package.log](backend-package.log),11:25:19 完成 |
| `git diff --check` | 通过,无空白错误 | 仅出现既有 CRLF 归一化提示 |

测试为本地单元/服务装配验证。名称含 `IntegrationTest` 的相关测试仍使用 Mockito mapper;不能把它们作为真实数据库并发、设备接入或浏览器 E2E 已通过的证据。带截图的浏览器验证和真实 API/数据库检查由同目录主报告汇总。

已观察到的非阻断警告:JVM 类共享提示、现有弃用/unchecked 编译警告、测试刻意模拟 Redis 失败后的数据库回源警告。没有据此修改依赖版本。

## 功能确认边界

本后端修复没有功能扩展;资料与现有产品的四项差异统一列于 [reference-review.md](reference-review.md),随主测试报告交用户确认。未放宽“全部步骤完成才可提交”,未引入自动评分或新渠道。


+ 56
- 0
reports/review-20260905/cleanup.json Ver fichero

@@ -0,0 +1,56 @@
{
"tasks": [
{
"id": 48,
"assignment_code": "UTE2E-REVIEW-MTNUE3OB-VIRTUAL"
},
{
"id": 49,
"assignment_code": "UTE2E-REVIEW-MTNUE3OB-PHYSICAL"
}
],
"deleted": {
"ut_teaching_step_run": 12,
"ut_teaching_command_receipt": 13,
"ut_teaching_xr_session": 0,
"ut_assistance_alert": 0,
"ut_assistance_evaluation": 0,
"ut_data_observation": 0,
"ut_teaching_event": 19,
"ut_teaching_intervention": 0,
"ut_teaching_assignment_fault": 0,
"ut_teaching_run_member": 2,
"ut_teaching_run": 2,
"ut_teaching_member": 2,
"ut_teaching_assignment": 2
},
"time": "2026-09-05T11:48:55.347416",
"testUsersDisabled": [
76,
77,
81,
78,
79,
74,
75,
88,
80,
82,
83,
85,
86,
87,
91,
93,
90,
94,
92
],
"originalRowsUnchanged": {
"ut_teaching_assignment": true,
"ut_teaching_run": true,
"ut_teaching_run_member": true,
"ut_teaching_event": true
},
"remainingActiveTestUsers": 0
}

+ 143
- 0
reports/review-20260905/database-migration.json Ver fichero

@@ -0,0 +1,143 @@
{
"environment": "development unreal_tran/public",
"time": "2026-09-05T11:22:17.562488",
"backup": "E:\\work\\edit_3d\\.codex-tmp\\review-20260905\\database-before.json",
"validation": [
{
"interrupted": false,
"first": [
{
"file": "20260904_033_teaching_command_receipt.sql",
"sha256": "3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43",
"checks": [
{
"invalid_teaching_command_receipts": 0
},
{
"orphan_teaching_command_receipts": 0
}
]
},
{
"file": "20260905_035_teaching_step_run_kingbase_constraints.sql",
"sha256": "53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31",
"checks": [
{
"invalid_teaching_step_runs": 0
},
{
"orphan_teaching_step_runs": 0
}
]
}
],
"repeat": [
{
"file": "20260904_033_teaching_command_receipt.sql",
"sha256": "3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43",
"checks": [
{
"invalid_teaching_command_receipts": 0
},
{
"orphan_teaching_command_receipts": 0
}
]
},
{
"file": "20260905_035_teaching_step_run_kingbase_constraints.sql",
"sha256": "53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31",
"checks": [
{
"invalid_teaching_step_runs": 0
},
{
"orphan_teaching_step_runs": 0
}
]
}
]
},
{
"interrupted": true,
"first": [
{
"file": "20260904_033_teaching_command_receipt.sql",
"sha256": "3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43",
"checks": [
{
"invalid_teaching_command_receipts": 0
},
{
"orphan_teaching_command_receipts": 0
}
]
},
{
"file": "20260905_035_teaching_step_run_kingbase_constraints.sql",
"sha256": "53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31",
"checks": [
{
"invalid_teaching_step_runs": 0
},
{
"orphan_teaching_step_runs": 0
}
]
}
],
"repeat": [
{
"file": "20260904_033_teaching_command_receipt.sql",
"sha256": "3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43",
"checks": [
{
"invalid_teaching_command_receipts": 0
},
{
"orphan_teaching_command_receipts": 0
}
]
},
{
"file": "20260905_035_teaching_step_run_kingbase_constraints.sql",
"sha256": "53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31",
"checks": [
{
"invalid_teaching_step_runs": 0
},
{
"orphan_teaching_step_runs": 0
}
]
}
]
}
],
"executed": [
{
"file": "20260904_033_teaching_command_receipt.sql",
"sha256": "3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43",
"checks": [
{
"invalid_teaching_command_receipts": 0
},
{
"orphan_teaching_command_receipts": 0
}
]
},
{
"file": "20260905_035_teaching_step_run_kingbase_constraints.sql",
"sha256": "53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31",
"checks": [
{
"invalid_teaching_step_runs": 0
},
{
"orphan_teaching_step_runs": 0
}
]
}
]
}

+ 73
- 0
reports/review-20260905/database-review.html Ver fichero

@@ -0,0 +1,73 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>database-review · Code Review</title><style>*{box-sizing:border-box}body{margin:0;background:#edf2f7;color:#203248;font:16px/1.75 "Microsoft YaHei","Segoe UI",sans-serif}header{background:#0c2b47;color:white;padding:22px max(24px,calc((100vw - 1160px)/2))}header a{color:#c1e6ff;margin-right:24px}main{max-width:1200px;margin:24px auto 60px;padding:36px;background:white;border-radius:12px;box-shadow:0 6px 24px #1028400c}h1{font-size:30px;line-height:1.4;margin-top:0}h2{font-size:22px;margin:36px 0 14px;border-bottom:2px solid #dbe8f2;padding-bottom:8px}h3{font-size:18px}p{margin:13px 0}a{color:#126fba;text-decoration:none}a:hover{text-decoration:underline}table{border-collapse:collapse;width:100%;margin:18px 0;font-size:14px;table-layout:auto}th,td{padding:11px 13px;border:1px solid #dce5ed;text-align:left;vertical-align:top;overflow-wrap:anywhere}th{background:#edf5fc}tr:nth-child(even){background:#f9fbfd}code{font:13px/1.5 Consolas,monospace;background:#edf2f7;padding:2px 5px;border-radius:4px;overflow-wrap:anywhere}pre{padding:18px;overflow:auto;background:#edf2f7;border-radius:8px}pre code{padding:0}img{max-width:100%;height:auto}details{padding:12px;background:#f7f9fc;margin:12px 0}.gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}figure{margin:0;border:1px solid #dce5ed;border-radius:8px;overflow:hidden;background:#f8fafc}figure img{display:block;width:100%;height:300px;object-fit:contain;background:#e8eef3}figcaption{padding:12px;font-size:14px;color:#37526b}footer{color:#657c91;font-size:13px;margin-top:36px}li{margin:8px 0}@media(max-width:760px){main{margin:0;padding:20px;border-radius:0}.gallery{grid-template-columns:1fr}table{display:block;overflow:auto}h1{font-size:25px}header{padding:16px}}@media print{body{background:white}main{margin:0;box-shadow:none;max-width:none}header{display:none}figure{break-inside:avoid}h2{break-after:avoid}}</style></head><body><header><a href="index.html">审查总报告</a><a href="live/html/index.html">真实 E2E</a><a href="frontend-e2e/html/index.html">模拟接口回归</a></header><main><h1 id="数据库迁移与清理独立复核">数据库迁移与清理独立复核</h1>
<p>复核日期:2026-09-05。本附录只读取迁移文件、全量 DDL、<code>tools/review-db.py</code> 和执行 JSON,并做静态比较;没有再次连接数据库、执行迁移或修改业务数据。实际执行证据来自主审查生成的 <a href="database-migration.json">database-migration.json</a> 和 <a href="cleanup.json">cleanup.json</a>。</p>
<h2 id="结论与原始缺陷">结论与原始缺陷</h2>
<p>原 034 为运行、运行成员和事件建立独立唯一索引,再以组合外键引用。主执行者在目标 KingbaseES <code>V008R006C009B0014</code> 的隔离 schema 验证发现,该版本要求外键目标为显式 <code>UNIQUE</code> 约束,仅有独立唯一索引时创建步骤表失败,报外键没有匹配唯一约束(SQLSTATE <code>42830</code>)。这会阻断新步骤事实表部署。</p>
<p>修复新增 <code>20260905_035_teaching_step_run_kingbase_constraints.sql</code>:在 005 基础结构和 033 后执行,以 035 替代 034;历史 034 文件保持原样。035 使用系统目录判断并创建三个 <code>_aggregate_fk</code> 显式唯一约束,再幂等建立步骤表和查询索引。它不包含业务行更新、删除或历史事实回填。</p>
<p>部署文档已同步为当前开发库执行 033、035;其他环境必须独立核对迁移状态。035 含 <code>DO $$...$$</code>,执行客户端必须正确识别 dollar quote,不能简单按分号切分。</p>
<h2 id="035-与全量-ddl-一致性">035 与全量 DDL 一致性</h2>
<p>独立比较 <code>035</code> 与 <code>database/unreal_tran_full.sql</code> 的步骤表定义,除 <code>IF NOT EXISTS</code> 和空白格式外完全一致。</p>
<table>
<thead>
<tr>
<th>对照项</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td>父表聚合 UNIQUE 约束</td>
<td>3 个;名称与列顺序一致:运行 <code>(id, assignment_id)</code>、运行成员 <code>(id, run_id)</code>、事件 <code>(id, run_id, assignment_id)</code></td>
</tr>
<tr>
<td>步骤表外键</td>
<td>4 个:任务、运行归属、运行成员归属、事件归属;均为 <code>RESTRICT</code></td>
</tr>
<tr>
<td>步骤活动唯一键</td>
<td>2 个:<code>(run_id, step_code, delete_token)</code> 和 <code>(run_id, step_order, delete_token)</code></td>
</tr>
<tr>
<td>CHECK 约束</td>
<td>11 个,覆盖顺序、目标进度、状态、尝试/失败次数、证据来源、时间、回填标记、版本和软删除字段</td>
</tr>
<tr>
<td>步骤查询索引</td>
<td>3 个,按任务状态、运行当前步骤和最近事件查询</td>
</tr>
</tbody>
</table>
<p>父表新增组合唯一键都包含已有主键 <code>id</code>,不改变既有行的业务唯一性。步骤表字段和四个外键引用列一致,未发现本次修复会破坏既有业务行或放松运行归属校验的问题。</p>
<h2 id="实际执行与恢复验证边界">实际执行与恢复验证边界</h2>
<p><a href="database-migration.json">database-migration.json</a> 记录的目标为开发环境 <code>unreal_tran/public</code>,迁移前记录时间为 <code>2026-09-05T11:22:17.562488</code>。执行文件指纹与复核时工作区文件一致:</p>
<table>
<thead>
<tr>
<th>文件</th>
<th>SHA-256</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>20260904_033_teaching_command_receipt.sql</code></td>
<td><code>3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43</code></td>
</tr>
<tr>
<td><code>20260905_035_teaching_step_run_kingbase_constraints.sql</code></td>
<td><code>53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31</code></td>
</tr>
</tbody>
</table>
<p>执行工具先读取本地开发配置并核对数据库/schema,再执行以下路径:</p>
<ol>
<li>在专用隔离 schema 建立最小父表,首次完整执行 033、035,再完整重跑。</li>
<li>在另一专用隔离 schema 模拟“033 回执表已提交、035 首个父表唯一约束已提交”的中断状态,再完整执行和重跑。</li>
<li>隔离检查成功后,切回 <code>public</code> 执行 033、035。</li>
</ol>
<p>JSON 中上述各路径的无效记录和孤儿记录检查均为 0。当前执行工具也会在迁移后检查出现非零结果时抛错,避免继续报告成功。隔离验证证明的是这两个增量在最小父表上的首次、重复和指定中断点恢复;它不是全量建库脚本、任意中断点或历史数据回填的验收。</p>
<p>此执行记录只证明所列开发环境已迁移,不表示其他开发实例、测试库或生产库已执行。</p>
<h2 id="备份与测试数据清理">备份与测试数据清理</h2>
<p>执行前的 <code>database-before.json</code> 位于工作区 <code>.codex-tmp/review-20260905/</code>,复核确认文件存在。它保存任务、运行、运行成员、事件四张相关表的既有数据以及列、索引元数据,是本次扩展式迁移的本地对照快照;不是整库物理备份,也未据此宣称完成整库恢复演练。报告不复制快照中的业务明细或连接凭据。</p>
<p>清理工具按 <code>UTE2E-REVIEW-</code> 测试任务前缀选取记录,并排除迁移前快照已有任务 ID。删除在同一事务中按外键依赖顺序执行:先步骤事实、命令回执及运行附属测试数据,再事件、干预/故障关联、运行成员、运行、教学成员和任务。<code>ut_teaching_run.member_id</code> 引用的教学成员在运行删除之后处理。</p>
<p>测试用户按本轮时间及测试标识筛选,临时管理员另按本地记录的精确 ID、用户名和非保护身份确认。清理撤销其会话,并软删除角色关系、凭证与用户,同时停用用户、递增令牌版本;并非物理删除所有审计记录。临时管理员明文凭据文件在清理后移除。</p>
<p>复核读取的清理记录中,四张快照表既有行的 <code>originalRowsUnchanged</code> 均为 <code>true</code>,<code>remainingActiveTestUsers</code> 为 0。该比较只覆盖这四张表的快照既有行,不代表逐字比较了整库全部数据。重复截图测试后的最终清理时间、任务列表和数量以最新 <a href="cleanup.json">cleanup.json</a> 为准,本附录不固定清理数量。</p>
<p>未发现当前执行记录反映出误删既有业务数据或约束损坏。复核提出的清理验收建议已落实:工具写出记录后,若任一既有行对照不一致或仍有活动测试用户,就以错误退出。主任务负责最终清理和主报告汇总。</p><footer>2026-09-05 · 开发环境审查 · 图片可点击查看原尺寸</footer></main></body></html>

+ 56
- 0
reports/review-20260905/database-review.md Ver fichero

@@ -0,0 +1,56 @@
# 数据库迁移与清理独立复核

复核日期:2026-09-05。本附录只读取迁移文件、全量 DDL、`tools/review-db.py` 和执行 JSON,并做静态比较;没有再次连接数据库、执行迁移或修改业务数据。实际执行证据来自主审查生成的 [database-migration.json](database-migration.json) 和 [cleanup.json](cleanup.json)。

## 结论与原始缺陷

原 034 为运行、运行成员和事件建立独立唯一索引,再以组合外键引用。主执行者在目标 KingbaseES `V008R006C009B0014` 的隔离 schema 验证发现,该版本要求外键目标为显式 `UNIQUE` 约束,仅有独立唯一索引时创建步骤表失败,报外键没有匹配唯一约束(SQLSTATE `42830`)。这会阻断新步骤事实表部署。

修复新增 `20260905_035_teaching_step_run_kingbase_constraints.sql`:在 005 基础结构和 033 后执行,以 035 替代 034;历史 034 文件保持原样。035 使用系统目录判断并创建三个 `_aggregate_fk` 显式唯一约束,再幂等建立步骤表和查询索引。它不包含业务行更新、删除或历史事实回填。

部署文档已同步为当前开发库执行 033、035;其他环境必须独立核对迁移状态。035 含 `DO $$...$$`,执行客户端必须正确识别 dollar quote,不能简单按分号切分。

## 035 与全量 DDL 一致性

独立比较 `035` 与 `database/unreal_tran_full.sql` 的步骤表定义,除 `IF NOT EXISTS` 和空白格式外完全一致。

| 对照项 | 结果 |
|---|---|
| 父表聚合 UNIQUE 约束 | 3 个;名称与列顺序一致:运行 `(id, assignment_id)`、运行成员 `(id, run_id)`、事件 `(id, run_id, assignment_id)` |
| 步骤表外键 | 4 个:任务、运行归属、运行成员归属、事件归属;均为 `RESTRICT` |
| 步骤活动唯一键 | 2 个:`(run_id, step_code, delete_token)` 和 `(run_id, step_order, delete_token)` |
| CHECK 约束 | 11 个,覆盖顺序、目标进度、状态、尝试/失败次数、证据来源、时间、回填标记、版本和软删除字段 |
| 步骤查询索引 | 3 个,按任务状态、运行当前步骤和最近事件查询 |

父表新增组合唯一键都包含已有主键 `id`,不改变既有行的业务唯一性。步骤表字段和四个外键引用列一致,未发现本次修复会破坏既有业务行或放松运行归属校验的问题。

## 实际执行与恢复验证边界

[database-migration.json](database-migration.json) 记录的目标为开发环境 `unreal_tran/public`,迁移前记录时间为 `2026-09-05T11:22:17.562488`。执行文件指纹与复核时工作区文件一致:

| 文件 | SHA-256 |
|---|---|
| `20260904_033_teaching_command_receipt.sql` | `3b62137942c54d2fc50f6ee9975d20c44f8d2b240d8fff0c153970052e5b8d43` |
| `20260905_035_teaching_step_run_kingbase_constraints.sql` | `53d0baae9b90e37172277dd5e346542cce3d95b02595dc94e742c4ecaa8e3c31` |

执行工具先读取本地开发配置并核对数据库/schema,再执行以下路径:

1. 在专用隔离 schema 建立最小父表,首次完整执行 033、035,再完整重跑。
2. 在另一专用隔离 schema 模拟“033 回执表已提交、035 首个父表唯一约束已提交”的中断状态,再完整执行和重跑。
3. 隔离检查成功后,切回 `public` 执行 033、035。

JSON 中上述各路径的无效记录和孤儿记录检查均为 0。当前执行工具也会在迁移后检查出现非零结果时抛错,避免继续报告成功。隔离验证证明的是这两个增量在最小父表上的首次、重复和指定中断点恢复;它不是全量建库脚本、任意中断点或历史数据回填的验收。

此执行记录只证明所列开发环境已迁移,不表示其他开发实例、测试库或生产库已执行。

## 备份与测试数据清理

执行前的 `database-before.json` 位于工作区 `.codex-tmp/review-20260905/`,复核确认文件存在。它保存任务、运行、运行成员、事件四张相关表的既有数据以及列、索引元数据,是本次扩展式迁移的本地对照快照;不是整库物理备份,也未据此宣称完成整库恢复演练。报告不复制快照中的业务明细或连接凭据。

清理工具按 `UTE2E-REVIEW-` 测试任务前缀选取记录,并排除迁移前快照已有任务 ID。删除在同一事务中按外键依赖顺序执行:先步骤事实、命令回执及运行附属测试数据,再事件、干预/故障关联、运行成员、运行、教学成员和任务。`ut_teaching_run.member_id` 引用的教学成员在运行删除之后处理。

测试用户按本轮时间及测试标识筛选,临时管理员另按本地记录的精确 ID、用户名和非保护身份确认。清理撤销其会话,并软删除角色关系、凭证与用户,同时停用用户、递增令牌版本;并非物理删除所有审计记录。临时管理员明文凭据文件在清理后移除。

复核读取的清理记录中,四张快照表既有行的 `originalRowsUnchanged` 均为 `true`,`remainingActiveTestUsers` 为 0。该比较只覆盖这四张表的快照既有行,不代表逐字比较了整库全部数据。重复截图测试后的最终清理时间、任务列表和数量以最新 [cleanup.json](cleanup.json) 为准,本附录不固定清理数量。

未发现当前执行记录反映出误删既有业务数据或约束损坏。复核提出的清理验收建议已落实:工具写出记录后,若任一既有行对照不一致或仍有活动测试用户,就以错误退出。主任务负责最终清理和主报告汇总。

+ 54
- 0
reports/review-20260905/diagnostics/cleanup-h264-probe.json Ver fichero

@@ -0,0 +1,54 @@
{
"tasks": [
{
"id": 46,
"assignment_code": "UTE2E-REVIEW-MTNU5WZM-VIRTUAL"
},
{
"id": 47,
"assignment_code": "UTE2E-REVIEW-MTNU5WZM-PHYSICAL"
}
],
"deleted": {
"ut_teaching_step_run": 12,
"ut_teaching_command_receipt": 13,
"ut_teaching_xr_session": 0,
"ut_assistance_alert": 0,
"ut_assistance_evaluation": 0,
"ut_data_observation": 0,
"ut_teaching_event": 19,
"ut_teaching_intervention": 0,
"ut_teaching_assignment_fault": 0,
"ut_teaching_run_member": 2,
"ut_teaching_run": 2,
"ut_teaching_member": 2,
"ut_teaching_assignment": 2
},
"time": "2026-09-05T11:46:59.169164",
"testUsersDisabled": [
76,
77,
81,
78,
79,
74,
75,
88,
80,
82,
83,
85,
86,
87,
91,
90,
89
],
"originalRowsUnchanged": {
"ut_teaching_assignment": true,
"ut_teaching_run": true,
"ut_teaching_run_member": true,
"ut_teaching_event": true
},
"remainingActiveTestUsers": 0
}

+ 52
- 0
reports/review-20260905/diagnostics/cleanup-initial.json Ver fichero

@@ -0,0 +1,52 @@
{
"tasks": [
{
"id": 41,
"assignment_code": "UTE2E-REVIEW-MTNTPI4D-VIRTUAL"
},
{
"id": 42,
"assignment_code": "UTE2E-REVIEW-MTNTSDB9-VIRTUAL"
},
{
"id": 43,
"assignment_code": "UTE2E-REVIEW-MTNTSDB9-PHYSICAL"
}
],
"deleted": {
"ut_teaching_step_run": 20,
"ut_teaching_command_receipt": 14,
"ut_teaching_xr_session": 0,
"ut_assistance_alert": 0,
"ut_assistance_evaluation": 0,
"ut_data_observation": 0,
"ut_teaching_event": 23,
"ut_teaching_intervention": 0,
"ut_teaching_assignment_fault": 0,
"ut_teaching_run_member": 3,
"ut_teaching_run": 3,
"ut_teaching_member": 3,
"ut_teaching_assignment": 3
},
"time": "2026-09-05T11:33:17.346755",
"testUsersDisabled": [
76,
77,
81,
78,
79,
74,
75,
80,
82,
83,
73
],
"originalRowsUnchanged": {
"ut_teaching_assignment": true,
"ut_teaching_run": true,
"ut_teaching_run_member": true,
"ut_teaching_event": true
},
"remainingActiveTestUsers": 0
}

+ 52
- 0
reports/review-20260905/diagnostics/cleanup-resource-wait.json Ver fichero

@@ -0,0 +1,52 @@
{
"tasks": [
{
"id": 44,
"assignment_code": "UTE2E-REVIEW-MTNU0I0K-VIRTUAL"
},
{
"id": 45,
"assignment_code": "UTE2E-REVIEW-MTNU4T2Z-PHYSICAL"
}
],
"deleted": {
"ut_teaching_step_run": 12,
"ut_teaching_command_receipt": 13,
"ut_teaching_xr_session": 0,
"ut_assistance_alert": 0,
"ut_assistance_evaluation": 0,
"ut_data_observation": 0,
"ut_teaching_event": 19,
"ut_teaching_intervention": 0,
"ut_teaching_assignment_fault": 0,
"ut_teaching_run_member": 2,
"ut_teaching_run": 2,
"ut_teaching_member": 2,
"ut_teaching_assignment": 2
},
"time": "2026-09-05T11:41:19.926058",
"testUsersDisabled": [
76,
77,
81,
78,
79,
74,
75,
88,
80,
82,
83,
85,
86,
87,
84
],
"originalRowsUnchanged": {
"ut_teaching_assignment": true,
"ut_teaching_run": true,
"ut_teaching_run_member": true,
"ut_teaching_event": true
},
"remainingActiveTestUsers": 0
}





+ 315
- 0
reports/review-20260905/diagnostics/live-chromium-h264-unsupported.json Ver fichero

@@ -0,0 +1,315 @@
{
"config": {
"configFile": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\review-live.config.ts",
"rootDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
"forbidOnly": false,
"fullyParallel": false,
"globalSetup": null,
"globalTeardown": null,
"globalTimeout": 0,
"grep": {},
"grepInvert": null,
"maxFailures": 0,
"metadata": {
"actualWorkers": 1
},
"preserveOutput": "always",
"reporter": [
[
"list",
null
],
[
"html",
{
"outputFolder": "reports/review-20260905/live/html",
"open": "never"
}
],
[
"json",
{
"outputFile": "reports/review-20260905/live/results.json"
}
]
],
"reportSlowTests": {
"max": 5,
"threshold": 300000
},
"quiet": false,
"projects": [
{
"outputDir": "E:/work/edit_3d/unreal_tran/ute2e/reports/review-20260905/live/results",
"repeatEach": 1,
"retries": 0,
"metadata": {
"actualWorkers": 1
},
"id": "",
"name": "",
"testDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
"testIgnore": [],
"testMatch": [
"review-live.spec.ts"
],
"timeout": 120000
}
],
"shard": null,
"updateSnapshots": "missing",
"updateSourceMethod": "patch",
"version": "1.55.0",
"workers": 1,
"webServer": null
},
"suites": [
{
"title": "review-live.spec.ts",
"file": "review-live.spec.ts",
"column": 0,
"line": 0,
"specs": [
{
"title": "live login roles and formal virtual route",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 9181,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:41:24.437Z",
"annotations": [],
"attachments": [
{
"name": "01-login-three-roles",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\01-login-three-roles-f86162e84bea73eafa87bb0016f894f5e08d87b7.png"
},
{
"name": "02-virtual-formal-task-center",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\02-virtual-formal-task-center-b028ec31b409a20f12f7cf73dc7811535baab32e.png"
},
{
"name": "03-virtual-1920",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\03-virtual-1920-a16d403e0842f961f464136f9289448b6ab9b665.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-8b85e2f4bb6d5591db73",
"file": "review-live.spec.ts",
"line": 101,
"column": 1
},
{
"title": "live teacher role and content write boundary",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 3742,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:41:37.239Z",
"annotations": [],
"attachments": [
{
"name": "04-teacher-content",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-teacher-role-and-content-write-boundary\\attachments\\04-teacher-content-9ec9c5654b6ad956d784269cf0e1e26d7dbe0821.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-ccf9c84f5598cdf4198c",
"file": "review-live.spec.ts",
"line": 123,
"column": 1
},
{
"title": "live virtual start replay step facts submit and review",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 240000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 96799,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:41:40.993Z",
"annotations": [],
"attachments": [
{
"name": "05-virtual-started-no-exam-overlay",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\05-virtual-started-no-exam-overlay-75bbe0fc16a0254e73c609a1a3640d65844c77c7.png"
},
{
"name": "06-virtual-all-steps-completed",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\06-virtual-all-steps-completed-1fb317f851bbfea81cb72348d0c3fa6677c15524.png"
},
{
"name": "07-virtual-reviewed",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\07-virtual-reviewed-c18c9c866c30a33295adba3a8277bc7dd76aab69.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-3ecee091831f9a0e83fd",
"file": "review-live.spec.ts",
"line": 134,
"column": 1
},
{
"title": "live physical failure retry rejects stale evidence",
"ok": false,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "failed",
"duration": 56458,
"error": {
"message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 45000ms exceeded while waiting on the predicate",
"stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 45000ms exceeded while waiting on the predicate\n at E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts:233:3",
"location": {
"file": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts",
"column": 3,
"line": 233
},
"snippet": " 231 | const video = page.locator('[data-physical-video=\"primary\"]')\n 232 | await expect(video).toBeVisible()\n> 233 | await expect.poll(() => video.evaluate((element: HTMLVideoElement) => element.readyState), { timeout: 45000 }).toBeGreaterThanOrEqual(2)\n | ^\n 234 | await video.evaluate((element: HTMLVideoElement) => { element.pause(); element.currentTime = 3 })\n 235 | await expect.poll(() => video.evaluate((element: HTMLVideoElement) => !element.seeking && element.readyState >= 2)).toBe(true)\n 236 | await shot(page, info, '09-physical-offline-replay-boundary')"
},
"errors": [
{
"location": {
"file": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts",
"column": 3,
"line": 233
},
"message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 45000ms exceeded while waiting on the predicate\n\n 231 | const video = page.locator('[data-physical-video=\"primary\"]')\n 232 | await expect(video).toBeVisible()\n> 233 | await expect.poll(() => video.evaluate((element: HTMLVideoElement) => element.readyState), { timeout: 45000 }).toBeGreaterThanOrEqual(2)\n | ^\n 234 | await video.evaluate((element: HTMLVideoElement) => { element.pause(); element.currentTime = 3 })\n 235 | await expect.poll(() => video.evaluate((element: HTMLVideoElement) => !element.seeking && element.readyState >= 2)).toBe(true)\n 236 | await shot(page, info, '09-physical-offline-replay-boundary')\n at E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts:233:3"
}
],
"stdout": [],
"stderr": [],
"retry": 0,
"steps": [
{
"title": "Expect \"poll toBeGreaterThanOrEqual\"",
"duration": 44540,
"error": {
"message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 45000ms exceeded while waiting on the predicate",
"stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\nCall Log:\n- Timeout 45000ms exceeded while waiting on the predicate\n at E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts:233:3",
"location": {
"file": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts",
"column": 3,
"line": 233
},
"snippet": " 231 | const video = page.locator('[data-physical-video=\"primary\"]')\n 232 | await expect(video).toBeVisible()\n> 233 | await expect.poll(() => video.evaluate((element: HTMLVideoElement) => element.readyState), { timeout: 45000 }).toBeGreaterThanOrEqual(2)\n | ^\n 234 | await video.evaluate((element: HTMLVideoElement) => { element.pause(); element.currentTime = 3 })\n 235 | await expect.poll(() => video.evaluate((element: HTMLVideoElement) => !element.seeking && element.readyState >= 2)).toBe(true)\n 236 | await shot(page, info, '09-physical-offline-replay-boundary')"
}
}
],
"startTime": "2026-09-05T03:43:17.807Z",
"annotations": [],
"attachments": [
{
"name": "08-physical-api-task-list",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\attachments\\08-physical-api-task-list-7e7551f3cc59abf587e5d2dc7d0767de16241cf6.png"
},
{
"name": "failure-live-physical-failure-retry-rejects-stal",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\attachments\\failure-live-physical-failure-retry-rejects-stal-9b9b8ac01847809d43c66bfa7b814a6e686ef35e.png"
},
{
"name": "error-context",
"contentType": "text/markdown",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\error-context.md"
}
],
"errorLocation": {
"file": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\tests\\review-live.spec.ts",
"column": 3,
"line": 233
}
}
],
"status": "unexpected"
}
],
"id": "351158f247962162d567-f2520dc34ab1c6ead4ee",
"file": "review-live.spec.ts",
"line": 201,
"column": 1
}
]
}
],
"errors": [],
"stats": {
"startTime": "2026-09-05T03:41:23.265Z",
"duration": 172634.013,
"expected": 3,
"skipped": 0,
"unexpected": 1,
"flaky": 0
}
}

+ 283
- 0
reports/review-20260905/diagnostics/live-resource-wait-timeout.json Ver fichero

@@ -0,0 +1,283 @@
{
"config": {
"configFile": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\review-live.config.ts",
"rootDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
"forbidOnly": false,
"fullyParallel": false,
"globalSetup": null,
"globalTeardown": null,
"globalTimeout": 0,
"grep": {},
"grepInvert": null,
"maxFailures": 0,
"metadata": {
"actualWorkers": 1
},
"preserveOutput": "always",
"reporter": [
[
"list",
null
],
[
"html",
{
"outputFolder": "reports/review-20260905/live/html",
"open": "never"
}
],
[
"json",
{
"outputFile": "reports/review-20260905/live/results.json"
}
]
],
"reportSlowTests": {
"max": 5,
"threshold": 300000
},
"quiet": false,
"projects": [
{
"outputDir": "E:/work/edit_3d/unreal_tran/ute2e/reports/review-20260905/live/results",
"repeatEach": 1,
"retries": 0,
"metadata": {
"actualWorkers": 1
},
"id": "",
"name": "",
"testDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
"testIgnore": [],
"testMatch": [
"review-live.spec.ts"
],
"timeout": 120000
}
],
"shard": null,
"updateSnapshots": "missing",
"updateSourceMethod": "patch",
"version": "1.55.0",
"workers": 1,
"webServer": null
},
"suites": [
{
"title": "review-live.spec.ts",
"file": "review-live.spec.ts",
"column": 0,
"line": 0,
"specs": [
{
"title": "live login roles and formal virtual route",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 10256,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:37:11.787Z",
"annotations": [],
"attachments": [
{
"name": "01-login-three-roles",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\01-login-three-roles-f86162e84bea73eafa87bb0016f894f5e08d87b7.png"
},
{
"name": "02-virtual-formal-task-center",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\02-virtual-formal-task-center-b028ec31b409a20f12f7cf73dc7811535baab32e.png"
},
{
"name": "03-virtual-1920",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\03-virtual-1920-a16d403e0842f961f464136f9289448b6ab9b665.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-8b85e2f4bb6d5591db73",
"file": "review-live.spec.ts",
"line": 101,
"column": 1
},
{
"title": "live teacher role and content write boundary",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 52926,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:37:25.888Z",
"annotations": [],
"attachments": [
{
"name": "04-teacher-content",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-teacher-role-and-content-write-boundary\\attachments\\04-teacher-content-9ec9c5654b6ad956d784269cf0e1e26d7dbe0821.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-ccf9c84f5598cdf4198c",
"file": "review-live.spec.ts",
"line": 123,
"column": 1
},
{
"title": "live virtual start replay step facts submit and review",
"ok": false,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "timedOut",
"duration": 131103,
"error": {
"message": "\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m",
"stack": "\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m"
},
"errors": [
{
"message": "\u001b[31mTest timeout of 120000ms exceeded.\u001b[39m"
}
],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:38:18.835Z",
"annotations": [],
"attachments": [
{
"name": "05-virtual-started-no-exam-overlay",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\05-virtual-started-no-exam-overlay-75bbe0fc16a0254e73c609a1a3640d65844c77c7.png"
},
{
"name": "06-virtual-all-steps-completed",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\06-virtual-all-steps-completed-1fb317f851bbfea81cb72348d0c3fa6677c15524.png"
},
{
"name": "07-virtual-reviewed",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\07-virtual-reviewed-c18c9c866c30a33295adba3a8277bc7dd76aab69.png"
},
{
"name": "failure-live-virtual-start-replay-step-facts-sub",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\failure-live-virtual-start-replay-step-facts-sub-cd0e8dd222e097c02b3a85ee382e8ce140e70b84.png"
},
{
"name": "error-context",
"contentType": "text/markdown",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\error-context.md"
}
]
}
],
"status": "unexpected"
}
],
"id": "351158f247962162d567-3ecee091831f9a0e83fd",
"file": "review-live.spec.ts",
"line": 134,
"column": 1
},
{
"title": "live physical failure retry rejects stale evidence",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 1,
"parallelIndex": 0,
"status": "passed",
"duration": 10859,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:40:32.720Z",
"annotations": [],
"attachments": [
{
"name": "08-physical-api-task-list",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\attachments\\08-physical-api-task-list-7e7551f3cc59abf587e5d2dc7d0767de16241cf6.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-f2520dc34ab1c6ead4ee",
"file": "review-live.spec.ts",
"line": 193,
"column": 1
}
]
}
],
"errors": [],
"stats": {
"startTime": "2026-09-05T03:37:09.607Z",
"duration": 219129.49300000002,
"expected": 3,
"skipped": 0,
"unexpected": 1,
"flaky": 0
}
}

+ 41
- 0
reports/review-20260905/diagnostics/video-probe-msedge.json Ver fichero

@@ -0,0 +1,41 @@
{
"video": {
"source": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"currentSrc": "http://127.0.0.1:6180/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"readyState": 4,
"networkState": 1,
"error": null,
"mp4": "probably",
"webm": "probably"
},
"events": [
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 200,
"type": "video/mp4"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"failed": "net::ERR_ABORTED"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 206,
"type": "video/mp4"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"failed": "net::ERR_ABORTED"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 206,
"type": "video/mp4"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 206,
"type": "video/mp4"
}
]
}

+ 36
- 0
reports/review-20260905/diagnostics/video-probe.json Ver fichero

@@ -0,0 +1,36 @@
{
"video": {
"source": null,
"currentSrc": "http://127.0.0.1:6180/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"readyState": 0,
"networkState": 0,
"error": null,
"mp4": "",
"webm": "probably"
},
"events": [
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 200,
"type": "video/mp4"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"failed": "net::ERR_ABORTED"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 206,
"type": "video/mp4"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"failed": "net::ERR_ABORTED"
},
{
"path": "/videos/chinese-maintenance/china-military-repair-workshop-hd-45s-system-pose.mp4",
"status": 206,
"type": "video/mp4"
}
]
}

+ 29
- 0
reports/review-20260905/environment.json Ver fichero

@@ -0,0 +1,29 @@
{
"time": "2026-09-05T03:49:45.613Z",
"results": [
{
"url": "http://127.0.0.1:6180/",
"status": 200
},
{
"url": "http://127.0.0.1:6180/api/auth/v1/auth/login-context",
"status": 200
},
{
"url": "http://127.0.0.1:6100/actuator/health",
"status": 200
},
{
"url": "http://127.0.0.1:6101/actuator/health",
"status": 200
},
{
"url": "http://127.0.0.1:6102/actuator/health",
"status": 200
},
{
"url": "http://192.168.31.168:6180/",
"status": 200
}
]
}



+ 67
- 0
reports/review-20260905/frontend-e2e/README.md Ver fichero

@@ -0,0 +1,67 @@
# 前端浏览器回归报告(API mock)

2026-09-05 11:37:55(Asia/Shanghai)开始,最终 **15 项通过,0 项失败,0 项跳过**,耗时117.7秒。共保存17张截图,截图同时嵌入 [Playwright HTML报告](html/index.html)。机器结果见 [results.json](results.json)。

本组使用真实 Vite 页面与 Chromium,接口全部由 Playwright 按契约模拟;没有修改开发数据库。这些结果证明界面交互、保存请求与条件渲染通过回归,不等于真实后端鉴权、持久化、并发事务或现场硬件验收。真实服务 E2E 见主报告的 live 部分。

## 测试结果

| 测试 | 数量 | 验证结果 |
|---|---:|---|
| 已保存步骤上移、删除、复制 | 3 | VIRTUAL为默认且PHYSICAL同时启用;改变已保存步骤后,发出的PUT顺序连续,进度分别为33/67/100、50/100、25/50/75/100;原稳定步骤编码保留,复制没有重号。保存后刷新仍保持顺序。保存请求JSON作为HTML报告附件。 |
| 通用编辑器多渠道保存 | 1 | 修改VIRTUAL渲染配置后保存并刷新仍保留;PHYSICAL原有工位与CONFRONTATION原配置不丢;切换默认对抗时读取既有6人/45分钟,改60分钟后保存刷新保持;顶层modeConfig与默认profile一致,三个启用渠道保留。归一化补入默认字段属于现有契约允许行为。 |
| 失败步骤重试与结束状态 | 3 | 运行IN_PROGRESS且当前步骤FAILED时显示“重试当前步骤”;点击发出一个start,body.commandId与Idempotency-Key一致;返回事实后尝试2次、未通过1次,重试按钮消失,刷新一致。SUBMITTED/TERMINATED即使有FAILED事实也没有重试入口。 |
| 关闭遮罩状态矩阵 | 6 | 普通虚拟、实装、对抗组件及进行中正式考核均无关闭遮罩且存在Canvas;正式考核ACCEPTED/SUBMITTED有关闭遮罩且无Canvas。六例没有页面运行时异常。 |
| 军工绿/暗色主题视觉验证 | 2 | 场景宿主与场景窗宽高差不超过2px;安全确认卡为不透明主题背景,标题/复选框文字对比度均不低于4.5;点击实际复选框后显示1/2,保留截图与尺寸/颜色测量附件。 |

实装、对抗遮罩案例通过正式虚拟运行路由承载公共 `TeachingRunView` 并传入相应渠道响应,用于隔离检查组件渲染。它们没有宣称默认实装离线回放页面已经接通真实事件,也没有宣称默认对抗入口已完成服务端迁移。

## 截图证据

### 步骤重排后保存

![重排保存后的步骤及进度](screenshots/01-saved-step-move.png)

### 多渠道配置切换与保存

对抗原每队6人保留,单局时长由45改为60,保存刷新后保持。

![对抗渠道配置保存后](screenshots/03-multichannel-confrontation-save.png)

### 失败步骤重试

重试前:

![重试前](screenshots/05-failed-before-retry.png)

重试后:

![重试后](screenshots/06-failed-after-retry.png)

运行图使用最小场景夹具,网格/缺少模型提示不作为三维资源正确性的证据。真实模型渲染以live报告截图为准。

### 正式考核提交后关闭场景

![正式考核提交后关闭场景](screenshots/04-overlay-6-VIRTUAL-SUBMITTED.png)

### 场景高度与安全确认区主题

原有场景根元素定位被scoped样式覆盖,导致画布只占场景窗上部;安全确认区缺少独立背景,浅主题文字落在深色场景上。CSS修复后,画布铺满场景窗,安全条件成为可读且可交互的主题卡片。

1440×1100浏览器视口中,两主题的场景宿主与场景窗均为630×583.72px,尺寸一致。标题/复选框文字对比度:军工绿12.00,暗色15.30(断言下限4.5)。

![军工绿主题安全确认区](screenshots/08-visual-safety-military.png)

![暗色主题安全确认区](screenshots/08-visual-safety-dark.png)

## 复现与检查

测试代码:[review-frontend-regressions.spec.ts](../../../tests/review-frontend-regressions.spec.ts)。专用配置:[review-frontend.config.ts](../../../review-frontend.config.ts)。

在 `E:/work/edit_3d/unreal_tran/ute2e` 且前端6180运行时执行:

```powershell
node node_modules/@playwright/test/cli.js test --config review-frontend.config.ts
```

新增测试文件及专用配置已通过TypeScript严格检查。最终结果未使用自动重试掩盖失败(retries=0)。测试开发阶段修正过夹具权限、Vite模块拦截范围、Element Plus复选框点击目标和断言文案,最终15例为同一轮完整运行结果。


















+ 76
- 0
reports/review-20260905/frontend-e2e/html/index.html
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 794
- 0
reports/review-20260905/frontend-e2e/results.json
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero



















+ 4
- 0
reports/review-20260905/frontend-e2e/test-results/.last-run.json Ver fichero

@@ -0,0 +1,4 @@
{
"status": "passed",
"failedTests": []
}


















+ 100
- 0
reports/review-20260905/frontend-review.html Ver fichero

@@ -0,0 +1,100 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>frontend-review · Code Review</title><style>*{box-sizing:border-box}body{margin:0;background:#edf2f7;color:#203248;font:16px/1.75 "Microsoft YaHei","Segoe UI",sans-serif}header{background:#0c2b47;color:white;padding:22px max(24px,calc((100vw - 1160px)/2))}header a{color:#c1e6ff;margin-right:24px}main{max-width:1200px;margin:24px auto 60px;padding:36px;background:white;border-radius:12px;box-shadow:0 6px 24px #1028400c}h1{font-size:30px;line-height:1.4;margin-top:0}h2{font-size:22px;margin:36px 0 14px;border-bottom:2px solid #dbe8f2;padding-bottom:8px}h3{font-size:18px}p{margin:13px 0}a{color:#126fba;text-decoration:none}a:hover{text-decoration:underline}table{border-collapse:collapse;width:100%;margin:18px 0;font-size:14px;table-layout:auto}th,td{padding:11px 13px;border:1px solid #dce5ed;text-align:left;vertical-align:top;overflow-wrap:anywhere}th{background:#edf5fc}tr:nth-child(even){background:#f9fbfd}code{font:13px/1.5 Consolas,monospace;background:#edf2f7;padding:2px 5px;border-radius:4px;overflow-wrap:anywhere}pre{padding:18px;overflow:auto;background:#edf2f7;border-radius:8px}pre code{padding:0}img{max-width:100%;height:auto}details{padding:12px;background:#f7f9fc;margin:12px 0}.gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}figure{margin:0;border:1px solid #dce5ed;border-radius:8px;overflow:hidden;background:#f8fafc}figure img{display:block;width:100%;height:300px;object-fit:contain;background:#e8eef3}figcaption{padding:12px;font-size:14px;color:#37526b}footer{color:#657c91;font-size:13px;margin-top:36px}li{margin:8px 0}@media(max-width:760px){main{margin:0;padding:20px;border-radius:0}.gallery{grid-template-columns:1fr}table{display:block;overflow:auto}h1{font-size:25px}header{padding:16px}}@media print{body{background:white}main{margin:0;box-shadow:none;max-width:none}header{display:none}figure{break-inside:avoid}h2{break-after:avoid}}</style></head><body><header><a href="index.html">审查总报告</a><a href="live/html/index.html">真实 E2E</a><a href="frontend-e2e/html/index.html">模拟接口回归</a></header><main><h1 id="前端增量-code-review">前端增量 Code Review</h1>
<p>范围:<code>unreal_tran_web</code> 的 <code>0aac918..1576728</code>,以及新增正式入口所依赖的运行页与通用内容编辑器。按资料、产品原型、设计、开发、测试核对;审查完成后按用户授权提交为 <code>3c7f295</code>。</p>
<h2 id="资料与产品原型">资料与产品原型</h2>
<ul>
<li>原始资料与确认原型的详细比对由本次资料审查分工提供,见同目录汇总报告。本分工未独立宣称核读全部 PDF。</li>
<li>已完整阅读前端 <code>AGENTS.md</code>:正式教学页面必须消费 API;训练编排复用已发布场景目标;工程修改须保持教员身份门槛;保留原型工作台与接口工作台已有入口。</li>
<li>保留既有功能与页面结构,只修复排序、保存兼容和错误遮罩。实装默认离线回放、正式虚拟训练与原型交互能力的差异属于汇总报告中的功能确认事项,本分工未改路由或扩建交互。</li>
</ul>
<h2 id="设计合同">设计合同</h2>
<ul>
<li><code>unreal_tran_api/wiki/19-训练定义渠道合同.md</code>:一份公共步骤、多渠道差异配置;默认渠道必须启用;<code>modeConfig</code> 与默认渠道 profile 一致。</li>
<li><code>unreal_tran_api/wiki/20-教学命令幂等.md</code>:四类命令 body/header 使用同一 commandId;结果未知时复用原命令号与冻结载荷。</li>
<li><code>unreal_tran_api/wiki/21-教学步骤运行事实.md</code>:新运行展示服务端步骤状态;空列表仅兼容历史运行;全部步骤完成方可提交。</li>
<li>后端 <code>TeachingAssignmentService.validateRuntimeDefinition</code> 明确要求实装步骤进度按顺序严格递增,末步 100%。</li>
</ul>
<h2 id="已修复问题">已修复问题</h2>
<table>
<thead>
<tr>
<th>编号</th>
<th>问题与影响</th>
<th>根因</th>
<th>最小修复</th>
</tr>
</thead>
<tbody>
<tr>
<td>WEB-01</td>
<td>实装步骤倒序、删除或复制后,保存保留旧序号和旧进度,导致排序事实不一致、发布被拒。多渠道工程同样受影响。</td>
<td>新 <code>resequenceSteps</code> 直接调用归一化函数,而归一化优先保留已有 <code>order/progressPercent</code>。例如三步反转产生序号 <code>3,2,1</code> 与进度 <code>100,67,33</code>。</td>
<td>按编辑后的数组重算连续序号、实装严格递增进度;保留稳定 stepCode 及已有事件规则扩展字段。</td>
</tr>
<tr>
<td>WEB-02</td>
<td>通用编辑器修改默认配置后仅更新旧 <code>modeConfig</code>,与已有 <code>channelProfiles</code> 冲突;在接口编排器重开后新值被旧 profile 覆盖。切换默认渠道也未同步正式字段。</td>
<td>通用编辑器未跟进新渠道合同,只读写单个旧配置对象;对抗时长仍只写旧字段。</td>
<td>各渠道按对应 profile 回填;保存同步默认渠道、启用渠道、profile 和兼容镜像;保留其他渠道。对抗时长新旧字段同步,兼容历史 <code>roundDuration</code>,避免 45 分钟被默认 30 分钟替代。</td>
</tr>
<tr>
<td>WEB-03</td>
<td>普通虚拟训练、实装、对抗以及执行中的正式考核,可能错误显示“正式考核执行场景已关闭”。该问题在正式虚拟页面成为默认入口后更容易暴露。</td>
<td>遮罩的 <code>v-else</code> 实际绑定紧邻的“虚拟拆卸存放区”条件,而非正式考核生命周期。</td>
<td>改为显式判断“正式考核且运行不在 IN_PROGRESS”,其他训练与执行中的考核不显示关闭遮罩。</td>
</tr>
<tr>
<td>WEB-04</td>
<td>当前步骤失败后聚合运行仍为 IN_PROGRESS,页面没有可进入正式重试状态的入口。</td>
<td>原“开始训练”仅在 ACCEPTED 显示,未覆盖新步骤事实的 FAILED→IN_PROGRESS 重试合同。</td>
<td>仅当学员的运行仍在 IN_PROGRESS 且当前步骤事实为 FAILED 时显示“重试当前步骤”,复用原 start 命令及幂等 tracker;已提交、归档和已完成步骤不显示。</td>
</tr>
<tr>
<td>WEB-05</td>
<td>真实 E2E 截图显示场景只占视口上方约 315px,其下安全确认区在深色场景底色上显示深色文字,难以阅读。</td>
<td>场景组件 scoped <code>position: relative</code> 覆盖全局绝对定位,运行页缺少预览页已有的填充规则;<code>runtime-safety-gate</code> 没有任何对应样式。</td>
<td>仅在运行视口下补场景 absolute/inset 定位;安全区使用已有主题 surface/text 颜色,补齐卡片间距、14px 复选框文字和底部定位,不修改安全规则或推进逻辑。</td>
</tr>
<tr>
<td>WEB-06</td>
<td>离线回放默认视频被替换成约 32 秒的新片,但候选与抓拍仍对应约 45 秒已验收片;34 秒与 45.2 秒两个候选不可达。</td>
<td>当前默认文件实际 31.973991 秒;旧 12 候选时间轴不按 metadata 缩放,视频结束却会把五阶段标记为已回放。既有视频 SHA 契约实际失败。</td>
<td>默认引用改为仓库保留的 <code>pose1.mp4</code>,该文件 42,865,492 字节、SHA-256 为 <code>B588101115196061E0D2BCE4B0BD0CB1B01CAEB7561B31D0F26F5C0E980696D4</code>,与既有已验收视频契约完全一致,实际 45.422 秒。新 32 秒文件原样保留,是否换用新片及重标候选/抓拍列 F5 待确认,不猜测新时间轴。</td>
</tr>
</tbody>
</table>
<p>修改文件:</p>
<ul>
<li><code>unreal_tran_web/src/features/training/trainingDocument.ts</code></li>
<li><code>unreal_tran_web/src/views/content/ContentEditorView.vue</code></li>
<li><code>unreal_tran_web/src/views/teaching/TeachingRunView.vue</code></li>
<li><code>unreal_tran_web/src/styles/teaching-run-workspaces.scss</code></li>
<li><code>unreal_tran_web/src/features/physical-legacy/runtime.ts</code></li>
<li><code>unreal_tran_web/scripts/physical-offline-replay.contract.test.mjs</code></li>
<li><code>unreal_tran_web/scripts/training-channel-profiles.contract.test.mjs</code></li>
<li><code>unreal_tran_web/scripts/teaching-step-run.contract.test.mjs</code></li>
</ul>
<h2 id="验证">验证</h2>
<p>先加入已保存步骤排序回归:修改前 8 项测试中 2 项失败,实际观察到序号 <code>[3,2,1]</code> 与 <code>[2,1]</code>,确认不是推测问题。</p>
<p>修复后的命令:</p>
<pre><code class="language-text">node --experimental-strip-types --test scripts/physical-offline-replay.contract.test.mjs scripts/training-channel-profiles.contract.test.mjs scripts/teaching-step-run.contract.test.mjs scripts/teaching-command-idempotency.contract.test.mjs scripts/core-role-contract.test.mjs scripts/virtual-training-formal-route.contract.test.mjs scripts/training-scene-grounding.test.mjs
pnpm run build
git diff --check
</code></pre>
<ul>
<li>Node 回归:25/25 通过,详见 <a href="frontend-contract-tests.log">frontend-contract-tests.log</a>。其中包含实际执行的渠道转换、已保存步骤重排、旧文档迁移、配置保存再打开、冻结命令载荷、失败步骤重试资格矩阵及离线视频 SHA/12 候选/抓拍资源等断言;路由与模板条件部分属于静态契约检查。</li>
<li>最后一轮构建通过(退出码 0):包含 <code>vue-tsc --noEmit</code>、Vite 打包与旧模型 iframe 校验,详见 <a href="frontend-build.log">frontend-build.log</a>。已有资源 URL、包体大小告警不按构建失败统计。</li>
<li><code>git diff --check</code> 未发现空白错误;工作区 Git 的 LF/CRLF 提醒属于换行提示。</li>
<li>浏览器 API mock 回归 15/15 通过,详见 <a href="frontend-e2e/README.html">frontend-e2e/README.md</a>。其中军工绿/暗色两套视觉用例确认场景和 host 同为 630×583.72px;安全区标题及复选框文本对比分别为 12.00/15.30,17 张截图均保留。真实 API E2E 另见同目录汇总,不能以 mock 或 Node 检查替代。</li>
<li>视频兼容性单独核实:静态服务 HEAD 200、Range 206、<code>video/mp4</code>;ffprobe 显示 H.264 Main/yuv420p + AAC LC。bundled Chromium 对新旧 H.264 都返回不支持并触发 <code>DEMUXER_ERROR_NO_SUPPORTED_STREAMS</code>,不属于该视频的产品解码缺陷;根代理使用本机 Edge 验证 H.264 可播放,真实离线回放使用 Edge 完成。</li>
</ul>
<h2 id="交给-e2e-的验证点">交给 E2E 的验证点</h2>
<ol>
<li>在接口编排器重排已保存的实装/多渠道步骤,保存后检查序号与实装进度;删除末步后新末步为 100%。</li>
<li>通用编辑器修改默认配置并保存,再进接口编排器,确认新值未丢失,未修改渠道原配置保留;切默认渠道后合同字段一致。</li>
<li>普通三渠道运行以及执行中的正式考核不出现考核关闭遮罩;未开始/提交后的正式考核显示关闭态。</li>
<li>正式虚拟主入口、旧 <code>/api</code> 重定向、命令丢响应后的重放、步骤事实失败与重试、教员/管理员身份边界继续验证。</li>
<li>失败步骤显示“重试当前步骤”;点击后通过 start 端点进入执行中、尝试次数增加、失败次数保持,重试按钮消失。</li>
<li>等待 <code>.teaching-three-scene</code> 的 <code>aria-label</code>/<code>.teaching-scene-status</code> 成为“已加载发布训练场景 · N 个对象”且无资产失败提示,再检查真实资产下载响应与截图。canvas 存在不等于 GLB 就绪。场景 host 应填满运行视口;明暗主题下安全确认区均应可读。</li>
<li>本机 Edge 播放离线素材,3 秒出现首条候选,跳转至 <code>video.duration - 0.03</code> 后 <code>[data-event-timeline] [data-physical-event][data-offline-replay="true"]</code> 应为 12 条,包含 <code>offline-replay:replay-complete</code>;候选仍只供教员复核,不写正式运行进度。</li>
</ol>
<p>本分工没有数据库变更、依赖版本变更或新增产品功能。所有待确认功能范围统一列入总报告,避免分散审批。</p><footer>2026-09-05 · 开发环境审查 · 图片可点击查看原尺寸</footer></main></body></html>

+ 68
- 0
reports/review-20260905/frontend-review.md Ver fichero

@@ -0,0 +1,68 @@
# 前端增量 Code Review

范围:`unreal_tran_web` 的 `0aac918..1576728`,以及新增正式入口所依赖的运行页与通用内容编辑器。按资料、产品原型、设计、开发、测试核对;审查完成后按用户授权提交为 `3c7f295`。

## 资料与产品原型

- 原始资料与确认原型的详细比对由本次资料审查分工提供,见同目录汇总报告。本分工未独立宣称核读全部 PDF。
- 已完整阅读前端 `AGENTS.md`:正式教学页面必须消费 API;训练编排复用已发布场景目标;工程修改须保持教员身份门槛;保留原型工作台与接口工作台已有入口。
- 保留既有功能与页面结构,只修复排序、保存兼容和错误遮罩。实装默认离线回放、正式虚拟训练与原型交互能力的差异属于汇总报告中的功能确认事项,本分工未改路由或扩建交互。

## 设计合同

- `unreal_tran_api/wiki/19-训练定义渠道合同.md`:一份公共步骤、多渠道差异配置;默认渠道必须启用;`modeConfig` 与默认渠道 profile 一致。
- `unreal_tran_api/wiki/20-教学命令幂等.md`:四类命令 body/header 使用同一 commandId;结果未知时复用原命令号与冻结载荷。
- `unreal_tran_api/wiki/21-教学步骤运行事实.md`:新运行展示服务端步骤状态;空列表仅兼容历史运行;全部步骤完成方可提交。
- 后端 `TeachingAssignmentService.validateRuntimeDefinition` 明确要求实装步骤进度按顺序严格递增,末步 100%。

## 已修复问题

| 编号 | 问题与影响 | 根因 | 最小修复 |
| --- | --- | --- | --- |
| WEB-01 | 实装步骤倒序、删除或复制后,保存保留旧序号和旧进度,导致排序事实不一致、发布被拒。多渠道工程同样受影响。 | 新 `resequenceSteps` 直接调用归一化函数,而归一化优先保留已有 `order/progressPercent`。例如三步反转产生序号 `3,2,1` 与进度 `100,67,33`。 | 按编辑后的数组重算连续序号、实装严格递增进度;保留稳定 stepCode 及已有事件规则扩展字段。 |
| WEB-02 | 通用编辑器修改默认配置后仅更新旧 `modeConfig`,与已有 `channelProfiles` 冲突;在接口编排器重开后新值被旧 profile 覆盖。切换默认渠道也未同步正式字段。 | 通用编辑器未跟进新渠道合同,只读写单个旧配置对象;对抗时长仍只写旧字段。 | 各渠道按对应 profile 回填;保存同步默认渠道、启用渠道、profile 和兼容镜像;保留其他渠道。对抗时长新旧字段同步,兼容历史 `roundDuration`,避免 45 分钟被默认 30 分钟替代。 |
| WEB-03 | 普通虚拟训练、实装、对抗以及执行中的正式考核,可能错误显示“正式考核执行场景已关闭”。该问题在正式虚拟页面成为默认入口后更容易暴露。 | 遮罩的 `v-else` 实际绑定紧邻的“虚拟拆卸存放区”条件,而非正式考核生命周期。 | 改为显式判断“正式考核且运行不在 IN_PROGRESS”,其他训练与执行中的考核不显示关闭遮罩。 |
| WEB-04 | 当前步骤失败后聚合运行仍为 IN_PROGRESS,页面没有可进入正式重试状态的入口。 | 原“开始训练”仅在 ACCEPTED 显示,未覆盖新步骤事实的 FAILED→IN_PROGRESS 重试合同。 | 仅当学员的运行仍在 IN_PROGRESS 且当前步骤事实为 FAILED 时显示“重试当前步骤”,复用原 start 命令及幂等 tracker;已提交、归档和已完成步骤不显示。 |
| WEB-05 | 真实 E2E 截图显示场景只占视口上方约 315px,其下安全确认区在深色场景底色上显示深色文字,难以阅读。 | 场景组件 scoped `position: relative` 覆盖全局绝对定位,运行页缺少预览页已有的填充规则;`runtime-safety-gate` 没有任何对应样式。 | 仅在运行视口下补场景 absolute/inset 定位;安全区使用已有主题 surface/text 颜色,补齐卡片间距、14px 复选框文字和底部定位,不修改安全规则或推进逻辑。 |
| WEB-06 | 离线回放默认视频被替换成约 32 秒的新片,但候选与抓拍仍对应约 45 秒已验收片;34 秒与 45.2 秒两个候选不可达。 | 当前默认文件实际 31.973991 秒;旧 12 候选时间轴不按 metadata 缩放,视频结束却会把五阶段标记为已回放。既有视频 SHA 契约实际失败。 | 默认引用改为仓库保留的 `pose1.mp4`,该文件 42,865,492 字节、SHA-256 为 `B588101115196061E0D2BCE4B0BD0CB1B01CAEB7561B31D0F26F5C0E980696D4`,与既有已验收视频契约完全一致,实际 45.422 秒。新 32 秒文件原样保留,是否换用新片及重标候选/抓拍列 F5 待确认,不猜测新时间轴。 |

修改文件:

- `unreal_tran_web/src/features/training/trainingDocument.ts`
- `unreal_tran_web/src/views/content/ContentEditorView.vue`
- `unreal_tran_web/src/views/teaching/TeachingRunView.vue`
- `unreal_tran_web/src/styles/teaching-run-workspaces.scss`
- `unreal_tran_web/src/features/physical-legacy/runtime.ts`
- `unreal_tran_web/scripts/physical-offline-replay.contract.test.mjs`
- `unreal_tran_web/scripts/training-channel-profiles.contract.test.mjs`
- `unreal_tran_web/scripts/teaching-step-run.contract.test.mjs`

## 验证

先加入已保存步骤排序回归:修改前 8 项测试中 2 项失败,实际观察到序号 `[3,2,1]` 与 `[2,1]`,确认不是推测问题。

修复后的命令:

```text
node --experimental-strip-types --test scripts/physical-offline-replay.contract.test.mjs scripts/training-channel-profiles.contract.test.mjs scripts/teaching-step-run.contract.test.mjs scripts/teaching-command-idempotency.contract.test.mjs scripts/core-role-contract.test.mjs scripts/virtual-training-formal-route.contract.test.mjs scripts/training-scene-grounding.test.mjs
pnpm run build
git diff --check
```

- Node 回归:25/25 通过,详见 [frontend-contract-tests.log](frontend-contract-tests.log)。其中包含实际执行的渠道转换、已保存步骤重排、旧文档迁移、配置保存再打开、冻结命令载荷、失败步骤重试资格矩阵及离线视频 SHA/12 候选/抓拍资源等断言;路由与模板条件部分属于静态契约检查。
- 最后一轮构建通过(退出码 0):包含 `vue-tsc --noEmit`、Vite 打包与旧模型 iframe 校验,详见 [frontend-build.log](frontend-build.log)。已有资源 URL、包体大小告警不按构建失败统计。
- `git diff --check` 未发现空白错误;工作区 Git 的 LF/CRLF 提醒属于换行提示。
- 浏览器 API mock 回归 15/15 通过,详见 [frontend-e2e/README.md](frontend-e2e/README.md)。其中军工绿/暗色两套视觉用例确认场景和 host 同为 630×583.72px;安全区标题及复选框文本对比分别为 12.00/15.30,17 张截图均保留。真实 API E2E 另见同目录汇总,不能以 mock 或 Node 检查替代。
- 视频兼容性单独核实:静态服务 HEAD 200、Range 206、`video/mp4`;ffprobe 显示 H.264 Main/yuv420p + AAC LC。bundled Chromium 对新旧 H.264 都返回不支持并触发 `DEMUXER_ERROR_NO_SUPPORTED_STREAMS`,不属于该视频的产品解码缺陷;根代理使用本机 Edge 验证 H.264 可播放,真实离线回放使用 Edge 完成。

## 交给 E2E 的验证点

1. 在接口编排器重排已保存的实装/多渠道步骤,保存后检查序号与实装进度;删除末步后新末步为 100%。
2. 通用编辑器修改默认配置并保存,再进接口编排器,确认新值未丢失,未修改渠道原配置保留;切默认渠道后合同字段一致。
3. 普通三渠道运行以及执行中的正式考核不出现考核关闭遮罩;未开始/提交后的正式考核显示关闭态。
4. 正式虚拟主入口、旧 `/api` 重定向、命令丢响应后的重放、步骤事实失败与重试、教员/管理员身份边界继续验证。
5. 失败步骤显示“重试当前步骤”;点击后通过 start 端点进入执行中、尝试次数增加、失败次数保持,重试按钮消失。
6. 等待 `.teaching-three-scene` 的 `aria-label`/`.teaching-scene-status` 成为“已加载发布训练场景 · N 个对象”且无资产失败提示,再检查真实资产下载响应与截图。canvas 存在不等于 GLB 就绪。场景 host 应填满运行视口;明暗主题下安全确认区均应可读。
7. 本机 Edge 播放离线素材,3 秒出现首条候选,跳转至 `video.duration - 0.03` 后 `[data-event-timeline] [data-physical-event][data-offline-replay="true"]` 应为 12 条,包含 `offline-replay:replay-complete`;候选仍只供教员复核,不写正式运行进度。

本分工没有数据库变更、依赖版本变更或新增产品功能。所有待确认功能范围统一列入总报告,避免分散审批。

+ 295
- 0
reports/review-20260905/index.html Ver fichero

@@ -0,0 +1,295 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>review-report · Code Review</title><style>*{box-sizing:border-box}body{margin:0;background:#edf2f7;color:#203248;font:16px/1.75 "Microsoft YaHei","Segoe UI",sans-serif}header{background:#0c2b47;color:white;padding:22px max(24px,calc((100vw - 1160px)/2))}header a{color:#c1e6ff;margin-right:24px}main{max-width:1200px;margin:24px auto 60px;padding:36px;background:white;border-radius:12px;box-shadow:0 6px 24px #1028400c}h1{font-size:30px;line-height:1.4;margin-top:0}h2{font-size:22px;margin:36px 0 14px;border-bottom:2px solid #dbe8f2;padding-bottom:8px}h3{font-size:18px}p{margin:13px 0}a{color:#126fba;text-decoration:none}a:hover{text-decoration:underline}table{border-collapse:collapse;width:100%;margin:18px 0;font-size:14px;table-layout:auto}th,td{padding:11px 13px;border:1px solid #dce5ed;text-align:left;vertical-align:top;overflow-wrap:anywhere}th{background:#edf5fc}tr:nth-child(even){background:#f9fbfd}code{font:13px/1.5 Consolas,monospace;background:#edf2f7;padding:2px 5px;border-radius:4px;overflow-wrap:anywhere}pre{padding:18px;overflow:auto;background:#edf2f7;border-radius:8px}pre code{padding:0}img{max-width:100%;height:auto}details{padding:12px;background:#f7f9fc;margin:12px 0}.gallery{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}figure{margin:0;border:1px solid #dce5ed;border-radius:8px;overflow:hidden;background:#f8fafc}figure img{display:block;width:100%;height:300px;object-fit:contain;background:#e8eef3}figcaption{padding:12px;font-size:14px;color:#37526b}footer{color:#657c91;font-size:13px;margin-top:36px}li{margin:8px 0}@media(max-width:760px){main{margin:0;padding:20px;border-radius:0}.gallery{grid-template-columns:1fr}table{display:block;overflow:auto}h1{font-size:25px}header{padding:16px}}@media print{body{background:white}main{margin:0;box-shadow:none;max-width:none}header{display:none}figure{break-inside:avoid}h2{break-after:avoid}}</style></head><body><header><a href="index.html">审查总报告</a><a href="live/html/index.html">真实 E2E</a><a href="frontend-e2e/html/index.html">模拟接口回归</a></header><main><h1 id="更新项目-code-review-与-e2e-报告">更新项目 Code Review 与 E2E 报告</h1>
<p>审查日期:2026-09-05 · 开发环境:<code>E:/work/edit_3d</code> · 状态:审查与验证完成,4 项功能差异和 1 项素材选择待确认</p>
<p>本轮按 <strong>资料 → 产品原型 → 设计 → 开发 → 测试</strong> 审查更新,定位并修复 8 处问题:前端 6 处、后端 1 处、数据库迁移 1 处。修复沿用现有产品和接口合同。另有 4 项资料与现有功能的差异,以及 1 项素材选择,列在本报告末尾待确认。</p>
<p>前端与三个后端服务已启动。页面地址:<a href="http://127.0.0.1:6180/">本机 6180</a>,<a href="http://192.168.31.168:6180/">局域网 6180</a>。审查完成后已按用户授权提交:API <code>53d860b</code>、Web <code>3c7f295</code>。报告与测试脚本在 <code>ute2e</code> 独立提交。</p>
<h2 id="1-更新了什么">1. 更新了什么</h2>
<table>
<thead>
<tr>
<th>仓库与范围</th>
<th>同事本次更新的主要内容</th>
<th>本次重点</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>unreal_tran_api</code>:<code>3307c23..b683bb7</code>,57 个文件</td>
<td>多渠道训练定义;开始/推进/提交/评定命令幂等;步骤运行事实;三个登录身份与教员内容写权限;029–034 SQL</td>
<td>发布快照、权限、重复命令、失败重试、提交门槛、数据库兼容性</td>
</tr>
<tr>
<td><code>unreal_tran_web</code>:<code>0aac918..1576728</code>,29 个文件</td>
<td>虚拟训练切到正式 API 主入口;旧链接重定向;渠道编辑;步骤状态展示;命令追踪;角色、Three 资源及回放内容调整</td>
<td>默认入口可用性、编辑保存一致性、步骤顺序、运行页和重试入口</td>
</tr>
</tbody>
</table>
<h2 id="2-从资料到测试的追溯">2. 从资料到测试的追溯</h2>
<p>已逐页查看《维修训练内容设计.pdf》6 页和《电器控制系统故障维修脚本.pdf》5 页,并保留 <a href="reference-review.html#1-原始资料与阅读证据">11 页资料图像</a>。这些文件没有可提取文本层;维修设计第 6 页下半流程图扫描过淡,未据此臆测执行规则。</p>
<p><code>AGENTS.md</code> 指定的相邻 <code>unreal_tran_demo</code> 当前缺失。本次结合正式前端中移植的原型源码、适配层和 API wiki 追溯,未把废弃原型目录当作最新定稿。完整依据见 <a href="reference-review.html">资料与原型审查</a>。</p>
<table>
<thead>
<tr>
<th>资料要求</th>
<th>原型与设计</th>
<th>开发检查</th>
<th>测试证据</th>
</tr>
</thead>
<tbody>
<tr>
<td>同一任务贯穿不同训练方式</td>
<td>移植训练原型;wiki 19:公共步骤与渠道 profile、固定发布快照</td>
<td>渠道配置读写与默认配置镜像;切换默认渠道;保留其他渠道</td>
<td>浏览器模拟接口回归验证保存载荷与重新打开;Node 执行渠道转换逻辑</td>
</tr>
<tr>
<td>教员编排、学员训练、权限分工</td>
<td>现有角色合同与 wiki 14</td>
<td>三种登录角色;内容写入需有效教员身份</td>
<td>真实登录与教员列表;纯管理员创建内容返回 403;后端权限回归</td>
</tr>
<tr>
<td>逐步骤留痕、失败可重试</td>
<td>电气脚本五阶段;wiki 21 的四种步骤状态</td>
<td>顺序/进度;开始、失败、重试、完成;迟到证据</td>
<td>真实 API/数据库步骤事实、迟到事件 400;浏览器重试按钮与状态变化</td>
</tr>
<tr>
<td>操作不重复记账</td>
<td>wiki 20 的命令号与冻结载荷</td>
<td>同载荷重放、异载荷冲突、运行版本与事务边界</td>
<td>真实 start/progress/review 重放不增加版本,异载荷 409;服务测试</td>
</tr>
<tr>
<td>完成后提交、结果可查询</td>
<td>当前 wiki 21 要求全步骤完成;资料的提前交卷另列确认</td>
<td>提交门槛、评定、刷新后的服务端结果</td>
<td>真实未完成提交 409;8 步完成后 UI 提交;评定后刷新</td>
</tr>
<tr>
<td>训练页面可操作、状态清晰</td>
<td>正式 API 主入口与已有工作台设计</td>
<td>错误考核遮罩、场景尺寸、安全提示对比度</td>
<td>真实场景就绪截图;军工绿/暗色主题尺寸与文字对比测量</td>
</tr>
</tbody>
</table>
<h2 id="3-已修复的-8-处问题">3. 已修复的 8 处问题</h2>
<p>P1 表示阻断目标环境部署;P2 表示功能、状态或界面正确性问题。下列均已修改代码或 DDL,详细复现、修改文件与验证见 <a href="frontend-review.html">前端审查</a>、<a href="backend-review.html">后端审查</a> 和 <a href="database-review.html">数据库审查</a>。</p>
<table>
<thead>
<tr>
<th>编号</th>
<th>级别</th>
<th>触发与原行为</th>
<th>修复后的行为</th>
</tr>
</thead>
<tbody>
<tr>
<td>DB-01</td>
<td>P1</td>
<td>KingbaseES 当前版本不接受 034 中独立 UNIQUE INDEX 作为组合外键目标,步骤事实表迁移失败</td>
<td>新增 035,使用真正 UNIQUE 约束;同步全量 DDL,保留历史 034;开发库执行 033 → 035</td>
</tr>
<tr>
<td>API-01</td>
<td>P2</td>
<td>步骤失败后重试,收到重试开始前的迟到成功/失败事件,可能错误完成当前尝试或增加失败次数</td>
<td>从已有激活事件校验本次尝试开始时间;旧事件返回 400,不改变当前事实</td>
</tr>
<tr>
<td>WEB-01</td>
<td>P2</td>
<td>实装步骤移动、删除或复制后仍保留旧顺序和进度,如 <code>3,2,1</code> / <code>100,67,33</code>,发布可能被拒</td>
<td>按编辑后的顺序重算顺序号(order)和严格递增进度,末步 100%;保留步骤标识和事件扩展字段</td>
</tr>
<tr>
<td>WEB-02</td>
<td>P2</td>
<td>通用编辑器只修改旧 <code>modeConfig</code>,重开被旧渠道 profile 覆盖;切默认渠道字段不一致</td>
<td>同步默认渠道、启用渠道、profile 和兼容镜像;保留其他渠道与历史对抗时长</td>
</tr>
<tr>
<td>WEB-03</td>
<td>P2</td>
<td><code>v-else</code> 绑定到拆卸侧栏条件,普通训练也显示“正式考核执行场景已关闭”</td>
<td>仅正式考核且不在执行中显示关闭态</td>
</tr>
<tr>
<td>WEB-04</td>
<td>P2</td>
<td>当前步骤 FAILED,但运行仍 IN_PROGRESS,学员没有重试入口</td>
<td>补“重试当前步骤”,复用已有 start 和幂等命令;结束状态不出现该按钮</td>
</tr>
<tr>
<td>WEB-05</td>
<td>P2</td>
<td>场景仅占视口上方,安全确认区暗底深字</td>
<td>补运行场景填充定位和已有主题颜色、间距;安全确认规则保持原有语义</td>
</tr>
<tr>
<td>WEB-06</td>
<td>P2</td>
<td>默认离线视频替换为约 32 秒的新素材,但候选、阶段与抓拍仍对应 45 秒旧片;34 秒和 45.2 秒候选无法到达</td>
<td>将默认映射指向仓库保留、哈希与既有验收合同一致的 45 秒视频;新素材保留,重标与切换列 F5 确认</td>
</tr>
</tbody>
</table>
<h2 id="4-测试结果与边界">4. 测试结果与边界</h2>
<table>
<thead>
<tr>
<th>验证</th>
<th>最终结果</th>
<th>证据与范围</th>
</tr>
</thead>
<tbody>
<tr>
<td>后端全量测试</td>
<td>402/402 通过,0 失败、错误、跳过</td>
<td>Gateway 1、Auth 86、Tran 315;含新增迟到事件回归</td>
</tr>
<tr>
<td>后端构建</td>
<td>通过</td>
<td>Java 17 / Maven;三个服务打包完成</td>
</tr>
<tr>
<td>前端 Node 回归</td>
<td>25/25 通过</td>
<td>原 24 项加视频资产、候选与抓拍合同</td>
</tr>
<tr>
<td>前端构建</td>
<td>通过</td>
<td>vue-tsc、Vite、旧模型 iframe 校验</td>
</tr>
<tr>
<td>真实服务 E2E(Edge)</td>
<td>4/4 通过,0 失败/跳过/重试</td>
<td>62.3 秒;开始时间 2026-09-05T03:47:45.212Z(UTC)</td>
</tr>
<tr>
<td>浏览器模拟接口回归</td>
<td>15/15 通过,0 失败/跳过/重试</td>
<td>117.7 秒;编辑保存、状态组合、军工绿/暗色主题</td>
</tr>
<tr>
<td>数据库迁移</td>
<td>通过</td>
<td>033、035;首次、重跑和指定中断点恢复;开发库检查值均为 0</td>
</tr>
<tr>
<td>测试脚本类型检查、Git 空白检查</td>
<td>通过</td>
<td>两份专用 Playwright 测试与配置;API/Web/E2E 仓库</td>
</tr>
</tbody>
</table>
<p>最终通过结果来自各自一次完整运行,未启用自动重试。补入三次完整场景加载与截图后,真实虚拟用例曾超过原 120 秒测试总预算,断言和评定已完成但截图收尾超时;保留 <a href="diagnostics/live-resource-wait-timeout.json">该轮记录</a>,随后将该用例预算调到 240 秒,并在其他浏览器回归结束后重新完整运行。单次资源就绪仍有 60 秒上限。</p>
<p>实装视频新增检查还发现本机 Playwright 自带 Chromium 不支持 H.264(<code>canPlayType</code> 返回空字符串);<a href="diagnostics/live-chromium-h264-unsupported.json">失败轮次</a>与<a href="diagnostics/video-probe-msedge.json">Edge 播放探针</a>保留。最终真实 E2E 使用已安装 Microsoft Edge,模拟接口回归继续使用 Chromium。另独立修复了实际素材与候选配置不匹配的 WEB-06;最终用例验证默认视频可播、片长覆盖末尾候选,并在结尾得到 12 条候选。测试开发阶段的权限夹具、请求拦截范围与 UI 选择器错误已修正,不计为产品缺陷。</p>
<p>真实 E2E 使用本机正在运行的 Web、Gateway、Auth、Tran 与开发数据库,没有拦截 <code>/api</code>。通过专用临时账号和临时任务验证,避免改动现有训练记录。</p>
<p>浏览器回归使用真实前端组件与 Playwright,但对 API 返回进行模拟,以稳定重现排序、多渠道保存、遮罩及失败重试的状态组合。它能验证页面与请求载荷,不能作为后端持久化已通过的证据。</p>
<p>真实虚拟训练用例通过 UI 开始、UI 提交,通过真实 API 逐步推进 8 个步骤并验证服务端事实和重放。它验证了页面、接口与数据库的联通,<strong>没有逐个通过三维鼠标操作完成训练动作</strong>。三维资源加载与场景尺寸单独等待和检查。</p>
<p>实装测试验证的是失败 → 重试 → 拒绝旧证据 → 接受新证据的真实 API/数据库流程。现有实装样例的源项目已回到 DRAFT,因此复制其历史发布快照为隔离测试任务并配置人工事件;未更改原项目。默认实装运行仍是离线回放,现场 AI、硬件、MR、完整电气训练和自动评分不属于本轮通过项。真实并发压力与故障注入恢复未作为完整系统验收执行。</p>
<h2 id="5-截图证据">5. 截图证据</h2>
<p>以下截图来自最终通过轮次。点击图像可查看原尺寸。模拟接口截图的图注明确标注,资料扫描不计入 E2E 截图。</p>
<p>共 27 张最终 E2E 截图。</p>
<h3>真实服务与页面</h3>
<div class="gallery">
<figure><a href="live/screenshots/01-login-three-roles.png"><img loading="lazy" src="live/screenshots/01-login-three-roles.png" alt="真实服务:登录仅展示教员、学员、管理员"></a><figcaption>真实服务:登录仅展示教员、学员、管理员</figcaption></figure>
<figure><a href="live/screenshots/02-virtual-formal-task-center.png"><img loading="lazy" src="live/screenshots/02-virtual-formal-task-center.png" alt="真实服务:正式虚拟任务中心"></a><figcaption>真实服务:正式虚拟任务中心</figcaption></figure>
<figure><a href="live/screenshots/03-virtual-1920.png"><img loading="lazy" src="live/screenshots/03-virtual-1920.png" alt="真实服务:1920 宽度任务中心"></a><figcaption>真实服务:1920 宽度任务中心</figcaption></figure>
<figure><a href="live/screenshots/04-teacher-content.png"><img loading="lazy" src="live/screenshots/04-teacher-content.png" alt="真实服务:教员训练编排列表已加载"></a><figcaption>真实服务:教员训练编排列表已加载</figcaption></figure>
<figure><a href="live/screenshots/05-virtual-started-no-exam-overlay.png"><img loading="lazy" src="live/screenshots/05-virtual-started-no-exam-overlay.png" alt="真实服务:开始训练、模型就绪,无错误考核遮罩"></a><figcaption>真实服务:开始训练、模型就绪,无错误考核遮罩</figcaption></figure>
<figure><a href="live/screenshots/06-virtual-all-steps-completed.png"><img loading="lazy" src="live/screenshots/06-virtual-all-steps-completed.png" alt="真实服务:8/8 步骤完成,真实模型已加载"></a><figcaption>真实服务:8/8 步骤完成,真实模型已加载</figcaption></figure>
<figure><a href="live/screenshots/07-virtual-reviewed.png"><img loading="lazy" src="live/screenshots/07-virtual-reviewed.png" alt="真实服务:提交评定后刷新"></a><figcaption>真实服务:提交评定后刷新</figcaption></figure>
<figure><a href="live/screenshots/08-physical-api-task-list.png"><img loading="lazy" src="live/screenshots/08-physical-api-task-list.png" alt="真实服务:实装任务清单"></a><figcaption>真实服务:实装任务清单</figcaption></figure>
<figure><a href="live/screenshots/09-physical-offline-replay-boundary.png"><img loading="lazy" src="live/screenshots/09-physical-offline-replay-boundary.png" alt="真实页面:离线视频就绪,并标明不推进正式进度、不计分"></a><figcaption>真实页面:离线视频就绪,并标明不推进正式进度、不计分</figcaption></figure>
<figure><a href="live/screenshots/10-physical-twelve-candidates.png"><img loading="lazy" src="live/screenshots/10-physical-twelve-candidates.png" alt="真实页面:匹配视频到达末尾,12 条离线候选可达、等待复核"></a><figcaption>真实页面:匹配视频到达末尾,12 条离线候选可达、等待复核</figcaption></figure>
</div>
<h3>模拟接口浏览器回归</h3>
<div class="gallery">
<figure><a href="frontend-e2e/screenshots/01-saved-step-delete.png"><img loading="lazy" src="frontend-e2e/screenshots/01-saved-step-delete.png" alt="模拟接口:删除步骤后末步进度 100%"></a><figcaption>模拟接口:删除步骤后末步进度 100%</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/01-saved-step-duplicate.png"><img loading="lazy" src="frontend-e2e/screenshots/01-saved-step-duplicate.png" alt="模拟接口:复制步骤后重新编号"></a><figcaption>模拟接口:复制步骤后重新编号</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/01-saved-step-move.png"><img loading="lazy" src="frontend-e2e/screenshots/01-saved-step-move.png" alt="模拟接口:移动已保存步骤后保持正确顺序"></a><figcaption>模拟接口:移动已保存步骤后保持正确顺序</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/02-multichannel-virtual-save.png"><img loading="lazy" src="frontend-e2e/screenshots/02-multichannel-virtual-save.png" alt="模拟接口:虚拟渠道配置保存与重开"></a><figcaption>模拟接口:虚拟渠道配置保存与重开</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/03-multichannel-confrontation-save.png"><img loading="lazy" src="frontend-e2e/screenshots/03-multichannel-confrontation-save.png" alt="模拟接口:对抗渠道配置保存与重开"></a><figcaption>模拟接口:对抗渠道配置保存与重开</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/04-overlay-1-VIRTUAL-IN_PROGRESS.png"><img loading="lazy" src="frontend-e2e/screenshots/04-overlay-1-VIRTUAL-IN_PROGRESS.png" alt="模拟接口:关闭遮罩状态矩阵 1-VIRTUAL-IN_PROGRESS"></a><figcaption>模拟接口:关闭遮罩状态矩阵 1-VIRTUAL-IN_PROGRESS</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/04-overlay-2-PHYSICAL-IN_PROGRESS.png"><img loading="lazy" src="frontend-e2e/screenshots/04-overlay-2-PHYSICAL-IN_PROGRESS.png" alt="模拟接口:关闭遮罩状态矩阵 2-PHYSICAL-IN_PROGRESS"></a><figcaption>模拟接口:关闭遮罩状态矩阵 2-PHYSICAL-IN_PROGRESS</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/04-overlay-3-CONFRONTATION-IN_PROGRESS.png"><img loading="lazy" src="frontend-e2e/screenshots/04-overlay-3-CONFRONTATION-IN_PROGRESS.png" alt="模拟接口:关闭遮罩状态矩阵 3-CONFRONTATION-IN_PROGRESS"></a><figcaption>模拟接口:关闭遮罩状态矩阵 3-CONFRONTATION-IN_PROGRESS</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/04-overlay-4-VIRTUAL-IN_PROGRESS.png"><img loading="lazy" src="frontend-e2e/screenshots/04-overlay-4-VIRTUAL-IN_PROGRESS.png" alt="模拟接口:关闭遮罩状态矩阵 4-VIRTUAL-IN_PROGRESS"></a><figcaption>模拟接口:关闭遮罩状态矩阵 4-VIRTUAL-IN_PROGRESS</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/04-overlay-5-VIRTUAL-ACCEPTED.png"><img loading="lazy" src="frontend-e2e/screenshots/04-overlay-5-VIRTUAL-ACCEPTED.png" alt="模拟接口:关闭遮罩状态矩阵 5-VIRTUAL-ACCEPTED"></a><figcaption>模拟接口:关闭遮罩状态矩阵 5-VIRTUAL-ACCEPTED</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/04-overlay-6-VIRTUAL-SUBMITTED.png"><img loading="lazy" src="frontend-e2e/screenshots/04-overlay-6-VIRTUAL-SUBMITTED.png" alt="模拟接口:关闭遮罩状态矩阵 6-VIRTUAL-SUBMITTED"></a><figcaption>模拟接口:关闭遮罩状态矩阵 6-VIRTUAL-SUBMITTED</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/05-failed-before-retry.png"><img loading="lazy" src="frontend-e2e/screenshots/05-failed-before-retry.png" alt="模拟接口:失败步骤出现重试入口"></a><figcaption>模拟接口:失败步骤出现重试入口</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/06-failed-after-retry.png"><img loading="lazy" src="frontend-e2e/screenshots/06-failed-after-retry.png" alt="模拟接口:重试后尝试 2 次、失败 1 次"></a><figcaption>模拟接口:重试后尝试 2 次、失败 1 次</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/07-no-retry-SUBMITTED.png"><img loading="lazy" src="frontend-e2e/screenshots/07-no-retry-SUBMITTED.png" alt="模拟接口:已提交不出现重试按钮"></a><figcaption>模拟接口:已提交不出现重试按钮</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/07-no-retry-TERMINATED.png"><img loading="lazy" src="frontend-e2e/screenshots/07-no-retry-TERMINATED.png" alt="模拟接口:已终止不出现重试按钮"></a><figcaption>模拟接口:已终止不出现重试按钮</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/08-visual-safety-dark.png"><img loading="lazy" src="frontend-e2e/screenshots/08-visual-safety-dark.png" alt="模拟接口:暗色主题安全区,对比度 15.30"></a><figcaption>模拟接口:暗色主题安全区,对比度 15.30</figcaption></figure>
<figure><a href="frontend-e2e/screenshots/08-visual-safety-military.png"><img loading="lazy" src="frontend-e2e/screenshots/08-visual-safety-military.png" alt="模拟接口:军工绿主题安全区,对比度 12.00"></a><figcaption>模拟接口:军工绿主题安全区,对比度 12.00</figcaption></figure>
</div>

<h2 id="6-数据库运行环境与清理">6. 数据库、运行环境与清理</h2>
<p>目标数据库为开发环境 <code>unreal_tran/public</code>,KingbaseES V008R006C009B0014。迁移前保存四张教学主表原始行、字段和索引信息,恢复快照位于 <code>E:/work/edit_3d/.codex-tmp/review-20260905/database-before.json</code>;该文件含业务数据,未打进报告包。</p>
<p>实际执行 033 与新增 035。029–032 未在本次审查中重新执行,未注入新的前泵课目。035 与全量 DDL 的步骤表、父表约束保持一致;验证了隔离 schema 首次执行、重复执行、中途提交后续跑,所有无效行/孤儿行检查为 0。隔离 schema 已删除。详见 <a href="database-migration.json">迁移执行记录</a>。其他环境部署应先核对现状,按文档执行 <strong>033 → 035</strong>,迁移工具需支持 dollar quote。</p>
<p>最终清理时间:<code>2026-09-05T11:48:55.347416</code>。删除本轮临时任务及关联记录(step_run 12, command_receipt 13, event 19, run_member 2, run 2, member 2, assignment 2),测试账号已停用/软删除,会话已撤销,临时管理员密码文件已移除。四张原始教学表的既有行逐行对照均未变化,剩余活动测试账号为 0。审计与软删除记录按系统机制保留。见 <a href="cleanup.json">最终清理记录</a>;较早轮次见 <code>diagnostics/cleanup-*.json</code>。</p>
<p>环境检查记录见 <a href="environment.json">environment.json</a>。三个服务健康接口及本机、局域网前端访问返回 200。Nacos 与 Redis 保留运行。</p>
<p>构建中的既有资源 URL、包体积和部分 Java 弃用警告未阻断打包;未因此升级依赖版本。报告不包含密码或会话令牌。</p>
<h2 id="7-待你确认的功能差异与素材选择">7. 待你确认的功能差异与素材选择</h2>
<p>这些涉及产品口径或新增接入,未作为本轮 bugfix 擅自扩展。建议逐项确认后安排对应实现与验收。</p>
<table>
<thead>
<tr>
<th>编号</th>
<th>现状与资料差异</th>
<th>需要确认的决定</th>
</tr>
</thead>
<tbody>
<tr>
<td>F1:实装正式训练</td>
<td>电气资料及移植原型为五阶段、15 分钟;默认实装页面当前是约 45 秒离线识别回放,未接入正式步骤与计分</td>
<td>此次交付继续保留回放,还是把电气模板发布为正式内容、接入真实事件/人工证据、提交与评定?</td>
</tr>
<tr>
<td>F2:混合现实归属</td>
<td>资料列虚拟、混合现实、AI 实装;当前渠道为 VIRTUAL、PHYSICAL、CONFRONTATION</td>
<td>MR 作为虚拟渠道的呈现能力/沉浸入口,还是新增渠道?对抗是否属于另一维度的组织形式?</td>
</tr>
<tr>
<td>F3:提前提交规则</td>
<td>资料允许未收尾提交并扣分;当前新运行要求全部步骤 COMPLETED 才能提交</td>
<td>保留全部完成门槛,还是新增提前交卷/放弃结算及未完成、失败、超时计分规则?</td>
</tr>
<tr>
<td>F4:评分与复盘范围</td>
<td>资料包括自动结算、90/80/70 分档、全过程复盘和知识拓展;当前以服务端规则/教员评定为权威</td>
<td>这些是否纳入此次交付?若纳入,需要确认评分规则、等级文案、复盘证据及知识资源来源。</td>
</tr>
<tr>
<td>F5:离线回放素材</td>
<td>新增约 32 秒素材与现有 12 条候选/抓拍不匹配;本轮默认恢复指向已验收的约 45 秒素材</td>
<td>继续使用当前匹配素材,还是采用新片?采用新片需要先重新标注候选、阶段时间与证据抓拍,再整体切换。</td>
</tr>
</tbody>
</table>
<p>各项 PDF 页码、原型和代码依据见 <a href="reference-review.html#4-需要用户确认的功能差异">完整差异说明</a>。</p>
<h2 id="8-报告文件与复跑">8. 报告文件与复跑</h2>
<ul>
<li><a href="live/html/index.html">真实接口 Playwright 报告</a> · <a href="live/results.json">JSON</a> · <a href="live/api-evidence.json">API 断言证据</a></li>
<li><a href="frontend-e2e/html/index.html">前端模拟接口 Playwright 报告</a> · <a href="frontend-e2e/results.json">JSON</a></li>
<li><a href="backend-mvn-test.log">后端测试日志</a> · <a href="backend-package.log">后端打包日志</a></li>
<li><a href="frontend-contract-tests.log">前端回归日志</a> · <a href="frontend-build.log">前端构建日志</a></li>
<li><a href="reference-review.html">资料与原型</a> · <a href="frontend-review.html">前端审查</a> · <a href="backend-review.html">后端审查</a> · <a href="database-review.html">数据库审查</a></li>
</ul>
<p>测试源码位于 <code>ute2e/tests/review-live.spec.ts</code> 和 <code>tests/review-frontend-regressions.spec.ts</code>,配置为 <code>review-live.config.ts</code> / <code>review-frontend.config.ts</code>。回归脚本读取本地配置,不内嵌密码。</p>
<p>已具备开发库表结构时,可在 <code>ute2e</code> 顺序运行:</p>
<pre><code class="language-powershell">python tools/review-db.py test-admin
pnpm exec playwright test --config review-live.config.ts
python tools/review-db.py cleanup
pnpm exec playwright test --config review-frontend.config.ts
</code></pre>
<p>真实测试后即使失败也需执行 cleanup。当前开发库已完成迁移,<code>prepare</code> 会因目标表存在而拒绝重跑。报告 JSON 保留时间、断言和最终结果,可据此核验,测试计数不等同于完整功能覆盖率。</p><footer>2026-09-05 · 开发环境审查 · 图片可点击查看原尺寸</footer></main></body></html>

+ 119
- 0
reports/review-20260905/live/api-evidence.json Ver fichero

@@ -0,0 +1,119 @@
[
{
"case": "admin content create requires teacher role",
"status": 403
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E8%BD%A6.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E8%BD%A6.glb",
"status": 200
},
{
"case": "published scene ready",
"status": "已加载发布训练场景 · 2 个对象",
"height": 1149.40625
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E8%BD%A6.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "published scene ready",
"status": "已加载发布训练场景 · 2 个对象",
"height": 1149.40625
},
{
"case": "scene asset response",
"path": "/models/%E8%BD%A6.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E8%BD%A6.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E5%9C%B0%E5%BD%A2.glb",
"status": 200
},
{
"case": "scene asset response",
"path": "/models/%E8%BD%A6.glb",
"status": 200
},
{
"case": "published scene ready",
"status": "已加载发布训练场景 · 2 个对象",
"height": 1149.40625
},
{
"case": "virtual lifecycle",
"runId": "42",
"steps": 8,
"startReplay": true,
"progressReplay": true,
"reviewReplay": true,
"earlySubmit": 409,
"conflictingCommand": 409
},
{
"case": "physical offline replay",
"readyState": 4,
"duration": 45.422,
"candidates": 12,
"formalProgressAutomatic": false
},
{
"case": "physical retry evidence",
"runId": "43",
"staleStatus": 400,
"final": "COMPLETED",
"attemptCount": 2,
"failureCount": 1
},
{
"cleanupUser": "93",
"status": 200
},
{
"cleanupUser": "94",
"status": 200
}
]

+ 24
- 0
reports/review-20260905/live/cleanup-manifest.json Ver fichero

@@ -0,0 +1,24 @@
{
"prefix": "UTE2E-REVIEW-MTNUE3OB",
"tasks": [
{
"id": "48",
"code": "UTE2E-REVIEW-MTNUE3OB-VIRTUAL"
},
{
"id": "49",
"code": "UTE2E-REVIEW-MTNUE3OB-PHYSICAL",
"version": 0
}
],
"users": [
{
"id": "93",
"username": "rv_teacher_mtnue4h0"
},
{
"id": "94",
"username": "rv_student_mtnue4z2"
}
]
}











+ 76
- 0
reports/review-20260905/live/html/index.html
La diferencia del archivo ha sido suprimido porque es demasiado grande
Ver fichero


+ 285
- 0
reports/review-20260905/live/results.json Ver fichero

@@ -0,0 +1,285 @@
{
"config": {
"configFile": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\review-live.config.ts",
"rootDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
"forbidOnly": false,
"fullyParallel": false,
"globalSetup": null,
"globalTeardown": null,
"globalTimeout": 0,
"grep": {},
"grepInvert": null,
"maxFailures": 0,
"metadata": {
"actualWorkers": 1
},
"preserveOutput": "always",
"reporter": [
[
"list",
null
],
[
"html",
{
"outputFolder": "reports/review-20260905/live/html",
"open": "never"
}
],
[
"json",
{
"outputFile": "reports/review-20260905/live/results.json"
}
]
],
"reportSlowTests": {
"max": 5,
"threshold": 300000
},
"quiet": false,
"projects": [
{
"outputDir": "E:/work/edit_3d/unreal_tran/ute2e/reports/review-20260905/live/results",
"repeatEach": 1,
"retries": 0,
"metadata": {
"actualWorkers": 1
},
"id": "",
"name": "",
"testDir": "E:/work/edit_3d/unreal_tran/ute2e/tests",
"testIgnore": [],
"testMatch": [
"review-live.spec.ts"
],
"timeout": 120000
}
],
"shard": null,
"updateSnapshots": "missing",
"updateSourceMethod": "patch",
"version": "1.55.0",
"workers": 1,
"webServer": null
},
"suites": [
{
"title": "review-live.spec.ts",
"file": "review-live.spec.ts",
"column": 0,
"line": 0,
"specs": [
{
"title": "live login roles and formal virtual route",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 10075,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:47:46.359Z",
"annotations": [],
"attachments": [
{
"name": "01-login-three-roles",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\01-login-three-roles-f86162e84bea73eafa87bb0016f894f5e08d87b7.png"
},
{
"name": "02-virtual-formal-task-center",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\02-virtual-formal-task-center-b028ec31b409a20f12f7cf73dc7811535baab32e.png"
},
{
"name": "03-virtual-1920",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-login-roles-and-formal-virtual-route\\attachments\\03-virtual-1920-a16d403e0842f961f464136f9289448b6ab9b665.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-8b85e2f4bb6d5591db73",
"file": "review-live.spec.ts",
"line": 101,
"column": 1
},
{
"title": "live teacher role and content write boundary",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 3621,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:48:00.081Z",
"annotations": [],
"attachments": [
{
"name": "04-teacher-content",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-teacher-role-and-content-write-boundary\\attachments\\04-teacher-content-9ec9c5654b6ad956d784269cf0e1e26d7dbe0821.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-ccf9c84f5598cdf4198c",
"file": "review-live.spec.ts",
"line": 123,
"column": 1
},
{
"title": "live virtual start replay step facts submit and review",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 240000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 29615,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"startTime": "2026-09-05T03:48:03.714Z",
"annotations": [],
"attachments": [
{
"name": "05-virtual-started-no-exam-overlay",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\05-virtual-started-no-exam-overlay-75bbe0fc16a0254e73c609a1a3640d65844c77c7.png"
},
{
"name": "06-virtual-all-steps-completed",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\06-virtual-all-steps-completed-1fb317f851bbfea81cb72348d0c3fa6677c15524.png"
},
{
"name": "07-virtual-reviewed",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-virtual-s-f74ac-tep-facts-submit-and-review\\attachments\\07-virtual-reviewed-c18c9c866c30a33295adba3a8277bc7dd76aab69.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-3ecee091831f9a0e83fd",
"file": "review-live.spec.ts",
"line": 134,
"column": 1
},
{
"title": "live physical failure retry rejects stale evidence",
"ok": true,
"tags": [],
"tests": [
{
"timeout": 120000,
"annotations": [],
"expectedStatus": "passed",
"projectId": "",
"projectName": "",
"results": [
{
"workerIndex": 0,
"parallelIndex": 0,
"status": "passed",
"duration": 12182,
"errors": [],
"stdout": [],
"stderr": [],
"retry": 0,
"steps": [
{
"title": "Expect \"poll toBeGreaterThanOrEqual\"",
"duration": 37
},
{
"title": "Expect \"poll toBe\"",
"duration": 182
}
],
"startTime": "2026-09-05T03:48:33.345Z",
"annotations": [],
"attachments": [
{
"name": "08-physical-api-task-list",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\attachments\\08-physical-api-task-list-7e7551f3cc59abf587e5d2dc7d0767de16241cf6.png"
},
{
"name": "09-physical-offline-replay-boundary",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\attachments\\09-physical-offline-replay-boundary-8d0df1dc3e5920624b8c5bf326857aceffaacf5f.png"
},
{
"name": "10-physical-twelve-candidates",
"contentType": "image/png",
"path": "E:\\work\\edit_3d\\unreal_tran\\ute2e\\reports\\review-20260905\\live\\results\\review-live-live-physical--961b7-etry-rejects-stale-evidence\\attachments\\10-physical-twelve-candidates-6f4ae9579c7b7e5444e0b2ddbc0742a662f98d70.png"
}
]
}
],
"status": "expected"
}
],
"id": "351158f247962162d567-f2520dc34ab1c6ead4ee",
"file": "review-live.spec.ts",
"line": 201,
"column": 1
}
]
}
],
"errors": [],
"stats": {
"startTime": "2026-09-05T03:47:45.212Z",
"duration": 62279.288,
"expected": 4,
"skipped": 0,
"unexpected": 0,
"flaky": 0
}
}

+ 4
- 0
reports/review-20260905/live/results/.last-run.json Ver fichero

@@ -0,0 +1,4 @@
{
"status": "passed",
"failedTests": []
}



Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio

Cargando…
Cancelar
Guardar