Skip to content

fix(delegation): preserve preflight rejection reasons - #4757

Merged
huangruiteng merged 3 commits into
mainfrom
codex/delegation-preflight-errors
Sep 20, 2026
Merged

huangruiteng merged 3 commits into
mainfrom
codex/delegation-preflight-errors

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Summary

  • project missing canonical authority as typed authority_unavailable readiness without inspecting or launching a provider
  • normalize Turn plan results in the TypeScript control plane before the Python host adapter reads a transaction, preserving the original safe rejection instead of collapsing to KeyError
  • keep authority promotion explicit and leave Python responsible only for host I/O and exception capture

Product surfaces

The shared delegation read model changes for CLI, managed Turn, and the existing HTTP/frontend consumer. No new frontend or Lark control is required: both already consume this projection, and the change adds a typed blocked state rather than a new setting or action.

Validation

  • uv run --extra test python -m pytest -q tests/test_delegation_preflight.py — 18 passed
  • delegation CLI/worker regression group — 37 passed
  • node --no-warnings --experimental-strip-types --test tests/control_plane_ts/delegation.test.ts — 7 passed
  • npm run -s typecheck:control-plane — passed
  • loopx canary premerge --from-git-diff — standard tier passed, 10/10 selected checks
  • real read-only inspect on an unpromoted Goal returned ok=true, state=authority_unavailable, and all effects false

The full npm run test:control-plane run exposed an existing local toolchain issue: subprocess tests resolved Apple Python 3.9, which does not support dataclass(slots=...). Re-running the affected todo_continuation.test.ts with the project Python 3.13 first on PATH passed 36/36.

Boundaries

  • no provider execution, authority promotion, permission expansion, frontend source of truth, private state, credentials, local paths, or generated run artifacts are included
  • this is a control-plane change and should remain maintainer-reviewed rather than self-merged

@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)

Reviewed exact head 098066619de810156661e0051376ca19488034b8. I found one blocking P1 consumer-contract regression.

动机

这个 PR 要解决的 backend 问题成立:canonical authority 缺失不应迫使 inspect 猜测/启动 provider,Turn plan 的安全 rejection 也不应在 Python 先读 transaction.turn_key 时退化成 KeyError。把这些分支交给 TypeScript decision boundary 归一化、Python 只提供 host facts 和捕获异常,是正确方向。

但 PR body 同时声明 shared delegation read model 会被 CLI、managed Turn 和“现有 HTTP/frontend consumer”共同消费,并据此判断无需前端 companion change。exact-head 调用链证明这项声明不成立:新 backend response 扩展了状态词汇和字段 nullability,而 active frontend 仍按旧闭集读取。

改动思路

backend 分为两个清晰决策:

  1. delegationPreflight 接收 canonical authority readiness;缺少 authority 时返回 state="authority_unavailable"、turn_route=null、executor=null、authority_reason=...,且四类 effect 全为 false。
  2. delegationTurnPlanDecision 在 host adapter 读取 transaction 前区分 rejected/planned;只有 ok=true 且 turn_key 满足 sha256:<64 hex> 才允许进入 running。
  3. Delegations.inspect/_execute 负责采集 authority/CLI 结果和持久化原 operation 的 rejection,不提升 authority、不创建 replacement Turn。

这些 backend 边界本身是合理的;问题在于 shared read model 没有端到端完成到现有浏览器 consumer。

具体改动

关键代码讲解

  • delegationTurnPlanDecision(delegation.ts:44):保留 plan.error/reason,成功分支严格校验 turn key,避免 Python 重新发明 Turn plan 语义。
  • delegationPreflight(delegation.ts:64):新 authority_unavailable 分支正确禁止 preview/acceptance/effects,并给出显式 repair guidance。
  • Delegations.inspect(collaboration_mcp.py:165):把 missing canonical authority 从 raw exception 变成 typed observation;再次核验 binding,避免 source drift。
  • Delegations._execute(collaboration_mcp.py:397):先调用 typed decision,再读取 turn_key;outer operation wrapper 会把 rejection 写回原 operation。
  • 遗漏的 active consumer:chat.ts:898 的 DelegationPreflight union 没有 authority_unavailable,仍声明 turn_route: string 和非空 executor;goal-team-work.tsx:60-62 随后无条件读取 executor.profile/host/reason。

对主干的风险

[P1] 新 missing-authority 路径会让 Team execution 面板崩溃,而不是显示 repair reason

触发条件就是本 PR 要改善的场景:Goal 已配置 delegation binding,但尚未 promote canonical authority。

实际链路:

GoalTeamWork 点击“检查启动条件” → HTTP operation=inspect → Delegations.inspect 捕获 missing authority → delegationPreflight 返回 {state:"authority_unavailable", turn_route:null, executor:null, authority_reason:"..."} → React render 读取 executor.profile(随后还读取 host/reason)。

