feat(code-runtime-python): make the TypedDict wire mirror an executable gate

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.
This commit is contained in:
Chinesezjc
2026-08-07 13:27:54 +08:00
parent 146a9d9f61
commit 8a60b5f005
9 changed files with 76 additions and 29 deletions
@@ -6,13 +6,14 @@ import { describe, expect, it } from 'vitest'
import { logTruncationMarker } from '../src/protocol.ts'
/**
* Cross-language mirror check for the two protocol surfaces the host and the
* CPython subprocess share at runtime, spawning a real `python3` to read them
* from `py/protocol.py`. `src/protocol.ts` and `py/protocol.py` declare the same
* frame vocabulary on two sides of the wire; the only values both sides EXECUTE
* against are `PROTOCOL_FD` (the fd the channel is pinned to) and the log
* truncation marker text (emitted verbatim by whichever ledger exhausts first),
* so a drift there silently corrupts a live run. Self-skips when no `python3` is
* Cross-language mirror check between `src/protocol.ts` and `py/protocol.py`,
* spawning a real `python3` to read the Python side. Two things are asserted:
* the runtime surfaces both sides EXECUTE against — `PROTOCOL_FD` and the log
* truncation marker text, where a drift silently corrupts a live run — and the
* per-frame wire field sets (required/optional keys of each `TypedDict`), which
* turns the otherwise review-only shape mirror into an executable check that
* catches the round-12 kind of drift (a renamed/dropped field, or one side
* making a field optional the other requires). Self-skips when no `python3` is
* on PATH — CI provides one; the pure-TS `protocol.spec.ts` covers the host
* codec unconditionally.
*/
@@ -46,10 +47,53 @@ describe.skipIf(!python3Available)('protocol.py mirrors protocol.ts at runtime',
].join('\n')
const { stdout } = await execFileAsync('python3', ['-I', '-c', probe])
const seen = JSON.parse(stdout) as { fd: number; markers: string[] }
// fd 3 is the wire contract, not a tunable: index.ts pins it positionally.
// fd 3 is the wire contract, not a tunable: the host pins it positionally
// when it spawns the child.
expect(seen.fd).toBe(3)
expect(seen.markers).toEqual(budgets.map(budget => logTruncationMarker(budget)))
})
it('agrees on every frame type\'s wire field set between the TS and Python declarations', async () => {
// Turn the TypedDict mirror from a review-only obligation into an executable
// check: read each Python TypedDict's required/optional key sets and assert
// them against the wire field names the TS side declares. `global` is the
// reserved-keyword key the Python side carries via functional TypedDict —
// catching exactly the round-12 kind of drift (a renamed/dropped field, an
// optional field the other side made required).
const probe = [
'import json, sys',
`sys.path.insert(0, ${JSON.stringify(pyDir)})`,
'import protocol as p',
'def keys(td): return {"required": sorted(td.__required_keys__), "optional": sorted(td.__optional_keys__)}',
'print(json.dumps({',
' "BootMessage": keys(p.BootMessage),',
' "Namespace": keys(p.Namespace),',
' "RunMessage": keys(p.RunMessage),',
' "BootAckMessage": keys(p.BootAckMessage),',
' "CallMessage": keys(p.CallMessage),',
' "LogMessage": keys(p.LogMessage),',
' "DoneErrorField": keys(p.DoneErrorField),',
' "DoneMessage": keys(p.DoneMessage),',
' "ErrorClass": keys(p.ErrorClass),',
'}))',
].join('\n')
const { stdout } = await execFileAsync('python3', ['-I', '-c', probe])
const seen = JSON.parse(stdout) as Record<string, { required: string[]; optional: string[] }>
// The wire field sets each frame carries, mirroring src/protocol.ts. `global`
// is the JSON key `CallMessage`/`Namespace` send (a Python keyword, declared
// functionally on the Python side).
expect(seen).toEqual({
BootMessage: { required: ['addressSpaceBytes', 'cpuSeconds', 'maxLogBytes', 'maxValueBytes', 'namespaces', 'type'], optional: [] },
Namespace: { required: ['global', 'names'], optional: ['errorClass'] },
RunMessage: { required: ['program', 'type'], optional: [] },
BootAckMessage: { required: ['type'], optional: [] },
CallMessage: { required: ['args', 'global', 'id', 'name', 'type'], optional: [] },
LogMessage: { required: ['text', 'type'], optional: ['truncated'] },
DoneErrorField: { required: ['kind', 'message'], optional: [] },
DoneMessage: { required: ['type'], optional: ['error', 'value'] },
ErrorClass: { required: ['memberNameProperty', 'name'], optional: [] },
})
})
})
it('names the py/ directory that ships with the package', () => {
@@ -135,8 +135,8 @@ describe('lossless-number scan', () => {
it('walks wide arrays and objects one member at a time', () => {
// `call.args` carries no seam byte cap, so a wide forged payload has no
// budget to be rejected against — the walk must hold one cursor per
// NESTING LEVEL, not one entry per member, or a flat payload just below
// the 256 MiB frame ceiling would allocate tens of millions of stack
// NESTING LEVEL, not one entry per member, or a flat payload at the top of
// the host's inbound frame-size cap would allocate tens of millions of stack
// entries (and `Object.values` a second full-breadth copy). Observable
// through the boundary: a wide payload whose per-member cost the old shape
// would have paid still scans, and a violation ANYWHERE in it is found