fix(a2a): 收敛取消/回滚/候选重跑后 Pipeline 快照的非终态悬挂节点 - #299
Closed
ruanzhengxin-zhuxing wants to merge 2 commits into
Closed
Conversation
pyproject 里 ruff 未固定版本(ruff>=0.4.0),uv 解析到 0.15.10,其 formatter 与当前提交树最后一次格式化所用版本不一致,导致 `make format` 每次都会重写这 16 个与本次改动无关的文件;由于 format 钩子是全树执行(pass_filenames: false),任何提交都会被它拦下。 这里只落地 `make format` 的机械输出,不含任何逻辑改动,以便后续修复的 diff 保持可评审。
…k/restart 取消、回滚、候选重跑之后,a2a-snapshot.json 中被打断的 step/candidate/ candidate step 仍停留在 working 且 conclusion 为 null,前端因此永远渲染 "进行中",且同一逻辑步骤会出现新旧两条非终态记录。 根因在 _PipelineSnapshotReducer:pipeline_canceled / pipeline_failed 只 改写快照顶层 status 并清空 activeCandidateRunIds,从不下钻步骤树; rollback_completed 只追加历史;candidate_restart_requested 只把候选置为 非终态的 restarting,都不会终结被取代的节点。 - 引入 _TERMINAL_NODE_STATUSES / superseded 词表与 _finalize_node,仅在 节点尚未终态且 conclusion 为空时补写状态、时间与显式 conclusion; - 运行终止时下钻整棵步骤树收敛悬挂节点,仅传播 canceled/failed,避免为 从未上报 step_completed 的节点伪造成功; - rollback_completed 将同一 step id 的旧 attempt 折叠为 superseded; - candidate_started 终结同一候选更早的 attempt, candidate_restart_requested 终结被重启候选的子步骤。 语义与 web/pipeline_transcript.py 的 _finalize_active_markers 对齐;归约 保持幂等,历史快照重放即自愈,schemaVersion 不变、无需数据迁移。 新增 7 条回归用例,分别覆盖三个证据 Session(afb47b236ee54db5a22d295609b69de6 取消、e0c70d3197184b49826469b509a6fd21 回滚、7eaff0d9bcff4de7a1ba510ed030f017 候选重启)、成功运行不被伪造、已终态节点不被覆盖与重放幂等。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
问题
取消、回滚、候选重跑之后,
a2a-snapshot.json中被打断的 step / candidate /candidate step 仍停留在
working且conclusion为null,导致:architecture_planning在 run 已取消后仍为working);step id + index出现新旧两条记录,旧的working/null与新的completed并存;evaluate_candidates:1及其候选/子步骤停留在working/pending;quality_signals(failed=0、canceled=0)与事件日志、步骤快照三方互相矛盾。证据 Session:
afb47b236ee54db5a22d295609b69de6(取消)、e0c70d3197184b49826469b509a6fd21(回滚)、7eaff0d9bcff4de7a1ba510ed030f017(候选重启)。根因
缺陷不在
pipeline/engine/state_machine.py(它已把中间态标为STALE,但那是引擎内部状态,不是快照投影面),而在
a2a/pipeline_snapshot.py的_PipelineSnapshotReducer:pipeline_canceled/pipeline_failedstatus并清空activeCandidateRunIds,从不下钻步骤树working+conclusion: nullrollback_completedrollbackHistory;而pipeline_events会 bump stepattemptworkingcandidate_restart_requestedrestarting,不碰子步骤working/pending三条路径都不写
conclusion,所以它始终为null。改动
src/iac_code/a2a/pipeline_snapshot.py_TERMINAL_NODE_STATUSES/_SUPERSEDED_NODE_STATUS/_FINALIZED_TIME_KEY_BY_STATUS词表与_is_terminal_node/_finalize_nodehelper;_finalize_non_terminal_nodes:run 终止时下钻整棵步骤树收敛悬挂节点;_supersede_rolled_back_steps:rollback_completed时把同一 step id 的旧 attempt 折叠为superseded;_supersede_earlier_candidate_attempts:candidate_started时终结同一候选(id + index)更早的 attempt;_supersede_restarted_candidate:candidate_restart_requested时终结被重启候选的子步骤。两条防伪造保护
canceled/failed。 成功的 run 会为每个步骤发step_completed,若把
completed下推给从未上报完成的节点,等于伪造一次没有发生的成功。conclusion仅在为None时补写,已终态节点直接跳过,绝不覆盖流水线真实上报的结论。一致性与兼容性
web/pipeline_transcript.py既有的_finalize_active_markers/_on_pipeline_canceled先例,而非另造一套状态模型,两个投影面保持一致;schemaVersion不变;web/static/js/app.js:2641把任何非working状态视为已结束,2858 行已能渲染
canceled,components/pipeline.js:431的活跃状态表也已覆盖。测试
tests/a2a/test_pipeline_snapshot.py新增 7 条用例,逐一对应证据 Session 与不变量:test_reduce_pipeline_canceled_finalizes_dangling_nodesafb47b236ee54db5a22d295609b69de6取消场景,step/candidate/子步骤三层收敛test_reduce_pipeline_failed_finalizes_dangling_nodestest_reduce_pipeline_completed_does_not_fabricate_step_successtest_reduce_rollback_completed_supersedes_previous_step_attempte0c70d3197184b49826469b509a6fd21回滚折叠,新记录独立test_reduce_candidate_restart_finalizes_candidate_sub_steps7eaff0d9bcff4de7a1ba510ed030f017候选重启,子步骤终态化test_finalize_keeps_existing_terminal_status_and_conclusiontest_finalize_non_terminal_nodes_is_idempotent_on_replay另外更新了 1 条既有用例
test_reduce_candidate_restart_removes_old_run_from_active_when_next_attempt_starts:它原本断言旧 attempt 永久保持
restarting,而这正是本工作项要消除的悬挂非终态,故改为断言
superseded+ 非空conclusion。验证:
uv run pytest tests/a2a/test_pipeline_snapshot.py -q→ 59 passeduv run pytest tests/a2a/ -q→ 1496 passedmake lint(ruff + ty)→ All checks passedmake test→ 14309 passed / 26 failed;26 项失败全部预置,与本改动无关(在未改动的基线提交
88ed89c上复跑同一批用例得到相同失败集:i18n 缺 POT/msgfmt工具链、setup_packaging 与 selling skills 依赖 references 符号链接、desktop sidecar
构建、prerequisites 二进制下载、repl_e2e 依赖 mtime 精度、providers 一条 30s 网络超时)。
关于第一个提交(
chore: normalize formatting)pyproject.toml中 ruff 未固定版本("ruff>=0.4.0"),uv解析到 0.15.10,其formatter 与提交树最后一次格式化所用版本不一致:在未改动的
88ed89c上uv run ruff format --check src/ tests/即报 16 个无关文件需重排。而.pre-commit-config.yaml的format钩子是全树执行(pass_filenames: false),因此任何提交都会被它改写并失败。
为了既不用
--no-verify绕过钩子、也不把 16 个无关文件混进修复提交,这里把make format的机械输出单独放在第一个提交,使第二个提交的 diff 只含pipeline_snapshot.py与其测试,保持可评审。建议仓库侧把 ruff 固定到具体版本并全树 format 一次,否则每个新环境解析到不同 ruff 都会重复遇到这个提交阻塞。