refactor(ptc): return ordered text and image output
This commit is contained in:
1 parent
4f6ccd772d
commit
a2ea3970fd
72 files changed
+415
-430
No files matched your search
@@ -54,8 +54,8 @@
|
||||
en: 40294c1c31b74824
|
||||
zh: d805c6bd7270505e
|
||||
/deepseek-ai-dsh-experimental-ptc-runtime-python/known-limitations-and-deferred-work:
|
||||
en: 5d62ed07fefbefc4
|
||||
zh: 2a03d9bc9265a200
|
||||
en: 6df63c3fc7f9357d
|
||||
zh: 8f1730b8b33cb7e9
|
||||
/deepseek-ai-dsh-experimental-ptc-runtime-python/known-limitations-and-deferred-work/dev-note:
|
||||
en: e67f633dc6fe7773
|
||||
zh: 3bb0c20c93f16f98
|
||||
@@ -133,12 +133,12 @@ These limits define what the package does and does not cover; they are current p
|
||||
- **A binding REPLY value has no seam-level byte or depth cap** — `maxValueBytes` meters only the done frame's completion value; a wide binding reply is rebuilt host-side (`snapshotJsonValue` traversal) and encoded whole, bounded on both sides only by process memory (like a binding argument, which has no child-side budget either).
|
||||
- **No shipped profile mounts this provider** — the keyless `ptc-python-turn` snapshot replaces the headless PTC runtime through the real Loader; released profiles use the sandboxed Node process backend.
|
||||
- **Workflow execution requires Node** — compositions using this Python provider disable `workflow-ptc`, `tool-workflow`, and `tool-ralph`; the workflow provider rejects an incompatible runtime when loaded.
|
||||
- **Cross-channel log interleaving is backend-dependent** — Python stdout, stderr, and fd-3 log frames travel independently; each channel preserves its own order, while their total order in `result.logs` may differ.
|
||||
- **Cross-channel log interleaving is backend-dependent** — Python stdout, stderr, and fd-3 log frames travel independently; each channel preserves its own order, while their total order in `result.output` may differ.
|
||||
- **CPython 3.10 or newer is required** — the configured executable is resolved and version-probed at load; unsupported interpreters fail before `ctx.ptcRuntime` is registered.
|
||||
- **Execution is local and Unix-only** — the interpreter, working directory, and sandbox runner must belong to the host running this plugin. The provider uses POSIX resource limits and process-group signals; it does not support Windows or remote execution providers.
|
||||
- **Sandbox enforcement inherits backend limits** — a successful program can report partial enforcement, and file confinement does not establish multi-tenant isolation or complete descendant cleanup.
|
||||
- **Diagnostic prefixes omit the package's experimental qualifier** — the marker `[dsh-ptc-runtime-python] log capture truncated at <N> bytes` identifies this provider independently of its npm package name. The protocol mirror verifies identical marker bytes in TypeScript and Python.
|
||||
- **`run()` is one-shot** — `logs` become available only after `PtcRunResult` resolves; there is no streaming-log or progress interface for output produced by a running program.
|
||||
- **`run()` is one-shot** — `output` becomes available only after `PtcRunResult` resolves; there is no streaming-log or progress interface for output produced by a running program.
|
||||
- **No state persists across runs** — every request executes in a fresh subprocess; a persistent REPL-style kernel stays deferred until a backend brings its own logging scheme.
|
||||
- **An fd-3 frame whose raw length exceeds the effective frame parse cap settles the run as a worker-exit** — the cap is 64 MiB, or lower when the host's configured heap cannot safely parse a near-cap frame (`hostFrameParseCeiling`); `maxLogBytes`/`maxValueBytes` are load-bounded to the same cap so an honest child's frames always fit; a model-constructed binding ARGUMENT above the cap (a value with no seam-level budget) trips it too — an accepted residual of the OOM guard.
|
||||
- **A child that stops reading its replies settles the run as a worker-exit once the reply backlog passes 1024 frames** — the host writes replies one at a time, waiting for `drain` when the pipe is full; a child that keeps sending calls without consuming replies would otherwise grow the retained backlog (and the binding results it pins) until the wall clock, so the backlog cap fails the run early. Binding results carry no seam-level byte cap, so this is a count bound, not a byte bound.
|
||||
|
||||
@@ -133,12 +133,12 @@ The program is the body of an async Python function. Top-level await and return
|
||||
- **binding 回复值没有 seam 级字节或深度上限**——`maxValueBytes` 只计量 done 帧的完成值;宽 binding 回复在宿主侧重建(`snapshotJsonValue` 遍历)并整帧编码,两侧都只受进程内存约束(与没有子进程侧预算的 binding 实参一样)。
|
||||
- **已发布 profile 均不挂载本提供方**——keyless `ptc-python-turn` 快照通过真实 Loader 替换 headless PTC 运行时;已发布 profile 使用沙箱 Node 进程后端。
|
||||
- **工作流执行需要 Node**——使用本 Python 提供方的组合禁用 `workflow-ptc`、`tool-workflow` 和 `tool-ralph`;工作流提供方在加载时拒绝不兼容的运行时。
|
||||
- **跨通道日志交错由后端决定**——Python stdout、stderr 与 fd-3 日志帧彼此独立传输;每个通道保留自身顺序,但它们在 `result.logs` 中的总顺序可能不同。
|
||||
- **跨通道日志交错由后端决定**——Python stdout、stderr 与 fd-3 日志帧彼此独立传输;每个通道保留自身顺序,但它们在 `result.output` 中的总顺序可能不同。
|
||||
- **需要 CPython 3.10 或更高版本**——配置的可执行文件会在加载期完成解析与版本探测;不受支持的解释器会在 `ctx.ptcRuntime` 注册前失败。
|
||||
- **执行限于本地 Unix 宿主**——解释器、工作目录与沙箱 runner 必须位于运行本插件的宿主上。提供方使用 POSIX 资源限制和进程组信号;不支持 Windows 或远程执行提供方。
|
||||
- **沙箱强制能力继承后端限制**——成功的程序也可能报告部分强制能力,文件约束不构成多租户隔离或完整的后代清理保证。
|
||||
- **诊断前缀省略包名中的 experimental 限定词**——标记 `[dsh-ptc-runtime-python] log capture truncated at <N> bytes` 独立于 npm 包名来标识本提供方。协议镜像检查 TypeScript 与 Python 的标记字节完全相同。
|
||||
- **`run()` 是一次性的**——`logs` 只有在 `PtcRunResult` resolve 后才能获得;没有为运行中程序产生的输出提供流式日志或进度接口。
|
||||
- **`run()` 是一次性的**——`output` 只有在 `PtcRunResult` resolve 后才能获得;没有为运行中程序产生的输出提供流式日志或进度接口。
|
||||
- **运行之间不保留状态**——每次请求都在全新子进程中执行;持久 REPL 风格内核在某个后端带来自己的日志方案之前保持延期。
|
||||
- **原始长度超过有效帧解析上限的 fd-3 帧会让本次运行以 worker-exit 结算**——上限为 64 MiB,或当宿主的配置堆无法安全解析接近上限的帧时更低(`hostFrameParseCeiling`);`maxLogBytes`/`maxValueBytes` 在加载期被限制到同一上限,因此诚实子进程的帧总能放得下;模型构造的超过该上限的 binding 实参(一个在 seam 层没有预算的值)会触发同一上限——这是该 OOM 防护的已接受残余。
|
||||
- **停止读取回复的子进程会在回复积压超过 1024 帧时以 worker-exit 结算运行**——宿主每次写一条回复,管道满时等待 `drain`;只持续发送调用而不消费回复的子进程会让保留的积压(及其钉住的 binding 结果)一直增长到墙钟,因此积压上限让运行提前失败。binding 结果在 seam 层没有字节上限,所以这是计数上限而非字节上限。
|
||||
|
||||
@@ -1022,7 +1022,7 @@ export class PythonPtcRuntime extends PtcRuntime {
|
||||
try {
|
||||
input = bootstrapInput()
|
||||
} catch (error: unknown) {
|
||||
return { logs: [], sandbox, error: { kind: 'worker-exit', message: `failed to read the python bootstrap: ${messageOf(error)}` } }
|
||||
return { output: [], sandbox, error: { kind: 'worker-exit', message: `failed to read the python bootstrap: ${messageOf(error)}` } }
|
||||
}
|
||||
let confined: ConfinedArgv | undefined
|
||||
// Unbuffered native output stays observable before process-group teardown.
|
||||
@@ -1045,7 +1045,7 @@ export class PythonPtcRuntime extends PtcRuntime {
|
||||
return await pending
|
||||
} catch (error: unknown) {
|
||||
return {
|
||||
logs: [], sandbox,
|
||||
output: [], sandbox,
|
||||
error: controller.signal.aborted ? controller.signal.reason as PtcRunFailure
|
||||
: { kind: error instanceof SandboxUnavailableError ? 'sandbox-unavailable' : 'worker-exit', message: messageOf(error) },
|
||||
}
|
||||
@@ -1176,7 +1176,7 @@ export class PythonPtcRuntime extends PtcRuntime {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
complete()
|
||||
return Promise.resolve({ logs: [], sandbox, error: {
|
||||
return Promise.resolve({ output: [], sandbox, error: {
|
||||
kind: confined !== undefined && isRunnerSpawnFailure(error, argv[0], request.cwd) ? 'sandbox-unavailable' : 'worker-exit',
|
||||
message: `python spawn error: ${messageOf(error)}`,
|
||||
} })
|
||||
@@ -2147,16 +2147,16 @@ export class PythonPtcRuntime extends PtcRuntime {
|
||||
let resolved = false
|
||||
// The decided terminal result for a live child, recorded by finish() and
|
||||
// read by the `close` handler that settles it once the pipes have drained.
|
||||
let decided: Omit<PtcRunResult, 'logs'>
|
||||
let decided: Omit<PtcRunResult, 'output'>
|
||||
|
||||
// The single settlement point: resolve run() with the decided result and
|
||||
// mark the fiber quiescent. Idempotent — the first call wins, so a later
|
||||
// `close` after done/timeout/abort is absorbed as a no-op.
|
||||
const settle = (result: Omit<PtcRunResult, 'logs'>): void => {
|
||||
const settle = (result: Omit<PtcRunResult, 'output'>): void => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
if (closeDeadline !== undefined) clearTimeout(closeDeadline)
|
||||
resolve({ ...result, logs, sandbox: { ...sandbox } })
|
||||
resolve({ ...result, output: logs.map(text => ({ type: 'text', text })), sandbox: { ...sandbox } })
|
||||
// Mark the fiber quiescent for THIS run: drop it from `live` and resolve
|
||||
// `finished` (what teardown awaits). Deferred until the process group is
|
||||
// actually empty — dropping from `live` before then would let a
|
||||
@@ -2240,7 +2240,7 @@ export class PythonPtcRuntime extends PtcRuntime {
|
||||
pollGroup()
|
||||
}
|
||||
|
||||
const finish = (result: Omit<PtcRunResult, 'logs'>): void => {
|
||||
const finish = (result: Omit<PtcRunResult, 'output'>): void => {
|
||||
if (settled) return
|
||||
settled = true
|
||||
decided = result
|
||||
|
||||
@@ -562,7 +562,7 @@ describe('PythonPtcRuntime — seam descriptors and misuse', () => {
|
||||
const result = await runtime.run(runtime.resolve({ program: 'return 1', bindings: [], signal }))
|
||||
expect(result.error?.kind).toBe('abort')
|
||||
expect(result.error?.message).toContain('already-cancelled')
|
||||
expect(result.logs).toEqual([])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([])
|
||||
})
|
||||
|
||||
it('short-circuits on an already-aborted signal whose reason cannot be converted', async () => {
|
||||
@@ -576,7 +576,7 @@ describe('PythonPtcRuntime — seam descriptors and misuse', () => {
|
||||
const result = await runtime.run(runtime.resolve({ program: 'return 1', bindings: [], signal }))
|
||||
expect(result.error?.kind).toBe('abort')
|
||||
expect(result.error?.message).toBe('<unrenderable rejection value>')
|
||||
expect(result.logs).toEqual([])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([])
|
||||
})
|
||||
|
||||
it('delivers packaged Python modules without exposing a writable bootstrap file', async () => {
|
||||
@@ -671,7 +671,7 @@ describe('PythonPtcRuntime — seam descriptors and misuse', () => {
|
||||
const result = await runtime.run(runtime.resolve({ program: 'return 42', bindings: [] }))
|
||||
expect(result.error?.kind).toBe('worker-exit')
|
||||
expect(result.error?.message).toContain('failed to read the python bootstrap')
|
||||
expect(result.logs).toEqual([])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([])
|
||||
expect(result.value).toBeUndefined()
|
||||
} finally {
|
||||
failNextReadOf.value = undefined
|
||||
@@ -972,7 +972,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
expect(result.value).toEqual({ answer: 42 })
|
||||
// `print` in Python emits: text, ' ', text, '\n'. Concat the captured
|
||||
// fragments and assert the model-visible message survives.
|
||||
expect(result.logs.join('')).toContain('hello 42')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('hello 42')
|
||||
// 15s: this is usually the suite's first real subprocess — a cold python3
|
||||
// start (interpreter + asyncio import) on a loaded CI runner can exceed
|
||||
// the 5s default alone; later tests reuse the warm page cache.
|
||||
@@ -994,7 +994,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toEqual({ tmpdir: tmpdir(), path: null, home: null, token: null })
|
||||
expect(result.logs).toEqual([])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([])
|
||||
})
|
||||
|
||||
it('bridges binding calls both ways and rejects the program-side call on a host rejection', async () => {
|
||||
@@ -1269,7 +1269,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a b', 'c'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a b', 'c'])
|
||||
})
|
||||
|
||||
it('flushes a print with no trailing newline', async () => {
|
||||
@@ -1279,7 +1279,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['partial'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['partial'])
|
||||
})
|
||||
|
||||
it('aggregates a large newline-free native write into one log entry, not one per pipe chunk', async () => {
|
||||
@@ -1296,7 +1296,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['A'.repeat(size)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['A'.repeat(size)])
|
||||
})
|
||||
|
||||
it('splits native output on its own newlines, one entry per line', async () => {
|
||||
@@ -1308,7 +1308,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['one', 'two', 'three'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['one', 'two', 'three'])
|
||||
})
|
||||
|
||||
it('preserves each native stream order while allowing backend-dependent interleaving', async () => {
|
||||
@@ -1325,8 +1325,8 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.indexOf('stdout-one')).toBeLessThan(result.logs.indexOf('stdout-two'))
|
||||
expect(result.logs.indexOf('stderr-one')).toBeLessThan(result.logs.indexOf('stderr-two'))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).indexOf('stdout-one')).toBeLessThan(result.output.filter(block => block.type === 'text').map(block => block.text).indexOf('stdout-two'))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).indexOf('stderr-one')).toBeLessThan(result.output.filter(block => block.type === 'text').map(block => block.text).indexOf('stderr-two'))
|
||||
})
|
||||
|
||||
it('bounds a newline-free native flood by the ledger instead of buffering it whole', async () => {
|
||||
@@ -1342,9 +1342,9 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(4096))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(4096))
|
||||
// The retained output is bounded by the budget, not the 2 MB flood.
|
||||
expect(result.logs.join('').length).toBeLessThan(4096)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('').length).toBeLessThan(4096)
|
||||
})
|
||||
|
||||
it('bounds a newline-free single-character Python write drip by the fragment cap, not OOM', async () => {
|
||||
@@ -1369,7 +1369,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(4096))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(4096))
|
||||
})
|
||||
|
||||
it('bounds a control-char-dense native residual by serialized cost, not raw length', async () => {
|
||||
@@ -1387,7 +1387,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(4096))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(4096))
|
||||
})
|
||||
|
||||
it('rejects an output budget that could breach addressSpaceMb during encode at load', async () => {
|
||||
@@ -1460,7 +1460,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(4096))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(4096))
|
||||
})
|
||||
|
||||
it('drops a second stray line in the same chunk once the first truncated the ledger', async () => {
|
||||
@@ -1479,8 +1479,8 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(64))
|
||||
expect(result.logs.join('\n')).not.toContain('SECOND')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(64))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('\n')).not.toContain('SECOND')
|
||||
})
|
||||
|
||||
it('charges a broken multibyte sequence its U+FFFD bytes, split across pipe chunks', async () => {
|
||||
@@ -1502,8 +1502,8 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.join('')).toContain('A')
|
||||
expect(result.logs.join('')).toContain('�')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('A')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('�')
|
||||
})
|
||||
|
||||
it('charges the exact serialized cost of short-escape and quote/backslash characters', async () => {
|
||||
@@ -1519,7 +1519,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['\ta"b\\c\x01é'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['\ta"b\\c\x01é'])
|
||||
})
|
||||
|
||||
it('fails a completion dict with a non-string key as invalid-output (no key coercion)', async () => {
|
||||
@@ -2065,7 +2065,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['ab'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['ab'])
|
||||
}, 15_000)
|
||||
|
||||
it('keeps a SEALED open hold when the run ends with it still open', async () => {
|
||||
@@ -2084,7 +2084,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['x' + 'a'.repeat(3000)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['x' + 'a'.repeat(3000)])
|
||||
}, 15_000)
|
||||
|
||||
it('keeps a flushed unterminated line when the run ends with it still open', async () => {
|
||||
@@ -2100,7 +2100,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['committed'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['committed'])
|
||||
}, 15_000)
|
||||
|
||||
it('skips the hold for a zero-content open continuation', async () => {
|
||||
@@ -2120,7 +2120,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['xy'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['xy'])
|
||||
}, 15_000)
|
||||
|
||||
it('seals the open hold past MAX_PENDING_CHUNKS without changing the merged entry', async () => {
|
||||
@@ -2142,7 +2142,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['x' + 'a'.repeat(3000) + 'y'])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['x' + 'a'.repeat(3000) + 'y'])
|
||||
}, 15_000)
|
||||
|
||||
it('bounds a forged open-frame flood against the log budget', async () => {
|
||||
@@ -2162,7 +2162,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a'.repeat(60), logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a'.repeat(60), logTruncationMarker(64)])
|
||||
}, 15_000)
|
||||
|
||||
it('commits a sealed open hold before the truncation marker', async () => {
|
||||
@@ -2186,8 +2186,8 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs[0]).toBe('x' + 'a'.repeat(3000))
|
||||
expect(result.logs[result.logs.length - 1]).toBe(logTruncationMarker(65536))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)[0]).toBe('x' + 'a'.repeat(3000))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)[result.output.filter(block => block.type === 'text').map(block => block.text).length - 1]).toBe(logTruncationMarker(65536))
|
||||
}, 15_000)
|
||||
|
||||
it('commits a flushed open prefix before the truncation marker', async () => {
|
||||
@@ -2204,7 +2204,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['committed', logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['committed', logTruncationMarker(64)])
|
||||
}, 15_000)
|
||||
|
||||
it('drops a forged fd-3 frame with illegal UTF-8 instead of accepting a mangled value', async () => {
|
||||
@@ -2241,7 +2241,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a'.repeat(60), logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a'.repeat(60), logTruncationMarker(64)])
|
||||
}, 15_000)
|
||||
|
||||
it('bills a merged open entry once, not per fragment', async () => {
|
||||
@@ -2262,7 +2262,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['x'.repeat(16)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['x'.repeat(16)])
|
||||
}, 15_000)
|
||||
|
||||
it('admits a compliant merged entry whose closing frame fits the remaining budget', async () => {
|
||||
@@ -2281,7 +2281,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a'.repeat(30) + 'b'.repeat(25)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a'.repeat(30) + 'b'.repeat(25)])
|
||||
}, 15_000)
|
||||
|
||||
it('rejects an open frame that would overflow the ledger by one byte', async () => {
|
||||
@@ -2301,7 +2301,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual([logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(64)])
|
||||
}, 15_000)
|
||||
|
||||
it('bills the closing frame as the merged tail under an exact-fit budget', async () => {
|
||||
@@ -2319,7 +2319,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a'.repeat(30) + 'b'.repeat(30)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a'.repeat(30) + 'b'.repeat(30)])
|
||||
}, 15_000)
|
||||
|
||||
it('does not over-reject an exact-fit closing line while an open entry accumulates', async () => {
|
||||
@@ -2344,7 +2344,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a'.repeat(29) + 'b'.repeat(30)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a'.repeat(29) + 'b'.repeat(30)])
|
||||
}, 15_000)
|
||||
|
||||
|
||||
@@ -2364,7 +2364,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['a'.repeat(59), logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['a'.repeat(59), logTruncationMarker(64)])
|
||||
}, 15_000)
|
||||
|
||||
it('truncates when the closing frame of a merged entry overflows the budget', async () => {
|
||||
@@ -2382,7 +2382,7 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs).toEqual(['x'.repeat(40), logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['x'.repeat(40), logTruncationMarker(64)])
|
||||
}, 15_000)
|
||||
|
||||
it('keeps a float completion exact when the program mutates the decimal context', async () => {
|
||||
@@ -2487,8 +2487,8 @@ describe('PythonPtcRuntime — programs and bindings', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('ok')
|
||||
expect(result.logs).toContain('first')
|
||||
expect(result.logs).toContain('second')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('first')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('second')
|
||||
})
|
||||
|
||||
it('raises the declared errorClass with the member name on rejection', async () => {
|
||||
@@ -3473,7 +3473,7 @@ describe('PythonPtcRuntime — budgets, termination, disposal', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('escaped')
|
||||
expect(result.logs).toContain('leader-diagnostic-no-newline')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('leader-diagnostic-no-newline')
|
||||
}, 8000)
|
||||
|
||||
it('closes the child stdin so a program read sees EOF instead of blocking', async () => {
|
||||
@@ -3869,7 +3869,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
const markers = result.logs.filter(line => line.includes('log capture truncated at 128 bytes'))
|
||||
const markers = result.output.filter(block => block.type === 'text').map(block => block.text).filter(line => line.includes('log capture truncated at 128 bytes'))
|
||||
expect(markers.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
@@ -4446,7 +4446,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
}, 20_000)
|
||||
|
||||
it('stops host capture at the child ledger truncation, keeping exactly one marker', async () => {
|
||||
@@ -4473,11 +4473,11 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
const markers = result.logs.filter(line => line.includes('log capture truncated'))
|
||||
const markers = result.output.filter(block => block.type === 'text').map(block => block.text).filter(line => line.includes('log capture truncated'))
|
||||
expect(markers).toHaveLength(1)
|
||||
// The marker is the LAST entry: nothing was retained after truncation.
|
||||
expect(result.logs.at(-1)).toBe(markers[0])
|
||||
expect(result.logs.join('\n')).not.toContain('AFTER')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(markers[0])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('\n')).not.toContain('AFTER')
|
||||
}, 20_000)
|
||||
|
||||
it('keeps one marker when a program forges repeated truncation frames', async () => {
|
||||
@@ -4500,13 +4500,13 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
// The non-boolean flag did not truncate, so its text was captured normally.
|
||||
expect(result.logs).toContain('first')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('first')
|
||||
// The first genuine flag stopped capture and emitted the HOST's own marker;
|
||||
// the frame's own text is discarded, so neither payload appears.
|
||||
expect(result.logs).not.toContain('MARK-A')
|
||||
expect(result.logs).not.toContain('MARK-B')
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(4096))
|
||||
expect(result.logs.filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).not.toContain('MARK-A')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).not.toContain('MARK-B')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(4096))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
}, 20_000)
|
||||
|
||||
it('discards the text of a forged truncation frame instead of retaining it', async () => {
|
||||
@@ -4530,8 +4530,8 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
expect(result.value).toBe('done')
|
||||
// Only the host marker is kept, so the total stays orders of magnitude below
|
||||
// what the forgery carried — and below the cap it was trying to escape.
|
||||
expect(result.logs).toEqual([logTruncationMarker(64)])
|
||||
expect(result.logs.join('').length).toBeLessThan(forgedBytes / 1000)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('').length).toBeLessThan(forgedBytes / 1000)
|
||||
}, 30_000)
|
||||
|
||||
it('coalesces unframed fd-3 fragments without recopying the sealed prefix', async () => {
|
||||
@@ -4587,7 +4587,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toContain('after-trickle')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('after-trickle')
|
||||
// Sealing appends a finished block rather than re-merging everything held, so
|
||||
// each byte is copied once. Re-concatenating the whole buffer at every
|
||||
// threshold made the cumulative copy volume quadratic — 10 MiB trickled a
|
||||
@@ -4654,9 +4654,9 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
// The retained text is bounded by the budget, not the 1 MB the program wrote.
|
||||
expect(result.logs.join('\n').length).toBeLessThan(4096)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('\n').length).toBeLessThan(4096)
|
||||
}, 20_000)
|
||||
|
||||
it('discards empty writes instead of buffering one list slot each', async () => {
|
||||
@@ -4679,7 +4679,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toEqual([])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([])
|
||||
}, 30_000)
|
||||
|
||||
it('stops scanning a single-write newline flood once the log ledger truncates', async () => {
|
||||
@@ -4695,7 +4695,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
expect(Date.now() - start).toBeLessThan(8_000)
|
||||
}, 15_000)
|
||||
|
||||
@@ -4722,7 +4722,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toEqual([logTruncationMarker(256)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(256)])
|
||||
}, 40_000)
|
||||
|
||||
it('bounds a newline-free write against the already-buffered chunks before joining them', async () => {
|
||||
@@ -4753,7 +4753,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toEqual([logTruncationMarker(256)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(256)])
|
||||
}, 40_000)
|
||||
|
||||
it('bounds a newline-terminated write against the already-buffered chunks before joining them', async () => {
|
||||
@@ -4780,7 +4780,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toEqual([logTruncationMarker(256)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(256)])
|
||||
}, 40_000)
|
||||
|
||||
it('emits pending text on an explicit flush, before the run can be killed', async () => {
|
||||
@@ -4800,7 +4800,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error?.kind).toBe('timeout')
|
||||
expect(result.logs).toContain('before hang')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('before hang')
|
||||
}, 15_000)
|
||||
|
||||
it('marks a dropped tail when the ledger lands on exactly zero remaining', async () => {
|
||||
@@ -4825,10 +4825,10 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toContain('y'.repeat(100))
|
||||
expect(result.logs.filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('y'.repeat(100))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
// The dropped tail is not retained, but its loss is now reported.
|
||||
expect(result.logs.some(line => line.includes('z'))).toBe(false)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('z'))).toBe(false)
|
||||
}, 15_000)
|
||||
|
||||
it('keeps an admitted log within the serialized array envelope at the exact limit', async () => {
|
||||
@@ -4850,9 +4850,9 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
expect(result.value).toBe('done')
|
||||
// The 60-character line was admitted; the 61-character line was not (a
|
||||
// single 'b' would also match the marker's "bytes", so check for the line).
|
||||
expect(result.logs).toContain('a'.repeat(60))
|
||||
expect(result.logs.some(line => line.includes('b'.repeat(61)))).toBe(false)
|
||||
expect(result.logs.some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('a'.repeat(60))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('b'.repeat(61)))).toBe(false)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
}, 15_000)
|
||||
|
||||
it('rejects a log budget too small to serialize the truncation marker', async () => {
|
||||
@@ -4877,10 +4877,10 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
// Serialized (escaped) size of retained entries stays in the budget's
|
||||
// neighborhood: well under the ~30 kB an uncharged flood would retain.
|
||||
const serialized = Buffer.byteLength(JSON.stringify(result.logs), 'utf8')
|
||||
const serialized = Buffer.byteLength(JSON.stringify(result.output.filter(block => block.type === 'text').map(block => block.text)), 'utf8')
|
||||
expect(serialized).toBeLessThan(1024)
|
||||
})
|
||||
|
||||
@@ -4903,9 +4903,9 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).filter(line => line.includes('log capture truncated'))).toHaveLength(1)
|
||||
// Nothing of the line itself was retained: the ledger refused the whole entry.
|
||||
expect(result.logs.every(line => !line.includes(String.fromCharCode(0)))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).every(line => !line.includes(String.fromCharCode(0)))).toBe(true)
|
||||
}, 30_000)
|
||||
|
||||
it('bounds a huge unterminated tail after an early newline without copying it whole', async () => {
|
||||
@@ -4933,7 +4933,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
}, 30_000)
|
||||
|
||||
it('flushes logs before framing the value so their peaks do not add against RLIMIT_AS', async () => {
|
||||
@@ -5368,8 +5368,8 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.length).toBeLessThanOrEqual(65)
|
||||
expect(result.logs.some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).length).toBeLessThanOrEqual(65)
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).some(line => line.includes('log capture truncated'))).toBe(true)
|
||||
})
|
||||
|
||||
it('reassembles multibyte UTF-8 split across stray-output pipe chunks', async () => {
|
||||
@@ -5399,7 +5399,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
const text = result.logs.join('')
|
||||
const text = result.output.filter(block => block.type === 'text').map(block => block.text).join('')
|
||||
expect(text).toContain('\u0900\u4f60\u597d\u{1f600}\u{10ffff}')
|
||||
expect(text).not.toContain('\ufffd')
|
||||
})
|
||||
@@ -5421,7 +5421,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.join('')).toContain('�')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('�')
|
||||
})
|
||||
|
||||
it('rejects reserved words of EITHER backend language as binding globals', async () => {
|
||||
@@ -5452,8 +5452,8 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.join('')).toContain('stray stdout')
|
||||
expect(result.logs.join('')).toContain('stray stderr')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('stray stdout')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('stray stderr')
|
||||
})
|
||||
|
||||
it('flushes bytes written through sys.__stdout__/sys.__stderr__ before the done frame', async () => {
|
||||
@@ -5483,8 +5483,8 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs.join('')).toContain('orig stdout')
|
||||
expect(result.logs.join('')).toContain('orig stderr')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('orig stdout')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).join('')).toContain('orig stderr')
|
||||
}, 15_000)
|
||||
|
||||
it('escalates to SIGKILL when the program traps SIGTERM and ignores the grace period', async () => {
|
||||
@@ -5577,7 +5577,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('settled')
|
||||
// The frame was dropped as one truncation marker, not retained.
|
||||
expect(result.logs).toEqual([logTruncationMarker(128)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(128)])
|
||||
// The escaped copy (~144 MiB) was never materialized.
|
||||
expect(process.memoryUsage().heapUsed - before).toBeLessThan(256 * 1024 * 1024)
|
||||
}, 90_000)
|
||||
@@ -5598,7 +5598,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('settled')
|
||||
expect(result.logs).toEqual([logTruncationMarker(64)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual([logTruncationMarker(64)])
|
||||
}, 8000)
|
||||
|
||||
it('caps a forged done error.message from its code-unit prefix, never encoding the whole message', async () => {
|
||||
@@ -5835,7 +5835,7 @@ describe('PythonPtcRuntime — hostile peer', () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toContain('after-cap-frames')
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toContain('after-cap-frames')
|
||||
}, 120_000)
|
||||
|
||||
it('rejects an oversized first frame that lands on the sealing threshold with a newline', async () => {
|
||||
|
||||
@@ -45,7 +45,7 @@ it('seals stray fragments without recopying the sealed prefix', async () => {
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.value).toBe('done')
|
||||
expect(result.logs).toEqual(['x'.repeat(60_000)])
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text)).toEqual(['x'.repeat(60_000)])
|
||||
// Sealing copies each byte at most twice; merging every accumulated prefix
|
||||
// instead copies over a megabyte for these 60,001 controlled fragments.
|
||||
expect(maxFragments).toBeLessThanOrEqual(1024)
|
||||
@@ -75,7 +75,7 @@ it.each([
|
||||
bindings: [],
|
||||
}))
|
||||
expect(result.error).toBeUndefined()
|
||||
expect(result.logs.at(-1)).toBe(logTruncationMarker(3072))
|
||||
expect(result.output.filter(block => block.type === 'text').map(block => block.text).at(-1)).toBe(logTruncationMarker(3072))
|
||||
// Each raw byte decodes to U+FFFD (three UTF-8 bytes), so a 3072-byte
|
||||
// budget flushes near 1024 raw bytes. Charging raw or structural widths
|
||||
// instead retains over 2048 bytes before flushing these payloads.
|
||||
|
||||
Reference in new issue
Block a user