Issue 18799 — evidence capture, 2026-10-21 UTC BASE: 70e5e384dab96a1d1e4a253ee5a9415ed5d8ce30 REQUESTED INITIAL HEAD: 47c49b60d9271020bf21323767045d788b9955e6 VERIFIED UPDATED HEAD: c64f3d8d473f5e4da1b7cc6848eda69d06a4b93d The lead advanced HEAD during capture to fix the decode return type. Final green and runtime runs use c64f3d8d47. No claim that 47c49b60d9 typechecks. ENVIRONMENT: Linux 6.1.102 x86_64 GNU/Linux; Node v24.21.0; V8 13.6.233.17-node.53; effect 4.0.5; vite-plus 1.3.0; vite 8.3.4; vitest 5.0.5. Checkout: /home/ubuntu/wt/issue-18799. Installed dependencies reused; no installation, browser or application server started. All inputs synthetic. No credentials, real conversation data, or environment variable dumps included. RUNTIME COMMANDS cwd /home/ubuntu/wt/issue-18799 at c64f3d8d47: cp /tmp/18799-evidence/runtime-evidence.ts packages/effect-acp/issue18799-runtime-evidence.ts node --expose-gc packages/effect-acp/issue18799-runtime-evidence.ts > /tmp/18799-evidence/runtime-final.log 2>&1 EXIT: 0 rm packages/effect-acp/issue18799-runtime-evidence.ts The temporary script sits in-package only for module resolution; its complete source is appended below. It never listens on a port or starts the T3 server. All protocol delivery milestones use Queue or Deferred, without sleep/poll loops. One setImmediate crosses a JS job boundary before explicit GC (WeakRef liveness rule); no time-based readiness assumption. ACTUAL OUTPUT {"case":"retention newest suffix","count":32,"payloadMiB":1,"indexes":[18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"retainedLineChars":15729705,"limitChars":16777216,"callbacks":32} {"case":"retention newest suffix","count":100,"payloadMiB":1,"indexes":[86,87,88,89,90,91,92,93,94,95,96,97,98,99,100],"retainedLineChars":15729706,"limitChars":16777216,"callbacks":100} {"case":"retention newest suffix","count":40,"payloadMiB":8,"indexes":[40],"retainedLineChars":8388679,"limitChars":16777216,"callbacks":40} {"case":"oversized newest retained; next small replaces oversized","oversizedChars":17825792,"alone":[1],"afterSmall":[3]} {"case":"count cap","sent":40,"indexes":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40]} {"case":"sustained active consumer","sent":256,"callbacks":256,"delivered":256,"ordered":true} {"case":"slow consumer drain/refill","rounds":8,"callbacks":328,"delivered":264,"discardedOldest":64,"ordered":true} {"case":"typed routes and raw incoming caller","tags":["SessionUpdate","ElicitationComplete","ElicitationComplete","ExtNotification"],"methods":["session/update","elicitation/complete","session/elicitation/complete","x/evidence"]} {"case":"EOF releases pending extension request","outboundMethod":"x/pending","exit":"Failure"} {"case":"scope cleanup GAP","scopeCloseReturned":true,"externalPendingAutoReleased":false,"externalIncomingReaderAutoReleased":false,"ownerExplicitlyInterruptedBoth":true,"note":"scope-only closure does not auto-release external pending request or incoming reader; EOF case separately passes"} {"case":"extension request roundtrips","outboundResolved":true,"inboundResponded":true} {"case":"exact line character boundary","equalBudgetRetains":[1,2],"oneCharOverEvictsOldest":[4],"budget":16777216} {"case":"mixed live oversized and restart accounting","mixedLiveAll9Delivered":true,"refillRounds":4,"orderedSuffixes":[[16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],[36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],[56,57,58,59,60,61,62,63,64,65,66,67,68,69,70],[76,77,78,79,80,81,82,83,84,85,86,87,88,89,90]],"callbacks":89} {"case":"fragmented/coalesced UTF8 transformed route and decoded logs","notifications":2,"transformedSessionId":true,"splitInsideEmojiPreserved":true,"decodedLogCount":2,"internalSizeWrappersAbsent":true} {"case":"client.raw.notifications","callbacks":40,"indexes":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40],"ordered":true} {"case":"agent.raw.notifications","callbacks":40,"indexes":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40],"ordered":true} {"case":"repeated connection-owner scope disposal","cycles":12,"oversizedUnreadBeforeReader":true,"pendingAndReaderFibersSettled":true,"transportWeakReferencesReclaimed":12,"note":"GC observes transport objects; not a complete heap census"} {"case":"retention loop timing","count":1000,"elapsedMs":47.89,"note":"single sample includes setup/routing/drain; bounded queue max32 plus newest transient; no asymptotic claim from timings"} {"case":"retention loop timing","count":2000,"elapsedMs":83.907,"note":"single sample includes setup/routing/drain; bounded queue max32 plus newest transient; no asymptotic claim from timings"} {"case":"retention loop timing","count":4000,"elapsedMs":176.642,"note":"single sample includes setup/routing/drain; bounded queue max32 plus newest transient; no asymptotic claim from timings"} {"case":"malformed token timing","tokens":3000,"elapsedMs":3.219,"includes":"protocol setup, parse failure and scoped teardown; single sample, not a complexity proof"} {"case":"malformed token timing","tokens":6000,"elapsedMs":0.768,"includes":"protocol setup, parse failure and scoped teardown; single sample, not a complexity proof"} {"case":"malformed token timing","tokens":12000,"elapsedMs":0.69,"includes":"protocol setup, parse failure and scoped teardown; single sample, not a complexity proof"} ALL RUNTIME ASSERTIONS PASSED BASELINE OWNERSHIP COMPARISON cwd /tmp/18799-evidence/red-checkout (archived base implementation): cp /tmp/18799-evidence/scope-probe.ts packages/effect-acp/issue18799-scope-probe.ts node packages/effect-acp/issue18799-scope-probe.ts EXIT: 0 {"case":"scope cleanup GAP","scopeCloseReturned":true,"externalPendingAutoReleased":false,"externalIncomingReaderAutoReleased":false,"ownerExplicitlyInterruptedBoth":true,"note":"scope-only closure does not auto-release external pending request or incoming reader; EOF case separately passes"} FRESH MEMORY RUNS Used the existing measure-retention.ts source, copied under a unique temporary name; lead's files untouched. This source records post-GC heapUsed+external deltas, rounded MiB, after callback acknowledgements. It does not count RSS, exact retained bytes, or prove every remaining object is protocol-owned. Unexpected sub-payload head values at late 8-MiB samples are recorded as observed, not normalized. For each cwd (base /tmp/18799-evidence/red-checkout; head /home/ubuntu/wt/issue-18799): cp /tmp/18799-evidence/measure-retention.ts packages/effect-acp/issue18799-memory-evidence.ts node --expose-gc packages/effect-acp/issue18799-memory-evidence.ts 1 100 node --expose-gc packages/effect-acp/issue18799-memory-evidence.ts 8 40 All four commands exited 0. Temporary head file removed. base-memory-1mib.json {"sizeMiB":1,"count":100,"rows":[{"notifications":1,"retainedMiB":1},{"notifications":8,"retainedMiB":8.1},{"notifications":16,"retainedMiB":16.1},{"notifications":24,"retainedMiB":24.2},{"notifications":32,"retainedMiB":32.2},{"notifications":40,"retainedMiB":32.2},{"notifications":48,"retainedMiB":32.2},{"notifications":56,"retainedMiB":32.2},{"notifications":64,"retainedMiB":32.2},{"notifications":72,"retainedMiB":32.2},{"notifications":80,"retainedMiB":32.2},{"notifications":88,"retainedMiB":32.2},{"notifications":96,"retainedMiB":32.2},{"notifications":100,"retainedMiB":32.2}]} head-memory-1mib.json {"sizeMiB":1,"count":100,"rows":[{"notifications":1,"retainedMiB":1},{"notifications":8,"retainedMiB":8.1},{"notifications":16,"retainedMiB":15.1},{"notifications":24,"retainedMiB":15.2},{"notifications":32,"retainedMiB":15.2},{"notifications":40,"retainedMiB":15.2},{"notifications":48,"retainedMiB":15.2},{"notifications":56,"retainedMiB":15.2},{"notifications":64,"retainedMiB":15.2},{"notifications":72,"retainedMiB":15.2},{"notifications":80,"retainedMiB":15.2},{"notifications":88,"retainedMiB":15.4},{"notifications":96,"retainedMiB":15.2},{"notifications":100,"retainedMiB":15.2}]} base-memory-8mib.json {"sizeMiB":8,"count":40,"rows":[{"notifications":1,"retainedMiB":8},{"notifications":8,"retainedMiB":64.1},{"notifications":16,"retainedMiB":128.1},{"notifications":24,"retainedMiB":192.2},{"notifications":32,"retainedMiB":256.2},{"notifications":40,"retainedMiB":251.9}]} head-memory-8mib.json {"sizeMiB":8,"count":40,"rows":[{"notifications":1,"retainedMiB":8},{"notifications":8,"retainedMiB":8.1},{"notifications":16,"retainedMiB":8.2},{"notifications":24,"retainedMiB":8.2},{"notifications":32,"retainedMiB":3.2},{"notifications":40,"retainedMiB":3.2}]} MATRIX COVERAGE / LIMITS 1 Retention: head exact suffix and full line characters for 32x1, 100x1, 40x8 MiB, all direct callbacks observed. Base/head fresh memory series above. Base suffix IDs were not separately drained in the memory run; base regression proves the count-retained backlog instead. 2 Oversized: whole 17-Mi-character payload equality checked; newest alone retained; small successor evicts it. Exact limit and limit+1 full-line cases pass. 3 Count: 40 tiny retains exactly ordered 9..40. Both real client.raw.notifications and agent.raw.notifications exercised. 4 Accounting: 256 live ordered messages; mixed oversized live; four stop/refill/restart rounds; eight slow gate bursts; all direct callbacks acknowledged. Raw loss during deliberate overload is expected oldest eviction. 5 Cleanup: EOF settles pending extension request. Protocol-only scope close does not settle external-owner request/reader on either base or head; owner must interrupt them. Twelve connection-owner scope disposals settle owned fibers and reclaim 12 transport WeakRefs after GC. This is not a full heap census or a new EOF stream-completion contract. 6 Routing: typed session/update and both elicitation spellings, ext notification, both extension RPC directions, fragmented/coalesced NDJSON split inside UTF-8 emoji, transformed session ID, decoded logs without internal wrappers; all asserted. Scoped client/agent/protocol suite also passed. Timing: 3,000 malformed tokens measured above; 6,000/12,000 are additional single samples including setup and teardown, not microbenchmarks. Retention loop uses max32 buffered entries plus transient newest; each eviction removes one entry. For each message there are <=32 evictions; total evictions <= admissions. Parser split/JSON parse still scales with input length, and a single oversized newest remains intentionally unbounded. Measurements do not prove asymptotic complexity or process-memory bounds. No visual fixtures apply (matrix []). No browser/application server or captures. Prior /tmp before/after data remains intact; fresh series above are this capture's observations. SOURCE SHA256 799b777a5b94efe3d3e4887a77ac2d46971d023b22d21c8ead03774beb54b027 runtime-evidence.ts COMPLETE RUNTIME SCRIPT (reproduce by placing in packages/effect-acp) import assert from 'node:assert/strict'; import * as Effect from 'effect/Effect'; import * as Queue from 'effect/Queue'; import * as Stream from 'effect/Stream'; import * as Deferred from 'effect/Deferred'; import * as Fiber from 'effect/Fiber'; import * as Scope from 'effect/Scope'; import * as Exit from 'effect/Exit'; import * as Client from './src/client.ts'; import * as Agent from './src/agent.ts'; import * as Schema from 'effect/Schema'; import * as Protocol from './src/protocol.ts'; import { makeInMemoryStdio } from './src/_internal/stdio.ts'; const M=1024*1024, enc=new TextEncoder(); const line=(method:string,params:unknown)=>JSON.stringify({jsonrpc:'2.0',method,params})+'\n'; const index=(n:Protocol.AcpIncomingNotification)=>(n.params as {index:number}).index; const report=(name:string,data:unknown)=>console.log(JSON.stringify({case:name,...data as object})); const setup=Effect.gen(function*(){ const io=yield* makeInMemoryStdio(); const callbacks=yield* Queue.unbounded(); const p=yield* Protocol.makeAcpPatchedProtocol({stdio:io.stdio,serverRequestMethods:new Set(),onNotification:n=>Queue.offer(callbacks,n).pipe(Effect.asVoid)}); const send=(i:number,text:string)=>Queue.offer(io.input,enc.encode(line('x/evidence',{index:i,text}))).pipe(Effect.andThen(Queue.take(callbacks))); return {...io,p,send,callbacks}; }); const collect=(p:Protocol.AcpPatchedProtocol,n:number)=>p.incoming.pipe(Stream.take(n),Stream.runCollect); for(const [count,payloadMiB] of [[32,1],[100,1],[40,8]] as const){ await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup; const sizes:number[]=[]; for(let i=1;i<=count;i++){ sizes.push(line('x/evidence',{index:i,text:'x'.repeat(payloadMiB*M)}).length-1); yield* send(i,'x'.repeat(payloadMiB*M)); } let total=0,start=count; while(start>0&&total+sizes[start-1]!<=16*M){total+=sizes[--start]!;} const got=yield* collect(p,count-start); assert.deepEqual(got.map(index),Array.from({length:count-start},(_,i)=>start+i+1)); report('retention newest suffix',{count,payloadMiB,indexes:got.map(index),retainedLineChars:total,limitChars:16*M,callbacks:count}); }).pipe(Effect.scoped)); } await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup;yield* send(1,'x'.repeat(17*M)); const big=yield* collect(p,1);assert.equal(index(big[0]!),1);assert.equal((big[0]!.params as {text:string}).text,'x'.repeat(17*M)); yield* send(2,'x'.repeat(17*M));yield* send(3,'small');const small=yield* collect(p,1);assert.equal(index(small[0]!),3); report('oversized newest retained; next small replaces oversized',{oversizedChars:17*M,alone:big.map(index),afterSmall:small.map(index)}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup;for(let i=1;i<=40;i++)yield* send(i,'tiny');const got=yield* collect(p,32);assert.deepEqual(got.map(index),Array.from({length:32},(_,i)=>i+9));report('count cap',{sent:40,indexes:got.map(index)}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup;const received=yield* Queue.unbounded();const reader=yield* p.incoming.pipe(Stream.runForEach(n=>Queue.offer(received,index(n))),Effect.forkScoped); for(let i=1;i<=256;i++){yield* send(i,'live');assert.equal(yield* Queue.take(received),i);} yield* Fiber.interrupt(reader);report('sustained active consumer',{sent:256,callbacks:256,delivered:256,ordered:true}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup; const received=yield* Queue.unbounded(); const permits=yield* Queue.unbounded(); const live=yield* p.incoming.pipe(Stream.runForEach(n=>Queue.offer(received,index(n)).pipe(Effect.andThen(Queue.take(permits)))),Effect.forkScoped); // Each consumed item blocks at a permit; refill while blocked, then release deterministically. for(let round=0;round<8;round++){ const first=round*41+1;yield* send(first,'live');assert.equal(yield* Queue.take(received),first); for(let j=1;j<=40;j++)yield* send(first+j,'x'.repeat(64*1024)); for(let j=9;j<=40;j++){yield* Queue.offer(permits,undefined);assert.equal(yield* Queue.take(received),first+j);} yield* Queue.offer(permits,undefined); } yield* Fiber.interrupt(live);report('slow consumer drain/refill',{rounds:8,callbacks:328,delivered:264,discardedOldest:64,ordered:true}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,input,callbacks}=yield* setup; const frames=[line('session/update',{sessionId:'evidence',update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:'safe'}}}),line('elicitation/complete',{elicitationId:'safe'}),line('session/elicitation/complete',{elicitationId:'safe-alias'}),line('x/evidence',{index:4,text:'safe'})]; for(const f of frames){yield* Queue.offer(input,enc.encode(f));yield* Queue.take(callbacks);} const got=yield* collect(p,4);assert.deepEqual(got.map(x=>x._tag),['SessionUpdate','ElicitationComplete','ElicitationComplete','ExtNotification']);report('typed routes and raw incoming caller',{tags:got.map(x=>x._tag),methods:got.map(x=>x.method)}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,input,output}=yield* setup;const pending=yield* p.request('x/pending',{safe:true}).pipe(Effect.exit,Effect.forkScoped);const wire=JSON.parse(yield* Queue.take(output));yield* Queue.end(input);const result=yield* Fiber.join(pending);assert.equal(result._tag,'Failure');report('EOF releases pending extension request',{outboundMethod:wire.method,exit:result._tag}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const scope=yield* Scope.make();const io=yield* makeInMemoryStdio(); const p=yield* Protocol.makeAcpPatchedProtocol({stdio:io.stdio,serverRequestMethods:new Set()}).pipe(Effect.provideService(Scope.Scope,scope)); const reader=yield* p.incoming.pipe(Stream.runDrain,Effect.forkScoped); const pending=yield* p.request('x/scope-pending',{}).pipe(Effect.exit,Effect.forkScoped);yield* Queue.take(io.output); yield* Scope.close(scope,Exit.void); const pendingDone=pending.pollUnsafe()!==undefined;const readerDone=reader.pollUnsafe()!==undefined; yield* Fiber.interrupt(reader);yield* Fiber.interrupt(pending); report('scope cleanup GAP',{scopeCloseReturned:true,externalPendingAutoReleased:pendingDone,externalIncomingReaderAutoReleased:readerDone,ownerExplicitlyInterruptedBoth:true,note:'scope-only closure does not auto-release external pending request or incoming reader; EOF case separately passes'}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const io=yield* makeInMemoryStdio();const ext=yield* Deferred.make(); const p=yield* Protocol.makeAcpPatchedProtocol({stdio:io.stdio,serverRequestMethods:new Set(),onExtRequest:(method,params)=>Deferred.succeed(ext,method).pipe(Effect.as({safe:true}))}); const request=yield* p.request('x/outbound',{safe:true}).pipe(Effect.forkScoped);const outbound=JSON.parse(yield* Queue.take(io.output)); yield* Queue.offer(io.input,enc.encode(JSON.stringify({jsonrpc:'2.0',id:outbound.id,result:{safe:true}})+'\n')); assert.deepEqual(yield* Fiber.join(request),{safe:true}); yield* Queue.offer(io.input,enc.encode(JSON.stringify({jsonrpc:'2.0',id:77,method:'x/inbound',params:{safe:true}})+'\n')); assert.equal(yield* Deferred.await(ext),'x/inbound');const response=JSON.parse(yield* Queue.take(io.output));assert.equal(response.id,77);assert.deepEqual(response.result,{safe:true}); report('extension request roundtrips',{outboundResolved:true,inboundResponded:true}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup; const half=8*M; const text=(i:number,n:number)=>'x'.repeat(n-(line('x/evidence',{index:i,text:''}).length-1)); yield* send(1,text(1,half));yield* send(2,text(2,half));let got=yield* collect(p,2);assert.deepEqual(got.map(index),[1,2]); yield* send(3,text(3,half));yield* send(4,text(4,half+1));got=yield* collect(p,1);assert.deepEqual(got.map(index),[4]); report('exact line character boundary',{equalBudgetRetains:[1,2],oneCharOverEvictsOldest:[4],budget:16*M}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const {p,send}=yield* setup;const received=yield* Queue.unbounded(); const live=yield* p.incoming.pipe(Stream.runForEach(n=>Queue.offer(received,index(n))),Effect.forkScoped); for(let i=1;i<=9;i++){yield* send(i,i%3===0?'x'.repeat(17*M):'tiny');assert.equal(yield* Queue.take(received),i);}yield* Fiber.interrupt(live); const suffixes:number[][]=[]; for(let round=0;round<4;round++){ for(let j=1;j<=20;j++)yield* send(round*20+10+j,'x'.repeat(M));const got=yield* collect(p,15);const ids=got.map(index);assert.deepEqual(ids,Array.from({length:15},(_,j)=>round*20+16+j));suffixes.push(ids); } report('mixed live oversized and restart accounting',{mixedLiveAll9Delivered:true,refillRounds:4,orderedSuffixes:suffixes,callbacks:89}); }).pipe(Effect.scoped)); await Effect.runPromise(Effect.gen(function*(){ const io=yield* makeInMemoryStdio();const handled=yield* Queue.unbounded();const logs:Array=[]; const p=yield* Protocol.makeAcpPatchedProtocol({stdio:io.stdio,serverRequestMethods:new Set(),logIncoming:true,logger:e=>Effect.sync(()=>{logs.push(e)}),transformSessionUpdate:n=>({...n,sessionId:'transformed'}),onNotification:()=>Queue.offer(handled,undefined).pipe(Effect.asVoid)}); const content=line('session/update',{sessionId:'original',update:{sessionUpdate:'agent_message_chunk',content:{type:'text',text:'safe'}}})+line('x/utf8',{text:'😀'}); const bytes=enc.encode(content);const emoji=bytes.findIndex(x=>x===0xf0);assert.ok(emoji>0); yield* Queue.offer(io.input,bytes.slice(0,17));yield* Queue.offer(io.input,bytes.slice(17,emoji+2));yield* Queue.offer(io.input,bytes.slice(emoji+2));yield* Queue.take(handled);yield* Queue.take(handled); const got=yield* collect(p,2);assert.equal((got[0]!.params as {sessionId:string}).sessionId,'transformed');assert.deepEqual(got[1]!.params,{text:'😀'}); const decoded=logs.filter(e=>e.stage==='decoded').flatMap(e=>e.payload as unknown[]);assert.equal(decoded.length,2);assert.ok(decoded.every(x=>typeof x==='object'&&x!==null&&'_tag' in x&&!('message' in x)&&!('size' in x))); report('fragmented/coalesced UTF8 transformed route and decoded logs',{notifications:2,transformedSessionId:true,splitInsideEmojiPreserved:true,decodedLogCount:decoded.length,internalSizeWrappersAbsent:true}); }).pipe(Effect.scoped)); for(const [name,make] of [['client',Client.make],['agent',Agent.make]] as const){ await Effect.runPromise(Effect.gen(function*(){ const io=yield* makeInMemoryStdio();const api=yield* make(io.stdio);const handled=yield* Queue.unbounded(); yield* api.handleExtNotification('x/raw',Schema.Struct({index:Schema.Number}),()=>Queue.offer(handled,undefined).pipe(Effect.asVoid)); for(let i=1;i<=40;i++){yield* Queue.offer(io.input,enc.encode(line('x/raw',{index:i})));yield* Queue.take(handled);} const got=yield* api.raw.notifications.pipe(Stream.take(32),Stream.runCollect);assert.deepEqual(got.map(index),Array.from({length:32},(_,i)=>i+9)); report(name+'.raw.notifications',{callbacks:40,indexes:got.map(index),ordered:true}); }).pipe(Effect.scoped)); } const weak:Array>=[]; for(let cycle=0;cycle<12;cycle++){ await Effect.runPromise(Effect.gen(function*(){ const owned=yield* Scope.make();const io=yield* makeInMemoryStdio();const handled=yield* Queue.unbounded(); const p=yield* Protocol.makeAcpPatchedProtocol({stdio:io.stdio,serverRequestMethods:new Set(),onNotification:()=>Queue.offer(handled,undefined).pipe(Effect.asVoid)}).pipe(Effect.provideService(Scope.Scope,owned));weak.push(new WeakRef(p)); yield* Queue.offer(io.input,enc.encode(line('x/dispose',{index:cycle,text:'x'.repeat(17*M)})));yield* Queue.take(handled); // Fibers created in the connection's owner scope are interrupted with that owner. const pending=yield* p.request('x/dispose-pending',{}).pipe(Effect.forkIn(owned));yield* Queue.take(io.output); const reader=yield* p.incoming.pipe(Stream.runDrain,Effect.forkIn(owned)); yield* Scope.close(owned,Exit.void);assert.notEqual(pending.pollUnsafe(),undefined);assert.notEqual(reader.pollUnsafe(),undefined); }).pipe(Effect.scoped)); } await new Promise(resolve=>setImmediate(resolve)); (globalThis as {gc?:()=>void}).gc?.();(globalThis as {gc?:()=>void}).gc?.(); const reclaimed=weak.filter(w=>w.deref()===undefined).length;assert.equal(reclaimed,12); report('repeated connection-owner scope disposal',{cycles:12,oversizedUnreadBeforeReader:true,pendingAndReaderFibersSettled:true,transportWeakReferencesReclaimed:reclaimed,note:'GC observes transport objects; not a complete heap census'}); for(const count of [1000,2000,4000]){ const started=performance.now();await Effect.runPromise(Effect.gen(function*(){const {p,send}=yield* setup;for(let i=1;i<=count;i++)yield* send(i,'tiny');const got=yield* collect(p,32);assert.deepEqual(got.map(index),Array.from({length:32},(_,i)=>count-31+i));}).pipe(Effect.scoped)); report('retention loop timing',{count,elapsedMs:Number((performance.now()-started).toFixed(3)),note:'single sample includes setup/routing/drain; bounded queue max32 plus newest transient; no asymptotic claim from timings'}); } for(const tokens of [3000,6000,12000]){ const started=performance.now(); await Effect.runPromise(Effect.gen(function*(){const io=yield* makeInMemoryStdio();const done=yield* Deferred.make();yield* Protocol.makeAcpPatchedProtocol({stdio:io.stdio,serverRequestMethods:new Set(),onTermination:e=>Deferred.succeed(done,e._tag).pipe(Effect.asVoid)});yield* Queue.offer(io.input,enc.encode('['+'0,'.repeat(tokens)+'oops]\n'));assert.equal(yield* Deferred.await(done),'AcpProtocolParseError');}).pipe(Effect.scoped)); report('malformed token timing',{tokens,elapsedMs:Number((performance.now()-started).toFixed(3)),includes:'protocol setup, parse failure and scoped teardown; single sample, not a complexity proof'}); } console.log('ALL RUNTIME ASSERTIONS PASSED'); COMPLETE MEMORY SCRIPT import * as Effect from "effect/Effect"; import * as Queue from "effect/Queue"; import * as AcpProtocol from "./src/protocol.ts"; import { makeInMemoryStdio } from "./src/_internal/stdio.ts"; const MIB = 1024 * 1024; const sizeMiB = Number(process.argv[2] ?? 8); const count = Number(process.argv[3] ?? 40); const gc = (globalThis as { gc?: () => void }).gc!; const mib = (n: number) => Math.round((n / MIB) * 10) / 10; const used = () => { gc(); gc(); const m = process.memoryUsage(); return m.heapUsed + m.external; }; // Built in a helper so no local keeps the payload alive during measurement. const encodeLine = (i: number): Uint8Array => new TextEncoder().encode( `{"jsonrpc":"2.0","method":"x/large","params":{"index":${i},"text":"${String.fromCharCode(97 + (i % 26)).repeat(sizeMiB * MIB)}"}}\n`, ); const program = Effect.gen(function* () { const { stdio, input } = yield* makeInMemoryStdio(); const handled = yield* Queue.unbounded(); let seen = 0; yield* AcpProtocol.makeAcpPatchedProtocol({ stdio, serverRequestMethods: new Set(), onNotification: () => Queue.offer(handled, ++seen).pipe(Effect.asVoid), }); const base = used(); const rows: Array<{ notifications: number; retainedMiB: number }> = []; for (let i = 1; i <= count; i++) { yield* Queue.offer(input, encodeLine(i)); yield* Queue.take(handled); if (i === 1 || i % 8 === 0 || i === count) rows.push({ notifications: i, retainedMiB: mib(used() - base) }); } return rows; }).pipe(Effect.scoped); const rows = await Effect.runPromise(program); console.log(JSON.stringify({ sizeMiB, count, rows })); FRICTION Scope-only closure leaves external-owner raw reader and pending extension request suspended on baseline and head; EOF and owner-scope interruption pass. GC confirms transport-object reclamation, not every internal allocation. Base memory run does not independently assert suffix IDs. Timings are single observations; no exact heap-byte or complexity guarantee is claimed. Initial writer-exit cleanup probe stalled because that test hook runs only for successful writer exit; corrected ownership probe uses scope completion and fiber state. No implementation edits or committed tests, no PR/forge writes. Final HEAD checked as c64f3d8d473f5e4da1b7cc6848eda69d06a4b93d; only original lead untracked files remain.