Skip to content

fix(quota): name the admission refusal when the projection already selects the requested Todo - #4985

Merged
huangruiteng merged 2 commits into
mainfrom
codex/unsettled-turn-refusal-naming-0924
Sep 24, 2026
Merged

huangruiteng merged 2 commits into
mainfrom
codex/unsettled-turn-refusal-naming-0924

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

A same-turn --todo-id whose Todo is already the projection's current selection was reported as a selection conflict, so the guard named the same id on both sides of its own sentence:

requested Todo todo_x is neither the projection's current selection
(todo_x) nor deferred or rejected by it (qualification state: qualified)

That message cannot be acted on: there is no conflict to reconcile, and it hides the refusal that actually happened. When the receipt write is what failed because an earlier must-attempt Turn still owes its closeout receipts, the caller needs that prior Turn named. And when the delivery boundary itself refused the work, the caller needs those facts, not prose.

What changed

  • QuotaActionSelectionConflictError gains a distinct not_admitted kind. conflict now only describes a genuine disagreement between the requested and projected ids; not_admitted describes "the projection selects exactly what you asked for, and this Turn was not admitted to settle it".
  • The preflight raises not_admitted when requested_todo_id == selected_todo_id and the qualification state is neither deferred nor rejected.
  • The refusal carries typed admission facts: agent_channel.must_attempt and delivery_allowed are printed in the reason and published as action_selection_conflict.admission.
  • When the same payload carries unsettled_host_turn_recovery, the reason names the prior Turn and its typed repair, and the failure payload publishes unsettled_prior_turn_instance_id / unsettled_repair.
  • Every eligibility predicate is unchanged. The two admission facts are read once and reused, so delivery_refused is still "delivery_allowed is explicitly False". This is refusal naming, not admission: no Todo becomes selectable, and nothing is settled or spent differently.

Validation

  • tests/control_plane/test_quota_action_selection_conflict.py 7 passed: the requested-equals-selected case is typed not_admitted and no longer contains the contradictory sentence; the unsettled-prior-Turn case names the prior turn id and repair; the delivery-refused case publishes the typed admission facts. The pre-existing conflict payload contract is unchanged and still pinned by its own test.
  • tests/control_plane/test_unadmitted_selection_construction.py, tests/control_plane/test_quota_settlement_cli.py, tests/control_plane/test_quota_authority_settlement_journey.py 106 passed.
  • ruff check clean on the changed files; mypy reports no error in the changed files (the project-wide baseline in this environment is noisy for unrelated modules).
  • Known non-blocking: the windows-powershell check fails on this head, and it also fails on the current main head 7938834b4 for this PR's base commit, so it is not introduced here.

Boundary

