fix(control-plane): reconcile redirects and watch-only monitors - #4805
Conversation
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
There was a problem hiding this comment.
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
动机
这个 PR 处理的是两个真实且会持续放大的控制面问题:仓库迁移后,旧仓库名绑定的 exact PR wait 可能永久无法恢复;watch-only monitor 若与普通 due monitor 混同,要么被隐藏、要么抢占真正的 advancement。以当前 exact head 8c325299ac412b2464023c006c8e4bffb3048183 为准,我确认这两个目标都值得在同一批中处理:它们都属于“外部事实已经变化,但自动化仍被旧投影卡住”的 stranded-automation 问题。
改动思路
整体所有权选择是对的:
- PR 重定向只在 provider 返回同一 PR number 时采用 canonical repository,并把旧 repository 作为精确 alias;真正的 resume 判断仍由 TypeScript
todos/resume_condition.ts负责,没有退化成“只看编号”。 - watch-only / ordinary-due 的分区进入现有 TypeScript Todo summary 与 quota-planning owner;Python 只保留旧 packet 兼容和 CLI/Lark 渲染,没有重新建立第二套调度权威。
work_lane_contract保持 advancement 为主,并把 watch-only due 暴露为required=false、preempts_advancement=false、spend_policy=no_spend的辅助观察。
更小但不充分的方案包括:仅在 UI 隐藏 watch-only debt(会丢失可调度性)、继续让所有 due monitor 抢占(会饿死 advancement)、或按 PR number 跨仓库匹配(会削弱身份约束)。当前 typed partition + exact alias 的方案更合理。
具体改动
关键代码讲解
-
build_issue_fix_pr_lifecycle_monitor_packet/append_pr_merge_rollout_event- 将 provider-returned canonical repo 写入 observation,并仅为“相同 PR number 的旧 repo”生成
source_refsalias。 - unrelated repo 和 mismatched PR number 的负例仍被拒绝。
- 将 provider-returned canonical repo 写入 observation,并仅为“相同 PR number 的旧 repo”生成
-
projectTodoSummaryLanes/projectQuotaSelection- 在 capability admission 和 agent scope 之后生成
watch_only_monitor_due_items与non_watch_only_monitor_due_items。 convergent_open_items排除 watch-only monitor,但monitor_due_items仍保留它,因此“收敛”和“可轮询”没有被错误绑定成同一个概念。
- 在 capability admission 和 agent scope 之后生成
-
_build_interaction_cli_channel(loopx/control_plane/work_items/interaction_contract.py:1349-1356)- 新增了
auxiliary_monitor_poll.command,但这里存在当前阻塞项:命令只拼接 agent/capability/scheduler/todo 和--execute,没有拼接本轮--turn-instance-id,也没有提供 monitor writeback 必需的--result-hash/material-change 输入契约。
- 新增了
对主干的风险
[P1] 投影出的辅助 monitor 命令无法执行,也没有绑定当前 turn
我用 disposable registry/runtime 建立了一个已提交 heartbeat receipt 的 advancement turn:turn-owner-review-4805,随后加入到期的 watch-only monitor,并读取 exact head 生成的 interaction contract。实际命令是:
loopx ... quota monitor-poll --goal-id settlement-cli-fixture --agent-id codex-settlement-cli --available-capability external_evidence_poll --available-capability network --codex-app --todo-id todo_fixture_due_monitor --execute
有两个具体问题:
- 缺少
--result-hash。按原样通过真实loopx.cli执行,退出码为 1,返回error_code=invalid_request,理由为monitor todo writeback requires --result-hash。因此 PR body 所称“executable command”当前并不可执行。 - 缺少当前
--turn-instance-id。即使调用者自行补上 result hash,这条公开投影也不会把辅助 observation 绑定到已提交的 advancement receipt,无法证明该 no-spend observation 属于本轮 settlement。
现有测试没有捕获它,因为 test_due_watch_only_monitor_is_an_auxiliary_no_spend_route 只检查命令包含 --todo-id ... --execute,没有执行生成命令。
最小修复:复用现有 settlement/turn binding 生成逻辑,投影当前 --turn-instance-id,并把实际 observation 的 result_hash / material_change 设计成明确、可填写且不会误执行的 typed command contract;然后用 disposable receipt 执行“quota should-run → 读取生成命令 → monitor-poll → readback”,断言:
settlement_todo_id仍是主 advancement Todo;todo_id是辅助 monitor;- observation 成功且可 replay;
- spend count 仍为 0。
验证结果
- 12 个相关 Python 测试通过。
- 38 个相关 TypeScript 测试通过。
npm run typecheck:control-plane通过。- Ruff 与
git diff --check通过。 - 真实生成命令的负向集成验证失败,见上述 P1。
- capability packet 指定
wait_for_ci=false,因此本次没有轮询 hosted CI;这不影响本地可确定复现的阻塞项。
语义与 CI 对齐
该 PR 是对现有 typed vocabulary 的合理扩展,而不是另起一套语义;但 auxiliary_monitor_poll_v0 的“executable/no-spend/current-turn observation”承诺与实际 CLI contract 尚未对齐,所以当前 whole-PR observable semantics 仍未成立。
我的整体评价
PR 的方向、TS/Python 边界、exact repository identity 和 watch-only lane algebra 都是扎实的;我也没有发现 substring 分类、领域特定文案、默认路径漂移或额外 authority 扩张。但新的用户/agent 入口必须能按投影原样工作。当前 exact head 的核心失败不是文档细节,而是新 command contract 在真实 CLI 边界必然报错,并丢失 turn receipt 绑定。因此先请求修改;补齐命令契约与执行型回归后,我会按新 exact head 复审,不要求重做已经通过的重定向与 lane 分区设计。
English verdict: REQUEST_CHANGES - head 8c32529; the redirect and typed watch-only lane design are sound, but the projected auxiliary monitor command omits both required observation evidence and the current turn binding, so it fails at the real CLI boundary.
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com> # Conflicts: # loopx/control_plane/todos/todo_summary.py
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
huangruiteng
left a comment
There was a problem hiding this comment.
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
当前 exact head 6942ad6671928303c912b28801e08bf68edb50ed 修复了两个会让自动化长期滞留的通用控制面问题:仓库改名后,绑定旧 repository 的 exact PR wait 无法被 provider 返回的 canonical merge 唤醒;watch-only monitor 若与普通 due monitor 共用抢占规则,则会在“不可调度”和“饿死 advancement”之间二选一。
本次自修复还闭合了上一轮精确 head 审查指出的 P1:初版 auxiliary_monitor_poll.command 没有携带当前 Turn,也没有可填写的 observation digest,因此虽然字符串看起来像命令,真实 CLI 必然返回 invalid_request。新 head 已把这个公开投影修成可执行、可 replay、no-spend 且 fail-closed 的完整路径。
这是一个完整、可回滚的交付切片:redirect identity、typed lane、work-lane route、真实 CLI writeback 与双语公共契约都在同一 head 上闭合;不引入新的 GitHub authority、benchmark 语义、frontend 配置或跨仓库 number-only 匹配。
改动思路
所有权保持在仓库既有边界内:
- provider 侧只负责把同一 PR number 的 requested/canonical identity 变成 canonical
pr_ref加精确source_refsalias;真正的 repository-qualified resume 仍由 TypeScripttodos/resume_condition.ts判断。 - watch-only / ordinary-due 分区进入既有 TypeScript
summary_lanes.ts与quota_selection.ts;Python 只消费 typed lanes、兼容旧 packet,并投影 frontier/work-lane/interaction。 work_lane_contract继续把 runnable advancement 作为 machine-enforced 主工作;watch-only due 只投影为required=false、preempts_advancement=false、spend_policy=no_spend的辅助观察。- CLI route 复用现有 runtime/agent/capability/scheduler/Turn 作用域,要求调用者通过
LOOPX_MONITOR_RESULT_HASH提供本次新鲜 observation digest,并分别暴露 unchanged 与 material-change 命令。缺少 Turn 时不生成命令,缺少 digest 时 shell/CLI 在写回前失败关闭。
拒绝的更小方案也有明确理由:只按 PR number 匹配会削弱 repository identity;只在 Python 过滤会复制 typed authority;隐藏 watch-only 会丢失 schedulability;继续让所有 due monitor 抢占会饿死 advancement;仅修改测试 argv 则会保留不可执行的公开 command contract。
具体改动
文件职责分成五组:
loopx/capabilities/issue_fix/pr_lifecycle.py与pr_lifecycle_rollout.py负责同号 redirect canonicalization、alias source-ref 与幂等 rollout receipt。loopx/control_plane/todos/{summary_lanes.ts,quota_selection.ts}是 watch-only/ordinary due 和 convergence 分区的 typed owner;相应 Python summary/semantics 文件只投影或兼容旧事实。goal_frontier、work_lane_context.py与work_lane.py消费 typed partition,保证 watch-only 不计入收敛债务、不抢占 advancement,但仍可作为 due observation。interaction_contract.py把选中的辅助 observation 渲染成 Turn-bound、result-bound 的 CLI/Lark 共享 contract。- 双语文档和 TS/Python 测试覆盖公开行为、旧路径 parity、负例、真实命令执行与 replay。
关键代码讲解
build_issue_fix_pr_lifecycle_monitor_packet(pr_lifecycle.py:923):只有 provider ref 仍是 pull request 且 number 与 requested ref 相同,才采用 canonical ref;否则保留 requested identity。它不直接写状态。append_pr_merge_rollout_event(pr_lifecycle_rollout.py:16):canonicalpr_ref仍是事件主 identity,requested repo 被规范化、去重并按同一 number 写入source_refs;现有 event id 继续负责 replay 幂等。projectTodoSummaryLanes(summary_lanes.ts:73):monitor_due_items仍包含 watch-only,证明它可调度;同时产出watch_only_monitor_due_items、non_watch_only_monitor_due_items与convergent_open_items,把“可轮询”“可抢占”“影响收敛”拆成 typed facts。build_work_lane_contract(work_lane.py:509):普通 due monitor 仍可按现有优先级抢占;只有 watch-only due 时保持 advancement 主 lane,并添加 optional auxiliary route。legacy caller 没有新 partition 时仍按旧规则把 due 当普通 monitor,安全回退。_build_interaction_cli_channel(interaction_contract.py:1279):只有 monitor id 与 Turn 都有效时才生成命令;命令带 exact--turn-instance-id、--todo-id和 shell-required--result-hash,并区分 unchanged/material-change。真实副作用仍由既有quota monitor-poll --execute与 settlement/replay owner 管理。
正向运行链已经贯通:provider redirect → canonical event + old-repo alias → TypeScript exact resume;另一条链为 typed watch-only due → advancement 主 lane + auxiliary route → 读取生成命令 → monitor-poll writeback → replay。回执显示 settlement_todo_id 保持主 advancement Todo、todo_id 为辅助 monitor、第二次执行 replayed=true,spend count 始终为 0。
对主干的风险
最强回归风险是两类 authority 泄漏:错误 alias 唤醒无关 repository 的同号 PR,或辅助 monitor 意外成为主 Turn settlement / 产生 quota spend。当前 head 用四层约束压住这两个风险:同号 canonical guard、repository-qualified TypeScript matcher、typed ordinary/watch-only lane、以及 Turn-bound/no-spend monitor receipt。
负向路径已明确验证:不同 PR number 不生成 alias;无关 repository 同号不匹配;缺少 Turn 时 route 只有 turn_binding_required 且无 command;缺少 result digest 在写回前失败;高优先级 watch-only 不能隐藏普通 due monitor;capability-blocked monitor 不进入 admitted due lane;重复 observation 只 replay、不追加、不 spend。
验证结果:
npm run typecheck:control-plane通过。- 39/39 个相关 TypeScript 测试通过。
- 14/14 个相关 Python 集成测试通过,其中包括从 interaction contract 读取生成命令、通过真实 CLI fixture 执行并 replay。
- Ruff 与
git diff --check通过。 - 标准 premerge canary 19/19 通过:10 个控制面 catalog canary、8 个风险画像 smoke、1 个 public/private boundary scan;无失败、跳过、warning 或 manual hold。
- 发布审查时 hosted CI 为 7 个通过、12 个 pending、4 个预期 skip、0 个失败;按用户明确要求不等待剩余 CI。pending 状态仍是独立 maintainer 的 merge-readiness 输入,不被这份 review 绕过。
语义与 CI 对齐
该 PR 是对现有 PR source-ref、Todo lane、work-lane、Turn settlement 与 monitor-poll vocabulary 的受控扩展,不是第二套状态机。新增 v0 字段名称与实际生命周期/authority 一致;watch_only 缺省仍为 false,非 redirect 路径不生成 alias,旧 summary 仍有保守兼容回退。公共文档、typed producer/consumer 与测试同步更新。
我的整体评价
未发现新的 P0/P1/P2/P3 actionable finding。上一轮 P1 已在真实边界闭合,整个 head 的设计所有权、默认路径隔离、领域中立性、guidance/obligation 区分、失败关闭与代码规模都与问题相称。剩余风险主要是仍在运行的 hosted CI 所覆盖的跨平台或仓库全量交互;本地 exact-head 验证、19 项 risk-based canary 和已完成的 hosted checks 均为绿色,当前无失败。
从代码审查角度我批准 6942ad6671928303c912b28801e08bf68edb50ed;但它改变 loopx/control_plane/** 的共享运行时语义,仓库规则仍要求独立 maintainer 合并,作者不能据此自合并。
English verdict: APPROVE at exact head 6942ad6 — the redirect identity and typed watch-only scheduling changes preserve existing owners, and the prior auxiliary-command blocker is repaired with Turn/result binding and real CLI replay evidence; local qualification and 7 hosted checks are green with 12 hosted checks still pending under the user's explicit no-wait instruction, while independent maintainer merge readiness remains required.
Summary
auxiliary_monitor_pollthrough the existing managed Turn / CLI / Lark interaction contractMotivation
Two domain-neutral control-plane failures exposed drift in shared contracts:
The first exact-head review also found that the projected auxiliary CLI command could not execute because it omitted the current Turn identity and the required observation digest. The repair now fails closed without a Turn binding, declares the fresh-result input contract, and exposes separate unchanged and material-change commands.
TypeScript migration receipt
todos/resume_condition.tstodos/summary_lanes.tsandtodos/quota_selection.tstodo.summary_lanes.projectandtodo.quota_planning.projectboundariesProduct entry points
LOOPX_MONITOR_RESULT_HASHValidation on
6942ad6671928303c912b28801e08bf68edb50ednpm run typecheck:control-planegit diff --checkpassedloopx canary premerge --from-git-diff --git-diff-base origin/main --tier standard: 19/19 checks passedFuture-facing boundary pass
The change keeps monitor selection in the typed Todo owners and CLI rendering in the existing interaction-contract owner. No second selection rule, provider-specific contract, or speculative abstraction was added.
Delivery policy
Independent review is required. This changes shared control-plane semantics and must not be author-self-merged.