Commit Graph
165 Commits
Author SHA1 Message Date
Chinesezjc e7ac747e2d docs(code-runtime-python): register the zero-billed empty open-frame hold as a known limitation
The review's suggestion: an empty open continuation frame bills zero and holds
one host slot, so a forged empty-open flood grows the held fragment array
without touching logBudget. Accepted as a residual (per-frame host cost far
below its ~30-byte fd-3 wire cost, bounded by pipe throughput, model-code trust
level equal to bash) and now registered in the README's Known Limitations on
both sides, paired and re-recorded.
2026-08-31 15:04:43 +08:00
Chinesezjc 909d5c334b docs(code-runtime-python): restate the buffered-chunks pre-check comment as invariant-preserving
The review's revision: the buffered-chunks pre-check's open-aware overhead is
observationally inert — when the +3 form trips and the open-aware form does not
(pending + newline in [remaining - 2, remaining]), _push_bounded_prefix
re-slices the same newline-free line text and _push_locked admits it under the
same open-aware billing, byte for byte. The comment now states that the
open-aware form keeps _push_bounded_prefix's 'certain to reject' precondition
true, contrasting with the scan pre-check whose slice carries the newline and
therefore genuinely truncates.
2026-08-31 15:04:43 +08:00
Chinesezjc fdcfec4977 docs(code-runtime-python): align the en wire-contract section with the open flag; fix the case comment
The review's warning: the en README's inner 'Wire contract' section still
described only the truncated flag while the zh counterpart (and the outer 'The
wire' section) described open. The inner en section now matches. The exact-fit
closing-line case comment described the buffered-chunks pre-check recipe while
the program actually drives the scan pre-check; the comment now states the
actual arithmetic and path (and the buffered variant was dropped — its writes
coalesce into one call in the test environment, so it did not discriminate).
2026-08-31 15:04:43 +08:00
Chinesezjc 9194dfebc8 fix(code-runtime-python): make the write-path pre-checks open-aware; document the open flag in zh
The review's warning: while an open entry accumulates, the newline pre-checks in
the write path still charged a NEW entry's +3 cheap-bound overhead (quotes +
separator), so an exact-fit merged TAIL was truncated (or the pre-check
over-rejected it and flushed a truncated prefix). Both pre-checks now charge
the overhead only when no open entry is in progress, matching _push_locked's
open-aware bound. A regression case (the review's recipe: flush an open
fragment, then write one exact-fit newline-terminated line) is verified to
truncate when the +3 is restored.

The zh README's wire-contract section now describes the open flag like the en
side (the fd-3 Agent Note holds the split-billing arithmetic; a cross-doc link
was omitted to keep the bilingual link sequence aligned).
2026-08-31 15:04:43 +08:00
Chinesezjc 371303822f test(code-runtime-python): give the first-fragment cap a discriminating case; dedupe the note
The review's warning: the one-byte overflow case ran through the CHILD ledger
(print path), so the host's first-fragment cap (logBudget - 1) never executed,
and the sub-2-byte guard test does not discriminate logBudget from
logBudget - 1 (a reverted cap still trips the guard). The frame is now forged
on fd 3, so a reverted cap of logBudget admits it and flushes it at settlement
— verified to turn the test red.

The review's dedupe suggestion: the split-billing arithmetic was stated in both
notes; the settlement note's Decision paragraph now links to the fd-3 protocol
note's wire-contract section (one home per fact), paired and re-recorded.
2026-08-31 15:04:43 +08:00
Chinesezjc 22e2dc454d test(code-runtime-python): cover the sub-2-byte guard of the exact-cost walk
The new jsonStringCostUpTo guard (returns undefined below a 2-byte cap) was
uncovered: forged open frames drive the host ledger down to one byte, and a new
open entry's first-fragment cap (logBudget - 1 = 0) trips the guard and
truncates to the marker, asserted as the merged entry plus the marker.
2026-08-31 15:03:21 +08:00
Chinesezjc 3001cc23be fix(code-runtime-python): correct the open-merge cap arithmetic on both sides
The review's arithmetic checks: the closing-frame walk used cap
logBudget - openCost, so a compliant merged entry (58-byte wire cost under a
64-byte budget) could see a negative cap and truncate; the first-fragment cap
used logBudget instead of the ledger's logBudget - 1, so an open frame costing
63 was admitted with a bill of 64, pushing the ledger negative and letting a
subsequent empty frame ride in one byte past the configured cap; and the child
billed a closing frame as a fresh entry (quotes+separator again) instead of the
merged tail, truncating an exact-fit 30+30 entry.