Control-plane change (loopx/**): proposed for review and left for the maintainer to merge, not self-merged.

…lects the requested Todo

A same-turn `--todo-id` whose Todo is already the projection's current
selection was reported as a selection conflict, so the message named the same id
on both sides ("requested Todo X is neither the projection's current selection
(X) nor deferred or rejected by it") and left the caller with no next read.

The refusal that actually happened was not-admitted-to-settle, and when the
payload already carries `unsettled_host_turn_recovery` the real blocker is the
prior Turn that still owes its closeout - the case the lane hit on
2026-09-23T07:44Z, where the identical bind succeeded after that prior Turn's
refresh and spend were recorded.

The preflight now raises a distinct `not_admitted` kind when the requested Todo
equals the projection's current selection, names the prior unsettled Turn and
its typed repair when the payload carries them, and publishes both in the
failure payload. Every eligibility predicate is unchanged: only the refusal
naming changes.

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.

中文评审(精确 head 012b280

结论
建议合并(APPROVE),无阻断项。这是一次纯措辞修复:把一个自相矛盾的拒绝改成能指向下一步的拒绝,判定条件与准入谓词完全不变。作者自有 PR,无法用 GitHub 的形式化 approve,因此以本评论作为该精确 head 的评审记录。

里程碑 / 基线

  • 基线:origin/main 7938834b4,分支 codex/unsettled-turn-refusal-naming-0924 基于它 rebase,PR base = main。
  • 改动面:loopx/control_plane/quota/error_codes.pyloopx/cli_commands/quota_action_selection.pyloopx/cli_commands/quota_failure_report.py,加一个聚焦测试文件,共 4 文件 / +140 −11。
  • 行为边界:控制面(loopx/**),仅提案不自动合并。

依据

  • 复现路径来自实测:当轮请求的 --todo-id 恰好是投影当前选中的 Todo、质询态为 qualified、但该轮未被准入结算时,旧实现抛 conflict,理由文本把同一个 id 放在句子两边("neither the projection's current selection (X) nor deferred or rejected by it"),调用方无法据以动作。
  • 新实现把该情形判为独立的 not_admitted 类别:conflict 只描述"请求 id 与投影 id 真的不一致";not_admitted 描述"投影选的就是你请求的,是这一轮没被准入结算"。
  • 当同一 payload 已带 unsettled_host_turn_recovery 时,理由中会点名此前未结清的 Turn 及其 typed repair,失败载荷同时发布 unsettled_prior_turn_instance_id / unsettled_repair。这正是"重录上一轮 refresh + spend 后同一次绑定就成功"的那类真实成因,而不是选择冲突。
  • 验证:新测试文件 6 passed;test_unadmitted_selection_construction.py + test_quota_settlement_cli.py + test_quota_authority_settlement_journey.py 合跑 106 passed;改动文件 ruff check 干净;mypy 对改动文件无新增报错(该环境下无关模块基线本身噪声很大)。既有 conflict 的失败载荷字段契约由既有测试原样锁定,未被改动。

缺口

  1. 该拒绝路径还有第二类真实成因:准入事实(agent_channel.must_attempt / delivery_allowed)才是原因、且没有可点名的未结清前轮。本次只把这种情形改成语义诚实的 "not admitted to settle",尚未把这两项事实作为 typed 字段一并发布到失败载荷。建议作为后继小切片,不并入本 PR。
  2. test_selection_added_after_pending_guard_reports_final_boundary 在一次三文件合跑中曾失败一次,随后在同一合跑与单文件运行中均通过(含两份重复运行),本次未能稳定复现,判定为环境/时序抖动而非本改动引入;如实记录,未将其作为绿色结论。
  3. 未运行 loopx canary premerge --from-git-diff(本 PR 为控制面提案,交由 maintainer 与 CI 覆盖),如需我再补跑请指出。

下一步

  • maintainer 决定合并;如需拆分第二类成因的 typed 准入事实字段,我按实际需要另开一个聚焦 PR。
  • 合并后建议用同一条拒绝路径回归一次:同轮同 id 重复绑定,确认返回 not_admitted 且在有未结清前轮时点名该 Turn。

English verdict

APPROVE for exact head 012b28071893dca3c7f58a1c8609af1de229d3bc. The change only renames an unreconcilable explicit --todo-id refusal: when the requested Todo equals the projection's current selection the guard now reports not_admitted (naming the prior unsettled Turn and its typed repair when the payload carries unsettled_host_turn_recovery) instead of a self-contradictory conflict sentence. No admission predicate, no Todo reachability and no settlement behaviour changes; the pre-existing conflict payload contract is pinned by the existing tests and stays intact. Focused validation: 6 new/updated tests plus 106 tests across the settlement CLI, unadmitted-selection and settlement-journey suites, ruff clean. Author-owned, so this comment is the exact-head review record; not self-merged.

Telling a caller "the Turn was not admitted to settle that Todo" is only
actionable if it can see which side of the delivery boundary refused it. The
refusal now carries `agent_channel.must_attempt` and `delivery_allowed` as typed
facts: they are printed in the reason and published as
`action_selection_conflict.admission`, so a caller can branch on the reason
instead of re-deriving it from prose.

The two facts are read once and reused for the admission predicate, so the
predicate itself is unchanged (delivery_refused is still "delivery_allowed is
explicitly False").

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.

中文评审(精确 head 1f9b5be

结论
建议合并(APPROVE),无阻断项。本 head 在原改动之上补了第二个提交:把决定准入的两项事实 typed 化并发布,从而把"这一轮没被准入结算"从一句说明变成可分支的数据。准入谓词与可达性完全不变。作者自有 PR,无法用 GitHub 的形式化 approve,因此以本评论作为该精确 head 的评审记录。

里程碑 / 基线

  • 基线:origin/main 7938834b4(分支已基于它 rebase),PR base = main。
  • 两个提交:012b28071(拒绝命名:not_admitted 取代自相矛盾的 conflict)与 1f9b5be62(发布 typed 准入事实)。
  • 改动面:loopx/control_plane/quota/error_codes.pyloopx/cli_commands/quota_action_selection.pyloopx/cli_commands/quota_failure_report.py 加一个聚焦测试文件;控制面(loopx/**),仅提案不自动合并。

依据

  • 复现路径来自实测:请求的 --todo-id 恰是投影当前选中项、质询态 qualified、但该轮未被准入结算时,旧实现把同一个 id 放在理由句两边,调用方无从动作。
  • conflict 现在只表示"请求 id 与投影 id 真的不一致";not_admitted 表示"投影选的就是你请求的,是这一轮没被准入"。
  • 准入事实 typed 化:agent_channel.must_attemptdelivery_allowed 读一次、既喂给准入谓词也在理由中印出,并以 action_selection_conflict.admission 发布,调用方可以按原因分支而不必从散文本里再猜。谓词等价性由实现保证:delivery_refused 仍是"delivery_allowed 显式为 False"。
  • 若同一 payload 带 unsettled_host_turn_recovery,理由点名此前未结清的 Turn 及其 typed repair,失败载荷同时发布 unsettled_prior_turn_instance_id / unsettled_repair
  • 验证:测试文件 7 passed(含请求==选中不再是 conflict、点名未结清前轮、投递边界拒绝时 typed 事实三例);test_unadmitted_selection_construction.py + test_quota_settlement_cli.py + test_quota_authority_settlement_journey.py 合跑 106 passed;改动文件 ruff 干净、mypy 无新增报错;既有 conflict 载荷契约由既有测试原样锁定。

缺口

  1. 未覆盖:拒绝路径的第三层成因(例如自治重规划义务与显式选择的交叉)仍走既有 conflict/retained_selection 分支,本次没有改动其语义,也没有为它增加 typed 事实。若需要,另开聚焦切片。
  2. windows-powershell 检查在本 head 失败;同一检查在 PR base 所指向的 main head 7938834b4 上同样失败,因此不是本 PR 引入;但本 PR 未对其做任何修复。
  3. 未运行 loopx canary premerge --from-git-diff,交给 CI 与 maintainer 覆盖。

下一步

  • maintainer 决定合并。合并后建议用同一条拒绝路径回归一次:同轮同 id 重复绑定,确认返回 not_admitted、准入事实为 typed 字段、且在有未结清前轮时点名该 Turn。

English verdict

APPROVE for exact head 1f9b5be6215ebbe0a2370f0dedc6257c4a108f73. Two commits: the first replaces the self-contradictory conflict refusal (same id on both sides) with a typed not_admitted that names an unsettled prior Turn and its typed repair when the payload carries unsettled_host_turn_recovery; the second publishes agent_channel.must_attempt / delivery_allowed as typed facts on that refusal and in the failure payload so a caller can branch on the reason. No admission predicate, Todo reachability or settlement behaviour changes, and the pre-existing conflict payload contract stays pinned by its own test. Validation: 7 focused tests plus 106 tests across the settlement CLI, unadmitted-selection and settlement-journey suites, ruff clean. The windows-powershell failure also reproduces on this PR's base main head and is not introduced here. Author-owned, so this comment is the exact-head review record; not self-merged.

@huangruiteng
huangruiteng merged commit 718bc8f into main Sep 24, 2026
9 of 10 checks passed
@huangruiteng
huangruiteng deleted the codex/unsettled-turn-refusal-naming-0924 branch September 24, 2026 08:17
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