fix(quota): resolve an explicit Todo id beyond the display lanes - #4512
Conversation
…ecutor A manager connection stored the executor endpoint as a decision when it was created, and the Lark route, the authorized-connection resolution and the answering Turn all read that record instead of the machine. A machine that later selected another steward executor therefore kept answering on the endpoint that was the default on the day of the connection: its capability readback reported the managed host while the channel still ran, and failed, on the interactive CLI endpoint. The connection write path had the same shape and no surface to change it, so the value could not be corrected at all. The machine configuration is now the one owner of that choice. The connection record keeps the resolved endpoint as an observation with its source; every read path re-resolves through the new `manager_connection_executor_endpoint` owner, and a connection write records the machine's current resolution while refusing a request that tries to override it. A Session left behind by a machine that changed its selection is refused with the typed `manager_channel_executor_rebind_required` receipt, and the reply names the one action that repairs it instead of the generic manager failure. Verified: the changed Lark, manager-channel, handoff and Lark-API suites pass (182 passed), including four new cases covering route precedence, the write record and refusal, authorized-session matching and the typed rebind reply; the steward channel-binding, steward managed-chat and managed-turn operator-flow smokes pass. The pre-existing failure of `test_every_production_steward_caller_passes_the_machine_defaults` reproduces unchanged on origin/main. Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
`quota should-run --todo-id X` resolves X through `quota_runnable_action_candidates`, which is seeded only from the three display-bounded lanes (`active_next_action_executable_items`, `first_executable_items`, `executable_backlog_items`). A row that this agent owns, that is open, and that carries `task_class=advancement_task` but that no display lane lists therefore produced no candidate, and the qualification failed with `candidate_not_currently_eligible`. The caller then had no legal way to bind its own quota guard to that Todo, so the turn could not be settled at all. An explicitly named id is a selection the caller already made, so it is now resolved against the same non-terminal planning rows the rest of the control plane reads. The builder still applies every eligibility predicate (actionable open, advancement class, capability availability, claim scope), so this widens the reachability of the lookup, not what may be selected; the bounded lanes keep their presentation budget and the existing receipt-upgrade binding is unchanged. Observed on a live lane before the change: `--todo-id <owned P1 row>` returned exit 1 with `decision=skip`, `effective_action=quota_action_selection_rejected`, `reason=candidate_not_currently_eligible`, while the same command shape for a row inside a display lane returned `decision=run` and upgraded the receipt. Verified: the new case fails before this change (exit 1, the live symptom) and passes after, and it asserts the row is absent from the portfolio suggestions and from every bounded lane the payload publishes. `tests/control_plane/test_quota_settlement_cli.py` is 61 passed / 2 failed; both failures are `test_prior_host_closeout_survives_hidden_todo_lifecycle[*- sqlite]`, which reproduce unchanged with this change stashed (Node-based authority fixture failure in this environment). 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)
Reviewed exact head: 655ee3cf0497ee1492f5107e2764a5c30311bd19 (2 files, +75/-4).
动机
一条 lane 无法给自己的唤醒记账。quota should-run --todo-id X 只在三个"展示用有界 lane"(active_next_action_executable_items、first_executable_items、executable_backlog_items)里找 X;一个本 agent 拥有、open、task_class=advancement_task 但没被任何展示 lane 列出的行,因此解析不到 candidate,资格判定返回 candidate_not_currently_eligible 并 exit 1。由于 guard 必须绑定到恰好一个 Todo 才能结算,这一轮就失去了记录自己工作的合法途径。
改动思路
显式给出的 id 是调用方已经做出的选择,因此改为在同一份"非终态规划行"(select_planning_inventory_source_items)里解析,并与既有有界 candidate 取并集。build_explicit_advancement_next_action 仍然执行全部谓词(actionable open、advancement class、能力可用性、claim 范围),所以放宽的是可达性而不是可选择性;展示 lane 的预算与既有 receipt-upgrade 绑定路径均未改动。
具体改动
loopx/control_plane/quota/should_run_prepare.py:新增explicit_action_selection_items(有界 candidate ∪ 权威非终态规划行),仅用于requested_action_todo_id的解析,并注释说明 lane 是展示预算而非可行 Todo 集合。tests/control_plane/test_quota_settlement_cli.py:新增test_agent_can_select_an_owned_todo_outside_every_bounded_lane(含 filler 行的_configure_deep_alternative),断言目标行不在action_portfolio.suggested_actions、也不在本 payload 发布的任何有界 lane 中。
对主干的风险
只扩大"显式请求 id"这一条路径的可达性:谓词与资格投影(schema)未变,因此不会让被谓词拒绝的行变得可选;无 --todo-id 的默认路径逐字未变(其余 61 个结算 CLI 测试全过)。已知此前一次"seed patch"曾破坏 test_agent_can_select_eligible_todo_outside_bounded_suggestions;该测试本次未改且仍通过,因为改动是并集式的、未调整它断言的接受分支。
我的整体评价
无阻断性问题,建议合并:这次是把一个"解析范围"错误收窄到正确所有者,而不是放松资格判定。验证为 failing-then-passing:把生产改动 stash 后新案例以 exit 1、decision=skip、candidate_not_currently_eligible 失败(与线上症状一致),带上改动后通过;pytest tests/control_plane/test_quota_settlement_cli.py → 61 passed / 2 failed,两个失败均为 test_prior_host_closeout_survives_hidden_todo_lifecycle[0/6-sqlite],在 stash 掉本改动后同样失败(本机 Node 权威 fixture 报错),非回归。线上证据(改动前,同一条 lane):--todo-id <自有 P1 行> → exit 1、quota_action_selection_rejected;同样命令形态命中展示 lane 内的行 → decision=run 且 receipt upgraded。
残留风险:本机这两个 sqlite 权威 fixture 失败降低了本地结算覆盖信号——它们不是本 PR 引入的,但值得单独排查;若发现任何谓词被绕过,最小修复是回滚这一处并集。
English verdict: APPROVE - exact head 655ee3cf0. An explicit --todo-id is now resolved against the Goal's canonical non-terminal planning rows as well as the display-bounded candidates, so an owned, open, typed advancement Todo outside the presentation lanes is selectable and its quota guard can be bound; every typed eligibility predicate is unchanged, the default (no --todo-id) path is untouched, and the new case fails before the change with the exact live symptom and passes after. Two sqlite authority-fixture failures reproduce without this change and are not regressions.
Motivation
A manager lane could not settle its own wake.
quota should-run --todo-id Xresolves X throughquota_runnable_action_candidates, which is seeded only from the three display-bounded lanes (active_next_action_executable_items,first_executable_items,executable_backlog_items). A row the agent owns, that is open and typedtask_class=advancement_task, but that no display lane lists produced no candidate, so the qualification returnedcandidate_not_currently_eligibleand the command exited 1.Because the guard must be bound to exactly one Todo to be settleable, the turn was left with no legal way to record its own work.
Change
An explicitly named id is a selection the caller already made, so it is resolved against the same non-terminal planning rows (
select_planning_inventory_source_items) the rest of the control plane reads, unioned with the existing bounded candidates.build_explicit_advancement_next_actionstill applies every eligibility predicate — actionable open, advancement class, capability availability, claim scope — so this widens the reachability of the lookup, not what may be selected. The display lanes keep their presentation budget and the existing receipt-upgrade binding path is untouched.Known prior attempt note: an earlier "seed patch" regressed
test_agent_can_select_eligible_todo_outside_bounded_suggestions. That test is unchanged here and still passes, because the union is additive and the acceptance branch it asserts is not reordered.Validation
test_agent_can_select_an_owned_todo_outside_every_bounded_lane: with the production change stashed it fails with exit 1 (the live symptom:decision=skip,candidate_not_currently_eligible); with the change it passes and asserts the row is absent fromaction_portfolio.suggested_actionsand from every bounded lane the payload publishes.pytest tests/control_plane/test_quota_settlement_cli.py→ 61 passed, 2 failed. Both failures aretest_prior_host_closeout_survives_hidden_todo_lifecycle[0-sqlite]and[6-sqlite], which fail identically with this change stashed (Node-based authority fixture error in this environment), so they are not regressions.--todo-id <owned P1 row>→ exit 1,effective_action=quota_action_selection_rejected,reason=candidate_not_currently_eligible; the same command shape targeting a row inside a display lane →decision=runwithheartbeat_receipt.status=upgraded.Risk
Selection reachability grows for an explicitly requested id only. A rejected selection can now succeed where it previously failed closed, which is the intended repair; the typed predicates and the qualification projection are unchanged, so no row becomes selectable that the predicates reject. Default (no
--todo-id) behaviour is untouched.