Fixes: first-fragment cap logBudget - 1 (matching admit), continuation and
closing-frame cap logBudget + 2 (billed without quotes), jsonStringCostUpTo
returns undefined below 2 bytes, and the child's split billing keys off
_open_started alone (a closing frame pays content only) with the cheaper bound
len(text) while a merge is open. Regression cases cover all three arithmetic
paths.
2026-08-31 15:03:21 +08:00
Chinesezjc 4fd0068fb7 fix(code-runtime-python): bill a merged open entry incrementally on both sides
The review's critical: the open-merge branch re-joined and re-walked the whole
held text per frame, so k tiny open frames cost O(k * budget) (thousands of
1-byte frames against a near-64 MiB budget would re-traverse hundreds of GB and
block the host event loop). The host now holds a fragment ARRAY with an
incrementally billed cost — each fragment's jsonStringCostUpTo walks only its
own text — and the closing frame bills only its own content, so the merged
entry's wire cost is charged exactly once, split across the fragments. The
child bills symmetrically: the first open fragment pays quotes+separator, each
continuation pays only its content, matching the host ledger (the review's
warning: per-fragment full billing truncated a 16-char merged entry under
maxLogBytes: 64 that costs only 19 bytes as one entry).

Regression cases: 16 single-character flushes merge to one whole entry; a
closing frame that overflows the remaining budget truncates to the marker; a
closing frame after an open flood already truncated the ledger is a no-op; and
a forged open-frame flood stays bounded by the ledger. The closing-frame
post-truncation guard is an invariant-false branch (an open frame that would
trip the ledger resets openParts, so a non-empty hold implies no truncation)
and carries a v8 ignore with that reason.
2026-08-31 15:03:20 +08:00
Chinesezjc ea1d28a068 fix(code-runtime-python): bound the open-merge hold by the ledger budget
The review's critical: the open-merge branch accumulated the held fragment
before any ledger check, so a forged open flood could grow host memory without
touching logBudget. The held fragment is now bounded by the exact-cost walk
(jsonStringCostUpTo against the remaining budget; the closing frame's admit()
still bills the merged entry once), and the open field is registered in the
README wire-contract section and the fd-3 protocol note (en + zh). A forged
open-flood case asserts truncation to the marker under a 64-byte budget.
2026-08-31 15:03:20 +08:00
Chinesezjc 72691455e9 fix(code-runtime-python): merge a flushed unterminated line into the next log entry
The review's remaining warning: an explicit flush of an unterminated line
(print(..., end='', flush=True)) pushed a full log frame, so the following
print() landed in a second entry and logs.join('\n') rendered 'a\nb' for what
the program printed as one line — a model-visible output defect. The flush
frame now carries an  flag (LogMessage gains the optional field on both
sides and in the mirror test), the host holds it and appends the next log frame
to the same entry, and finish() admits the residual if the run ends with it
still open. The settlement note registers the decimal-context fix from the
previous commit.
2026-08-31 15:03:20 +08:00
Chinesezjc 35de0682c7 fix(code-runtime-python): make the float encoder context-independent; correct the binding-reply README entry
The review's critical: Decimal(repr(value)).normalize() read the process-global
decimal context, so a legitimate program setting getcontext().prec = 2 silently
rounded the completion value's digits and traps[Inexact] = True made the encode
raise, misclassifying a successful run as an exception. A fixed module-level
Context(prec=28) makes the spelling decision context-independent; a regression
case mutates both context knobs and asserts the float round-trips exactly.

