fix(code-runtime-python): bind the _done_with_value entry name and correct the residual documentation

Addresses the review's registration-text accuracy findings:
- _run binds _done_with_value into a local (done_with_value_bound) before the
  program runs, closing the __main__._done_with_value = boom success-rewrite
  vector; a regression test rebinds it and returns a legitimate value, asserting
  the success survives.
- README (en + zh): the CPU-recheck bullet now states the recheck runs
  unconditionally after the program returns (a pre-return overrun dies there as
  a timeout) and the false-success window is only a trap-SIGXCPU program that
  passes the recheck and overruns during the settlement flush/encode; the
  encoder-deps residual rationale is replaced with the actual one (bash-equivalent
  trust, verdict still delivered via the send_done fallback frame) and names the
  now-bound entry; the t.join() deadlock bullet fixes the subject/object (the
  main coroutine joins the worker, blocking the pump's main event loop).
- The portable-identifier-seam architecture note no longer claims the Python
  backend does not exist.
- Settlement note (en + zh) registers the entry-name binding and the new test.
- All pairings re-recorded; corpus-wide verify-translation-pairing passes 1004.
This commit is contained in:
Chinesezjc
2026-08-31 14:40:35 +08:00
committed by Tianyi Cui
parent e6b23e829b
commit 96597c5ed8
11 changed files with 50 additions and 19 deletions
@@ -39,7 +39,7 @@ host 与 CPython 子进程在子进程的 fd 3 上交换一个无版本号的 JS
- **调用 `setsid()` `start_new_session=True` 的后代会逃出 teardown。** 终止是用 `kill(-pid)` 向子进程的进程组发信号;一个把自己移入新会话的后代已不在该进程组内,任何信号都到不了它。若它同时释放了继承而来的 stdoutstderrfd-3 管道,leader 的 `close` 仍会结算该次运行,在 `closeDeadline` 到界之后 fiber 变为完全停稳,而那个孤儿仍在运行。这是 containment 边界,而非安全边界——模型代码具有等同 bash 的信任级别,一个 bash 工具同样能 `setsid` 逃逸。要够到这样的孤儿需要追踪每一个后代 pid(如 bash-local 后端的 process-inspector 所做),此项已推迟;进程组 teardown 会回收所有留在组内的进程。
- **日志与完成值的叠加峰值未被加载门建模。** 每项预算都是各自对照 `addressSpaceMb` 检查的。模型的 daemon 线程可以在完成值被计量并分帧的窗口内持续写入、把日志 pending 重填到接近 `maxLogBytes`,于是两个峰值以任何门都不曾放行也不曾拒绝的方式相加。对 `(maxLogBytes + maxValueBytes)` 设门的方案经评估后推迟:它的判别用例无法在 `RLIMIT_AS` 之下确定性地构造出来,因此该门只能证明自己的算术。叠加峰值被触及时该次运行死为 `worker-exit`——containment 仍然成立,只是失败分类失真。
- **1 秒双限 `ulimit -t 1` 下的 CPU 超限会被报告为 `worker-exit`,而非超时。** 当宿主在一个硬 CPU 限制等于软限制(`ulimit -t N` 同时设置两者)且该限制为 1 的环境下启动时,`_clamped` 无法把软限制降到 0,因此内核在同一 tick 直接 SIGKILL 忙循环,SIGXCPU 永不送达。宿主只在 `signal === 'SIGXCPU'` 时把 CPU 超限分类为超时,因此该超限被报告为 `worker-exit`。当双限为 2 或更大时,软限制会被降低一个单位,SIGXCPU 触发,该次运行成为超时。两种情况 containment 都成立;只是分类被降级。
- **一个 trap SIGXCPU 的程序可以在结算编码期间超过软 CPU 限制并仍报告成功。** 结算时的 CPU 复查在完成值被 flush 与编码之前运行;一个 trap SIGXCPU(软限制)并在构建与编码窗口内继续燃烧 CPU 的程序会在 `die_if_cpu_exhausted` 复查之前返回结果,因此该次运行报告成功。containment 成立——硬限制(软限制 + 1s)与墙钟仍会约束它——只是分类被降级。CPU 复查不在编码中途运行,因为那必须计量编码本身,而编码正是预算已经约束的路径。
- **编码器的直接依赖在调用时解析。** `_encode_json_plain` 通过模块全局查找到达 `_dump_scalar`/`_dump_string`/`json`,因此以 `__main__` 运行的程序在返回合法值后重绑这些名字之一(例如 `__main__._dump_scalar = boom`)可以让编码抛出、把成功降级为 `exception`。值路径顶层的 `_check_done_value`/`_encode_json_plain` 被绑定为 def 期默认值,但其传递依赖没有;这是已接受的残余,原因与对应的 `_dump_*` 辅助函数在实践中不被重绑相同
- **一个 trap SIGXCPU 的程序可以在结算编码期间超过软 CPU 限制并仍报告成功。** 结算时的 CPU 复查`die_if_cpu_exhausted`)在程序返回后、日志 flush 与完成值编码之前无条件运行;一个在返回前已超过软限制的程序会在这里死于重投递的 SIGXCPU,被归类为超时。唯一的误报窗口是一个通过复查后、trap SIGXCPU 并在结算 flush/编码窗口内越过软限制的程序。不做编码后复查,是因为那会把结算编码自身消耗的 CPU 记到程序头上、误分类一个合法的近限程序。containment 成立——硬限制(软限制 + 1s)与墙钟仍会约束它——只是分类被降级。
- **编码器的直接依赖在调用时解析。** `_encode_json_plain` 通过模块全局查找到达 `_dump_scalar`/`_dump_string`/`json`,因此以 `__main__` 运行的程序在返回合法值后重绑这些名字之一(例如 `__main__._dump_scalar = boom`)可以让编码抛出、把成功降级为 `exception`。值路径的入口名(`_done_with_value`)被绑定进 `_run` 局部、其顶层的 `_check_done_value`/`_encode_json_plain` def 期默认值,但编码器的传递依赖(`_dump_scalar`/`_dump_string`/`json`)仍在调用时解析。这是已接受的残余:在 bash-equivalent 信任模型下,这里的重绑只会伤害模型自身的运行,且判决仍必达宿主——`send_done` 的固定兜底帧即使在错误路径编码/写入抛出时也能送达一帧 done
- **宽 binding 回复会按成员展开宿主侧状态。** 回复经由 [`@deepseek-ai/dsh-session`](../../core/session/README.zh.md) 的 `snapshotJsonValue` 穿越,其 `walkJsonValue` 为每个成员压入一个任务帧,而 binding 回复在 seam 层没有字节上限。因此一个数百万元素的合法回复可以耗尽宿主堆。该性质属于那个共享遍历,而不属于本后端——worker-thread 后端消费同一个函数——所以修复应落在 `packages/core/session`,让所有消费方一并受益。
- **程序用同步的 `t.join()` 连接一个跨线程 binding 会死锁。** 这是 `process` 隔离后端特有的:回复泵运行在子进程的主事件循环上,因此一个用 `t.join()` 阻塞主协程的 worker 线程会卡住泵投递该 binding 回复所需的循环,`await` 直到墙钟才会恢复。worker-thread 后端不共享此结构,所以修复应落在这里,而非 `packages/core/session`
- **程序用同步的 `t.join()` 连接一个跨线程 binding 会死锁。** 这是 `process` 隔离后端特有的:回复泵运行在子进程的主事件循环上,因此当程序的主协程对一个仍在等待 binding 回复的 worker 线程调用 `t.join()` 时,join 会阻塞承载泵的主线程事件循环——正是泵投递该回复所需的循环——该 worker 的 `await` 直到墙钟才会恢复。worker-thread 后端不共享此结构,所以修复应落在这里,而非 `packages/core/session`