From 8976f3d25c17c4c9c05f71f8e2ba7436b457de12 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 27 Sep 2026 20:28:03 -0500 Subject: [PATCH] test(runtime): qualify near-limit bounded journal frames Exercise a 190 MiB journal made of individually bounded event frames and document off-thread proof quotas, retained evidence, and nonblocking file admission. Co-Authored-By: Paperclip --- .../paperclip-runner-compatibility.md | 25 ++++++++++++------- .../native-journal-projection-async.test.ts | 18 ++++++++++--- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/doc/architecture/paperclip-runner-compatibility.md b/doc/architecture/paperclip-runner-compatibility.md index 59a047303e5..2824fdb3121 100644 --- a/doc/architecture/paperclip-runner-compatibility.md +++ b/doc/architecture/paperclip-runner-compatibility.md @@ -220,18 +220,25 @@ Catalog generation and production authorization are separate steps. The durable writer and controller accept the same 192 MiB raw journal limit. Identity, cleanup, warm-transition, and quiescent recovery reads scan the JSON -with a fixed 48 KiB input buffer. They retain only the fields used to prove +off the API event loop with a fixed 48 KiB input buffer. A single worker admits +at most 32 queued proofs and enforces a 30-second deadline including queue time. +An expired or failed worker is terminated and joined before another job starts. +Its V8 heap is capped independently of the controller. Idle workers are unrefed +and stopped after 15 seconds. They retain only the fields used to prove ownership, completion, and settled side effects. Generic tool output and task -text do not become a second in-memory journal. One synchronous scan runs at a -time; every retained node and string shares an 8 MiB conservative allocation -budget, including fields materialized after command or event classification. +text do not become a second in-memory journal. The proof's retained JSON +representation is capped at 8 MiB; discarded values, temporary property keys, +and superseded duplicate keys do not consume that budget. Worker heap limits +separately bound the cost of objects and token decoding. Proof reads validate discarded JSON as well as retained fields. They preserve -original types, duplicate-key last-wins behavior, complete semantic evidence, -and complete `run.attach` commands and results. The full raw file digest covers -all omitted bytes. Warm-transition fingerprints keep their existing byte-exact -algorithm. Symlinks, changing files, invalid JSON, excessive nesting, or excess -essential evidence fail closed. A projection is read-only evidence and must +original types, duplicate-key last-wins behavior, semantic completion evidence, +negative replay evidence, provider process owners, and complete `run.attach` +commands and results. The full raw file digest covers all omitted bytes. +Warm-transition fingerprints keep their existing byte-exact +algorithm. Nonblocking opens reject special files before a worker can hang in +an operating-system read. Symlinks, changing files, invalid JSON, excessive +nesting, or excess essential evidence fail closed. A projection is read-only evidence and must never be written back as a replacement journal. ## Required compatibility matrix diff --git a/server/src/services/native-runtime/native-journal-projection-async.test.ts b/server/src/services/native-runtime/native-journal-projection-async.test.ts index 3ebc6c48db6..77f576ccffd 100644 --- a/server/src/services/native-runtime/native-journal-projection-async.test.ts +++ b/server/src/services/native-runtime/native-journal-projection-async.test.ts @@ -34,11 +34,21 @@ function fixture(historyMiB = 0) { const fd = openSync(path, "w"); writeSync( fd, - '{"schema":"paperclip.runner.durable.control-plane-state.v1","identity":{"runId":"bounded"},"connectionCount":0,"commands":[],"committedEvents":[{"envelope":{"payload":{"eventType":"item.completed","payload":{"text":"', + '{"schema":"paperclip.runner.durable.control-plane-state.v1","identity":{"runId":"bounded"},"connectionCount":0,"commands":[],"committedEvents":[', ); - const chunk = Buffer.alloc(1024 * 1024, 120); - for (let i = 0; i < historyMiB; i++) writeSync(fd, chunk); - writeSync(fd, '"}}}}]}'); + // A near-limit journal consists of individually bounded event frames, not + // one oversized output that the writer could never accept. + const chunk = Buffer.alloc(512 * 1024, 120); + for (let i = 0; i < Math.max(1, historyMiB * 2); i++) { + if (i) writeSync(fd, ","); + writeSync( + fd, + '{"envelope":{"payload":{"eventType":"item.completed","payload":{"text":"', + ); + if (historyMiB) writeSync(fd, chunk); + writeSync(fd, '"}}}}'); + } + writeSync(fd, "]}"); closeSync(fd); return { root, path }; }