The binding-reply README entry now states the fact (no seam-level cap;
maxValueBytes meters only the done frame; a wide reply is rebuilt and encoded
whole, bounded by process memory), matching the earlier reviewer wording.
2026-08-31 15:02:38 +08:00
Chinesezjc 666ff2855e docs(code-runtime-python): drop the placebo dispose test and finish the remaining doc drift
The review showed the added dispose case was a placebo (dispose in the same
tick as run means SIGTERM hits the group before the program body runs; the
group-emptied arm is already deterministically covered by the same-group
survivor case, which this removes the v8 ignore for). The test is deleted; the
stale silently-discards comment in the boundary test now says rejects; the
README Known Limitations gains the late-log-frame-drop and host-side
binding-value-memory entries. Pairing re-recorded.
2026-08-31 15:02:38 +08:00
Chinesezjc bc08f405cc test(code-runtime-python): pin the reap-poll group-emptied arm with a dispose-timing case
The review's premise that the group-emptied arm could not be pinned was
incorrect; the same-group reap case already exercises it. This adds the missing
seam-observable case: dispose() while a setsid orphan holds the pipes and the
run is unresolved — settle kills the child, the group empties (the orphan is in
its own session), and the poll finalizes promptly instead of waiting out the
60 s grace. The v8 ignore on that arm is removed.
2026-08-31 15:02:38 +08:00
Chinesezjc 9b7b5489be fix(code-runtime-python): cover the poll-group arm and align the last frame-cap comments
The review's remaining coverage gap: the group-emptied arm of pollGroup depends
on the close-driven settle winning the race against the grace SIGKILL, a timing
interleaving no seam-observable test pins deterministically (the same-group
cases assert the settle and the reap, not this exact interleaving) — the arm
now carries a v8 ignore with that reason. The load-check comment and the
FRAME_ENVELOPE_BYTES JSDoc say rejects-as-worker-exit instead of drops.
2026-08-31 15:02:38 +08:00
Chinesezjc d98965fbcc docs(code-runtime-python): remove the ack-gate v8 ignore and align the remaining doc drift
The forged-second-boot-ack regression makes the re-entry guard covered, so its
v8 ignore is removed. Doc drift: the python README and run() JSDoc state the
resolve-with-value/resolve-with-error contract without inversion; the README
Known Limitations gains the setsid-escaped-orphan entry (the settlement note
referenced it); the settlement note drops the stale drops/discard phrasing and
the two 256 MiB references; the fd-3 protocol zh note no longer claims the
codec is undelivered; the code-runtime seam README (en + zh) says both
backends ship. Pairings re-recorded.
2026-08-31 15:02:38 +08:00
Chinesezjc 3e0055edaf test(code-runtime-python): cover the boot-ack gate's re-entry guard and run-write failure
The review rejected the v8-ignore defense for the ack gate: a forged second
boot-ack is deterministically constructible (one os.write on fd 3) and the
run-write failure is deterministically constructible with the boot-write-failure
mock pattern. A program that forges an extra boot-ack asserts the run still
completes once (the gate does not re-send the run frame); a mocked child whose
fd-3 pipe accepts the boot frame but rejects the run write resolves a
worker-exit.
2026-08-31 15:00:33 +08:00
Chinesezjc cb26dd3804 test(code-runtime-python): pin the directory-skip in pythonBin resolution; cover the ack gate defenses
The resolvePythonBin directory branch now has a regression: a PATH whose first
entry is an executable DIRECTORY named python3 is skipped for a later real
interpreter (fail-before: without the isFile guard the directory would be
chosen and spawn would fail). The boot-ack gate's forged-second-ack re-entry
guard and its write-failure branch are covered by v8 ignore comments (the
honest child sends exactly one ack; the write failure needs the child to exit
between ack and write).
2026-08-31 15:00:32 +08:00
Chinesezjc 2b3b7f87dc fix(code-runtime-python): send the run frame after boot-ack; reject directories in pythonBin resolution
The review's two behavior items: the run frame was written back-to-back with
the boot frame (the seam contract puts run after boot-ack, which confirms the
namespaces were accepted); it now goes out from the boot-ack handler, so a
boot failure cannot race the run frame. resolvePythonBin now requires the
candidate to be a regular file — a directory passes X_OK and would otherwise
shadow a later real interpreter. Doc spots: the load-time overflow message
says worker-exit (not stranding to the wall clock), the run JSDoc spells out
the resolve-with-error contract, the PATH-stub test removes the stale v8
ignore, and the README's binding-value bullet names serialization cost.
2026-08-31 14:59:01 +08:00
Chinesezjc 981ade7611 fix(code-runtime-python): restore the runtime's cross-package dependency declarations
The earlier merge had adopted master's protocol-only package.json (peer/dev
limited to invariants and cordis, no dependencies), but src/index.ts imports
@deepseek-ai/dsh-code-runtime, dsh-session, dsh-timeout, and schemastery at
runtime — a published lib/index.js could not resolve those bare specifiers.
The manifest now mirrors code-runtime-worker-thread (the five peers, the
schemastery dependency, and the matching dev set); the lockfile, module graph,
and third-party notices are regenerated, and the module-graph zh pair is
re-synced.
2026-08-31 14:59:01 +08:00
Chinesezjc 65a4d64786 chore(code-runtime-python): adopt master's package.json peer dependencies
The earlier merge had kept the branch's older package.json while taking
master's lockfile, so a frozen install failed on mismatched specifiers for the
code-runtime-python package (master added dsh-code-runtime, dsh-session, and
dsh-timeout peers). The branch changes no dependencies, so it adopts master's
manifest verbatim.
2026-08-31 14:56:11 +08:00
Chinesezjc 6e8cc96351 docs(code-runtime-python): fix the doc-standard registry and README kind
The audited library registry still listed dsh-code-runtime-python as a plain
protocol library, but the shipped package's src/index.ts has a plugin default
export; the entry is removed from PACKAGE_LIBRARIES and both READMEs declare
kind: package-reference. The zh README heading is 概述 per the standard.
2026-08-31 14:56:11 +08:00
Chinesezjc af72ad4409 docs(code-runtime-python): rewrite the README to the repo documentation standard
The merge pulled master's README rewrite (front-matter, Summary, TOC, section
anchors, details-folding); its content described the pre-delivery protocol-only
package, contradicting the shipped backend. The README (en + zh) now follows
that structure with the delivered facts: PythonCodeRuntime, the fd-3 wire, the
load-validated caps, the 64 MiB frame parse cap (worker-exit settlement), and
the known limitations. Pairing re-recorded.
2026-08-31 14:56:11 +08:00
Chinesezjc 7fbf370d87 docs(code-runtime-python): drop the orphan receive-cap JSDoc and correct the frame comments
The review's three stale-comment items in index.ts: the orphan JSDoc above
FRAME_PARSE_CAP_BYTES (left over from the deleted receive ceiling), the
pre-join comment's change narration and its reference to a no-longer-existing
higher ceiling, and the first-frame comment's mention of a per-line cap check
that no longer exists. Test comments for the pythonBin and sealing-threshold
cases are weakened to their observable claims (both orders reject an over-cap
frame; the pythonBin case pins the contract, not a worker-exit distinction).
2026-08-31 14:55:02 +08:00
Chinesezjc 4903f7da1f docs(code-runtime-python): align stale frame-ceiling prose with the 64 MiB parse cap; pin pythonBin resolution
The review's doc drift items: the orphan receive-ceiling JSDoc, the frame-ceiling
references in index.ts/bootstrap.py/tests, and the README's 'dropped, stranding
to the wall clock' phrasing (the run now settles as a worker-exit) are all
updated to the 64 MiB FRAME_PARSE_CAP_BYTES semantics; the README notes the
>64 MiB binding-argument residual as a worker-exit trip of the same cap. A
regression case resolves a basename pythonBin against a PATH whose first entry
is relative ('.') and asserts the absolute entry is used.
2026-08-31 14:54:18 +08:00
Chinesezjc d62b63d529 fix(code-runtime-python): skip relative PATH entries in pythonBin resolution; pin the sealing-threshold rejection
The review's remaining code items:
- resolvePythonBin now skips RELATIVE PATH segments (a bare 'bin' or '.'): the
  returned candidate must be absolute, because spawn() resolves a relative
  pythonBin against the host CWD, outside the seam contract.
