Skip to content

feat(turn): resolve the default host from the operator credential - #4454

Closed
huangruiteng wants to merge 3 commits into
mainfrom
codex/credential-resolved-default-turn-host-20260915
Closed

huangruiteng wants to merge 3 commits into
mainfrom
codex/credential-resolved-default-turn-host-20260915

Conversation

@huangruiteng

@huangruiteng huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

The bounded Turn default host is now resolved from the operator's own credential
facts instead of being fixed to the managed host:

  • operator credential configured (DEEPSEEK_API_KEY): the default stays dsh
    (managed executor), authenticated by that credential;
  • no operator credential configured: the default resolves to codex-cli
    (individual executor), because a managed host nothing can authenticate would
    otherwise refuse to run.

An explicit --host or LOOPX_TURN_HOST still wins over either default, so a
credential resolves the shipped default only -- it never re-points a host the
operator already selected. The readback now carries the source
(explicit_config, operator_credential, no_operator_credential).

Changed surfaces

Runtime/API: loopx/control_plane/turn_driver/host_binding.py (default
resolution and readback source), loopx/control_plane/operator_credential.py
(module contract), loopx/cli_commands/turn_registration.py (comment only),
loopx/semantics/inventory_v0.json (regenerated).

CLI/plan/run-once: loopx turn plan and loopx turn run-once without --host.
No frontend change is needed: the resolved host and the managed_executor
readback are already what the Chat/Dashboard surfaces project, and this change
does not alter that projection.

Validation: both public smokes and both host-binding test modules.

Docs: Turn protocol reference, DeepSeek Harness connector guide, and the
DSH/Pi harness RFC (English + Chinese).

Default behavior change disclosure

Both plan and run-once previously defaulted to dsh regardless of the
credential, which made a lane without a credential fail closed on
operator_credential_unconfigured. A lane without an operator credential now
runs the individual CLI host by default.

--host dsh remains the explicit managed path and still fails closed with the
same typed reason when nothing can authenticate it. --host generic-cli remains
the compatibility path, and a machine that wants one fixed host should set
LOOPX_TURN_HOST once instead of relying on the ambient environment.

Validation

  • python -m pytest tests/test_turn_default_host_binding.py tests/test_turn_managed_executor_binding.py tests/test_loopx_turn_executor.py tests/test_host_mode_planner.py tests/test_host_parity_smoke.py -> 134 passed
  • python -m pytest tests/test_loopx_turn_driver.py tests/test_loopx_turn_managed_step.py tests/test_dsh_goal_mode.py tests/test_default_public_scan_root.py tests/control_plane/test_cli_output_budget.py -> 182 passed
  • python examples/loopx-turn-managed-executor-binding-smoke.py -> passed
  • python examples/loopx-turn-managed-default-flow-smoke.py -> passed (hermetic mock model endpoint; no operator key and no individual CLI subscription consumed)
  • python examples/semantic-vocabulary-drift-smoke.py -> ok (inventory regenerated)
  • python examples/docs-governance-smoke.py -> ok
  • loopx canary premerge --from-git-diff -> 0 failures across contract, risk-profile, and public-boundary checks

Open decision (closed 2026-09-16 -- resolved the other way; see the record update below)

The steward Chat channel keeps its own default (codex), because dsh still
has no interactive Chat transport and pointing the channel at it produces the
typed managed_host_chat_transport_unsupported gate. Whether the steward
channel should also become credential-resolved is a separate owner decision that
depends on giving dsh a chat transport; this PR does not change it.

Record update (2026-09-16)

This PR is a change record, not the delivery path. The reviewed change landed
as #4479 (squash 001e0c380), with the three findings raised against the frozen
head 2aee6ac93 fixed there: the semantic inventory was regenerated, the Turn
protocol reference now tells an operator who wants one fixed host to set
LOOPX_TURN_HOST explicitly, and the *_DEFAULT_TURN_HOST aliases are gone
(selected_turn_host() returns MANAGED_TURN_HOST / INDIVIDUAL_TURN_HOST
directly). GitHub cannot reopen this branch after the rebase that the fix
needed, so this head stays closed as the record; the two review bodies below
carry a correction banner and their verdicts describe only that frozen head.

