feat(runtime): fence first-party Host state by GoalRef - #5141
huangruiteng merged 8 commits into
Conversation
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
huangruiteng
left a comment
There was a problem hiding this comment.
English verdict: APPROVE
Reviewed head: 1a1e543294de7113b8f04caa0f94bbd926183012;immutable base: 39f2fd15e45987a01769bd9bd5a4da42e89e12d9。无本 PR 引入的阻断项。批准的是 partial M3 first_party_host_runtime enforcement,不是 source-profile execution activation 或完整进程/下游效果 drain 的验收。
动机
同一个 goal_id 被退休并重建后,Host descriptor、Turn journal、DSH session lineage 或 Kunlun runtime 不能把旧实例的状态与迟到结果交给新实例。已有 GoalRef/RFC 身份 owner 解决身份表达,不能自动约束这些实际持久化/返回边界。本 PR 把现有 first-party Host 路径接到 exact GoalRef,而不是新增 Goal ledger、peer 权限模型或替换 runtime。
最强的反对理由是“给每个 Host 加一个检查,仍无法证明整个 M3 安全开放”。这个反对成立,所以我按已接受 RFC Section 11 的分阶段边界审查:当前交付阻止过期状态选取和迟到结果进入指定 Host 状态提交;accepted-before-retirement downstream drain、warm/unsupported binaries 和其他 inventory owner 仍明确未验收。源码及登记仍保留 execution_authority: false,没有把局部测试提升成完整上线许可。
改动思路
TypeScript 决定 planned/current/Host GoalRef 是否一致及 typed refusal;Python 只读取 registry、复用 alias lifecycle lock,并在短状态提交时执行决定。锁不跨模型或网络请求,避免把 Goal lifecycle 卡在长调用上。更小的“比较一下 goal_id”解决不了重建,单纯给 session filename 加实例后缀也挡不住已缓存的 journal 和晚到结果;复用既有 exact parser/guard、把各真实提交点接入同一边界是有需求的最小完整 slice。
legacy 分支不要求新字段或重写历史记录;source 分支拒绝 alias-only/malformed/foreign/stale Host state,不自动给旧 descriptor 补上新实例身份。正常恢复与明确的 orphan/import 处理保持分开。
具体改动
全量 23 个文件、+1736/-63:10 个生产文件接线/实现,2 个 inventory/registry I/O 清单,9 个测试文件,2 份 RFC qualification ledger。没有新 CLI 参数、执行权限或前端配置入口。
关键代码讲解
first_party_host_runtime.ts::decideFirstPartyHostRuntime:复用parseExactGoalRef,以 operation/authority/Host state 的 typed 分支区分 start_new、resume、accept_result 和 refusal;non-source 先返回 legacy,不把 strict decoding 扩散到旧输入。FirstPartyHostGoalAdmission::select_state/accept_result:在既有 alias lifecycle guard 内重新读当前 authority,并把检查与 Host 状态 commit 放在同一短锁内;状态不存在可创建 exact-bound 新记录,alias-only/旧实例状态只能拒绝,不能 silent relabel。executor.py::run_loopx_turn_once:启动和 cached recovery 前验证 current;Host 结果/失败 journal checkpoint 通过 result admission 提交。迟到结果异常不会被普通 Host failure wrapper 吞掉后继续 validation/writeback/spend。codex_cli.py::run_codex_cli_host与runtime.py::run_native_goal:Codex descriptor 和 Kunlun runtime 加 exact binding、读旧状态先检查、接受新状态/终态再检查。DSH 只在 exact instance 存在时使用 v2 lineage digest,legacy v1 与缺 lineage 的 fallback 继续原样。
对主干的风险
关键风险有三类:legacy 误被 strict gate 覆盖;A→B 后旧 state/cached result 被重用;“拒绝旧 A”后新 B 也无法继续。独立 reviewer probe 使用真实 file-backed registry、TS journal 和实际 run_loopx_turn_once:legacy 整体 plan digest 与基线相同,首轮 commit、重放及三类效果各一次;A 的迟到结果不进入 host_result journal、不验证/写回/扣额/调度,旧 A retry 不再启动 Host;用新 B 的 exact plan 可 commit,旧 A journal 原样保留。进程内绕过 result admission 会重新接受已退休 A,反例能检测这个边界缺失。
Python 定向套件原始结果为 308 passed / 2 failed:registry I/O census 在 immutable base 与 head 有相同 usage_goal.py::_bound_codex_session 漏登记及 quota.py::_dispatch_quota_turn_start_hooks 的 metadata 漂移,该代码不在本 PR;另一项 30 秒 Codex e2e smoke 超时,base 与 head 同命令单独运行通过,head 约 12 秒。八项旧 Codex/DSH/Kunlun 会话、终态和 crash recovery 原生对照在两边均通过。
完整 TS 套件 3145 passed、0 failed、30 个既有 PostgreSQL 环境门控 skip,typecheck 和 changed-source Ruff 通过。这里未改变 SQL authority store、provider routing 或 promotion,PostgreSQL skip 不被算成这条 file-registry Host admission 路径的验收。标准 premerge 原始记录含一个 120 秒 CLI budget timeout;同一 smoke、同一 120 秒 wrapper、同一工作负载重跑,base/head 分别约 90/92 秒通过,不改预算或测试预期。其余 16 项 premerge smoke、direct checks 和全 changed-file 公共边界扫描通过。原始失败保留,不宣称第一次 canary 全绿,也不因与本 PR 无关的红检查 request changes。未查询或等待远端 CI。
共享语义与剩余边界
GoalRef owner、source registry codec 与 lifecycle guard 复用现有契约,inventory 正确写为 source_exact_partial,而非“已完成 M3”。新 refusal 是机器强制,不是可忽略 guidance;语义与错误文案不绑定某种项目/benchmark。
#5130 的 attached Chat binding、#5139 的 App turn acceptance 和 #5144 的 Host process supervision 是相邻但不同边界:session/Host admission 不证明子进程已 drain,App acceptance 不替代 exact GoalRef。它们不能因本次 approval 被视为一并验收。无 frontend/Lark schema 或设置改动,这个未开放执行的 qualification slice 不需要另造 UI 开关;未来 activation 仍须原 RFC 的完整接线和真实运行验收。
我的整体评价
APPROVE,保留现有 integration/merge-readiness holds,由 maintainer 合并。长期可靠性的增量是切断跨 Goal lifetime 的 state/result 复用,同时保留旧 lane 的正常重复工作;不是测试数或新字段本身。代码量主要是四条真实 Host 路径和负例,边界内合并审查合理。未来改动的简化检查已落实到 shared parser、既有 guard 与一个 TS decision owner;没有理由再加第二份 Python 状态机或通用 Host framework。M3 的下游 drain、unsupported process 和 orphan 恢复仍是已有 RFC 的独立剩余工作,不能凭这条 review 开启 execution_authority。
Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
Signed-off-by: duanjialing.777 <duanjialing.777@bytedance.com>
|
Hi @Duang777, the DCO If the log confirms a missing |
huangruiteng
left a comment
There was a problem hiding this comment.
PR #5141 复审 — exact head 9f34365fd832fcd8c49a99ce9d54428370a30e6b
动机
按 docs/architecture/rfcs/goal-instance-identity-and-orphan-recovery-v0.md 第 11 节 M3 的 first_party_host_runtime 归属,source-profile 的 Host 状态此前只按 goal_id 绑定。于是同一 alias 下的 Goal 被重建(新 goal_instance_id)之后,旧实例留下的 Turn journal、Codex session 描述符、DSH session 标识与 Kunlun 运行状态仍可能被恢复,迟到的 Host 结果也可能被新实例接受。本 PR 把 source profile 的状态选择与结果准入绑到精确的 {goal_id, goal_instance_id},让 Goal B 在 journal、validation、writeback、spend、scheduler 与 Kunlun closeout 之前就拒绝 Goal A 的状态与结果。
改动思路
决策所有权按仓库既定方向划分:TypeScript 拥有 GoalRef 决策,Python 只做 registry I/O、生命周期锁与 Host 状态提交。TS 侧新增 decideFirstPartyHostRuntime 并注册为 effect runtime handler,用 typed union 返回 legacy / start_new / resume / accept_result / reject(code),拒绝码是枚举而不是子串判断。Python 侧新增一个 admission 对象,在 alias 生命周期锁(guard_path)下把三种时机收敛成三个检查点:select_state(选状态,缺失则用精确 GoalRef 初始化)、require_current(Host 启动、closeout 每一步之前)、accept_result(任何状态提交写盘之前)。capture_first_party_host_goal_ref 在锁内读取 source authority 得到精确 GoalRef,registry 缺失或非 source profile 时返回 None,从而让非 source 计划完整保留既有路径、schema 与字节。
具体改动
新增 loopx/control_plane/goals/first_party_host_admission.py(212 行)与 loopx/control_plane/goals/first_party_host_runtime.ts(171 行),并在 effect_runtime_handlers.ts 注册 goal.first_party_host_runtime.decide。四个 Host 路径接入同一组检查点:turn.py 在 plan 阶段捕获 GoalRef 并传入 build_loopx_turn_plan,在 run-once 恢复分支重建 admission 并 require_current,把 admission 交给内置 Codex host 与 session binding resolver;executor.py 让 host request 携带 goal_ref,并把 _host_result_stage 的三处 journal 写入改为经 accept_result 提交;codex_cli.py 在 session 描述符里持久化 goal_ref,读取时用 select_state 比对,store/discard 都走 accept_result;dsh_goal_mode/turn_host_adapter.py 让 source session id 在 lineage 之外再纳入 goal_instance_id;kunluncode_goal_mode/runtime.py 把 goal_instance_id 写进 binding,并把所有 write_runtime_state 改为 _commit_runtime_state,在 closeout 的 delivery、todo complete、quota spend 各步之前 require_current。driver.py 与 transaction.py 让 goal_ref 进入 plan payload 与 turn identity;语义清单 goal_instance_binding_inventory_v1.json 更新了 producer/consumer/effect boundary/identity strength/disposition,RFC 增加 2026-09-27 的部分执行记录。测试侧补充了 TS 7 项 policy 测试与 Python 侧 Goal A/B、legacy state、cached recovery、并发提交/重建等负例。
对主干的风险
最大的风险是"默认关闭隔离"被破坏:非 source profile 必须逐字节保留旧行为。这一点在代码上有明确出口(enabled 为假时 select_state/require_current/accept_result 都直接走原回调,turn.py 用 strict_goal_admission is None 回退到旧调用),并由 legacy profile 测试与 non-source profiles preserve legacy behavior TS 测试固定;全量 npm run test:control-plane 3262 项 0 失败也在这一 head 上复验。第二个风险是新增持久化字段(Codex 描述符与 Kunlun binding 的 goal_ref、turn identity 里的 goal_ref):旧 source 记录没有 goal_instance_id,代码选择显式拒绝(legacy_host_state)而不是推断升级,方向正确,但意味着 source 档案一旦启用就要求先前的 Host 状态被清理或重建,这一点由 RFC 的剩余 hold 与 execution_authority: false 兜住。锁方面,所有检查都在 alias guard 内,测试覆盖了结果提交与重建串行化。需要单独记录、但不属于本 PR 的主干红灯:examples/control_plane/cli-output-budget-regression-smoke.py 在本 head 与当前 origin/main 上给出同一条 loopx_turn_plan/crowded/json emitted 14514 chars; baseline ceiling is 14500,而 PR base 39f2fd15e 上该 smoke 通过;对照 head 与 main 测得完全相同的 14514,说明本 PR 的 diff(该 fixture 的计划里并不存在 goal_ref)没有贡献增量,是主干自身的 envelope 增长越过了 341 字符的回归余量,应由预算所有者决定压缩还是重定基线。同类情形还有 examples/repository-hygiene-smoke.py 的 public/private boundary 断言,在 main 与 head 上以同一签名失败。
我的整体评价
这是一次边界清晰、所有权正确的部分执行:决策留在 TS,Python 只做 I/O 与锁,四个 Host 路径复用同一组检查点,而不是各自发明一套比较逻辑;三处 accept_result 的位置说明"提交前检查"覆盖了 journal、校验失败与正常结果三条写出路径。我复验了 TS 类型检查、全量 control-plane TS 套件(3262 项 0 失败)、PR 涉及的 Python 套件(305 项)与架构套件(131 项)、针对性 Ruff、git diff --check 与 premerge 的 public boundary 扫描,均通过;未通过的两条已独立归因到主干。剩余风险(warm/unsupported binaries、accepted-before-retirement drain、其余 inventory owners)已经在 RFC 与 PR 正文里显式保留为 hold,因此我不把它计为本 PR 的缺口。建议合入。
本次复审的 head 相对上一次复审只多了主干并入(#5131 的文档引用修正与 monorepo-distribution-split RFC 草稿),与 base 的差异仍是同一份 23 文件 +1736/-63;代码面未再变化,因此前述验证结论继续成立。
English verdict: APPROVE - 9f34365 binds source-profile Host state and result admission to the exact GoalRef behind a TypeScript-owned typed decision, keeps non-source plans byte-identical, and passes typecheck, the full 3262-test control-plane suite, 305 PR Python tests, 131 architecture tests and the premerge public-boundary scan; the two remaining red checks (CLI output budget and repository hygiene) fail with identical signatures on origin/main and the PR base passes the budget smoke.
|
Merged as
|
Goal And Delivered Outcome
goal-instance-identity-and-orphan-recovery-v0RFC, Section 11 M3, for thefirst_party_host_runtimeinventory owner.first_party_host_runtimeowner in the Goal instance RFC. Source-session Host state previously used onlygoal_id, so a recreated Goal could resume state or accept a late result from the retired instance.{goal_id, goal_instance_id}. Goal B rejects Goal A state and results before journal, validation, writeback, spend, scheduler, or Kunlun closeout work can continue.main; this change is independent of feat(chat): bind attached sessions to GoalRef #5130.Scope And Continuation
execution_authority: falseand the M3 activation hold remain unchanged.Validation
deee231c8664f8d2cb96e4339eb06e52667cf11eunitpassednpm run test:control-planesuite passed on pre-rebase revision071da8317: 3163 tests, 3133 passed, 30 environment-gated skips, 0 failures. The rebase changed no runtime or TypeScript file in this diff.integrationpassedstaticpassedgit diff --check, the 246-site project-registry I/O manifest, and semantic vocabulary drift at 43/43 passed.real_entrypointpassedregression_paritypassedstaticfailedexamples/repository-hygiene-smoke.pyreportsrelease timeline is missing version entries: v1.2.0; the changed-file set contains neither release metadata nor the hygiene checker, and the same failure was reproduced on the precedingorigin/mainbase.source_session_v1still has no execution authority. Open PR feat(usage): observe quota cycles and bound Codex turns across Hosts #5137 touches one telemetry line inturn_driver/executor.py; this PR keeps telemetry around the Host call and limits the GoalRef guard to short state commits.See validation disclosure guidance.
Frontend / Visual Evidence
Type of Change
LoopX Area
Technical Direction
first_party_host_runtimeenforcement ingoal-instance-identity-and-orphan-recovery-v0.Shared-authority RFC fixture impact
source_session_v1remains disabled for execution.Boundary Checklist
.loopx/,.codex/goals/, and liveACTIVE_GOAL_STATE.md).none.Signed-off-bytrailer (git commit -s).Maintainer repair at the merged head
The reviewed head
9f34365fd832fcd8c49a99ce9d54428370a30e6bwas reached by mergingorigin/maininto the branch to clear the conflict inloopx/cli_commands/turn.py(main's single-name import style from #5148 against this PR's addedfirst_party_host_admissionimport) and to pick up main-side fixes. The PR's own delta is unchanged: 23 files, +1736/-63.Verification at that head:
npm run typecheck:control-plane;npm run test:control-plane(3262 tests, 3232 passed, 30 environment-gated skips, 0 failures); 305 Python tests acrosstest_first_party_host_runtime.py,test_loopx_turn_driver.py,test_loopx_turn_codex_cli.py,test_loopx_turn_executor.py,test_dsh_goal_mode.py,test_kunluncode_goal_mode.py; the 7/7 TypeScript policy tests; 131 architecture tests; targeted Ruff;git diff --check; and the premerge public-boundary scan.Two red checks on this head are main-side and were independently attributed rather than repaired here:
examples/control_plane/cli-output-budget-regression-smoke.pyreportsloopx_turn_plan/crowded/json emitted 14514 chars; baseline ceiling is 14500identically on this head and onorigin/main, while the PR base39f2fd15epasses it. The fixture's plan carries nogoal_ref, and both sides measure exactly 14514, so the 355-char growth came from main's own TurnEnvelope work (the range registers fix(replan): preserve goal fidelity and audit completion evidence #5132). The budget owner needs to choose compaction or an evidence-backed rebaseline; the 341-char regression margin is exhausted.examples/repository-hygiene-smoke.pyreports a public/private boundary hit attests/control_plane/test_public_safety_credential_shape_owner.py:51/54on both this head andorigin/main.Merge used admin bypass because the
protect mainruleset requiresrequire_last_push_approvalanddismiss_stale_reviews_on_push, and the maintainer main-integration push made the maintainer the last pusher; the exact-head review is published at #5141 (review).