- A deterministic-ish regression pins the sealing-threshold corner: 64 MiB of
  4 KiB (<= PIPE_BUF, atomic) newline-free writes plus 12289 more A's before
  the first newline make the first frame exceed FRAME_PARSE_CAP_BYTES; the
  newline-bearing chunk reaches the first-frame check (sealing is the ELSE
  half of the newline branch), so the run reports worker-exit with the
  protocol-frame-exceeded message.
2026-08-31 14:53:33 +08:00
Chinesezjc a715bfd111 fix(code-runtime-python): seal only newline-free runs so the first-frame check cannot be skipped
The review's sealing corner: the fragment-count seal ran before the newline
branch and did not exclude a newline-bearing chunk, so the 1024th chunk (the
first to carry a newline) was concatenated into a sealed block, pendingChunks
was emptied, sawNewline stayed false, and the first-frame check was skipped for
a join that then contained the newline. Sealing now runs as the ELSE half of
the newline branch, so a newline-bearing chunk always reaches the join and its
first-frame check, and the invariant 'sealed blocks hold newline-free prefixes
only' is true — which is what makes the removed per-line check genuinely dead.
2026-08-31 14:53:33 +08:00
Chinesezjc 4c3e453080 fix(code-runtime-python): drop the now-dead per-line cap check again
The pre-join counter (single unframed line) and the first-frame check
(newline-bearing chunk) reject any frame past FRAME_PARSE_CAP_BYTES before the
join, so every line reaching this loop is within the cap by construction — the
per-line check was dead code and its continue branch could never fire, failing
the per-file 100% coverage gate.
2026-08-31 14:53:33 +08:00
Chinesezjc 295e020ea4 fix(code-runtime-python): reject only an oversized FIRST frame before the join, not a multi-frame buffer
The pre-join check charged the whole unframed buffer, which legitimately holds
several frames each within FRAME_PARSE_CAP_BYTES: a first frame of exactly the
cap followed by a second frame crossed the counter and was misreported as a
worker-exit. The pre-join rejection now fires only while the held bytes are a
single unframed line (this chunk carries no newline); once a newline arrives,
a FIRST-FRAME check measures the bytes up to the first newline across the held
chunks (including sealed blocks) and rejects only that frame before the join —
keeping the peak at one copy of its wire bytes — while later frames in the
same buffer are handled by the restored per-line check. Regression cases: a
72 MiB newline-free buffer is rejected pre-join (fail-before: joining would
have doubled it); two within-cap frames whose combined buffer crosses the cap
both survive (fail-before: the unconditional counter check turns it red).
2026-08-31 14:53:33 +08:00
Chinesezjc abf81a0905 fix(code-runtime-python): drop the now-dead per-line parse cap check
The unframed-buffer counter guard runs before every join and guarantees each
line is within FRAME_PARSE_CAP_BYTES, so the line-loop cap check was dead code
(its continue branch could never fire, failing the per-file 100% coverage gate
on index.ts). Removed with a comment explaining the invariant.
2026-08-31 14:53:33 +08:00
Chinesezjc 219d216c54 test(code-runtime-python): move the frame-overflow cases to the 64 MiB parse cap
The pendingBytes guard now trips at FRAME_PARSE_CAP_BYTES (64 MiB) instead of
the 256 MiB wire ceiling, so the three tests that flood/pin frames against the
guard assert the 67108864 message and write a 64 MiB-based workload.
2026-08-31 14:53:33 +08:00
Chinesezjc c8139589d2 fix(code-runtime-python): reject an oversized unframed frame before the join; cap the rejection diagnostic
The review's remaining critical: the fd-3 data handler checked the unframed
counter against the 256 MiB wire ceiling, so a single 64-256 MiB frame was
fully Buffer.concat-joined (a second copy) and only then dropped in the line
loop — the peak-memory doubling the pre-join check exists to prevent, for a
frame the parser is guaranteed to discard. The counter is now checked against
FRAME_PARSE_CAP_BYTES before the join; the regression case asserts a worker-exit
with 'protocol frame exceeded' (fail-before: reverting to the ceiling turns it
green, proving the join path). FRAME_CEILING_BYTES is removed.

The rejection-cap fix now has its regression: a completion value whose class
name is 70 MiB of Ns asserts invalid-output, not worker-exit (fail-before:
uncapping the diagnostic turns it red).

The settlement note (en + zh) updates the remaining stale bound text, and the
fd-3 protocol note (en + zh) no longer claims protocol-only exports or a
missing Python codec. Pairings re-recorded.
2026-08-31 14:53:33 +08:00
Chinesezjc 3f8b45f9bb fix(code-runtime-python): cap the done-frame rejection diagnostic and sync stale docs
The review's remaining items:
- _done_with_value's rejection branch now caps the _check_done_value diagnostic
  through _cap_message (a reason embedding a hostile class name could otherwise
  push the done frame past the host's 64 MiB parse cap, misreporting an
  invalid-output run as a worker-exit).
- The settlement note (en + zh) updates three stale facts (load bound is now
  parse-cap minus envelope at 67108800; the sink goes directly through the
  bound primitives); the fd-3 protocol note (en + zh) no longer claims the
  package ships protocol without the runtime; FRAME_ENVELOPE_BYTES' JSDoc and
  _cap_message's docstring follow the new bound.
Pairings re-recorded.
2026-08-31 14:52:57 +08:00
Chinesezjc d90155714b docs(code-runtime-python): correct the sink comment and register the frame parse cap
The review's remaining warning: the _run binding comment claimed the log sink
went 'through the bound send', contradicting the sink's actual direct use of the
bound encode+write primitives. The comment now states that; the settlement note
(en + zh) registers FRAME_PARSE_CAP_BYTES and the 65 MiB-frame regression case.
Pairing re-recorded.
2026-08-31 14:52:01 +08:00
Chinesezjc fca41b78ea fix(code-runtime-python): bound the load-time budget to the frame parser cap
The review found the 64 MiB parse cap contradicted the load-time budget bound:
maxLogBytes/maxValueBytes could be configured up to ceiling - envelope (~256 MiB),
but the receive path silently dropped any frame past the 64 MiB parser cap, so an
honest child's budget-internal done frame under such a config would be discarded
and the run stranded to the wall clock. The load bound is now parse-cap -
envelope, so a configured budget always fits through the parser; the boundary
test moves to 64 MiB - 64. The >64 MiB model-constructed binding-argument drop
is registered as an accepted residual in the README (en + zh).
2026-08-31 14:51:20 +08:00
Chinesezjc ab40136b02 fix(code-runtime-python): cap the raw frame length before JSON.parse and bound the log sink
Addresses the review's remaining two items:
- FRAME_PARSE_CAP_BYTES (64 MiB) drops an fd-3 frame whose raw length exceeds
  it BEFORE toString/JSON.parse: the 256 MiB wire ceiling bounds the bytes, not
  the decoded structure, and a compact wide frame near that ceiling could decode
  to far more host memory. A regression test writes a 65 MiB log frame plus a
  normal one and asserts the oversized frame is dropped while the trailing frame
  still lands in logs (fail-before: without the cap the oversized text is parsed
  and admitted, truncating the ledger so the trailing frame is dropped). The
  forged-oversized lower-bound test's frame is reduced to stay under the cap
  while still exercising the truncation path.
