Rename UnionCoversRoster/RosterCoversUnion to UnionSubsetOfRoster/RosterSubsetOfUnion so
the names read in the same direction as their extends clauses, share the python3 -I -B
flags between the two mirror probes, and align the README and Agent Note prose with the
checkDoneValue JSDoc: the escaped-size scan is the metering itself, not deferred work.
Regenerate docs/module-graph.md, which listed code-runtime-python twice.
Address the latest review round:
- WireFrameShapesCoverUnions checked only union ⊆ roster, so removing a frame
from a message union (e.g. dropping ReplyErr from ReplyMessage) left the check
true while the public TS union diverged from the wire. Replace it with a
bidirectional equivalence between MessageFrames and the roster's message-frame
value types (nested Namespace/ErrorClass/DoneErrorField excluded): both a frame
added to a union without a roster entry and a frame removed from a union now
fail typecheck (both verified).
- The mirror e2e's python3 probes imported protocol.py without -B, writing
py/__pycache__/*.pyc into the (un-ignored) source tree. Add -B to both.
- Refresh the metering prose (checkDoneValue JSDoc + README both sides + Agent
Note both sides): the incremental-work list no longer says "per-key
JSON.stringify" now that jsonStringBytesUpTo scans without stringifying;
re-record the README and Agent Note i18n pairings.
Address the remaining review findings on the wire-mirror layer:
- Export PROTOCOL_FD from protocol.ts as the TS-side source of truth the host
wires, and assert the Python constant against it in the mirror e2e instead of
a bare literal 3, so an fd drift on either side is caught.
- Correct the FrameFieldRoles JSDoc to point at the actual assertion site
(WIRE_FRAME_FIELD_ROLES's satisfies clause, not WIRE_FRAME_FIELDS).
- Drop the redundant explicit type annotation on WIRE_FRAME_FIELDS (the trailing
`as` cast already types it; Object.fromEntries returns an index signature).
- Refresh the mirror-test comment to describe the roles-map binding (a TS-side
add/remove/rename/optionality-flip fails typecheck; a Python-side change fails
the comparison).
The previous mirror binding (FrameFields<keyof T>) only checked membership: it
could not see a TS-side optionality flip (truncated? -> truncated leaves keyof
unchanged) or a field added on one side, so the "depends on the TS
declaration" claim was overstated.
- Promote the inline frame shapes (Namespace, ErrorClass, DoneErrorField,
RunMessage, and the two Reply variants) to named interfaces so every frame
binds uniformly.
- Derive FrameFields from RequiredKeys<T>/OptionalKeys<T>, so `required` and
`optional` each accept only that side's keys. An optionality flip or a rename
now fails typecheck (verified: flipping LogMessage.truncated to required
errors at the constant).
- Enumerate EVERY public TypedDict in py/protocol.py in the mirror e2e (not a
name list taken from the TS side) and assert both the frame roster and each
frame's required/optional sets by exact equality, so a frame or field present
on only one side of the wire fails the test.
Two gaps from the previous round's fixes:
- checkDoneValue flagged a non-lossless number but skipped counting its encoded
bytes, so a value over budget ONLY through that number classified as
non-lossless instead of over-budget (e.g. [Infinity] at cap 3, whose encoding
is 10 bytes). Count the scalar's bytes even when flagging, so the budget check
wins as the JSDoc promises. Add cap-3 regression cases.
- The mirror e2e compared the Python TypedDict keys against a hand-written
constant, so a field change on the TS side alone would not fail it, and the
reply frames were not probed at all. Introduce WIRE_FRAME_FIELDS in
protocol.ts, bound to each frame interface's key set via `satisfies` (a
renamed/removed field breaks typecheck — verified), and drive the mirror test
from it, now covering ReplyOk/ReplyErr too. The test therefore fails on
one-sided drift from either language.
Address the two standing review suggestions in this layer rather than deferring
them to PR #4:
- Extend tests/protocol-mirror.e2e.ts to read each py/protocol.py TypedDict's
required/optional key set and assert it against the wire field names
src/protocol.ts declares (global included, via functional TypedDict). The
round-12 class of drift — a renamed/dropped field, or one side making a field
optional the other requires — now fails a test instead of relying on review.
Field types remain review-guarded (no mechanical TS/Python equivalent).
- Drop the forward references to PR #4's internal mechanisms from this layer's
prose: the "256 MiB frame ceiling" figure and the "(index.ts)" fd-3 pinning
citation become an abstract "host-side inbound frame-size cap" so the JSDoc,
spec, README, and Agent Note describe only what this layer owns.
Update both README sides and the Agent Note (both languages) to state the
mirror is now executable, and re-record their i18n pairings.
- Cover the log-frame `truncated` rebuild branch: assert a literal-true flag
rides along and any other value (1, string, false) is dropped, closing the
protocol.ts branch the coverage gate flagged.
- Correct encodeJsonPlain's JSDoc: it matches compact JSON.stringify EXCEPT on
a beyond-safe-range integral double, where it emits the exact BigInt digits
(`...846976`) rather than the rounded `...847000` — the divergence the
"emits exact digits" test pins.
- Declare py/protocol.py's `global`-bearing frames (Namespace, CallMessage)
with functional TypedDict syntax so they carry the real wire key instead of
a `global_` attribute the wire never sends, and split optional-field messages
(Namespace/LogMessage/DoneMessage) into a required base plus a total=False
subclass so `type` and other required fields cannot be dropped. Widen
HostToChild to include the boot and run frames the host sends before replies.
- Reword the mirror e2e's py/ directory assertion to describe the source-tree
layout it actually checks.
Introduce @deepseek-ai/dsh-code-runtime-python with the versionless
JSON-lines protocol between the Node host and the CPython subprocess:
the host-side hostile-frame codec (validateChildFrame, encodeJsonPlain,
checkDoneValue, hasUnsafeIntegerToken, hasNonLosslessNumber,
logTruncationMarker) and the Python-side wire-vocabulary mirror
(py/protocol.py).
This is the protocol layer of the code-runtime-python stack, split from
#436 and based on the multi-language seam extension. The PythonCodeRuntime
implementation and its Python JSON codec land in the backend-core PR on
top of this branch.
Ship the minimal buildable package skeleton (package.json, tsconfig,
tsdown, barrel index, invariant companion, bilingual README) because the
workspace-constraint, coverage, and invariant-topology gates require the
package to exist and build the moment its directory does; the backend-core
PR extends those files rather than creating them.
Align py/protocol.py with src/protocol.ts (the round-12 review of #436
found LogMessage.truncated, DoneMessage.error.kind, and Namespace.errorClass
stale) and guard the two runtime-executed surfaces (PROTOCOL_FD and the log
truncation marker) with a real-python3 cross-language mirror e2e test.