The open decision recorded above has since been settled by the owner, the other
way: making the steward Chat channel credential-resolved is no longer pending.
On main, loopx/chat_manager.py::_resolve_manager_endpoint() resolves its
endpoint under the same rule as the Turn surface -- a configured operator
credential runs the managed dsh executor
(operator_credential_configured), its absence keeps the individual codex
endpoint (operator_credential_absent), and an explicit
LOOPX_MANAGER_ENDPOINT still wins -- with the endpoint source and default
reason projected in the channel readback (#4470, #4481, #4483).

The shipped default was the managed dsh host regardless of the credential, so a lane without one failed closed on operator_credential_unconfigured. Resolve the default from the operator's own credential facts instead: a configured credential keeps the managed dsh default, and no credential resolves the individual codex-cli host that can actually run here.

An explicit --host or LOOPX_TURN_HOST still wins over either default, so a credential never re-points a host the operator already selected; it only resolves the default that would otherwise have to be chosen without any evidence.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Rewrite the two host-binding test modules so they encode the resolved default instead of the previous fixed one, and update both public smokes to prove it end to end: without a credential the default plan resolves to codex-cli and claims no managed credential, and an explicitly selected dsh host still fails closed on the typed operator_credential_unconfigured reason.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Disclose the default behavior change in the Turn protocol reference, the connector guide, and the DSH/Pi harness RFC (English and Chinese): the default host is now resolved from the operator credential, an explicit selection still wins, a lane without a credential keeps running on the individual CLI host, and an explicitly selected managed host still fails closed with the typed reason when nothing can authenticate it.

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.

更正(2026-09-16):本 review 的 REQUEST_CHANGES 结论已被取代,而且推翻它的形态已经上线。

下面三条 finding 都在 rebase 之后的等价改动里逐条修掉了:语义清单重新生成(named_string_constants 2031 → 2032)、协议文档 Host Selection 段补上「要固定 host 请显式设 LOOPX_TURN_HOST」的说明、MANAGED_DEFAULT_TURN_HOST / INDIVIDUAL_DEFAULT_TURN_HOST 两个别名删除。修好后的同一改动以 #4479(squash 001e0c380)合入 main,所以本 PR 不会再有新的 head,GitHub 也不允许重开。

这份 review 从此只作为「当时受审 head 2aee6ac93 的判断」留存:其中的 P1 是 rebase 造成的计数器少算,P3 是命名与文档建议,都不是对行为设计的否决。当前契约请看 #4479 与 main 上的 selected_turn_host()。

Correction (2026-09-16): this REQUEST_CHANGES verdict is superseded. All three findings were fixed in the rebased equivalent and landed as #4479 (squash 001e0c380); the review below records the state of the head reviewed at the time.

动机

有界 Turn 的默认 host 一直固定指向托管 dsh:这在一台没有 DEEPSEEK_API_KEY 的机器上意味着默认的 loopx turn plan / run-once 会以 operator_credential_unconfigured 失败关闭,operator 必须先想清楚再加 --host codex-cli 才能跑起来。把默认值改成"由 operator 凭据解析"能直接消掉这个摩擦:有凭据就跑托管 host,没有就跑这台机器本来就能用的个体 CLI。方向我认可,尤其是它保留了显式选择永远优先、并新增了可判别的来源读回。

改动思路

  1. host_binding.selected_turn_host() 增加一条凭据分支:显式 LOOPX_TURN_HOST → explicit_config;有可用凭据 → dsh / operator_credential;否则 → codex-cli / no_operator_credential(原来的 product_default 取值被这两个新取值取代)。
  2. operator_credential.py 只更新模块契约文字(凭据可以解析未显式配置的 shipped default,但绝不重指显式选择),没有让它获得选择权。
  3. 默认执行模式继续跟随 host 派生;协议文档、connector 文档与 RFC(en+zh)同步改写,并在协议文档里显式声明这是一次默认行为变更。
  4. 两个公开冒烟与两个绑定测试模块改断言:无凭据默认流改成"真的跑起来",显式 --host dsh 无凭据仍然失败关闭。

具体改动

  • loopx/control_plane/turn_driver/host_binding.py(+55/-14)::91 的凭据分支、:51 新增 TURN_HOST_SOURCE_OPERATOR_CREDENTIAL / TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL、:48 新增 INDIVIDUAL_DEFAULT_TURN_HOST。
  • loopx/control_plane/operator_credential.py:模块 docstring 契约改写;loopx/cli_commands/turn_registration.py:注释 1 行;loopx/semantics/inventory_v0.json:计数器 1 行。
  • tests/test_turn_default_host_binding.py(:22 / :42 / :61 三个用例)、tests/test_turn_managed_executor_binding.py、examples/loopx-turn-managed-default-flow-smoke.py(:576 断言组)、examples/loopx-turn-managed-executor-binding-smoke.py(第 5 步显式传 host=dsh)。
  • 文档:docs/reference/protocols/loopx-turn-v0.md、docs/integrations/deepseek-harness-connector.md、docs/architecture/rfcs/harness-selection-dsh-pi-v0.md(en + zh-CN)。

关键内容讲解

  1. 默认值解析与读回是同一次改动:resolve_default_turn_host() 仍只返回 host,但 selected_turn_host() 现在返回 (host, source),于是读回能区分"这是显式选的"还是"这是凭据解析出来的"。我读了消费面:turn_registration.py:57/:169 是唯一两处调用点,managed_executor 读回 schema 未变,仓库内没有其它 Turn 侧消费者依赖消失的 product_default。
  2. 失败关闭没有被削弱:显式 dsh 且无凭据时仍然是 available=false + typed operator_credential_unconfigured,不调用 host、不写 Journal、不花 quota slot——冒烟第 5 步(现显式传 host=dsh)本地跑过,quota_slot_spend_count 保持不变。
  3. 本地验证:tests/test_turn_default_host_binding.py + tests/test_turn_managed_executor_binding.py → 37 passed;examples/loopx-turn-managed-executor-binding-smoke.py → passed;examples/loopx-turn-managed-default-flow-smoke.py → passed(含无凭据默认真的跑通一次 bounded Turn)。

对主干的风险

P1(本 head 的必需检查是红的,阻塞合并)。CI 在 head 2aee6ac93 上 test-shard (2) 与 test-shard (3) 失败,两者都是同一句:loopx/semantics/inventory_v0.json is stale。我把 CI 实际测试的合并提交取下来复现了:

git fetch origin refs/pull/4454/merge:refs/remotes/pr/4454-merge
git worktree add --detach /private/tmp/loopx-pr4454-merge refs/remotes/pr/4454-merge
cd /private/tmp/loopx-pr4454-merge && python scripts/generate_semantic_inventory.py --check
# → stale semantic inventory: loopx/semantics/inventory_v0.json

运行生成器会显示 named_string_constants 需要从 2031 改成 2032。这不是继承失败:main(4f33d5ac6)单独一棵树是 up to date,本 PR 单独一棵树(相对它自己的 base 53fed471e)也是 up to date——问题是本 PR 与另一处同样把该计数从 2030 改到 2031 的改动落在同一行,git 干净合并之后净增被少算了一次。最小修法:在最新 origin/main 上 rebase,跑 python scripts/generate_semantic_inventory.py 并提交结果(计数器应为 2032),再重跑两个冒烟与语义检查确认全绿。

P3(凭据存在与否现在是默认去向的隐式信号)。:91 的判定只看"凭据是否非空",所以一个为了别的用途被导出到环境里的 DEEPSEEK_API_KEY 会把默认 Turn 从 codex-cli 改到托管 dsh,连带改变计费端点与执行模式。文档已经披露这次默认变更,并给了 LOOPX_TURN_HOST 作为固定 host 的做法,所以这是可接受的取舍;但如果希望减少现场误判,建议在协议文档的 Host Selection 段落补一句"固定 host 的机器请显式设置 LOOPX_TURN_HOST"。

P3(常量命名现在名不副实)。MANAGED_DEFAULT_TURN_HOST(:47)与 INDIVIDUAL_DEFAULT_TURN_HOST(:48)是基础常量的别名,但"default"只在各自对应的凭据条件下成立;后续代码若直接引用它们,就会重新引入"默认固定是 dsh"的假设。最小修法:删掉这两个别名,直接用 MANAGED_TURN_HOST / INDIVIDUAL_TURN_HOST,把"默认"只留给 selected_turn_host 的来源语义。

其余残余风险:本次行为变化本身是"无凭据机器从失败关闭变成能跑个体 CLI",属于可用性改善且已披露;未验证面是真实 DeepSeek 端点(冒烟用 hermetic mock,只证明读回与失败关闭),以及 Windows/macOS 安装包下的同一解析。CI 侧我只读到 test-shard (2)/(3) 的失败日志,其它 job 当时仍在运行。

我的整体评价

REQUEST_CHANGES。改动的方向、读回设计与文档披露我都认可:默认值解析集中在既有 owner 里,显式选择优先级没有被削弱,托管 host 无凭据仍然失败关闭,两个冒烟把正反两条路径都跑成了真实执行。但受审 head 的必需检查是红的,而且是可复现地红在 CI 实际运行的合并树上——语义清单的 named_string_constants 停在 2031,合并后需要 2032。这是一个 rebase + 重生成计数器的修复,不涉及行为设计;修好并让 test-shard (2)/(3) 转绿之后,这条 PR 就可以按现在的形态合入。另外两条 P3(凭据隐式改默认的说明、两个别名的命名)属于顺手可改,不阻塞。

[Superseded 2026-09-16 — see the correction at the top; the finding is fixed on the delivery path] English verdict: REQUEST_CHANGES at 2aee6ac. The change itself is sound: selected_turn_host now resolves the shipped default from the operator credential (credential → dsh/operator_credential, none → codex-cli/no_operator_credential) while an explicit --host or LOOPX_TURN_HOST still wins, and the old fail-closed path is preserved for an explicitly selected managed host with no credential (no host invocation, no Journal, no quota spend). I reproduced the local positives (37 passed across the two binding test modules; both public smokes pass, including a real hermetic bounded Turn on the uncredentialed default). The blocking problem is CI: test-shard (2) and (3) fail at this head with loopx/semantics/inventory_v0.json is stale, and I reproduced it on the exact merge commit GitHub tests (refs/pull/4454/merge, head merged into main 4f33d5a): scripts/generate_semantic_inventory.py --check reports stale and regeneration wants named_string_constants 2031 → 2032. Neither main alone nor this head against its own base is stale, so the undercount comes from the PR and another 2030→2031 edit landing on the same counter line. Rebase onto current main, regenerate the inventory, and re-run the two smokes plus the semantic checks. Two non-blocking notes: credential presence now implicitly re-points the default host (billing/execution mode follow), which the docs disclose but could call out with the LOOPX_TURN_HOST advice, and MANAGED_DEFAULT_TURN_HOST/INDIVIDUAL_DEFAULT_TURN_HOST are aliases whose "default" reading is only conditionally true.

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

更正(2026-09-16):本 review 的 REQUEST_CHANGES 结论已被取代,而且推翻它的形态已经上线。

下面三条 finding 都在 rebase 之后的等价改动里逐条修掉了:语义清单重新生成(named_string_constants 2031 → 2032)、协议文档 Host Selection 段补上「要固定 host 请显式设 LOOPX_TURN_HOST」的说明、MANAGED_DEFAULT_TURN_HOST / INDIVIDUAL_DEFAULT_TURN_HOST 两个别名删除。修好后的同一改动以 #4479(squash 001e0c380)合入 main,所以本 PR 不会再有新的 head,GitHub 也不允许重开。

这份 review 从此只作为「当时受审 head 2aee6ac93 的判断」留存:其中的 P1 是 rebase 造成的计数器少算,P3 是命名与文档建议,都不是对行为设计的否决。当前契约请看 #4479 与 main 上的 selected_turn_host()。

Correction (2026-09-16): this REQUEST_CHANGES verdict is superseded. All three findings were fixed in the rebased equivalent and landed as #4479 (squash 001e0c380); the review below records the state of the head reviewed at the time.

Request changes conclusion Superseded 2026-09-16 (author-owned PR; GitHub blocks formal self-review)

动机

有界 Turn 的默认 host 一直固定指向托管 dsh:这在一台没有 DEEPSEEK_API_KEY 的机器上意味着默认的 loopx turn plan / run-once 会以 operator_credential_unconfigured 失败关闭,operator 必须先想清楚再加 --host codex-cli 才能跑起来。把默认值改成"由 operator 凭据解析"能直接消掉这个摩擦:有凭据就跑托管 host,没有就跑这台机器本来就能用的个体 CLI。方向我认可,尤其是它保留了显式选择永远优先、并新增了可判别的来源读回。

改动思路

  1. host_binding.selected_turn_host() 增加一条凭据分支:显式 LOOPX_TURN_HOST → explicit_config;有可用凭据 → dsh / operator_credential;否则 → codex-cli / no_operator_credential(原来的 product_default 取值被这两个新取值取代)。
  2. operator_credential.py 只更新模块契约文字(凭据可以解析未显式配置的 shipped default,但绝不重指显式选择),没有让它获得选择权。
  3. 默认执行模式继续跟随 host 派生;协议文档、connector 文档与 RFC(en+zh)同步改写,并在协议文档里显式声明这是一次默认行为变更。
  4. 两个公开冒烟与两个绑定测试模块改断言:无凭据默认流改成"真的跑起来",显式 --host dsh 无凭据仍然失败关闭。

具体改动

  • loopx/control_plane/turn_driver/host_binding.py(+55/-14)::91 的凭据分支、:51 新增 TURN_HOST_SOURCE_OPERATOR_CREDENTIAL / TURN_HOST_SOURCE_NO_OPERATOR_CREDENTIAL、:48 新增 INDIVIDUAL_DEFAULT_TURN_HOST。
  • loopx/control_plane/operator_credential.py:模块 docstring 契约改写;loopx/cli_commands/turn_registration.py:注释 1 行;loopx/semantics/inventory_v0.json:计数器 1 行。
  • tests/test_turn_default_host_binding.py(:22 / :42 / :61 三个用例)、tests/test_turn_managed_executor_binding.py、examples/loopx-turn-managed-default-flow-smoke.py(:576 断言组)、examples/loopx-turn-managed-executor-binding-smoke.py(第 5 步显式传 host=dsh)。
  • 文档:docs/reference/protocols/loopx-turn-v0.md、docs/integrations/deepseek-harness-connector.md、docs/architecture/rfcs/harness-selection-dsh-pi-v0.md(en + zh-CN)。

关键内容讲解

  1. 默认值解析与读回是同一次改动:resolve_default_turn_host() 仍只返回 host,但 selected_turn_host() 现在返回 (host, source),于是读回能区分"这是显式选的"还是"这是凭据解析出来的"。我读了消费面:turn_registration.py:57/:169 是唯一两处调用点,managed_executor 读回 schema 未变,仓库内没有其它 Turn 侧消费者依赖消失的 product_default。
  2. 失败关闭没有被削弱:显式 dsh 且无凭据时仍然是 available=false + typed operator_credential_unconfigured,不调用 host、不写 Journal、不花 quota slot——冒烟第 5 步(现显式传 host=dsh)本地跑过,quota_slot_spend_count 保持不变。
  3. 本地验证:tests/test_turn_default_host_binding.py + tests/test_turn_managed_executor_binding.py → 37 passed;examples/loopx-turn-managed-executor-binding-smoke.py → passed;examples/loopx-turn-managed-default-flow-smoke.py → passed(含无凭据默认真的跑通一次 bounded Turn)。

对主干的风险

P1(本 head 的必需检查是红的,阻塞合并)。CI 在 head 2aee6ac93 上 test-shard (2) 与 test-shard (3) 失败,两者都是同一句:loopx/semantics/inventory_v0.json is stale。我把 CI 实际测试的合并提交取下来复现了:

git fetch origin refs/pull/4454/merge:refs/remotes/pr/4454-merge
git worktree add --detach /private/tmp/loopx-pr4454-merge refs/remotes/pr/4454-merge
cd /private/tmp/loopx-pr4454-merge && python scripts/generate_semantic_inventory.py --check
# → stale semantic inventory: loopx/semantics/inventory_v0.json

运行生成器会显示 named_string_constants 需要从 2031 改成 2032。这不是继承失败:main(4f33d5ac6)单独一棵树是 up to date,本 PR 单独一棵树(相对它自己的 base 53fed471e)也是 up to date——问题是本 PR 与另一处同样把该计数从 2030 改到 2031 的改动落在同一行,git 干净合并之后净增被少算了一次。最小修法:在最新 origin/main 上 rebase,跑 python scripts/generate_semantic_inventory.py 并提交结果(计数器应为 2032),再重跑两个冒烟与语义检查确认全绿。

P3(凭据存在与否现在是默认去向的隐式信号)。:91 的判定只看"凭据是否非空",所以一个为了别的用途被导出到环境里的 DEEPSEEK_API_KEY 会把默认 Turn 从 codex-cli 改到托管 dsh,连带改变计费端点与执行模式。文档已经披露这次默认变更,并给了 LOOPX_TURN_HOST 作为固定 host 的做法,所以这是可接受的取舍;但如果希望减少现场误判,建议在协议文档的 Host Selection 段落补一句"固定 host 的机器请显式设置 LOOPX_TURN_HOST"。

P3(常量命名现在名不副实)。MANAGED_DEFAULT_TURN_HOST(:47)与 INDIVIDUAL_DEFAULT_TURN_HOST(:48)是基础常量的别名,但"default"只在各自对应的凭据条件下成立;后续代码若直接引用它们,就会重新引入"默认固定是 dsh"的假设。最小修法:删掉这两个别名,直接用 MANAGED_TURN_HOST / INDIVIDUAL_TURN_HOST,把"默认"只留给 selected_turn_host 的来源语义。

其余残余风险:本次行为变化本身是"无凭据机器从失败关闭变成能跑个体 CLI",属于可用性改善且已披露;未验证面是真实 DeepSeek 端点(冒烟用 hermetic mock,只证明读回与失败关闭),以及 Windows/macOS 安装包下的同一解析。CI 侧我只读到 test-shard (2)/(3) 的失败日志,其它 job 当时仍在运行。

我的整体评价

REQUEST_CHANGES。改动的方向、读回设计与文档披露我都认可:默认值解析集中在既有 owner 里,显式选择优先级没有被削弱,托管 host 无凭据仍然失败关闭,两个冒烟把正反两条路径都跑成了真实执行。但受审 head 的必需检查是红的,而且是可复现地红在 CI 实际运行的合并树上——语义清单的 named_string_constants 停在 2031,合并后需要 2032。这是一个 rebase + 重生成计数器的修复,不涉及行为设计;修好并让 test-shard (2)/(3) 转绿之后,这条 PR 就可以按现在的形态合入。另外两条 P3(凭据隐式改默认的说明、两个别名的命名)属于顺手可改,不阻塞。

[Superseded 2026-09-16 — see the correction at the top; the finding is fixed on the delivery path] English verdict: REQUEST_CHANGES at 2aee6ac. The change itself is sound: selected_turn_host now resolves the shipped default from the operator credential (credential → dsh/operator_credential, none → codex-cli/no_operator_credential) while an explicit --host or LOOPX_TURN_HOST still wins, and the old fail-closed path is preserved for an explicitly selected managed host with no credential (no host invocation, no Journal, no quota spend). I reproduced the local positives (37 passed across the two binding test modules; both public smokes pass, including a real hermetic bounded Turn on the uncredentialed default). The blocking problem is CI: test-shard (2) and (3) fail at this head with loopx/semantics/inventory_v0.json is stale, and I reproduced it on the exact merge commit GitHub tests (refs/pull/4454/merge, head merged into main 4f33d5a): scripts/generate_semantic_inventory.py --check reports stale and regeneration wants named_string_constants 2031 → 2032. Neither main alone nor this head against its own base is stale, so the undercount comes from the PR and another 2030→2031 edit landing on the same counter line. Rebase onto current main, regenerate the inventory, and re-run the two smokes plus the semantic checks. Two non-blocking notes: credential presence now implicitly re-points the default host (billing/execution mode follow), which the docs disclose but could call out with the LOOPX_TURN_HOST advice, and MANAGED_DEFAULT_TURN_HOST/INDIVIDUAL_DEFAULT_TURN_HOST are aliases whose "default" reading is only conditionally true.

@huangruiteng

huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

更正(2026-09-16):本 comment 的 Turn 侧结论已被推翻,而且推翻后的形态已经上线。 owner 最终采纳了本 PR 提出的「由凭据解析默认值」:loopx/control_plane/turn_driver/host_binding.py::selected_turn_host() 在配置了 operator 凭据时解析出托管默认 dsh,没有凭据时解析出个体默认 codex-cli,并把来源回读为 operator_credential / no_operator_credential / explicit_config(显式 --host / LOOPX_TURN_HOST 仍然优先)。它作为 #4479 合入(squash 001e0c380),交付的就是本 PR 自己这一版改动;管家通道与 managed worker 走同一套默认(#4470 82b5ff50c、#4481、#4483)。下面保留的是当轮判断的记录,其中「Turn 侧条件默认 deliberately not adopted」这一段不再成立,请以本行为准。

Closing as superseded

Both halves of this PR's premise have been settled elsewhere, so keeping it open
would leave two competing contracts for the same decision.

The steward-channel half is done, and done differently. The "Open decision"
section here said the channel keeps codex because dsh had no interactive chat
transport. That transport now exists: #4470 (merged) ships one bounded read-only
dsh segment per Chat turn, and the channel's shipped executor is conditional on
one reported local fact — dsh when the operator credential that authenticates
it is configured, codex otherwise. That is the same reachability goal this PR
was reaching for, with the credential deciding a disclosed default instead of
re-pointing an executor the operator already chose.

The Turn-host half contradicts the contract now on main. selected_turn_host
settles the governed Turn's default host independently of the environment, and
its contract says so explicitly: "A configured credential is never a selection
signal."
The credential authenticates the selected managed host; it does not
choose it. Making the Turn default resolve to codex-cli when no credential is
present would mean a lane with no operator credential silently runs bounded work
on an individual CLI login instead of failing closed. The typed
operator_credential_unconfigured refusal is the intended behaviour there: it
names the missing fact and the repair, and it keeps a managed Turn from quietly
spending a person's subscription.

So the reusable part of this work — an explicit, disclosed readback for which
default applied and why — has landed in the channel binding as
executor_endpoint_default_reason, and the Turn-side conditional default is
deliberately not adopted.

If you want a credential-conditional Turn host after all, it is a real product
decision rather than a rebase, and it should start from the current contract:
change selected_turn_host, update its stated semantics and the Turn protocol
reference together, and keep the typed refusal available for the explicit
--host dsh path. Reopen this or open a fresh PR against main and I will
review it on those terms.

@huangruiteng

huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

更正(2026-09-16):下面有两条 disposition 已被推翻。 Turn 侧的凭据解析默认最终被采纳并合入(#4479,squash 001e0c380;selected_turn_host() 有凭据 → dsh,无凭据 → codex-cli),所以 P3 那条「not adopted, resolved the opposite way」不成立;P1 也不再是「没有修复对象」,而是随交付路径转移而消解——交付路径是 #4479,main 上的 scripts/generate_semantic_inventory.py --check 为 up to date。P3 的命名问题按建议修掉了:main 上不存在 MANAGED_DEFAULT_TURN_HOST / INDIVIDUAL_DEFAULT_TURN_HOST,常量是 MANAGED_TURN_HOST / INDIVIDUAL_TURN_HOST,不再带 "default" 读法。

Review findings on this branch: disposition

The three findings in the review above are settled, so they are not left as open
blockers on a closed branch.

P1 — stale loopx/semantics/inventory_v0.json on the merge tree: correct, and
now moot.
Re-verified: refs/pull/4454/merge is gone, so I rebuilt the same tree
(4f33d5ac6 + 2aee6ac93) and ran scripts/generate_semantic_inventory.py --check; it still reports stale semantic inventory, and regeneration moves
named_string_constants from 2031 to 2032. main (1e4edab38) alone is up to
date. So the finding was real for this branch, is not inherited by main, and has
no repair target while this branch is not the delivery path.

P3 — credential presence as an implicit signal for the default Turn host: not
adopted, resolved the opposite way.
The finding asked for LOOPX_TURN_HOST
pinning advice because a configured credential would re-point the shipped default.
main settles the Turn host the other way: selected_turn_host() is
environment-independent, and its contract says so in the function itself — "A
configured credential is never a selection signal."
There is no implicit
re-point to warn about.

P3 — MANAGED_DEFAULT_TURN_HOST / INDIVIDUAL_DEFAULT_TURN_HOST naming: not
applicable.
Only MANAGED_DEFAULT_TURN_HOST exists on main, where the default
really is unconditional; INDIVIDUAL_DEFAULT_TURN_HOST was this branch's own
addition and left with it.

No code change is pending from these findings. If a credential-conditional Turn
host is wanted after all, it is a fresh product decision against the current
contract — change selected_turn_host and its stated semantics together with
docs/reference/protocols/loopx-turn-v0.md, and keep the typed
operator_credential_unconfigured refusal for the explicit --host dsh path.

@huangruiteng

huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

更正(2026-09-16):本 comment 里「PR 仍保持 closed/默认值策略仍待 owner 拍板」的判断已被取代——owner 已拍板,同一改动作为 #4479 合入(squash 001e0c380),main 现在的 Turn 默认值就是凭据解析出来的,RFC 的 promotion gate 也按这个形态改写了。详见本 PR 最后一条 "Comment 记录修正"。

Review findings: repaired on the branch head

The three findings from the review are no longer disposition-only. The branch is
rebased onto current main (ade21106b) and every finding is fixed at the new
head 6f5a9f500, with two additional consistency defects found while doing it.

P1 — stale loopx/semantics/inventory_v0.json: fixed. Reproduced first on the
old merge tree, then removed at the source: the branch is rebased onto current
main, the doc conflicts are resolved (the credentials narrative now sits beside
main's managed-execution-profile section instead of replacing it), and the
counter is regenerated against the rebased tree —
named_string_constants 2031 → 2046 as the counter catches up to main
plus this branch's two new source values.

P3 — credential presence as an implicit signal: fixed, in both directions.
The branch now states the LOOPX_TURN_HOST pin where the resolution is
described, so a machine that wants one fixed host sets it once instead of
reading a closed section; and the two surfaces are stated separately (Turn
resolves its default from the credential, the steward channel has its own
conditional default with its own readback), so the doc no longer implies one
rule covers both.

P3 — MANAGED_DEFAULT_TURN_HOST / INDIVIDUAL_DEFAULT_TURN_HOST aliases: removed.
selected_turn_host() returns MANAGED_TURN_HOST / INDIVIDUAL_TURN_HOST
directly, and the tests assert against those. "Default" now exists only in
selected_turn_host and its source value, where it is true for exactly one
credential branch.

Also fixed while rebasing (not in the review):

  • Mirror drift inside this branch. The English RFC table row still said the
    Turn host default was environment-independent while the English narrative
    three paragraphs below and the Chinese table row both described
    credential-resolved resolution. Both mirrors now say the same thing.
  • main-side prose this change falsifies. loopx/host_mode_planner.py's
    preview rationale, docs/reference/protocols/host-mode-plan-v0.md, and
    examples/host-mode-plan-smoke.py all asserted that "a credential never
    selects" a Turn host. That is still true of the preview (it pins no host and
    resolves nothing) but it is no longer true of the resolution, so all three now
    state why the preview stays credential-invariant instead.

Verification at 6f5a9f500

check result
pytest tests/test_turn_default_host_binding.py tests/test_turn_managed_executor_binding.py 43 passed
examples/host-mode-plan-smoke.py ok
examples/project/host-mode-plan-cli-smoke.py ok
examples/loopx-turn-managed-executor-binding-smoke.py passed
examples/loopx-turn-managed-default-flow-smoke.py passed
scripts/generate_semantic_inventory.py --check up to date
ruff check on the changed Python files passed
DCO trailers present on all 4 commits

Diff against main: 15 files, +240/−162, four commits (feat / test / docs
/ refactor).

This PR stays closed

Repairing the branch does not settle the product question, so I have not
reopened it or merged it. main still ships the other contract —
selected_turn_host() is environment-independent and a lane without an operator
credential fails closed with the typed operator_credential_unconfigured
refusal — and this branch reverses that, which the DSH/Pi RFC's own promotion
gate rules out for a default path: an individual lane must be selected, not
reached by default
, so a managed lane cannot land on one person's CLI
subscription because a credential was missing.

Note that a closed PR keeps its recorded head frozen, so the repaired commits
appear in the PR only if it is reopened. If the credential-resolved Turn default
is wanted after all, reopening is enough now — the branch is green and mergeable
— but that is a product decision to take explicitly, together with the
promotion-gate wording it changes.

English: the review findings on this branch are repaired, not just dispositioned.
The branch is rebased on ade21106b at 6f5a9f500: stale semantic inventory
regenerated, pin advice and the two-surface statement fixed, the
*_DEFAULT_TURN_HOST aliases removed, the branch's own EN/ZH mirror drift fixed,
and the main-side "a credential never selects" prose the change falsifies
updated. Verified: 43 focused tests, 4 public smokes, inventory check, ruff; DCO
clean. The PR is intentionally left closed: main ships an
environment-independent Turn host and fails closed without a credential, and
reversing that is a product decision against the RFC's promotion gate, not a
rebase. Reopening the PR now shows the green repaired head.

@huangruiteng

huangruiteng commented Sep 15, 2026 •

Copy link
Copy Markdown
Collaborator Author

更正(2026-09-16):本 comment 里「PR 仍保持 closed/默认值策略仍待 owner 拍板」的判断已被取代——owner 已拍板,同一改动作为 #4479 合入(squash 001e0c380),main 现在的 Turn 默认值就是凭据解析出来的,RFC 的 promotion gate 也按这个形态改写了。详见本 PR 最后一条 "Comment 记录修正"。

Fifth commit: the file I repaired no longer contradicts itself

Closing out the three findings meant re-reading the file I had just edited, and the
same RFC still said two things the change falsifies — both inside the table that
was edited:

  • the Supported alternative Turn host promotion gate read "an individual lane
    must be selected, not reached by default"
    , which the credential-resolved default
    row directly above it now contradicts;
  • "Credentials authenticate the selected profile; they never choose it." read as a
    blanket denial while the change lets a credential resolve the shipped host
    default;
  • in the steward-transport section, "no default path reaches an individual
    subscription"
    was not scoped to the channel it was written about.

Fixed at 1a1ab47e2 (fifth commit, docs only, EN and ZH mirrors). The gate is
rewritten in place rather than left contradictory, and the rewrite is stated
explicitly in the narrative so the changed rule is auditable instead of silent: it
keeps the original intent (no lane may depend on one person's login without the
operator being able to see that it did) and names the readback — no_operator_credential
— that makes the dependency visible. The credentials sentence is scoped to the
profile plus the one thing a credential resolves, and the steward-transport sentence
is scoped to the steward channel.

Re-verified at 1a1ab47e2: examples/docs-governance-smoke.py ok, and
scripts/generate_semantic_inventory.py --check up to date. The 43 focused tests and
4 public smokes reported at 6f5a9f500 still hold, because this commit changes two
Markdown files and no source file (docs-governance-smoke.py is the only check that
reads this RFC).

The PR is still deliberately closed. Fixing the findings and making the branch
self-consistent does not decide the product question in it: main ships an
environment-independent Turn host that fails closed without a credential, and this
branch makes the managed default resolve to the individual codex-cli host when no
credential is configured. That is a policy choice against the DSH/Pi promotion gate
— which this commit only rewrote, it did not resolve — so reopening and merging stays
the owner's call. Reopening now shows a green, self-consistent head.

English: a fifth docs-only commit (1a1ab47e2) resolves three statements inside the
RFC that the change itself falsified, including the alternative-host promotion gate
that the credential-resolved default contradicted. Verified with the docs governance
smoke and the semantic inventory check. The PR stays closed pending the owner's
decision on the default-resolution policy.

@huangruiteng

Copy link
Copy Markdown
Collaborator Author

The repair continues in #4479 — GitHub will not reopen this PR

The three review findings on this branch are fixed, but reopening is no longer
possible: closing out the P1 needed a rebase, and GitHub now rejects the state
change for this PR —

422 state cannot be changed. The codex/credential-resolved-default-turn-host-20260915
branch was force-pushed or recreated.

So the repaired head is delivered as #4479, and this PR stays as the change
record. What moved:

  • the recorded head 1a1ab47e2 is rebased onto current main (b132d0bfb) at
    b9c0fc440; the only semantic difference is the rebase itself plus the
    regenerated loopx/semantics/inventory_v0.json (named_string_constants
    2049 → 2050);
  • P1 fixed at the source instead of on a stale merge tree;
  • the P3 pin-advice and the *_DEFAULT_TURN_HOST alias findings fixed in the
    two RFC mirrors;
  • real-path readback on the rebased head: no credential →
    executor: codex-cli / individual; credential configured →
    executor: dsh / managed, execution_profile: deepseek-v4-flash@high,
    available: true;
  • loopx canary premerge --from-git-diff passed (tier=standard, 15 changed
    files, selected=17, failures=0), plus 262 focused tests and 4 public smokes.

English: this PR cannot be reopened after the rebase (GitHub: "was force-pushed
or recreated"), so the identical repaired change is delivered as #4479. No
finding from the review above remains open.

huangruiteng added a commit that referenced this pull request Sep 15, 2026
)

The shipped default Turn host is now resolved from the operator credential:
`dsh` when `DEEPSEEK_API_KEY` is configured, `codex-cli` otherwise. An explicit
`--host` or `LOOPX_TURN_HOST` still wins, and an explicitly selected managed
host without a credential still fails closed with the typed
`operator_credential_unconfigured` refusal and its remediation codes.

This is the repaired delivery of #4454, which GitHub will not reopen after the
rebase: the three review findings there (stale semantic inventory, pin advice,
`*_DEFAULT_TURN_HOST` aliases) are fixed at this head, the RFC statements the
change falsifies are rewritten in place, and the steward channel's already
shipped credential rule is now matched by the Turn surface.

Verified at the reviewed head: real CLI readback in both credential states plus
the explicit refusal path, 262 focused tests, four public smokes,
`loopx canary premerge` (17 checks, 0 failures), semantic inventory check, docs
governance smoke, ruff, and DCO on all five commits. Merge readiness returned
`ready=true` for `4479@b9c0fc4`; the admin
bypass covers only the formal-approval requirement, which GitHub blocks for an
author-owned pull request.
@huangruiteng

Copy link
Copy Markdown
Collaborator Author

Landed: the repaired change merged as #4479 (squash 001e0c380). This PR stays closed as the change record; no finding from the review above remains open.

@huangruiteng

huangruiteng commented Sep 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Comment 记录修正:凭据解析的默认值最终还是落地了

上面的 closing / disposition 两条 comment 是在 main 还持有相反契约时写的。owner 随后拍板,所以这份 trail 里有一部分现在是错的:这里做一次集中修正,并在那两条 comment 顶部加了同样的更正说明,避免后来的人把当时的判断当成当前契约。

实际落地的形态(main = edcddc7aa)

本 head 上那三条 review finding 的现状

  • P1 语义清单过期:在冻结 head 上真实存在,现在已消解——本 PR 不是交付路径,feat(turn): resolve the default host from the operator credential #4479 在 main 上重新生成了计数器,scripts/generate_semantic_inventory.py --check 在 main 上 up to date。
  • P3 凭据存在变成默认去向的隐式信号:被采纳了,同时补上了 finding 要求的缓解:默认值不仅披露,还带回读来源;协议参考写明「想要固定宿主就显式设置 LOOPX_TURN_HOST」;管家通道另有自己命名的默认来源原因。
  • P3 *_DEFAULT_TURN_HOST 别名命名:按建议修掉了。main 上这两个别名不存在,selected_turn_host 直接返回 MANAGED_TURN_HOST / INDIVIDUAL_TURN_HOST,「default」只留在该函数及其来源取值里。

这里记录的 review 判决(REQUEST_CHANGES@2aee6ac93)针对的是一个从未交付的 head,其 finding 要么已修、要么已消解,没有一条仍然开放。PR 记录 head 保持冻结(2aee6ac93,closed,CONFLICTING),所以本 PR 继续作为变更记录,#4479 作为交付。

English: the two disposition comments above were written while main shipped the opposite Turn-host contract, and the owner later settled it the other way, so this corrects the record in place. selected_turn_host() on main resolves the shipped default from the operator credential (credential → dsh/operator_credential, none → codex-cli/no_operator_credential, explicit selection always wins), which is this PR's own change, landed as #4479 (squash 001e0c380); the steward channel and managed worker default the same way (#4470/#4481/#4483). Of the three review findings, P1 is moot on the delivery path (#4479 regenerated the counter; main's inventory check is up to date), the P3 implicit-signal note was adopted together with the pin advice it asked for, and the P3 alias naming was fixed as recommended. No finding remains open; the frozen head stays a change record.


补充(2026-09-16):这次记录修正的范围比上面写的更大——本 PR 的两条 review 正文(5210751413、5211332288)顶部也补了同样的更正说明,verdict 行标为 Superseded,第二条的标题改成 ~~Request changes conclusion~~;PR body 的 "Open decision" 一节标注为已由 owner 反向拍板(main 上管家通道也按同一凭据规则解析 endpoint)。所以此处对齐的是 comment、review、body 三处记录。

English: the two review bodies and the PR body's "Open decision" section carry the same correction, so every public artifact on this record PR now matches main.

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