Skip to content

fix(control-plane): reconcile redirects and watch-only monitors - #4805

Merged
huangruiteng merged 3 commits into
mainfrom
codex/pr-merge-watch-monitor-repair
Sep 20, 2026
Merged

huangruiteng merged 3 commits into
mainfrom
codex/pr-merge-watch-monitor-repair

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • reconcile GitHub repository redirects by recording the provider-returned canonical PR plus an exact old-repository alias for the same PR number
  • keep PR resume matching in the existing TypeScript resume owner; Python only performs the external GitHub read and rollout-event adaptation
  • make watch-only monitors schedulable without letting them block convergence or preempt runnable advancement
  • expose an optional typed no-spend auxiliary_monitor_poll through the existing managed Turn / CLI / Lark interaction contract
  • bind the projected CLI route to the current Turn and require a fresh observation digest before monitor writeback

Motivation

Two domain-neutral control-plane failures exposed drift in shared contracts:

  1. a Todo waiting on a PR stayed deferred after the repository moved to a new canonical owner
  2. an overdue watch-only monitor disappeared from the agent-scoped due lane, while treating it as an ordinary due monitor would starve advancement

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

  • canonical owners before/after:
    • PR matching remains todos/resume_condition.ts
    • watch-only lane partitioning now lives in todos/summary_lanes.ts and todos/quota_selection.ts
    • Python no longer re-filters typed Todo summary lanes for convergence
  • Python retained only for:
    • GitHub provider reads and public-safe rollout event adaptation
    • legacy fact adaptation and CLI/Lark rendering
  • bridge economics:
    • no new leaf RPC
    • reuses the existing todo.summary_lanes.project and todo.quota_planning.project boundaries
    • cross-runtime call count is unchanged
  • compatibility exit:
    • legacy summaries without typed partitions still classify locally; this fallback can be removed after all installed runtimes emit the typed lanes

Product entry points

  • managed Turn / CLI: the typed auxiliary poll route includes executable unchanged and material-change commands, both bound to the current Turn and a caller-supplied LOOPX_MONITOR_RESULT_HASH
  • Lark: receives the same interaction-contract projection; no second chat-side source of truth
  • frontend: no new configuration field or user choice is introduced, so the existing shared read model remains the entry point; no separate frontend control is required

Validation on 6942ad6671928303c912b28801e08bf68edb50ed

  • npm run typecheck:control-plane
  • 39 targeted TypeScript tests passed
  • 14 targeted Python integration tests passed, including execution and replay of the generated auxiliary command through the real CLI fixture
  • Ruff and git diff --check passed
  • loopx canary premerge --from-git-diff --git-diff-base origin/main --tier standard: 19/19 checks passed
    • 10 control-plane catalog canaries
    • 8 risk-profile smokes
    • 1 public/private-boundary scan
  • failures: none
  • skips: none in the targeted validation set

Future-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.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 的方案更合理。

具体改动

关键代码讲解

  1. build_issue_fix_pr_lifecycle_monitor_packet / append_pr_merge_rollout_event

    • 将 provider-returned canonical repo 写入 observation,并仅为“相同 PR number 的旧 repo”生成 source_refs alias。
    • unrelated repo 和 mismatched PR number 的负例仍被拒绝。
  2. 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 仍保留它,因此“收敛”和“可轮询”没有被错误绑定成同一个概念。
  3. _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

有两个具体问题:

  1. 缺少 --result-hash。按原样通过真实 loopx.cli 执行,退出码为 1,返回 error_code=invalid_request,理由为 monitor todo writeback requires --result-hash。因此 PR body 所称“executable command”当前并不可执行。
  2. 缺少当前 --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 huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_refs alias;真正的 repository-qualified resume 仍由 TypeScript todos/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。

具体改动

文件职责分成五组:

  1. loopx/capabilities/issue_fix/pr_lifecycle.py 与 pr_lifecycle_rollout.py 负责同号 redirect canonicalization、alias source-ref 与幂等 rollout receipt。
  2. loopx/control_plane/todos/{summary_lanes.ts,quota_selection.ts} 是 watch-only/ordinary due 和 convergence 分区的 typed owner;相应 Python summary/semantics 文件只投影或兼容旧事实。
  3. goal_frontier、work_lane_context.py 与 work_lane.py 消费 typed partition,保证 watch-only 不计入收敛债务、不抢占 advancement,但仍可作为 due observation。
  4. interaction_contract.py 把选中的辅助 observation 渲染成 Turn-bound、result-bound 的 CLI/Lark 共享 contract。
  5. 双语文档和 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):canonical pr_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.

@huangruiteng
huangruiteng merged commit 632d037 into main Sep 20, 2026
21 of 23 checks passed
@huangruiteng
huangruiteng deleted the codex/pr-merge-watch-monitor-repair branch September 20, 2026 18:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant