Skip to content

refactor(host): separate request encoding from process lifecycle - #7802

Merged
loopx-agent merged 1 commit into
mainfrom
codex/release220-host-request-20261101
Nov 1, 2026
Merged

loopx-agent merged 1 commit into
mainfrom
codex/release220-host-request-20261101

Conversation

@loopx-agent

@loopx-agent loopx-agent commented Nov 1, 2026 •

Copy link
Copy Markdown
Collaborator

The complete 2.2 regression found an unreviewed Host transport function above the existing maintainability limit: 91 statements against 90. Separate the existing pre-launch request encoding from process containment, guarded handoff and cleanup. This keeps the wire mapping in a small typed helper and leaves lifecycle ordering together in its existing owner.

The request statements, public signature, parameter mapping and remaining lifecycle AST match the immutable base. No field, default, timeout, output budget, authority, compatibility decoder or exception policy changes. The TypeScript supervisor still owns Host lifetime decisions; Python retains its existing transport and Windows launcher mapping.

Validation at 43dda7511db1800c0d573633a8dd65cbfde69d3c:

  • Host-process and maintainability modules: 30 passed. The original maintainability test independently fails on the prior frozen candidate; the repaired lifecycle function has 86 statements and the encoder has eight, with no ceiling or exception change.
  • The same three actual turn run-once CLI cases pass at immutable base d41e71b0bee225b9cd69c48fae56e5fd23fa45b8 and this head. They use real child processes and isolated authority, exercise interrupted writeback/spend recovery and verify complete settlement/readback. Complete observed Host results match after normalizing only ephemeral Turn keys.
  • A controlled loss of request input traverses that same CLI and real Host, makes the independent success oracle fail, and leaves product source unchanged. This is sensitivity evidence, not a passed product execution.
  • Changed-module Ruff, configured strict mypy (17 files), bilingual diff, semantic advisory and native public-boundary scan pass. No supported new vocabulary carriers were reported; the advisory's limits remain.
  • Standard Goal premerge passes: six direct and ten selected checks, with no failures or manual holds. The exact-scope change-quality receipt is valid. Final release-source qualification and publication remain separate.

Existing real Windows .cmd integration remains a platform qualification boundary; the portable relay test and unchanged encoding do not claim native Windows execution. UI impact: none; this changes no rendered surface or user interaction.

Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>

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

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

Reviewer: actor_kind=model_agent; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max

宿主归属来自最近的执行记录;其 bounded read 未验证当前执行活性,不据此认证模型权重或赋予结论额外权限。

动机 / Motivation

面向准备发布 2.2 的维护者,以及通过通用命令或 Codex 适配器运行 Host 的调用方。

完整回归发现 Host 适配函数有 91 条语句,超过原有 90 条限额,因而候选版本不能完成资格检查;修复把命令、输入和时间预算的请求编码单独放在一个本地函数,生命周期函数降为 86 条,原有限额和例外表均不变。

维护性检查恢复通过,并已对照原版与精确修复版的真实进程分支、CLI 提交及中断恢复;未增加调用方设置或确认步骤。

本 PR 只修复这一处已复现的发布阻塞,不增加新协议、开关、权限或平台保证,也不将局部通过当成整个 2.2 release 合格。

发布维护者仍须在最终固定源码上完成整套回归、模型和原生 Goal、完整打包安装及渠道读回。

改动思路 / Approach

请求编码与生命周期是两个清楚的职责;保留同一 transport 模块和 TypeScript 监督 owner,把有严格顺序要求的清理留在原函数,比拆散清理或新增框架更容易核验。

本次仅提取已有请求编码,保持公共签名、每个参数、实际字段、默认值、清理顺序和持久记录语义。

最有力的反对意见是:为减少一条超额语句而加一个函数,可能只是在隐藏指标。这里认可的是实际职责拆分:编码函数无 I/O,先复制 argv、保留 Windows relay、整理原有请求字段;进程创建、准入 guard、输出消费、取消、回收和记录仍由原路径处理。单纯拼接语句、抬高阈值或给新债务登记例外都没有解决这个分界。没有新增类、模块、兼容版本或第二个决策 owner。

具体改动 / Changes

关键代码讲解

  • _host_process_request,第 264 行:接收原参数,构造原请求。argv 复制、Windows Python relay、秒到毫秒换算、stdout 限额和可选 delegated lease 的 AST 与原内联代码一致;它不判断授权,也不启动 Host。
  • run_host_process,第 291 行:在原来的位置调用编码函数,再沿原环境隔离、桥接和生命周期路径执行。公共签名、参数逐一映射,以及编码之后的全部生命周期 AST 均一致;嵌套 finally 仍保留 Windows Job finish 失败时的后续关闭顺序。

整个差异只有一份生产文件,增加 36 行、删除 13 行。没有新增状态或语义词表。规范为 docs/development/testing-and-quality.md#refactor-real-path-gate--重构真实路径门,spec_revision: d41e71b0bee225b9cd69c48fae56e5fd23fa45b8,并核对既有 docs/reference/protocols/loopx-turn-v0.md#managed-host-process-lifetime:

  • RW-1:implemented。从修改前调用方列出普通/guarded/leased、明确环境、有限/无截止、输出预算、回调失败和 POSIX 进程记录路径;同一份公开 Host 测试在 base/head 各通过 16 项。
  • RW-2:implemented。同一真实 turn run-once CLI 的普通提交、writeback 中断和 quota-spend 中断三例在 base/head 都通过,独立检查验证、完整收据、持久 journal 与恢复结果;实际后端为隔离的 File Goal、真实 Node/Python 子进程和命令 validator。
  • RW-3:implemented。在同一 CLI 路径把传给真实 Host 的请求输入故意变成空 JSON 对象,独立成功 oracle 失败;未变异的精确 head 通过。变异仅存在于测试进程,产品源文件和判定门不变。

三项 CLI 的完整 Host 结果只归一化每次运行的新 Turn key,其他字段一致。Host 全分支对照另有一对新进程 pid/pgid:原值保留,独立测试先证明它们等于该进程和持久记录、标记未继承且最终 drained,再归一化这两个临时身份。没有归一化错误、默认值、权限、输出或清理事实。最初的逐字比较因新 PID 不同而失败的记录也保留。

对主干的风险 / Risks

最强回归场景是编码提取丢失输入或改变限制,进而让所有上层 Host 请求出错。受控输入丢失确实穿过 CLI 和真实 Host 并使 oracle 失败,说明测试不只是给新 helper 返回预期结果。原记录不会因此伪造完成,验证及结算仍属于原 owner。回退可还原这一份代码;原维护性阻塞也会随之恢复,不能据此完成 release。

PASS:30 项 Host/维护性检查;base/head 各 16 项完整 Host 分支;base/head 各三项真实 CLI;受控输入丢失的预期失败;结构和参数映射核对;Ruff、严格 mypy 17 文件、双语差异、语义 advisory 和公开边界;标准 Goal premerge 六项直接及十项选择检查,零失败、零人工 hold。未查询、轮询或等待 PR CI。

便携 relay 用例在当前 POSIX 环境核对带空格、引号、特殊字符和 Unicode 的参数及 stdin;真实 Windows .cmd 解析和模型执行仍有各自平台/release 验证边界。本次不声称新 Windows 保证,也不把旧候选的测试或模型记录换成此 head 的资格。

我的整体评价 / Verdict

problem_context: justified_increment;long_horizon preserved,user_experience preserved。实际调用路径、截止、拒绝、清理、记录和恢复未见变化;调用方无需新增设置或确认。检查的唯一改动是 Host transport 的 Python 请求编码及其现有调用点,没有渲染内容、界面布局或交互变更。 本 PR 没有新的界面需要视觉验收;完整 release 的安装界面另行验证。

体量和兼容性判断为 proportionate:一个有实际调用点的本地编码函数,复用既有 transport/TS supervisor;没有新配置、双重规则或迁移成本。保留的 Windows relay 服务既有外部命令兼容,Python 和 TS 仍随同一包部署。本 head 可批准并在既有维护者授权及即时 readiness 通过后合并;完整 release 资格保持独立。

可复现的观测与输入丢失脚本(仅用于公开合成夹具)

将下方原样保存为 host-wire-observer.py,分别 checkout 上述 base/head,用同一环境执行;脚本只观察 Host 结果,变异模式则额外修改测试进程中的输入。路径只用作本地输出,不需要操作者配置或原会话。

python host-wire-observer.py --observation-file cli.json tests/test_loopx_turn_driver.py::test_turn_run_once_cli_commits_distinct_host_guidance_without_task_step_edit -q
python host-wire-observer.py --observation-file host.json tests/control_plane/test_host_process.py -q
# 仅在 head;预期失败,不能当作合格执行:
python host-wire-observer.py --observation-file mutation.json --mutate-input 'tests/test_loopx_turn_driver.py::test_turn_run_once_cli_commits_distinct_host_guidance_without_task_step_edit[None]' -q
"""Private observation/sensitivity harness for unchanged public CLI fixtures."""
import argparse
import hashlib
import json
import re
from pathlib import Path

import pytest

parser = argparse.ArgumentParser()
parser.add_argument('--observation-file', type=Path, required=True)
parser.add_argument('--mutate-input', action='store_true')
parser.add_argument('pytest_args', nargs=argparse.REMAINDER)
args = parser.parse_args()
observations = {'mutation':args.mutate_input,'mutation_calls':0,'host_results':[], 'test_reports':[]}


def normalize(value):
    if isinstance(value,dict):
        return {key:('<turn-key>' if key == 'turn_key' and isinstance(item,str)
                     and re.fullmatch(r'sha256:[a-f0-9]{64}',item) else normalize(item))
                for key,item in value.items()}
    if isinstance(value,list):
        return [normalize(item) for item in value]
    return value


class Observer:
    def pytest_sessionstart(self, session):
        from loopx.control_plane.turn_driver import executor, host_process_transport
        original_run = executor._run_host

        def observe(*positional, **keywords):
            value = original_run(*positional, **keywords)
            observations['host_results'].append(normalize(value))
            return value

        executor._run_host = observe
        if args.mutate_input:
            original_request = host_process_transport._host_process_request

            def lose_input(*positional, **keywords):
                value = original_request(*positional, **keywords)
                value['input'] = '{}'
                observations['mutation_calls'] += 1
                return value

            host_process_transport._host_process_request = lose_input

    def pytest_runtest_logreport(self, report):
        if report.when == 'call' or report.failed:
            observations['test_reports'].append({'nodeid':report.nodeid,'phase':report.when,'outcome':report.outcome})


code = pytest.main(args.pytest_args, plugins=[Observer()])
observations['pytest_exit_code'] = int(code)
observations['normalization'] = 'Only actual sha256 Turn keys are replaced; all other Host result fields are retained.'
data = json.dumps(observations,ensure_ascii=False,sort_keys=True,separators=(',',':')).encode()
args.observation_file.write_bytes(data+b'\n')
print(json.dumps({'pytest_exit_code':int(code),'mutation_calls':observations['mutation_calls'],'host_observation_count':len(observations['host_results']),'observation_sha256':hashlib.sha256(data).hexdigest()}),flush=True)
raise SystemExit(code)

English verdict: APPROVE — The extraction preserves the inspected wire and lifecycle semantics through real baseline/head CLI and Host paths, with a sensitive independent oracle; full release qualification remains separate.

@loopx-agent
loopx-agent merged commit 938efb1 into main Nov 1, 2026
16 of 36 checks passed
@loopx-agent
loopx-agent deleted the codex/release220-host-request-20261101 branch November 1, 2026 09:19
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