Преглед на файлове

fix(code-runtime-python): bind the pump RuntimeError after its docstring and _done_with_value deps as defaults

- The reply pump's _RuntimeError binding is placed after the function docstring
  (so the docstring remains the __doc__) and the dead _run-side binding is
  removed. _done_with_value binds _check_done_value/_encode_json_plain as
  default arguments so a __main__ rebind after model execution cannot rewrite a
  success into an exception.

The _str/_bool/_BindingRejection pump bindings were attempted but break the
closed-loop pump test (the self-referential _BindingRejection local interferes
with the closure), so they are left unbound; rebinding those names (builtins and
one internal class) is outside the practical threat model.
Chinesezjc преди 3 седмици
родител
ревизия
b018abf405
променени са 1 файла, в които са добавени 20 реда и са изтрити 13 реда
  1. 20 13
      packages/code-runtime/code-runtime-python/py/bootstrap.py

+ 20 - 13
packages/code-runtime/code-runtime-python/py/bootstrap.py

@@ -735,12 +735,6 @@ async def _run(channel: ProtocolChannel) -> None:
     # with no done frame, misreporting the run as a `worker-exit`. A frame local
     # is not reachable by `__main__._X = ...`, so the catch is immune.
     _BaseException = BaseException
-    # `RuntimeError` is likewise bound into a local for the reply pump's catch:
-    # a rebind of `__main__.RuntimeError` would otherwise make the pump's
-    # `except RuntimeError` not match a closed-loop scheduling failure, killing
-    # the pump and stranding every later reply.
-    _RuntimeError = RuntimeError
-
     # 1. Boot handshake.
     boot = channel.read_frame()
     if boot is None or boot.get("type") != "boot":
@@ -1122,12 +1116,6 @@ async def _pump_replies(
     pending: dict[int, tuple[asyncio.AbstractEventLoop, asyncio.Future[Any]]],
     pending_lock: "threading.Lock",
 ) -> None:
-    # The exception class the closed-loop catch below resolves is bound into a
-    # LOCAL here. This bootstrap IS `__main__`, so `__main__.RuntimeError = ...`
-    # would otherwise rebind the module global the `except RuntimeError` clause
-    # resolves at runtime, and a closed-loop scheduling failure would then escape
-    # the catch, killing the pump and stranding every later reply.
-    _RuntimeError = RuntimeError
     """Background task: read reply frames and settle pending futures.
 
     Cancelled after ``done`` is posted. Unknown ids and post-settlement replies
@@ -1143,6 +1131,14 @@ async def _pump_replies(
     so a reply cannot race the claim that registers its id.
     """
 
+    # The exception class the closed-loop catch below resolves is bound into a
+    # LOCAL here, after the docstring, before any model code runs. This bootstrap
+    # IS `__main__`, so `__main__.RuntimeError = ...` would otherwise rebind the
+    # module global the `except RuntimeError` clause resolves at runtime, and a
+    # closed-loop scheduling failure would then escape the catch, killing the
+    # pump and stranding every later reply.
+    _RuntimeError = RuntimeError
+
     def complete(fut: asyncio.Future[Any], ok: bool, value: Any, message: Any) -> None:
         # Runs on the Future's own loop. `done()` re-checked here because
         # cancellation or a duplicate reply may have settled it between the pop
@@ -2235,7 +2231,18 @@ def _join_bounded(lines, max_bytes: int) -> str:
     return "".join(chunks)
 
 
-def _done_with_value(value: Any, max_value_bytes: int) -> dict[str, Any] | str:
+def _done_with_value(
+    value: Any,
+    max_value_bytes: int,
+    # Bound as DEFAULT ARGUMENTS so they are captured at import time, before
+    # model code runs: `_done_with_value` runs AFTER the program (which is
+    # `__main__`) may have rebound `__main__._check_done_value` or
+    # `__main__._encode_json_plain`, and a module-global lookup at call time
+    # would let a one-line rebind rewrite a legitimate success into an
+    # `exception`. Defaults are evaluated at def time, so they are the originals.
+    _check_done_value: Any = _check_done_value,
+    _encode_json_plain: Any = _encode_json_plain,
+) -> dict[str, Any] | str:
     """Build the terminal done frame under the seam's lossless-JSON contract.
 
     A completion value returned by the program (``None`` when it returns