错误结果:用户看不到“显式修复/promote authority”的安全解释,反而遇到 render exception/空白面板。因为 requestJson<T> 只是泛型约束而不是 runtime schema parse,TypeScript build 也不会从真实 JSON 自动发现这个不一致;这解释了为何 backend tests 和 CI 都是绿的。

最小修复:

  1. 将 DelegationPreflight.state 扩展为包含 authority_unavailable;把 turn_route、executor 按 response contract 改为 nullable,并加入 authority_ready/authority_reason。
  2. GoalTeamWork 对 authority_unavailable 显示中英文 label 和 authority_reason,只有 executor !== null 时才读取 profile/host/reason。
  3. 增加一个 component 或 packaged browser regression,使用 exact response {state:"authority_unavailable", turn_route:null, executor:null, authority_reason:"..."},断言不崩溃、显示 repair guidance、没有 launch/effect。

验证结果:

  • source-checkout provenance:.venv/bin/python + exact-head loopx/__init__.py。
  • pytest -q tests/test_delegation_preflight.py:18 passed。
  • node --test tests/control_plane_ts/delegation.test.ts:7 passed。
  • git diff --check:通过。
  • GitHub exact-head checks:28 个 success(含 real PostgreSQL authority、adapter contract、merge gate);release/publish jobs是 intended skips。

这些证据充分证明 backend fix,但也明确暴露 validation gap:没有任何测试跨过 changed response → HTTP/frontend → React renderer 这个真实边界。green CI 不能覆盖上述具体反例。

我的整体评价

结论是 REQUEST_CHANGES。核心方向、typed state rule、authority semantics 和代码比例都合理;我不建议退回 Python catch-only 或自动 promotion。阻塞点是一个很小但必须同 PR 修完的 product-journey 完整性问题:既然 schema version 仍是 loopx_delegation_preflight_v0、PR 又明确承诺现有 frontend consumer 可直接消费,那么新增状态和 nullability 必须原子更新所有 active consumers。

未来向 pass:修复应复用现有 DelegationPreflight/GoalTeamWork owner,不需要新 adapter、新状态框架或另开 PR。完成上述三项并重跑真实 response-shape 场景后再复审。

English verdict: REQUEST_CHANGES (author-owned fallback: COMMENTED). The backend now preserves safe authority/Turn-plan rejections correctly, but the new authority_unavailable response sets executor=null while the active Dashboard contract excludes that state and dereferences executor.profile/host/reason; update the shared consumer and add an exact-response frontend regression before merge.

Exact head: 098066619de810156661e0051376ca19488034b8

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Addressed the frontend parity blocker in cdfc7b98c3cc247f5a8bd997c47405d3c1480ef1.

What changed:

  • added the authority_unavailable state plus nullable executor/turn_route, authority reason, and exact zero-effect fields to the shared dashboard response type;
  • made Goal Team Work render the bilingual unavailable-authority reason without dereferencing a missing executor;
  • added an exact-response server-rendered component smoke and wired it into both source and packaged personal-workspace smoke suites.

Validation:

  • uv run --extra test python -m pytest -q tests/test_delegation_preflight.py — 18 passed
  • node --no-warnings --experimental-strip-types --test tests/control_plane_ts/delegation.test.ts — 7 passed
  • npm run -s typecheck:control-plane — passed
  • dashboard npm run build — passed, including packaged chat assets
  • dashboard npm run smoke:personal-workspace — passed, including bilingual unavailable-authority regression and browser smoke
  • loopx canary premerge --from-git-diff — 17/18 selected checks passed; the sole failure is pre-existing environment incompatibility in semantic-vocabulary-drift-smoke.py because system Python 3.9 rejects zip(..., strict=...). No changed file is involved.

Entry points: CLI/managed Turn remain covered by the existing typed preflight response; the dashboard now consumes that same projection safely. Lark receives the shared receipt/projection and has no separate control or source of truth in this slice.

This remains a control-plane change and is intentionally left review-required for maintainer merge.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Follow-up: the first refreshed-head CI correctly caught that the packaged Dashboard bundle had not been committed. I rebuilt from the same source and pushed signed commit 00f6a1a8a64a170e89421eba3c2e4efe425f47f3; the tracked chat asset hash, index, and retention ledger now match npm run build:chat. This closes the packaged-frontend delivery gap rather than bypassing the check.

@huangruiteng
huangruiteng force-pushed the codex/delegation-preflight-errors branch from 00f6a1a to e61dfb3 Compare September 20, 2026 03:45
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/delegation-preflight-errors branch from e61dfb3 to a01444d Compare September 20, 2026 04:21
@huangruiteng
huangruiteng merged commit 8dcabb9 into main Sep 20, 2026
28 of 29 checks passed
@huangruiteng
huangruiteng deleted the codex/delegation-preflight-errors branch September 20, 2026 04:52
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