- The log sink writes through the def-time bound encode+write primitives (not
  send_sync, whose body resolves _encode_json_plain and self.write_encoded at
  call time), so a rebind cannot break a log frame.
2026-08-31 14:50:40 +08:00
Chinesezjc 125306324f fix(code-runtime-python): write dispatch frames through def-time bound primitives
The review's remaining functional item: send_sync's body resolves
_encode_json_plain (module global) and self.write_encoded (class attribute) at
call time, so a program rebinding either before the first binding call could
turn a legitimate call into an exception. dispatch now writes the call frame
through def-time bound write_encoded+_encode_json_plain, and the log sink goes
through the bound send; the dispatch rebind test also rebinds those two names
(verified fail-before by reverting to send_sync). The annotation test title
matches its assertion direction, and the note (en + zh) registers the
error-class constructor, dispatch primitives, and dont_inherit mechanisms.
Pairing re-recorded.
2026-08-31 14:50:39 +08:00
Chinesezjc c4c79f094d test(code-runtime-python): pin the error-class constructor and dispatch primitives with rebind cases
The review required regression cases for the two cfb35bef6 fixes:
- Rebinding __main__.Exception/__main__.setattr must not break the minted
  error class: a host rejection still surfaces as ToolCallError with the member
  property readable.
- Rebinding __main__._lossless_json_violation/__main__.asyncio/
  __main__.ProtocolChannel.send_sync must not break dispatch: a legitimate
  binding call still round-trips.
2026-08-31 14:49:54 +08:00
Chinesezjc 44205c4949 fix(code-runtime-python): stop the program's compile from inheriting the module's future annotations
bootstrap.py imports from __future__ import annotations; compile(wrapped) was
inheriting that PEP 563 flag, stringifying the program's type annotations and
changing the semantics of a legal program that reads f.__annotations__ at
runtime. compile(..., dont_inherit=True) stops the leak; a regression test
defines an annotated function and asserts the annotation is the live int class,
verified fail-before by removing dont_inherit (the test turns red).
2026-08-31 14:49:54 +08:00
Chinesezjc 6a659df999 fix(code-runtime-python): capture the error-class constructor and dispatch primitives
The review's remaining items:
- _make_error_class captures Exception and setattr as def-time defaults, so a
  rebind of __main__.Exception/__main__.setattr cannot break the rejection
  constructor.
- dispatch binds _lossless_json_violation, asyncio.get_event_loop, and the
  channel's send method into _run locals before the program runs, so a rebind
  cannot turn a legitimate binding call into an exception or a wall-clock
  timeout.
- The note (en + zh) corrects the stdin coverage phrasing: d3f9f57f5's direct
  EOF-observing case is the in-tree pin, not an approximation.
- Collapse two stray double blank lines in the test file.
Pairing re-recorded.
2026-08-31 14:49:54 +08:00
Chinesezjc c4d6c25ffc test(code-runtime-python): pin the pump reader against a class-attribute rebind; correct the staging comment
The review's three remaining items:
- A regression test rebinds __main__.ProtocolChannel.read_frame_async and asserts
  a binding reply still round-trips (the pump's reader is a bound method
  captured by _run before the program runs).
- The settlement note (en + zh) records that send_done's frame-shape check uses
  _run's bound _str/_isinstance.
- The staging-removal comment no longer claims teardown retries tracked state:
  teardown deliberately does not sweep staging, so a removal failure is the one
  case the gone-by-settlement contract degrades on.
Pairing re-recorded.
2026-08-31 14:49:15 +08:00
Chinesezjc aa685028a7 test(code-runtime-python): pin the stdin-close behavior with an EOF-observing case
The stdin destroy (child.stdin?.destroy() right after spawn) previously had no
in-tree coverage. A program that reads fd 0 now sees EOF immediately; without
the destroy it blocks and the run would hang to maxWallMs as a timeout —
verified fail-before by disabling the destroy (the test turns red at the wall
ceiling) and restoring it (green). The _str rebind regression was attempted but
is not viable: the success path's done-frame serialization reaches str
transitively through _encode_json_plain, which the README Known Limitations
already records as the accepted success-to-exception residual, so any rebind
test trips that documented residual before send_done's bound _str.
2026-08-31 14:47:57 +08:00
Chinesezjc 302bbb0f8f fix(code-runtime-python): close the remaining call-time lookup gaps in the reply and settlement paths
The review's completeness check found the def-time capture pattern was not yet
applied to every name the reply/settlement paths resolve at call time:
- _decode_json_plain now also captures isinstance/str/list.
- read_frame/read_frame_async capture len; read_frame_async captures
  asyncio.get_event_loop.
- send_done uses _run's bound _str/_isinstance for its frame-shape check.
- The reply pump's frame reader is a bound method captured by _run BEFORE the
  program runs and passed into _pump_replies, so a rebind of the class
  attribute cannot redirect it.
The decode-rebind regression test still pins the _decode_json_plain rebind;
rebinding builtins (len/isinstance/list/str) in a test is not viable because
the Python runtime itself resolves them implicitly.
2026-08-31 14:47:57 +08:00
Chinesezjc aa5e8fc345 fix(code-runtime-python): suppress the unnecessary-optional-chain lint for the stdin destroy
The boot-write-failure fake child carries no stdin at runtime, so the optional
call is the documented guard; the static type (ChildProcessWithoutNullStreams)
says stdin is non-null, which trips the no-unnecessary-condition lint.
2026-08-31 14:47:57 +08:00
Chinesezjc aecdec3f80 fix(code-runtime-python): guard the stdin destroy against a spawn-failure child
The boot-write-failure path's fake child carries no stdin handle, so the
unconditional destroy threw inside the spawn error handler and mislabeled the
worker-exit. Use the optional-call form; the no-stdin branch is exercised by
that same test.
2026-08-31 14:47:57 +08:00
Chinesezjc 40fbf92290 fix(code-runtime-python): close the child stdin handle and def-time capture the frame decode primitives
Addresses the review's two remaining items:
- The host closes the child's stdin write handle immediately after spawn. The
  program is an async body that reads nothing from fd 0; a live pipe would hold
  a host-side handle open past the run, so a setsid-escaped descendant
  inheriting fd 0 could keep the host process from exiting even after the
  closeDeadline forced settlement. The child (and any descendant) reads EOF on
  fd 0 and no host handle survives.
