@junghanacs/entwurf 0.12.10 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/AGENTS.md +2 -1
  2. package/BASELINE.md +45 -6
  3. package/CHANGELOG.md +16 -0
  4. package/CONTRIBUTING.md +4 -2
  5. package/DELIVERY.md +1 -1
  6. package/README.md +20 -5
  7. package/VERIFY.md +7 -4
  8. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/backend-adapter.js +148 -5
  9. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/config.js +16 -4
  10. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/models.js +66 -7
  11. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/overlay.js +190 -5
  12. package/mcp/entwurf-bridge/dist/pi-extensions/lib/acp/tool-surface.js +12 -4
  13. package/package.json +4 -2
  14. package/pi/settings.reference.json +1 -1
  15. package/pi-extensions/acp-provider.ts +20 -10
  16. package/pi-extensions/lib/acp/augment.ts +60 -2
  17. package/pi-extensions/lib/acp/backend-adapter.ts +183 -8
  18. package/pi-extensions/lib/acp/backend.ts +5 -1
  19. package/pi-extensions/lib/acp/config.ts +19 -5
  20. package/pi-extensions/lib/acp/engraving.ts +3 -1
  21. package/pi-extensions/lib/acp/event-mapper.ts +10 -3
  22. package/pi-extensions/lib/acp/models.ts +69 -7
  23. package/pi-extensions/lib/acp/overlay.ts +234 -5
  24. package/pi-extensions/lib/acp/tool-surface.ts +12 -4
  25. package/run.sh +152 -21
  26. package/scripts/check-acp-cortex.ts +668 -0
  27. package/scripts/check-acp-provider-surface.ts +50 -6
  28. package/scripts/check-acp-session-reuse.ts +64 -1
  29. package/scripts/check-gate-qualification.ts +2 -0
  30. package/scripts/check-probe-cli-shim.ts +879 -0
  31. package/scripts/check-probe-ordering.ts +2450 -0
  32. package/scripts/check-shell-quote.ts +4 -4
  33. package/scripts/fixtures/probe-cli-shim +20 -0
  34. package/scripts/fixtures/probe-mcp-server.ts +168 -12
  35. package/scripts/lib/probe-acp-turn.ts +207 -0
  36. package/scripts/lib/probe-cli-shim.ts +464 -0
  37. package/scripts/lib/probe-cli-target.ts +165 -0
  38. package/scripts/lib/probe-event-log.ts +383 -0
  39. package/scripts/lib/probe-verdict.ts +1213 -0
  40. package/scripts/mutants/acp-cortex.json +196 -0
  41. package/scripts/mutants/probe-ordering.json +1032 -0
  42. package/scripts/smoke-acp-cortex-live.ts +392 -0
  43. package/scripts/smoke-acp-ordering-probe-live.ts +848 -0
