Merge corrected #1148 checkpoint into #3289

# Conflicts:
#	docs/config-catalog.i18n.yaml
#	docs/config-catalog.zh.md
#	packages/experimental/code-runtime-python/README.i18n.yaml
#	packages/experimental/code-runtime-python/README.md
#	packages/experimental/code-runtime-python/README.zh.md
#	packages/experimental/code-runtime-python/src/index.ts
#	packages/experimental/code-runtime-python/tests/runtime.spec.ts
This commit is contained in:
Tianyi Cui
2026-08-31 16:10:13 +08:00
20 changed files with 691 additions and 155 deletions
@@ -1,10 +1,11 @@
import { existsSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs'
import { execFileSync } from 'node:child_process'
import { existsSync, mkdtempSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs'
import { mkdtemp, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { basename, dirname, join, relative } from 'node:path'
import { describe, expect, it, vi } from 'vitest'
import { basename, dirname, join, relative, resolve } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { Context } from '@deepseek-ai/cordis'
import { PythonCodeRuntime, readProcessStart, resolvePythonBin } from '../src/index.ts'
import { PythonCodeRuntime, hostFrameParseCeiling, readProcessStart, resolvePythonBin } from '../src/index.ts'
import { logTruncationMarker } from '../src/protocol.ts'
import type { Config } from '../src/index.ts'
@@ -27,9 +28,16 @@ import type { CodeBindingFunction, CodeJsonValue, CodeRunResult } from '@deepsee
* records the same race and solves it with argv-based identity; recording the
* mkdtempSync results is the fs-mock equivalent.
*/
const { failNextCopyOf, stagedDirs } = vi.hoisted(() => ({
const { failNextCopyOf, stagedDirs, tempDirs, tempFiles } = vi.hoisted(() => ({
failNextCopyOf: { value: undefined as string | undefined },
stagedDirs: [] as string[],
// Test-created temp dirs/files, registered by the helpers below and removed
// after each test: a suite run over real python3 subprocesses must not
// permanently accumulate `dsh-*` fixtures in the shared tmpdir (the runtime
// cleans its own per-run staging dir; these are the stubs and wrappers the
// tests themselves build).
tempDirs: [] as string[],
tempFiles: [] as string[],
}))
vi.mock('node:fs', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs')>()
@@ -68,6 +76,27 @@ function tools(functions: Record<string, CodeBindingFunction>) {
return [{ global: 'tools', functions }]
}
/** Create a test temp dir registered for afterEach removal. */
async function makeTempDir(prefix: string): Promise<string> {
const dir = await mkdtemp(join(tmpdir(), prefix))
tempDirs.push(dir)
return dir
}
/** Synchronous variant of {@link makeTempDir} for the PATH-stub fixtures. */
function makeTempDirSync(prefix: string): string {
const dir = mkdtempSync(join(tmpdir(), prefix))
tempDirs.push(dir)
return dir
}
// Remove every fixture this file created, so repeated runs do not accumulate
// `dsh-*` directories and wrappers in the shared tmpdir.
afterEach(() => {
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true })
for (const file of tempFiles.splice(0)) rmSync(file, { force: true })
})
describe('PythonCodeRuntime — seam descriptors and misuse', () => {
it('registers the seam descriptors', async () => {
const { runtime } = await setup()
@@ -149,6 +178,66 @@ describe('PythonCodeRuntime — seam descriptors and misuse', () => {
await boundary.dispose()
})
it('rejects a completion budget whose frame a constrained host heap cannot safely parse', async () => {
// The load gate bounds the CHILD's build-and-encode under RLIMIT_AS; it
// does not bound the HOST's JSON.parse, which materializes several times a
// wide frame's raw bytes in property storage. In a child node with a
// 128 MiB old space the heap-derived frame cap is ~14 MiB, so a 50 MiB
// budget is rejected at load even though the address-space gate alone
// would admit it (50 MiB * 12 = 600 MiB < 1 GiB - 64 MiB).
const script = [
"import { Context } from '@deepseek-ai/cordis'",
"import { PythonCodeRuntime } from './packages/experimental/code-runtime-python/src/index.ts'",
'const ctx = new Context()',
'try {',
' await ctx.plugin(PythonCodeRuntime, { maxValueBytes: 50 * 1024 * 1024, addressSpaceMb: 1024 })',
" console.log('LOADED')",
' process.exit(1)',
'} catch (error) {',
" console.log('REJECTED:' + (error instanceof Error ? error.message : String(error)))",
' process.exit(0)',
'}',
].join('\n')
const out = execFileSync(process.execPath, ['--max-old-space-size=128', '--import', 'tsx', '-e', script], {
cwd: resolve(import.meta.dirname, '../../../..'),
encoding: 'utf8',
timeout: 60_000,
env: { ...process.env, TSX_TSCONFIG_PATH: resolve(import.meta.dirname, '../../../../tsconfig.json') },
})
expect(out).toContain('REJECTED:')
expect(out).toContain('must not exceed')
}, 60_000)
it('parses a worst-shape frame at the derived cap on a constrained heap', async () => {
// The host-heap frame cap must be measured against the WORST parse shape —
// a dict of many short unique keys, which forces dictionary-mode property
// storage plus interned keys (~6.4x at 3M keys, trending up), not the ~3x
// of a repeated-key dict. A child node with a 128 MiB old space (~176 MiB
// heap limit) derives a cap of floor((176 - 64) / 16) = 7 MiB; the
// subprocess builds a unique-key dict whose frame is AT that cap and
// parses it, which must survive. Verified fail-before: with the multiple
// at 8 the derived cap doubles to 14 MiB and the same subprocess OOMs
// during the parse (plain JS, no tsx — the frame and parse are builtins).
const cap = hostFrameParseCeiling(176 * 1024 * 1024)
const script = [
`const cap = ${cap}`,
// Each entry "k<base36>:1," is ~9-12 raw bytes; a few hundred thousand
// unique keys put the frame just at the cap.
'const count = Math.floor(cap / 12)',
'const obj = {}',
'for (let i = 0; i < count; i++) obj[`k${i.toString(36)}`] = 1',
'const frame = JSON.stringify(obj)',
"if (Buffer.byteLength(frame, 'utf8') > cap) throw new Error('frame over cap: ' + frame.length)",
'JSON.parse(frame)',
"console.log('SURVIVED:' + Buffer.byteLength(frame, 'utf8'))",
].join('\n')
const out = execFileSync(process.execPath, ['--max-old-space-size=128', '-e', script], {
encoding: 'utf8',
timeout: 60_000,
})
expect(out).toContain('SURVIVED:')
}, 60_000)
it('rejects a pythonBin that spawn() would throw on, at load', async () => {
// Both values pass the string schema and both make `spawn` throw
// SYNCHRONOUSLY from inside run() — ERR_INVALID_ARG_VALUE for the empty
@@ -170,8 +259,8 @@ describe('PythonCodeRuntime — seam descriptors and misuse', () => {
// The message distinguishes the explicit-path failure from a basename that
// simply does not resolve on PATH.
const nodePath = await import('node:path')
const { mkdtempSync, writeFileSync, mkdirSync } = await import('node:fs')
const dir = mkdtempSync(nodePath.join(tmpdir(), 'dsh-bad-bin-'))
const { writeFileSync, mkdirSync } = await import('node:fs')
const dir = makeTempDirSync('dsh-bad-bin-')
const notExecutable = nodePath.join(dir, 'not-executable')
writeFileSync(notExecutable, '#!/bin/sh\nexit 0\n') // Regular file, but no X bit.
const directory = nodePath.join(dir, 'is-a-directory')
@@ -386,10 +475,9 @@ describe('PythonCodeRuntime — seam descriptors and misuse', () => {
// real interpreter is used.
const cp = await import('node:child_process')
const nodePath = await import('node:path')
const { mkdtempSync, mkdirSync } = await import('node:fs')
const { tmpdir } = await import('node:os')
const { mkdirSync } = await import('node:fs')
const realPythonDir = nodePath.dirname(cp.execFileSync('which', ['python3'], { encoding: 'utf8' }).trim())
const fakeDir = mkdtempSync(nodePath.join(tmpdir(), 'dsh-fake-bin-'))
const fakeDir = makeTempDirSync('dsh-fake-bin-')
mkdirSync(nodePath.join(fakeDir, 'python3')) // A directory named python3, executable by default.
vi.stubEnv('PATH', `${fakeDir}:${realPythonDir}`)
try {
@@ -608,7 +696,7 @@ describe('PythonCodeRuntime — seam descriptors and misuse', () => {
// `os.tmpdir()`, so pointing it at a path that is not a directory makes the
// real call fail without stubbing the module under test.
const previous = process.env.TMPDIR
const notADirectory = join(await mkdtemp(join(tmpdir(), 'dsh-staging-')), 'file')
const notADirectory = join(await makeTempDir('dsh-staging-'), 'file')
await writeFile(notADirectory, '')
process.env.TMPDIR = notADirectory
try {
@@ -694,7 +782,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
// `pythonBin` is the honest lever: a wrapper that lowers RLIMIT_AS and then
// execs the real interpreter reproduces the inherited-limit condition
// without touching this test process's own limits.
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-'))
const dir = await makeTempDir('dsh-rlimit-')
const wrapper = join(dir, 'python3-capped')
// 256 MiB, half the 512 MiB addressSpaceMb default, so the requested cap is
// unambiguously above the inherited ceiling.
@@ -722,7 +810,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
// rejected. The rejection surfaces as an 'exception' (bootstrap's
// setrlimit-phase failure class), not a mid-run OOM. The repro is Linux-only
// (macOS ignores `ulimit -v`); there the run proceeds.
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-'))
const dir = await makeTempDir('dsh-rlimit-')
const wrapper = join(dir, 'python3-tight')
await writeFile(wrapper, `#!/bin/sh\nulimit -v 131072\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
const { runtime } = await setup({ pythonBin: wrapper, maxLogBytes: 32 * 1024 * 1024, addressSpaceMb: 512 })
@@ -772,7 +860,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
// requested soft (`cpuSeconds`) sits above the inherited soft — the case that
// exposed the bug. RLIMIT_CPU is used because macOS ignores `ulimit -v`
// (RLIMIT_AS), which is exactly why the backend skips address space there.
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-soft-'))
const dir = await makeTempDir('dsh-rlimit-soft-')
const wrapper = join(dir, 'python3-soft-capped')
// Soft CPU 5 s, well below the configured 30 s, hard left unlimited.
await writeFile(wrapper, `#!/bin/sh\nulimit -S -t 5\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
@@ -797,7 +885,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
// timeout. This uses `ulimit -t 2` (hard == 2, so the soft is lowered to 1)
// and leaves SIGXCPU unhandled, so the kernel terminates the busy loop at
// 1 s with SIGXCPU and the host classifies it as a timeout.
const dir = await mkdtemp(join(tmpdir(), 'dsh-rlimit-dual-'))
const dir = await makeTempDir('dsh-rlimit-dual-')
const wrapper = join(dir, 'python3-dual-capped')
// Both soft and hard CPU 2 s; configured cpuSeconds 30 s.
await writeFile(wrapper, `#!/bin/sh\nulimit -t 2\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
@@ -848,6 +936,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
// before model code runs, so a busy loop still ends as a timeout rather
// than running to the hard limit and being misclassified as worker-exit.
const wrapper = join(tmpdir(), `dsh-xcpu-ignore-${process.pid}.sh`)
tempFiles.push(wrapper)
writeFileSync(wrapper, `#!/bin/sh\ntrap "" XCPU\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
try {
const { runtime } = await setup({ maxWallMs: 30_000, cpuSeconds: 1, pythonBin: wrapper })
@@ -900,7 +989,7 @@ describe('PythonCodeRuntime — inherited resource limits', () => {
// the inherited limit. The wrapper sets a 1 s soft CPU limit; the program
// traps SIGXCPU and busy-loops past it, then returns — the recheck must
// re-deliver SIGXCPU so the host classifies the run as a timeout.
const dir = await mkdtemp(join(tmpdir(), 'dsh-cpu-recheck-'))
const dir = await makeTempDir('dsh-cpu-recheck-')
const wrapper = join(dir, 'python3-cpu-capped')
await writeFile(wrapper, `#!/bin/sh\nulimit -S -t 1\nexec "${PYABS}" "$@"\n`, { mode: 0o755 })
const { runtime } = await setup({ pythonBin: wrapper, cpuSeconds: 30, maxWallMs: 12_000 })
@@ -3587,7 +3676,7 @@ describe('PythonCodeRuntime — budgets, termination, disposal', () => {
// means whether the killed descendant is reaped or lingers as a zombie (a
// SIGKILL'd process runs no more code either way). It sleeps 30 s as a safety
// net so a broken fix cannot leak it forever.
const handoff = await mkdtemp(join(tmpdir(), 'dsh-samegroup-'))
const handoff = await makeTempDir('dsh-samegroup-')
const readyMarker = join(handoff, 'ready')
const heartbeat = join(handoff, 'heartbeat')
const { runtime } = await setup({ maxWallMs: 10_000, graceMs: 300 })
@@ -3652,7 +3741,7 @@ describe('PythonCodeRuntime — budgets, termination, disposal', () => {
// is called; the heartbeat must be stale BY THE TIME dispose() resolves —
// proving teardown waited for the reap, not merely that the reap eventually
// happened.
const handoff = await mkdtemp(join(tmpdir(), 'dsh-dispose-quiesce-'))
const handoff = await makeTempDir('dsh-dispose-quiesce-')
const readyMarker = join(handoff, 'ready')
const heartbeat = join(handoff, 'heartbeat')
const { runtime, fiber } = await setup({ maxWallMs: 10_000, graceMs: 300 })
@@ -3705,7 +3794,7 @@ describe('PythonCodeRuntime — budgets, termination, disposal', () => {
// rather than cancel the unfired escalation — otherwise a SIGTERM-ignoring
// same-group survivor is released for good. A synchronous busy-loop after
// run() resolves reproduces the block deterministically.
const handoff = await mkdtemp(join(tmpdir(), 'dsh-deadline-'))
const handoff = await makeTempDir('dsh-deadline-')
const readyMarker = join(handoff, 'ready')
const heartbeat = join(handoff, 'heartbeat')
const graceMs = 300
@@ -5309,52 +5398,203 @@ describe('PythonCodeRuntime — hostile peer', () => {
expect(result.error?.message).toContain('call backlog exceeded')
}, 30_000)
it('compacts the reply queue mid-drain without dropping pending frames', async () => {
// A reply larger than the writable high-water mark makes the FIRST write
// return false, suspending the drain loop while the child's synchronous
// flood starves the reply pump; the frames queued behind it push the
// drain's consumed head past MAX_PENDING_REPLIES, so the resumed drain
// compacts the queue mid-run. The child reads fd 3 itself (blocking the
// asyncio pump, so its reads cannot race the host's pushes) and sends a
// second wave of calls AFTER reading part of the first wave's replies —
// those replies are still pending when the drain's head crosses the
// compaction bound, so a compaction that dropped pending frames would
// leave the child's reply count short and the read loop spinning to the
// wall clock. The second wave is sent mid-delivery (not with the first
// flood): pushing it earlier would trip the 1024-pending reply cap
// before the drain resumed.
it('runs a legitimate gather of more than 1024 concurrent binding calls', async () => {
// The in-flight call cap must not count a synchronous batch of instant
// calls: the async bodies' finallys run on the microtask queue, which
// drains only between 'data' events, so a per-frame check would trip on
// the 1025th frame of a single event even though every binding settled
// immediately — killing a valid large concurrent gather as worker-exit.
// The cap is checked at event boundaries (after the microtask queue
// drained), so this gather of 1025 instant calls completes.
const { runtime } = await setup({ maxWallMs: 30_000 })
const result = await runtime.run({
program: [
'import asyncio',
'return len(await asyncio.gather(*[tools.echo(i) for i in range(1025)]))',
].join('\n'),
bindings: [{ global: 'tools', functions: { echo: async (args: unknown) => args as CodeJsonValue } }],
})
expect(result.error).toBeUndefined()
expect(result.value).toBe(1025)
}, 30_000)
it('completes normally when a program returns with binding calls still outstanding', async () => {
// The in-flight call cap refuses to admit NEW calls past the bound; it must
// not reclassify a `done` frame as worker-exit just because the program
// returned with calls it started but never awaited. The child schedules
// exactly 1024 slow bindings (still pending when the program returns), so
// the done frame arrives with the outstanding count AT the cap — the event
// must complete with its value, not settle as `call backlog exceeded`.
const { runtime } = await setup({ maxWallMs: 30_000 })
const result = await runtime.run({
program: [
'import asyncio',
'for i in range(1024):',
' asyncio.create_task(tools.slow(i))',
'await asyncio.sleep(0.2)',
'return "done"',
].join('\n'),
bindings: [{
global: 'tools',
functions: { slow: async () => { await new Promise((resolve) => { setTimeout(resolve, 5_000) }); return 1 } },
}],
})
expect(result.error).toBeUndefined()
expect(result.value).toBe('done')
}, 30_000)
it('settles a single-batch never-settling flood as worker-exit without further frames', async () => {
// The outstanding-call cap must take effect even when the whole flood fits
// in ONE data event: a per-event admission snapshot never re-checks once no
// further frames arrive, so a single 62 KiB write of 1025 compact calls
// against a never-settling binding would otherwise wait out the full wall
// clock instead of tripping the cap. The post-macrotask check runs after
// the batch's finallys (which never run for this binding) and settles the
// run as worker-exit long before maxWallMs.
const { runtime } = await setup({ maxWallMs: 30_000 })
const result = await runtime.run({
program: [
'import os, time',
'frame = b\'{"type":"call","id":%d,"global":"tools","name":"hang","args":{}}\\n\'',
'payload = b"".join(frame % i for i in range(1025))',
'view = memoryview(payload)',
'while view:',
' view = view[os.write(3, view):]',
'time.sleep(30)',
'return "unreachable"',
].join('\n'),
bindings: [{ global: 'tools', functions: { hang: async () => await new Promise<never>(() => {}) } }],
})
expect(result.error?.kind).toBe('worker-exit')
expect(result.error?.message).toContain('call backlog exceeded')
}, 30_000)
it('runs a burst of 1300 instant calls whose frames split across pipe reads', async () => {
// Flowing mode can fire several 'data' events within one macrotask, before
// any microtask drains, so a per-event snapshot of the outstanding count
// could see the first chunk's in-flight calls in the second chunk's check
// and false-positive on a legitimate burst. The post-macrotask check always
// sees the true count (all finallys have run), so this burst of compact
// frames — sized so the pipe read splits it — completes with all results.
const { runtime } = await setup({ maxWallMs: 30_000 })
const result = await runtime.run({
program: [
'import asyncio',
'return len(await asyncio.gather(*[t.e(i) for i in range(1300)]))',
].join('\n'),
bindings: [{ global: 't', functions: { e: async (args: unknown) => args as CodeJsonValue } }],
})
expect(result.error).toBeUndefined()
expect(result.value).toBe(1300)
}, 30_000)
it('settles as worker-exit when a done frame lands in the same batch as a call flood', async () => {
// A done frame processed in the SAME data event as more than 1024 call
// frames settles the run before the post-macrotask check runs (which no-ops
// once settled), so a child could finish "successfully" while leaving the
// outstanding closures behind — one sub-64 KiB write carries 1025 compact
// calls plus a done. The done handler re-checks the count before accepting
// the frame, so the run settles as worker-exit with the call-backlog
// message instead.
const { runtime } = await setup({ maxWallMs: 30_000 })
const result = await runtime.run({
program: [
'import os, time',
'frame = b\'{"type":"call","id":%d,"global":"tools","name":"hang","args":{}}\\n\'',
'payload = b"".join(frame % i for i in range(1025)) + b\'{"type":"done","value":1}\\n\'',
'view = memoryview(payload)',
'while view:',
' view = view[os.write(3, view):]',
'time.sleep(30)',
'return "unreachable"',
].join('\n'),
bindings: [{ global: 'tools', functions: { hang: async () => await new Promise<never>(() => {}) } }],
})
expect(result.error?.kind).toBe('worker-exit')
expect(result.error?.message).toContain('call backlog exceeded')
}, 30_000)
it('rejects a completion whose dict keys fold to one JSON member', async () => {
// `_dump_string` folds a spelled-out surrogate pair into its astral code
// point, so `"\ud83d\ude00"` and `"\U0001f600"` are DIFFERENT Python keys
// that encode to the SAME JSON member — the host's JSON.parse would
// silently drop one of them, violating the lossless-JSON completion
// contract. The child's meter rejects the collision before encoding.
const { runtime } = await setup()
const result = await runtime.run({
program: 'return {"\\ud83d\\ude00": 1, "\\U0001f600": 2}',
bindings: [],
})
expect(result.error?.kind).toBe('invalid-output')
expect(result.error?.message).toContain('duplicate dict key')
}, 30_000)
it('rejects binding arguments whose dict keys fold to one JSON member', async () => {
// The same collision on the binding-argument path: the call is rejected as
// not lossless JSON, so the program's `await` raises and the program
// surfaces the rejection message.
const { runtime } = await setup()
const result = await runtime.run({
program: [
'try:',
' await tools.echo({"\\ud83d\\ude00": 1, "\\U0001f600": 2})',
' return "no-error"',
'except Exception as e:',
' return str(e)',
].join('\n'),
bindings: [{ global: 'tools', functions: { echo: async (args: unknown) => args as CodeJsonValue } }],
})
expect(result.error).toBeUndefined()
expect(result.value).toContain('duplicate dict key')
}, 30_000)
it('compacts the reply queue mid-drain without dropping pending frames', async () => {
// A reply larger than the writable high-water mark makes the FIRST write
// return false, suspending the drain loop; the frames queued behind it
// push the drain's consumed head past MAX_PENDING_REPLIES, so the resumed
// drain compacts the queue mid-run. The child reads fd 3 itself (blocking
// the asyncio pump, so its reads cannot race the host's pushes) and sends
// a second wave of calls AFTER reading part of the first wave's replies —
// those replies are still pending when the drain's head crosses the
// compaction bound, so a compaction that dropped pending frames would
// leave the child's reply count short and the read loop spinning to the
// wall clock. No fixed sleep: the child's reads pace at the drain's
// delivery rate (each write blocks until the child reads), and the host
// finishes pushing all of a wave within milliseconds — orders of magnitude
// before the head crosses the bound — so the queue is always full at the
// splice. Newlines are counted per chunk (each reply carries exactly one),
// never by re-scanning the accumulated total, which would be O(n²).
const { runtime } = await setup({ maxWallMs: 60_000 })
const result = await runtime.run({
program: [
'import os',
'frame = b\'{"type":"call","id":%d,"global":"tools","name":"big","args":{}}\\n\'',
'for i in range(1024):',
' view = memoryview(frame % i)',
' while view:',
' view = view[os.write(3, view):]',
'time.sleep(0.5)',
'total = b""',
'while total.count(b"\\n") < 500:',
'seen = 0',
'while seen < 500:',
' chunk = os.read(3, 65536)',
' if not chunk:',
' break',
' total += chunk',
' seen += chunk.count(b"\\n")',
'for i in range(500):',
' view = memoryview(frame % (1024 + i))',
' while view:',
' view = view[os.write(3, view):]',
'while total.count(b"\\n") < 1524:',
'while seen < 1524:',
' chunk = os.read(3, 65536)',
' if not chunk:',
' break',
' total += chunk',
' seen += chunk.count(b"\\n")',
'return "done"',
].join('\n'),
bindings: [{ global: 'tools', functions: { big: async () => 'x'.repeat(65 * 1024) } }],
})
expect(result.error).toBeUndefined()
expect(result.value).toBe('done')
}, 30_000)
}, 60_000)
it('bounds a flood of zero-byte log lines through the per-entry separator charge', async () => {
// Blank print() lines carry zero content bytes; without the +1 separator