- read_frame/read_frame_async bind their decode primitives (_decode_json_plain,
  os.read, _READ_CHUNK_BYTES, bytes) as def-time default arguments, and
  _decode_json_plain itself captures json.loads, its two regexes, and len the
  same way, so a __main__ rebind cannot kill the reply pump and strand every
  pending Future to the wall clock. _decode_json_plain and its regexes moved
  before the ProtocolChannel class so the defaults resolve at class-definition
  time. A regression test rebinds _decode_json_plain and asserts a binding reply
  still round-trips.
Note (en + zh) registers both mechanisms; pairings re-recorded.
2026-08-31 14:47:57 +08:00
Chinesezjc ac64039843 fix(code-runtime-python): bind str for dispatch's rejection message conversion
The review's remaining non-blocking suggestion: dispatch's call_failure(str(exc))
resolved the builtin str at call time, so a program rebinding __main__.str could
run a hostile callable when the binding-rejection message is formatted. Bind
_str into _run locals and use it in dispatch.
2026-08-31 14:47:18 +08:00
Chinesezjc 937ada4837 fix(code-runtime-python): bind RuntimeError and _BindingRejection for dispatch's rejection path
dispatch's call_failure and its except clause resolved the module globals at
call time, so a program rebinding __main__._BindingRejection = ValueError let
the internal marker type leak into model code. Bind _RuntimeError_cls and
_BindingRejection_cls into _run locals before the program runs (names distinct
from the module globals so the assignment RHS resolves the global, not an
unbound local); dispatch now uses the locals. A regression test rebinds
_BindingRejection and asserts a host rejection still surfaces as RuntimeError.

The sys.__stdout__ flush test now reconfigures the streams back to block
buffering (write_through=False) so the settlement drain path is what the case
pins — verified fail-before: binding the stream objects instead of their flush
methods turns the test red.
2026-08-31 14:47:18 +08:00
Chinesezjc 1efb0094c8 fix(code-runtime-python): bind the original std streams' flush methods, not the stream objects
The settlement drain iterated the bound stream OBJECTS, which are not
callable — every _flush() raised TypeError and was swallowed by the loop's
except, so the drain never ran and only the -u flag carried the behavior.
Bind sys.__stdout__.flush/sys.__stderr__.flush (bound methods, capturing the
stream at binding time, immune to a later sys.__stdout__ rebind; None-guarded).
Verified by removing -u temporarily: the sys.__stdout__ regression test still
passes, so the drain is a genuine backstop, not a documented-but-dead layer.
2026-08-31 14:47:18 +08:00
Chinesezjc 43a0879ad1 fix(code-runtime-python): clear stray buffers on truncation and drain the original std streams
Addresses the review's two carried warnings and the comment suggestion:
- Once the ledger truncates, every arm that marks it (admit()'s two ceilings and
  the child-marker frame arm) now clears both stray pipes' buffered output
  wholesale, so the end-path flushStray sees empty buffers instead of
  concat+decoding doomed data near a 256 MiB maxLogBytes; captureStray's newline
  loop re-checks the flag before re-retaining the residual.
- The child runs with -u (unbuffered), so sys.__stdout__/sys.__stderr__ writes
  are visible to stray capture immediately; the settlement flush still drains
  the original std streams before the done frame as a guard. A regression test
  writes through sys.__stdout__/sys.__stderr__ without an explicit flush and
  asserts both bytes land in logs. C-ext stdio remains an accepted residual,
  recorded in the README Known Limitations (en + zh).
- The ledger-comment arithmetic now states the exact boundary (serializes to
  exactly maxLogBytes; without the reserved byte it would be maxLogBytes + 1)
  in both host and child.
Note (en + zh) registers the stray-clear and -u/settlement-drain mechanisms and
the new test; pairings re-recorded; corpus passes 1029.
2026-08-31 14:47:18 +08:00
Chinesezjc 4c7811812d docs(code-runtime-python): state the macOS killGroup behavior directly and complete the residual sentence
The review flagged the change-narrative wording 'degrades to the pre-existing
behavior' (prohibited by docs/AGENTS.md) in four spots — README en/zh, the
readProcessStart JSDoc, and the test comment — and the incomplete :77 residual
sentence ('can still' with no verb complement). Reword the four to a direct
statement of current behavior (killGroup signals the pgid without the identity
re-check on macOS), complete the residual sentence with the actual consequence,
and re-record both pairings. Corpus-wide verify-translation-pairing passes 1029.
2026-08-31 14:46:31 +08:00