@@ -0,0 +1,879 @@
1
+ // Deterministic gate for the §11-7-c B-name-snapshot PRODUCER — the CLI shim
2
+ // (docs/acp-backend-rail.md §11-7-c, condition 5: "Byte-transparency,
3
+ // backpressure, and exit/signal propagation are proved by a fake-CLI
4
+ // deterministic gate"). THIS is that gate.
5
+ //
6
+ // The shim is an instrument that sits on the production spawn path of a LIVE,
7
+ // paid turn. Everything it can silently get wrong is a way to either destroy the
8
+ // turn it is measuring or — far worse — to FABRICATE the absence reading the
9
+ // B-name-snapshot ladder promotes. So the matrix below is organised by what a
10
+ // defect would buy:
11
+ //
12
+ // FABRICATED EVIDENCE — an init line whose `tools` is missing or mistyped must
13
+ // never be reported as an EMPTY name set, because an empty set is exactly what
14
+ // the ladder reads as "the measured id was absent". A shim that defaults to []
15
+ // manufactures the finding. Likewise the boot report must carry the REAL target
16
+ // identity: the classifier verifies it against the roster, and a fabricated
17
+ // hash would let a swapped binary vote.
18
+ //
19
+ // DESTROYED TURN — byte transparency, exit status, signal disposition, stderr,
20
+ // stdin EOF and backpressure. A wrapper that mangles a multi-byte character,
21
+ // swallows a signal, or reports exit 0 for a crash turns a measurement into an
22
+ // incident, and the operator would be debugging the CLI instead of the shim.
23
+ //
24
+ // LEAKED OPERATOR STATE — the scrub is an EXACT allowlist, and nothing about
25
+ // argv, env, auth or prompt bodies may reach the shared log.
26
+ //
27
+ // Everything here runs against FAKE CLIs — small executables written into a temp
28
+ // dir and pointed at by PROBE_SHIM_TARGET. No API, no network, no cost. The
29
+ // subject is driven as a REAL PROCESS (spawned exactly the way the SDK spawns
30
+ // the native branch: no shell, piped stdio, inherited cwd), because half of what
31
+ // this gate proves is process semantics that a unit call cannot reach.
32
+ //
33
+ // Kill-proof: scripts/mutants/probe-ordering.json carries one exact-once mutant
34
+ // per [QK:...] signature below. THREE properties are SOURCE-pinned rather than
35
+ // behaviour-pinned, and §12 states the measurement that sent each one there —
36
+ // this is the same carve-out §11-7-c already records for the fixture's and the
37
+ // shim's write-callback timing, not a softer bar invented here. The two callback
38
+ // PLACEMENTS cannot be separated from a placement just outside the callback by
39
+ // timing, because the shim pauses its source under backpressure and thereby
40
+ // couples the read to the write; the log door's `receivedAtMs ≤ tsMs` rule bounds
41
+ // them at runtime. The source of the PAUSE is pinned because peak RSS was
42
+ // measured and refused as a discriminator (numbers in §12).
43
+
44
+ import assert from "node:assert/strict";
45
+ import { spawn } from "node:child_process";
46
+ import { createHash } from "node:crypto";
47
+ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
48
+ import { tmpdir } from "node:os";
49
+ import { dirname, join, resolve } from "node:path";
50
+ import { fileURLToPath } from "node:url";
51
+ import { NdjsonLineScanner, SHIM_MAX_LINE_BYTES } from "./lib/probe-cli-shim.ts";
52
+ import { hashFileSha256, PROBE_SHIM_ENV, SDK_SCRIPT_SUFFIXES, SHIM_SCRUB_ENV_VARS } from "./lib/probe-cli-target.ts";
53
+ import { PROBE_EVENTS, type ProbeEvent, readProbeEvents } from "./lib/probe-event-log.ts";
54
+
55
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
56
+ const SHIM_LAUNCHER = join(REPO_ROOT, "scripts", "fixtures", "probe-cli-shim");
57
+ const SHIM_SRC = readFileSync(join(REPO_ROOT, "scripts", "lib", "probe-cli-shim.ts"), "utf8");
58
+ const LAUNCHER_SRC = readFileSync(SHIM_LAUNCHER, "utf8");
59
+
60
+ const TMP = mkdtempSync(join(tmpdir(), "probe-cli-shim-"));
61
+ process.on("exit", () => rmSync(TMP, { recursive: true, force: true }));
62
+
63
+ const sha256 = (buf: Buffer): string => createHash("sha256").update(buf).digest("hex");
64
+
65
+ // ---------------------------------------------------------------------------
66
+ // Fake CLIs — the stimulus side of the matrix.
67
+ // ---------------------------------------------------------------------------
68
+
69
+ // Every fake exits either NATURALLY (event loop drains only after pending pipe
70
+ // writes flush) or from inside a write callback. A bare `process.exit()` after a
71
+ // write truncates it on a pipe, which would make this gate flaky in exactly the
72
+ // dimension it is measuring — byte completeness.
73
+ function fakeCli(name: string, body: string): string {
74
+ const path = join(TMP, name);
75
+ writeFileSync(path, `#!/usr/bin/env node\n${body}`);
76
+ chmodSync(path, 0o755);
77
+ return path;
78
+ }
79
+
80
+ /** Byte mirror: whatever arrives on stdin leaves on stdout, unchanged. The
81
+ * transparency probe — the bytes cross the shim TWICE (stdin scan, stdout scan),
82
+ * so a reframe on either side shows up as a hash mismatch. */
83
+ const MIRROR_CLI = fakeCli("mirror-cli", `process.stdin.on("data", (c) => process.stdout.write(c));\n`);
84
+
85
+ /** The same mirror, but it ANNOUNCES itself first. Without a readiness signal a
86
+ * planted chunk boundary is FICTION: the gate's two writes land in the CLI's
87
+ * stdin pipe while Node is still booting (~40 ms), so the CLI's first read
88
+ * returns them coalesced and the split never reaches the shim's stdout path at
89
+ * all. That is exactly how the byte-transparency claim stayed green with a
90
+ * string-decoding shim planted — gate qualification called it WRONG-REASON and
91
+ * that is what exposed the vacuous test (2026-07-29). */
92
+ const READY_MARKER = "READY\n";
93
+ const SPLIT_MIRROR_CLI = fakeCli(
94
+ "split-mirror-cli",
95
+ `process.stdout.write(${JSON.stringify(READY_MARKER)});
96
+ process.stdin.on("data", (c) => process.stdout.write(c));
97
+ `,
98
+ );
99
+
100
+ /** A stream-json CLI shaped like the real one: a BOOT init emitted before any
101
+ * input is read, then a per-turn init + result for each `type:"user"` frame
102
+ * (acp-agent.js:1573-1587 — the SDK re-emits init per turn, which is the
103
+ * attribution §11-7-c binds to). */
104
+ const STREAM_CLI = fakeCli(
105
+ "stream-cli",
106
+ // The per-turn response is delayed 5 ms: the receive-axis binding is a STRICT
107
+ // `receivedAtMs > anchor` at millisecond resolution and a same-ms tie is the
108
+ // instrument's documented fail-closed (§11-7-c cond. 2). A localhost fake CLI
109
+ // answers a stdin frame in under a millisecond, so an immediate response
110
+ // MANUFACTURES that tie nondeterministically — measured 2026-07-29: anchor and
111
+ // per-turn receivedAtMs landed on the same ms in ~10% of runs, failing
112
+ // section 3 ahead of whatever claim the run was qualifying (WRONG-REASON
113
+ // noise across the whole mutant lane). The delay keeps the fixture on the
114
+ // section's actual subject: ordering, not tie semantics.
115
+ `const line = (o) => process.stdout.write(JSON.stringify(o) + "\\n");
116
+ line({ type: "system", subtype: "init", tools: ["BOOT_ONLY"], mcp_servers: [], model: "fake" });
117
+ let buf = "";
118
+ process.stdin.on("data", (c) => {
119
+ buf += c.toString("utf8");
120
+ let i;
121
+ while ((i = buf.indexOf("\\n")) >= 0) {
122
+ const raw = buf.slice(0, i);
123
+ buf = buf.slice(i + 1);
124
+ let o;
125
+ try { o = JSON.parse(raw); } catch { continue; }
126
+ if (o.type !== "user") continue;
127
+ setTimeout(() => {
128
+ line({ type: "system", subtype: "init", tools: ["mcp__probe__probe_nonce", "Bash"],
129
+ mcp_servers: [{ name: "probe", status: "connected" }], model: "fake" });
130
+ line({ type: "result", subtype: "success" });
131
+ }, 5);
132
+ }
133
+ });
134
+ `,
135
+ );
136
+
137
+ /** Init lines the shim must REFUSE to turn into snapshots: no `tools` at all, and
138
+ * a `tools` that is not an array of strings. Reporting either as an empty name
139
+ * set would fabricate the very absence the ladder promotes. */
140
+ const BAD_INIT_CLI = fakeCli(
141
+ "bad-init-cli",
142
+ `const line = (o) => process.stdout.write(JSON.stringify(o) + "\\n");
143
+ line({ type: "system", subtype: "init", mcp_servers: [], model: "fake" });
144
+ line({ type: "system", subtype: "init", tools: "not-an-array" });
145
+ line({ type: "system", subtype: "init", tools: ["ok", 7] });
146
+ line({ type: "result", subtype: "success" });
147
+ process.stdin.on("data", () => {});
148
+ `,
149
+ );
150
+
151
+ /** Reports its own launch facts so the gate can compare argv / cwd / env against
152
+ * what the shim was given. Not stream-json — this is also the argv-agnostic
153
+ * consumer (`claude auth logout` is the real one). */
154
+ const REPORT_CLI = fakeCli(
155
+ "report-cli",
156
+ `process.stdout.write(JSON.stringify({ argv: process.argv.slice(2), cwd: process.cwd(), env: process.env }) + "\\n");
157
+ process.stdout.write("not json at all\\n");
158
+ `,
159
+ );
160
+
161
+ const EXIT_CLI = fakeCli("exit-cli", `process.exit(Number(process.argv[2] || 0));\n`);
162
+
163
+ const SELF_SIGNAL_CLI = fakeCli(
164
+ "self-signal-cli",
165
+ `process.stdout.write("about to die\\n", () => process.kill(process.pid, process.argv[2]));\n`,
166
+ );
167
+
168
+ /** Writes a marker, then waits. Default SIGTERM disposition — no handler — so an
169
+ * inbound signal that reaches it kills it by that signal. */
170
+ const SLEEPER_CLI = fakeCli(
171
+ "sleeper-cli",
172
+ `process.stdout.write("up\\n");
173
+ setInterval(() => {}, 1000);
174
+ `,
175
+ );
176
+
177
+ const STDERR_CLI = fakeCli(
178
+ "stderr-cli",
179
+ `process.stderr.write("diagnostic tail from the CLI\\n");
180
+ process.stdout.write("out\\n", () => process.exit(3));
181
+ `,
182
+ );
183
+
184
+ /** One line larger than the shim's framing bound, followed by a perfectly good
185
+ * init line. The oversized line must lose its PARSE, never its BYTES, and the
186
+ * init line after it must still bind. */
187
+ // Derived from the shim's own cap, never hardcoded: if the bound moves, this
188
+ // stimulus has to move with it or the test quietly stops crossing it.
189
+ const OVERSIZED_LINE_BYTES = SHIM_MAX_LINE_BYTES + 1024;
190
+ const BIG_LINE_CLI = fakeCli(
191
+ "big-line-cli",
192
+ `process.stdout.write("x".repeat(${OVERSIZED_LINE_BYTES}) + "\\n");
193
+ process.stdout.write(JSON.stringify({ type: "system", subtype: "init", tools: ["AFTER_BIG"], mcp_servers: [] }) + "\\n");
194
+ process.stdin.on("data", () => {});
195
+ `,
196
+ );
197
+
198
+ /** Reads its stdin SLOWLY — parks the stream, drains later — so the shim's write
199
+ * to the child's stdin returns false and the stdin backpressure path is real.
200
+ * Echoes everything so the gate can check completeness, and exits on EOF so the
201
+ * stdin-EOF propagation is checked with it. */
202
+ const SLOW_READER_CLI = fakeCli(
203
+ "slow-reader-cli",
204
+ `const seen = [];
205
+ process.stdin.on("data", (c) => seen.push(c));
206
+ process.stdin.pause();
207
+ setTimeout(() => process.stdin.resume(), 300);
208
+ process.stdin.on("end", () => process.stdout.write(Buffer.concat(seen)));
209
+ `,
210
+ );
211
+
212
+ /** An oversized line that never meets a newline before EOF — the case a scanner
213
+ * counting only at newline boundaries under-reports. */
214
+ const UNTERMINATED_BIG_CLI = fakeCli(
215
+ "unterminated-big-cli",
216
+ `process.stdout.write("q".repeat(${OVERSIZED_LINE_BYTES}));
217
+ process.stdin.on("data", () => {});
218
+ `,
219
+ );
220
+
221
+ /** Floods stdout so the downstream reader's backpressure is real. Sized well past
222
+ * any pipe buffer AND past Node's baseline heap noise, because "bytes all
223
+ * arrived" alone cannot separate a shim that PAUSES its source from one that
224
+ * queues the whole flood in memory — both deliver every byte. The peak-RSS
225
+ * reading below is what makes the bound observable. */
226
+ const FLOOD_BYTES = 8 * 1024 * 1024;
227
+ const FLOOD_CLI = fakeCli(
228
+ "flood-cli",
229
+ `const chunk = "y".repeat(64 * 1024) + "\\n";
230
+ let written = 0;
231
+ const pump = () => {
232
+ while (written < ${FLOOD_BYTES}) {
233
+ written += chunk.length;
234
+ if (!process.stdout.write(chunk)) { process.stdout.once("drain", pump); return; }
235
+ }
236
+ process.stdout.write(JSON.stringify({ type: "system", subtype: "init", tools: ["AFTER_FLOOD"], mcp_servers: [] }) + "\\n");
237
+ };
238
+ pump();
239
+ `,
240
+ );
241
+
242
+ // ---------------------------------------------------------------------------
243
+ // Driving the subject as a real process.
244
+ // ---------------------------------------------------------------------------
245
+
246
+ interface ShimRun {
247
+ stdout: Buffer;
248
+ stderr: string;
249
+ code: number | null;
250
+ signal: NodeJS.Signals | null;
251
+ events: ProbeEvent[];
252
+ malformed: string[];
253
+ sequenceViolations: string[];
254
+ logPath: string;
255
+ diagPath: string;
256
+ /** Wall-clock the run took, so "ended promptly" is a measurable claim rather
257
+ * than an absence of complaint. */
258
+ elapsedMs: number;
259
+ /** Exactly the env the SHIM was launched with — the expectation the child's env
260
+ * is compared against, so "every other variable identical" is a claim about
261
+ * the whole object rather than about a hand-picked sample. */
262
+ launchEnv: NodeJS.ProcessEnv;
263
+ }
264
+
265
+ let runSeq = 0;
266
+
267
+ async function runShim(opts: {
268
+ target: string;
269
+ argv?: string[];
270
+ stdin?: Buffer[];
271
+ /** Env deltas applied on top of the probe trio; `undefined` DELETES a key. */
272
+ env?: Record<string, string | undefined>;
273
+ /** Hold the downstream reader paused this long, to make write backpressure real. */
274
+ holdReadsMs?: number;
275
+ /** Signal to send to the SHIM once it has produced its first stdout byte. */
276
+ signalShim?: NodeJS.Signals;
277
+ /** Destroy our READ end after the first chunk — the SDK giving up mid-turn. */
278
+ dropReaderAfterFirstChunk?: boolean;
279
+ /** Hard kill after this long. Lower it where a HANG is the defect under test,
280
+ * so a hung mutant fails fast instead of dragging the whole qualification. */
281
+ guardMs?: number;
282
+ /** Wait for the child's first stdout byte before writing stdin — the ordering a
283
+ * real turn has, where the boot init is received before the prompt is sent. */
284
+ waitForOutput?: boolean;
285
+ /** Delay between stdin chunks, so the OS delivers them as SEPARATE reads. Without
286
+ * it a pipe coalesces back-to-back writes and a chunk-boundary defect can hide. */
287
+ stdinGapMs?: number;
288
+ cwd?: string;
289
+ }): Promise<ShimRun> {
290
+ const id = ++runSeq;
291
+ const logPath = join(TMP, `events-${id}.ndjson`);
292
+ const env: NodeJS.ProcessEnv = {
293
+ ...process.env,
294
+ [PROBE_SHIM_ENV.target]: opts.target,
295
+ [PROBE_SHIM_ENV.eventLog]: logPath,
296
+ [PROBE_SHIM_ENV.runId]: `shim-run-${id}`,
297
+ };
298
+ for (const [key, value] of Object.entries(opts.env ?? {})) {
299
+ if (value === undefined) delete env[key];
300
+ else env[key] = value;
301
+ }
302
+
303
+ const child = spawn(SHIM_LAUNCHER, opts.argv ?? [], {
304
+ env,
305
+ cwd: opts.cwd ?? REPO_ROOT,
306
+ stdio: ["pipe", "pipe", "pipe"],
307
+ });
308
+ const out: Buffer[] = [];
309
+ let err = "";
310
+ let sawOutput = false;
311
+ let resolveFirst: (() => void) | undefined;
312
+ const firstOutput = new Promise<void>((res) => {
313
+ resolveFirst = res;
314
+ });
315
+
316
+ child.stdout.on("data", (chunk: Buffer) => {
317
+ out.push(chunk);
318
+ if (!sawOutput) {
319
+ sawOutput = true;
320
+ resolveFirst?.();
321
+ if (opts.signalShim) child.kill(opts.signalShim);
322
+ if (opts.dropReaderAfterFirstChunk) child.stdout.destroy();
323
+ }
324
+ });
325
+ if (opts.holdReadsMs !== undefined) {
326
+ child.stdout.pause();
327
+ setTimeout(() => child.stdout.resume(), opts.holdReadsMs);
328
+ }
329
+ child.stderr.on("data", (chunk: Buffer) => {
330
+ err += chunk.toString("utf8");
331
+ });
332
+
333
+ const closed = new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((res) => {
334
+ child.on("close", (code, signal) => res({ code, signal }));
335
+ });
336
+
337
+ if (opts.waitForOutput) await Promise.race([firstOutput, closed]);
338
+ for (const chunk of opts.stdin ?? []) {
339
+ child.stdin.write(chunk);
340
+ if (opts.stdinGapMs) await new Promise((res) => setTimeout(res, opts.stdinGapMs));
341
+ }
342
+ child.stdin.end();
343
+
344
+ const startedAtMs = Date.now();
345
+ const guard = setTimeout(() => child.kill("SIGKILL"), opts.guardMs ?? 30_000);
346
+ const { code, signal } = await closed;
347
+ clearTimeout(guard);
348
+ const elapsedMs = Date.now() - startedAtMs;
349
+
350
+ const parsed = readProbeEvents(logPath);
351
+ return {
352
+ stdout: Buffer.concat(out),
353
+ stderr: err,
354
+ code,
355
+ signal,
356
+ events: parsed.events,
357
+ malformed: parsed.malformed,
358
+ sequenceViolations: parsed.sequenceViolations,
359
+ logPath,
360
+ diagPath: `${logPath}.shim-diag`,
361
+ elapsedMs,
362
+ launchEnv: env,
363
+ };
364
+ }
365
+
366
+ const named = (run: ShimRun, event: string): ProbeEvent[] => run.events.filter((e) => e.event === event);
367
+ const ndjson = (obj: unknown): Buffer => Buffer.from(`${JSON.stringify(obj)}\n`, "utf8");
368
+ const USER_FRAME = ndjson({ type: "user", message: { role: "user", content: "probe prompt body SECRET-PROMPT-TEXT" } });
369
+ const CONTROL_FRAME = ndjson({ type: "control_request", request_id: "r1", request: { subtype: "initialize" } });
370
+
371
+ // ===========================================================================
372
+ // 1) The launcher sits on the SDK's native spawn branch
373
+ // ===========================================================================
374
+ {
375
+ assert.ok(
376
+ existsSync(SHIM_LAUNCHER) &&
377
+ SDK_SCRIPT_SUFFIXES.every((suffix) => !SHIM_LAUNCHER.endsWith(suffix)) &&
378
+ (statSync(SHIM_LAUNCHER).mode & 0o111) !== 0 &&
379
+ LAUNCHER_SRC.startsWith("#!/usr/bin/env node\n"),
380
+ "the shim launcher is extensionless, executable, and shebang-led — a script suffix would move it onto the SDK's " +
381
+ "`node|bun <path>` branch, which is NOT the branch the probe's target asserts [QK:SHIM-NATIVE-BRANCH-LAUNCHER]",
382
+ );
383
+ // The launcher must stay a launcher: behaviour belongs in the .ts SSOT, which
384
+ // is the only half tsc/biome/mutants can reach.
385
+ assert.ok(
386
+ LAUNCHER_SRC.includes('import { runProbeCliShim } from "../lib/probe-cli-shim.ts";') &&
387
+ LAUNCHER_SRC.includes("runProbeCliShim();"),
388
+ "the extensionless launcher only delegates to the typechecked .ts implementation",
389
+ );
390
+ }
391
+
392
+ // ===========================================================================
393
+ // 2) Byte transparency — adversarial framing must not reach the wire
394
+ // ===========================================================================
395
+ {
396
+ // Mid-UTF8 splits, CRLF framing, an unterminated final line, and a line far
397
+ // past any chunk size. Every one of these is a place a string-decoding
398
+ // passthrough silently corrupts bytes (replacement characters) or a naive
399
+ // line-reassembler re-frames the stream.
400
+ const payload = Buffer.concat([
401
+ Buffer.from('{"type":"user","message":"한글과 이모지 🌙🚀 그리고 ünïcödé"}\n', "utf8"),
402
+ Buffer.from('{"type":"other","crlf":true}\r\n', "utf8"),
403
+ Buffer.from(`{"type":"filler","big":"${"z".repeat(300_000)}"}\n`, "utf8"),
404
+ Buffer.from('{"type":"unterminated","newline":false}', "utf8"),
405
+ ]);
406
+ // Split at 7 bytes — coprime with everything, so multi-byte sequences and the
407
+ // CRLF pair both land across chunk boundaries.
408
+ const chunks: Buffer[] = [];
409
+ for (let i = 0; i < payload.length; i += 7) chunks.push(payload.subarray(i, i + 7));
410
+
411
+ // THE decisive run first, so a defect dies at its own signature: a pipe
412
+ // coalesces back-to-back writes, so the bulk run below may never deliver the
413
+ // boundary it planted. Here the two halves of one multi-byte character are
414
+ // written with a gap wide enough that the OS delivers two separate reads —
415
+ // this is what separates a byte-forwarding shim from a string-decoding one,
416
+ // which turns the split character into U+FFFD and moves the hash.
417
+ const multiByte = Buffer.from('{"t":"한 🌙"}\n', "utf8");
418
+ // Derive the cut rather than hardcoding an offset: the first CONTINUATION byte
419
+ // (10xxxxxx) is by definition inside a multi-byte sequence, so this stays a
420
+ // real mid-character split no matter how the payload above is edited.
421
+ const cut = multiByte.findIndex((byte) => (byte & 0xc0) === 0x80);
422
+ assert.ok(cut > 0, "the payload really carries a multi-byte sequence to split");
423
+ const split = await runShim({
424
+ target: SPLIT_MIRROR_CLI,
425
+ stdin: [multiByte.subarray(0, cut), multiByte.subarray(cut)],
426
+ stdinGapMs: 25,
427
+ waitForOutput: true,
428
+ });
429
+ const ready = Buffer.from(READY_MARKER, "utf8");
430
+ assert.ok(
431
+ split.stdout.subarray(0, ready.length).equals(ready) && split.stdout.length > ready.length,
432
+ "the split mirror announced itself and only THEN received the halves — the boundary was planted against a " +
433
+ "running reader, not against a pipe buffer filling while Node booted",
434
+ );
435
+ assert.equal(
436
+ sha256(split.stdout.subarray(ready.length)),
437
+ sha256(multiByte),
438
+ "a chunk boundary INSIDE a multi-byte UTF-8 sequence, delivered as two separate reads, still crosses the shim " +
439
+ "byte-for-byte — the scanner frames on bytes and only complete lines are ever decoded " +
440
+ "[QK:SHIM-BYTE-TRANSPARENCY]",
441
+ );
442
+
443
+ // Bulk framing, on top: CRLF pairs, a 300 KB line, and a final line with no
444
+ // newline at all — the shapes a naive line-reassembler re-frames.
445
+ const bulk = await runShim({ target: MIRROR_CLI, stdin: chunks });
446
+ assert.equal(bulk.code, 0, "the mirror CLI exits cleanly through the shim");
447
+ assert.equal(
448
+ sha256(bulk.stdout),
449
+ sha256(payload),
450
+ "bulk framing survives: CRLF, a 300 KB line and an unterminated final line all cross both scan sides unchanged",
451
+ );
452
+ assert.deepEqual(
453
+ [bulk.malformed, bulk.sequenceViolations],
454
+ [[], []],
455
+ "the shim's own events clear both log doors on a transparency run",
456
+ );
457
+ }
458
+
459
+ // ===========================================================================
460
+ // 3) The ordinal anchor counts PROMPT frames, not stdin lines
461
+ // ===========================================================================
462
+ {
463
+ const run = await runShim({
464
+ target: STREAM_CLI,
465
+ stdin: [CONTROL_FRAME, USER_FRAME],
466
+ waitForOutput: true,
467
+ });
468
+ const forwarded = named(run, PROBE_EVENTS.shimPromptForwarded);
469
+ assert.ok(
470
+ forwarded.length === 1 && forwarded[0].ordinal === 1,
471
+ "the control-request frame is NOT a prompt: the SDK writes initialize control traffic on the same stdin, so " +
472
+ 'counting lines would blow the exactly-one binding on every run — only `type:"user"` frames take an ' +
473
+ "ordinal [QK:SHIM-PROMPT-ORDINAL-USER-FRAMES-ONLY]",
474
+ );
475
+
476
+ // The boot init was RECEIVED before the prompt frame was forwarded, so it is a
477
+ // legal non-candidate; the per-turn init is the one that binds. This is the
478
+ // receive-axis binding the consumer half enforces — proved here end to end.
479
+ const snapshots = named(run, PROBE_EVENTS.shimInitSnapshot);
480
+ const anchorMs = forwarded[0].tsMs;
481
+ const candidates = snapshots.filter((s) => (s.receivedAtMs as number) > anchorMs);
482
+ assert.ok(
483
+ snapshots.length === 2 &&
484
+ (snapshots[0].tools as string[]).includes("BOOT_ONLY") &&
485
+ candidates.length === 1 &&
486
+ (candidates[0].tools as string[]).includes("mcp__probe__probe_nonce"),
487
+ "both init lines are recorded, and exactly the per-turn one is RECEIVED after the prompt anchor",
488
+ );
489
+ assert.ok(
490
+ snapshots.every((s) => (s.receivedAtMs as number) <= s.tsMs),
491
+ "the snapshot interval is sane on every line: receive stamp never after the envelope stamp",
492
+ );
493
+ }
494
+
495
+ // ===========================================================================
496
+ // 4) A malformed init is NOT an empty name set
497
+ // ===========================================================================
498
+ {
499
+ const run = await runShim({ target: BAD_INIT_CLI, stdin: [USER_FRAME] });
500
+ assert.equal(
501
+ named(run, PROBE_EVENTS.shimInitSnapshot).length,
502
+ 0,
503
+ "an init line with no `tools`, a non-array `tools`, or a `tools` holding a non-string yields NO snapshot: " +
504
+ "reporting it as an EMPTY name set would FABRICATE the absence reading the B-name-snapshot ladder promotes " +
505
+ "[QK:SHIM-INIT-REQUIRES-STRING-TOOL-ARRAY]",
506
+ );
507
+ }
508
+
509
+ // ===========================================================================
510
+ // 5) Boot identity — the fact the classifier verifies against the roster
511
+ // ===========================================================================
512
+ {
513
+ const run = await runShim({ target: STREAM_CLI, stdin: [USER_FRAME], waitForOutput: true });
514
+ const boots = named(run, PROBE_EVENTS.shimBoot);
515
+ assert.ok(
516
+ boots.length === 1 && boots[0].targetPath === STREAM_CLI && boots[0].targetSha256 === hashFileSha256(STREAM_CLI),
517
+ "the shim boots exactly once and reports the REAL path + content hash of what it exec'd — condition 5 has the " +
518
+ "classifier verify this against the roster's expected identity, so a fabricated or omitted hash would let a " +
519
+ "swapped binary vote in the pair [QK:SHIM-BOOT-TARGET-IDENTITY]",
520
+ );
521
+ assert.ok(
522
+ boots[0].seq < named(run, PROBE_EVENTS.shimInitSnapshot)[0].seq,
523
+ "the boot marker is appended before any snapshot — a spawn that dies still leaves the instrument's presence",
524
+ );
525
+ }
526
+
527
+ // ===========================================================================
528
+ // 6) argv / cwd fidelity, the exact-allowlist scrub, and argv-agnostic passthrough
529
+ // ===========================================================================
530
+ {
531
+ const decoyEnv = {
532
+ PROBE_KEEP_ME: "operator-owned-value",
533
+ ENTWURF_SHIM_DECOY: "another-operator-value",
534
+ CLAUDE_CODE_EXECUTABLE: SHIM_LAUNCHER,
535
+ };
536
+ const argv = ["auth", "logout", "--flag=value with spaces", "-x"];
537
+ const cwd = join(TMP, "session-cwd");
538
+ mkdirSync(cwd, { recursive: true });
539
+
540
+ const run = await runShim({ target: REPORT_CLI, argv, cwd, env: decoyEnv });
541
+ assert.equal(run.code, 0, "the report CLI exits cleanly through the shim");
542
+ const reported = JSON.parse(run.stdout.toString("utf8").split("\n")[0]) as {
543
+ argv: string[];
544
+ cwd: string;
545
+ env: Record<string, string>;
546
+ };
547
+
548
+ assert.deepEqual(reported.argv, argv, "argv reaches the real CLI unchanged, including a value carrying spaces");
549
+ assert.equal(reported.cwd, cwd, "the child inherits the shim's cwd — a relative target would resolve against it");
550
+
551
+ for (const scrubbed of SHIM_SCRUB_ENV_VARS) {
552
+ assert.ok(
553
+ !Object.hasOwn(reported.env, scrubbed),
554
+ `${scrubbed} is removed from the child env — leaving CLAUDE_CODE_EXECUTABLE would re-propagate the override ` +
555
+ "to grandchildren (recursion, or a sub-agent measured through a second shim)",
556
+ );
557
+ }
558
+ // "Every OTHER variable is identical" is a claim about the whole object, so it
559
+ // is checked as one: build the expectation from the env the shim was launched
560
+ // with, remove exactly the allowlist, and compare the child's entire env to it.
561
+ // A sampled comparison (two decoys + PATH) stayed green while an implementation
562
+ // deleted, say, HOME — the gate was weaker than the sentence it was defending
563
+ // (adversarial review 2026-07-29).
564
+ const expectedChildEnv: Record<string, string | undefined> = { ...run.launchEnv };
565
+ for (const scrubbed of SHIM_SCRUB_ENV_VARS) delete expectedChildEnv[scrubbed];
566
+ assert.deepEqual(
567
+ reported.env,
568
+ expectedChildEnv,
569
+ "the child's env is the launch env MINUS exactly the allowlist and nothing else — every remaining key and value " +
570
+ "byte-identical. A prefix scrub would eat the PROBE_-shaped operator variable this probe has no claim on, and " +
571
+ "an incidental deletion anywhere else would move this too [QK:SHIM-SCRUB-EXACT-ALLOWLIST]",
572
+ );
573
+
574
+ // The `claude auth logout` consumer: not a stream-json turn, so the only line
575
+ // the shim may log is its boot marker.
576
+ assert.ok(
577
+ run.events.length === 1 && run.events[0].event === PROBE_EVENTS.shimBoot,
578
+ "an invocation that is not a stream-json turn is PURE passthrough — `claudeCliPath()` has a second consumer " +
579
+ "(claude auth logout), and the shim must assume nothing about its argv [QK:SHIM-ARGV-AGNOSTIC-PASSTHROUGH]",
580
+ );
581
+ }
582
+
583
+ // ===========================================================================
584
+ // 7) Nothing about argv, env, or the prompt body reaches the log
585
+ // ===========================================================================
586
+ {
587
+ const secretArg = "--secret-argv-token-9f3a";
588
+ const secretEnv = "SECRET-ENV-VALUE-4c7b";
589
+ const run = await runShim({
590
+ target: STREAM_CLI,
591
+ argv: [secretArg],
592
+ stdin: [USER_FRAME],
593
+ env: { ENTWURF_SHIM_SECRET: secretEnv },
594
+ waitForOutput: true,
595
+ });
596
+ const logText = readFileSync(run.logPath, "utf8");
597
+ assert.ok(
598
+ !logText.includes(secretArg) &&
599
+ !logText.includes(secretEnv) &&
600
+ !logText.includes("SECRET-PROMPT-TEXT") &&
601
+ !logText.includes("ENTWURF_SHIM_SECRET"),
602
+ "the shared log carries no argv, no env name or value, and no prompt body — only the allowlisted init fields, " +
603
+ "an ordinal, and timings [QK:SHIM-LOG-PRIVACY]",
604
+ );
605
+ }
606
+
607
+ // ===========================================================================
608
+ // 8) Exit status, signal disposition, stderr
609
+ // ===========================================================================
610
+ {
611
+ const nonzero = await runShim({ target: EXIT_CLI, argv: ["42"] });
612
+ assert.ok(
613
+ nonzero.code === 42 && nonzero.signal === null,
614
+ "a nonzero CLI exit reaches the parent as the SAME code — a wrapper that reports 0 turns a crash into a " +
615
+ "measurement [QK:SHIM-EXIT-CODE-FIDELITY]",
616
+ );
617
+
618
+ const signalled = await runShim({ target: SELF_SIGNAL_CLI, argv: ["SIGKILL"] });
619
+ assert.ok(
620
+ signalled.signal === "SIGKILL" && signalled.code === null,
621
+ "a CLI that dies on a signal makes the SHIM die on that same signal: the parent's wait status must carry the " +
622
+ "child's real disposition, not a synthesised 128+n exit code [QK:SHIM-SIGNAL-RERAISE]",
623
+ );
624
+
625
+ const inbound = await runShim({ target: SLEEPER_CLI, signalShim: "SIGTERM" });
626
+ assert.ok(
627
+ inbound.signal === "SIGTERM",
628
+ "a signal sent to the shim is forwarded to the child, whose death then re-raises it here — otherwise the ACP " +
629
+ "child's teardown would leave the real CLI orphaned [QK:SHIM-INBOUND-SIGNAL-FORWARDED]",
630
+ );
631
+
632
+ const stderrRun = await runShim({ target: STDERR_CLI });
633
+ assert.ok(
634
+ stderrRun.stderr.includes("diagnostic tail from the CLI") &&
635
+ stderrRun.stdout.toString("utf8") === "out\n" &&
636
+ stderrRun.code === 3,
637
+ "stderr passes through untouched (the SDK reads a stderr tail for its own diagnostics) and does not leak into stdout",
638
+ );
639
+ }
640
+
641
+ // ===========================================================================
642
+ // 9) Spawn failures are NAMED, not swallowed
643
+ // ===========================================================================
644
+ {
645
+ const missing = await runShim({ target: join(TMP, "no-such-cli") });
646
+ assert.ok(
647
+ missing.code === 127 &&
648
+ missing.stderr.includes("ENOENT") &&
649
+ missing.stderr.includes("[probe-cli-shim] cannot read exec target"),
650
+ "a target that vanished under the pair fails loud with the shell convention for not-found — the runner asserted " +
651
+ "it was present, so reaching this is a fact the operator needs in words, on ONE errno mapping shared by the " +
652
+ "unreadable-at-hash and unspawnable-at-exec paths [QK:SHIM-SPAWN-ERROR-NAMED]",
653
+ );
654
+ assert.equal(
655
+ named(missing, PROBE_EVENTS.shimBoot).length,
656
+ 0,
657
+ "a target with no readable content has no knowable identity, so NO boot marker is written: a placeholder hash " +
658
+ "would fabricate the very fact condition 5 has the classifier verify, and the absence is already named " +
659
+ "upstream as snapshot-instrument-absent",
660
+ );
661
+
662
+ const noexec = join(TMP, "not-executable-cli");
663
+ writeFileSync(noexec, "#!/bin/sh\nexit 0\n");
664
+ chmodSync(noexec, 0o644);
665
+ const denied = await runShim({ target: noexec });
666
+ assert.ok(
667
+ denied.code === 126 && denied.stderr.includes("EACCES") && denied.stderr.includes("cannot execute"),
668
+ "a readable but non-executable target hashes fine and then fails at the spawn — EACCES as 126",
669
+ );
670
+ assert.equal(
671
+ named(denied, PROBE_EVENTS.shimBoot).length,
672
+ 1,
673
+ "that path DID know the identity, so the boot marker landed before the failed spawn — the instrument's presence " +
674
+ "is recorded even when the CLI never ran",
675
+ );
676
+ }
677
+
678
+ // ===========================================================================
679
+ // 10) The framing buffer is bounded, and a skipped parse never costs bytes
680
+ // ===========================================================================
681
+ {
682
+ const run = await runShim({ target: BIG_LINE_CLI, stdin: [USER_FRAME] });
683
+ const expected = Buffer.concat([
684
+ Buffer.from("x".repeat(OVERSIZED_LINE_BYTES), "utf8"),
685
+ Buffer.from("\n", "utf8"),
686
+ ndjson({ type: "system", subtype: "init", tools: ["AFTER_BIG"], mcp_servers: [] }),
687
+ ]);
688
+ const snapshots = named(run, PROBE_EVENTS.shimInitSnapshot);
689
+ assert.ok(
690
+ run.code === 0 &&
691
+ sha256(run.stdout) === sha256(expected) &&
692
+ snapshots.length === 1 &&
693
+ (snapshots[0].tools as string[])[0] === "AFTER_BIG" &&
694
+ existsSync(run.diagPath),
695
+ "a line past the framing bound loses its PARSE, never its BYTES: the oversized line is forwarded verbatim, the " +
696
+ "scanner recovers at the next newline so the following init still binds, and the skip is recorded in a " +
697
+ "forensic sidecar rather than as an unknown marker the log door would call MALFORMED " +
698
+ "[QK:SHIM-OVERSIZED-LINE-PARSE-SKIP]",
699
+ );
700
+ const diag = JSON.parse(readFileSync(run.diagPath, "utf8").trim()) as Record<string, unknown>;
701
+ assert.ok(
702
+ diag.stdoutLineParseSkipped === 1 && diag.stdinLineParseSkipped === 0,
703
+ "the sidecar names WHICH side skipped, so a missing snapshot is diagnosable as a bound hit rather than silence",
704
+ );
705
+ }
706
+
707
+ // ===========================================================================
708
+ // 10b) The framing bound holds in OBJECTS, not just bytes
709
+ // ===========================================================================
710
+ {
711
+ // A peer that writes one byte at a time reaches the byte cap having caused one
712
+ // retention per read. That is the shape a piece-list scanner walks straight
713
+ // through while still passing every byte-cap assertion above, so the object
714
+ // cost is measured here directly on the scanner.
715
+ const scanner = new NdjsonLineScanner();
716
+ const lineBytes = 256 * 1024;
717
+ const one = Buffer.alloc(1, 0x61);
718
+ let framed: Buffer | undefined;
719
+ for (let i = 0; i < lineBytes; i += 1) scanner.feed(one, () => {});
720
+ scanner.feed(Buffer.from("\n", "utf8"), (line) => {
721
+ framed = Buffer.from(line);
722
+ });
723
+ assert.ok(
724
+ framed !== undefined && framed.length === lineBytes,
725
+ "a line delivered one byte at a time is still framed whole and intact",
726
+ );
727
+ // Re-run to read the counter at its peak: geometric growth over 256 KiB from a
728
+ // 64 KiB floor is a handful of allocations; per-read retention would be 262144.
729
+ const counted = new NdjsonLineScanner();
730
+ for (let i = 0; i < lineBytes; i += 1) counted.feed(one, () => {});
731
+ assert.ok(
732
+ counted.retainedAllocations > 0 && counted.retainedAllocations <= 32,
733
+ `framing a ${lineBytes / 1024} KiB line out of ${lineBytes} single-byte reads cost ` +
734
+ `${counted.retainedAllocations} allocations — the bound is a bound on OBJECTS as well as bytes, or a hostile ` +
735
+ "stream reaches the byte cap holding millions of buffer headers and the 'bounded in-memory line buffer' " +
736
+ "claim is false exactly where it matters [QK:SHIM-FRAMING-BOUNDED-IN-OBJECTS]",
737
+ );
738
+ }
739
+
740
+ // ===========================================================================
741
+ // 11) Backpressure: a slow reader costs latency, never bytes
742
+ // ===========================================================================
743
+ {
744
+ const run = await runShim({ target: FLOOD_CLI, holdReadsMs: 300 });
745
+ const tail = ndjson({ type: "system", subtype: "init", tools: ["AFTER_FLOOD"], mcp_servers: [] });
746
+ // The flood writes whole 64 KiB+1 chunks until it has passed FLOOD_BYTES, so
747
+ // the exact length is the first multiple of the chunk size at or above it —
748
+ // derived here rather than hardcoded, and every byte of it is accounted for.
749
+ const chunkBytes = 64 * 1024 + 1;
750
+ const floodBytes = Math.ceil(FLOOD_BYTES / chunkBytes) * chunkBytes;
751
+ assert.equal(run.code, 0, "the flooding CLI exits cleanly through a stalled reader");
752
+ assert.ok(
753
+ run.stdout.length === floodBytes + tail.length &&
754
+ run.stdout.subarray(floodBytes).equals(tail) &&
755
+ named(run, PROBE_EVENTS.shimInitSnapshot).length === 1,
756
+ "a downstream reader that stalls for 300 ms costs latency, never bytes: every flooded byte still arrives, in " +
757
+ "order, and the init line written after the stall still binds",
758
+ );
759
+ }
760
+
761
+ // ===========================================================================
762
+ // 11b) stdin backpressure and EOF, and a downstream that dies mid-turn
763
+ // ===========================================================================
764
+ {
765
+ // The CLI parks its stdin for 300 ms, so the shim's write to the child returns
766
+ // false and the stdin-side pause/resume path actually runs. CP2 asked for stdin
767
+ // EOF and backpressure together, and they are one story: the shim must hold the
768
+ // upstream, resume on drain, and then still close the child's stdin so the CLI
769
+ // sees EOF and exits.
770
+ const payload = Buffer.alloc(2 * 1024 * 1024, 0x6b);
771
+ const chunks: Buffer[] = [];
772
+ for (let i = 0; i < payload.length; i += 64 * 1024) chunks.push(payload.subarray(i, i + 64 * 1024));
773
+ const slow = await runShim({ target: SLOW_READER_CLI, stdin: chunks });
774
+ assert.ok(
775
+ slow.code === 0 && sha256(slow.stdout) === sha256(payload),
776
+ "a CLI that parks its stdin gets every byte anyway, and stdin EOF still propagates so it exits — the shim holds " +
777
+ "its upstream under child-stdin backpressure instead of dropping or queueing without bound",
778
+ );
779
+
780
+ // The SDK giving up mid-turn: our read end dies while the CLI is still
781
+ // flooding. The shim can no longer deliver anything, so it must TEAR DOWN
782
+ // rather than park its source waiting for a 'drain' that a dead stream will
783
+ // never emit — a bare error-ignore turns a closed consumer into a hung
784
+ // instrument holding a live CLI open (adversarial review 2026-07-29).
785
+ const dropped = await runShim({ target: FLOOD_CLI, dropReaderAfterFirstChunk: true, guardMs: 8_000 });
786
+ assert.ok(
787
+ dropped.elapsedMs < 4_000,
788
+ `the shim took ${dropped.elapsedMs} ms to end after its downstream reader vanished. Measured dispositions: ` +
789
+ "tearing down on the error ends it in ~0.2 s, while merely IGNORING the error parks the source on a drain " +
790
+ "that can never arrive and it survives until something kills it — with the real CLI still alive behind it. " +
791
+ "Promptness is the discriminator; the exit disposition is not, because the ignore path can also die of its " +
792
+ "own unhandled error [QK:SHIM-DOWNSTREAM-DEATH-TEARS-DOWN]",
793
+ );
794
+ }
795
+
796
+ // ===========================================================================
797
+ // 11c) An oversized line with no terminator is still reported
798
+ // ===========================================================================
799
+ {
800
+ const run = await runShim({ target: UNTERMINATED_BIG_CLI, stdin: [USER_FRAME] });
801
+ const expected = Buffer.alloc(OVERSIZED_LINE_BYTES, 0x71);
802
+ assert.ok(
803
+ run.code === 0 && sha256(run.stdout) === sha256(expected),
804
+ "an oversized UNTERMINATED line still crosses byte-for-byte",
805
+ );
806
+ const diag = existsSync(run.diagPath)
807
+ ? (JSON.parse(readFileSync(run.diagPath, "utf8").trim()) as Record<string, unknown>)
808
+ : undefined;
809
+ assert.ok(
810
+ diag !== undefined && diag.stdoutLineParseSkipped === 1,
811
+ "a line that blew the framing bound and then hit EOF WITHOUT a newline is still counted: the scanner finalises at " +
812
+ "stream end, so the skip diagnostic cannot silently under-report the one shape that never meets a newline " +
813
+ "[QK:SHIM-OVERSIZE-COUNTED-AT-EOF]",
814
+ );
815
+ }
816
+
817
+ // ===========================================================================
818
+ // 12) Source pins — properties no cheap behaviour here can separate
819
+ // ===========================================================================
820
+ {
821
+ // WHY the source of the pause is pinned instead of measured: byte completeness
822
+ // above cannot tell a shim that pauses its SOURCE from one that queues the
823
+ // whole transcript, because both deliver every byte. Peak RSS was tried as the
824
+ // discriminator and REFUSED on measurement — against an 8x larger flood the
825
+ // pausing form read 173–217 MiB and the queueing form 243–252 MiB on this host,
826
+ // overlapping ranges dominated by GC timing rather than by held data. Shipping
827
+ // that as a threshold would have bought a flaky gate, not a proof.
828
+ assert.ok(
829
+ SHIM_SRC.includes(
830
+ " if (!flushed) {\n" +
831
+ " childStdout.pause();\n" +
832
+ ' process.stdout.once("drain", () => childStdout.resume());\n' +
833
+ " }",
834
+ ),
835
+ "under write backpressure on STDOUT the shim stops READING the CLI rather than letting the writable queue grow — " +
836
+ "peak memory is then set by the pipe buffers, not by the transcript size, which is what keeps an instrument " +
837
+ "on a live turn from becoming a memory hazard [QK:SHIM-STDOUT-BACKPRESSURE-PAUSES-SOURCE]",
838
+ );
839
+ assert.ok(
840
+ SHIM_SRC.includes(
841
+ " if (!flushed) {\n" +
842
+ " process.stdin.pause();\n" +
843
+ ' childStdin.once("drain", () => process.stdin.resume());\n' +
844
+ " }",
845
+ ),
846
+ "the SAME discipline holds on the stdin side: a CLI slow to read its input must park the SDK's stream, not be " +
847
+ "absorbed into this process's memory. 11b proves the bytes and the EOF survive it; which side does the " +
848
+ "parking is what is pinned here [QK:SHIM-STDIN-BACKPRESSURE-PAUSES-SOURCE]",
849
+ );
850
+ assert.ok(
851
+ SHIM_SRC.includes(
852
+ "const flushed = process.stdout.write(chunk, (err) => {\n" +
853
+ "\t\t\tif (err) return;\n" +
854
+ "\t\t\tfor (const snapshot of snapshots) emit(PROBE_EVENTS.shimInitSnapshot, { ...snapshot });\n" +
855
+ "\t\t});",
856
+ ),
857
+ "the snapshot append lives INSIDE the downstream write callback, so the one clock read that stamps the event IS " +
858
+ "the hand-off moment — the interval's single-SSOT end (§11-7-c condition 6). Timing cannot separate this " +
859
+ "placement from one just outside the callback, because the shim pauses its source under backpressure and " +
860
+ "couples the read to the write, so the shape is pinned here [QK:SHIM-SNAPSHOT-IN-WRITE-CALLBACK]",
861
+ );
862
+ assert.ok(
863
+ SHIM_SRC.includes(
864
+ "const flushed = childStdin.write(chunk, (err) => {\n" +
865
+ "\t\t\tif (err) return;\n" +
866
+ "\t\t\tfor (const ordinal of ordinals) emit(PROBE_EVENTS.shimPromptForwarded, { ordinal });\n" +
867
+ "\t\t});",
868
+ ),
869
+ "the prompt anchor is stamped inside the CHILD-STDIN write callback — 'fully passed to the CLI's stdin', not " +
870
+ "'we saw a newline'; and an errored write never stamps a hand-off that did not happen " +
871
+ "[QK:SHIM-PROMPT-IN-WRITE-CALLBACK]",
872
+ );
873
+ assert.ok(
874
+ SHIM_SRC.includes('stdio: ["pipe", "pipe", "inherit"],') && !/\bshell\s*:/.test(SHIM_SRC),
875
+ "the shim spawns with no shell and hands the child its own fd 2 — exact stderr passthrough, no buffering",
876
+ );
877
+ }
878
+
879
+ console.log("[check-probe-cli-shim] OK — §11-7-c producer matrix green (fake CLIs only, no API)");