@@ -200,6 +200,24 @@ test('treats empty configuration patches as read-only lookups', async () => {
200200 ] ) ;
201201} ) ;
202202
203+ test ( 'reads the bounded Host execution boundary summary' , async ( ) => {
204+ const { client, requests } = clientWithResponses ( [
205+ { kind : 'managed' , access : 'read_only' , revision : 4 } ,
206+ ] ) ;
207+
208+ assert . deepEqual ( await client . readExecutionBoundary ( 'session-1' ) , {
209+ kind : 'managed' ,
210+ access : 'read_only' ,
211+ revision : 4 ,
212+ } ) ;
213+ assert . deepEqual ( requests , [
214+ {
215+ operation : 'session.execution_boundary.query' ,
216+ input : { sessionId : 'session-1' } ,
217+ } ,
218+ ] ) ;
219+ } ) ;
220+
203221test ( 'binds message controls to the current Host Epoch' , async ( ) => {
204222 const { client, requests } = clientWithResponses ( [
205223 { disposition : 'steering' , queueRevision : 2 } ,
@@ -264,6 +282,118 @@ test('binds message controls to the current Host Epoch', async () => {
264282 ] ) ;
265283} ) ;
266284
285+ test ( 'uploads Attachment bytes in bounded Host chunks and commits the digest' , async ( ) => {
286+ const bytes = new Uint8Array ( 48 * 1024 + 3 ) . fill ( 7 ) ;
287+ const attachment = {
288+ kind : 'file' as const ,
289+ name : 'notes.txt' ,
290+ mimeType : 'text/plain' ,
291+ bytes : bytes . byteLength ,
292+ ref : {
293+ kind : 'session_file' as const ,
294+ sessionId : 'session-1' ,
295+ relativePath : 'artifact-1' ,
296+ } ,
297+ } ;
298+ const { client, requests } = clientWithResponses ( [
299+ { kind : 'upload_opened' , nextOffset : 0 } ,
300+ { kind : 'chunk_accepted' , nextOffset : 48 * 1024 } ,
301+ { kind : 'chunk_accepted' , nextOffset : bytes . byteLength } ,
302+ { kind : 'committed' , attachment } ,
303+ ] ) ;
304+
305+ assert . deepEqual (
306+ await client . ingestAttachment ( {
307+ sessionId : 'session-1' ,
308+ uploadId : 'upload-1' ,
309+ name : 'notes.txt' ,
310+ mimeType : 'text/plain' ,
311+ content : bytes ,
312+ } ) ,
313+ attachment ,
314+ ) ;
315+ assert . deepEqual (
316+ requests . map ( ( { operation, input } ) => ( {
317+ operation,
318+ kind : ( input as { kind ?: string } ) . kind ,
319+ offset : ( input as { offset ?: number } ) . offset ,
320+ } ) ) ,
321+ [
322+ { operation : 'artifact.ingest' , kind : 'begin' , offset : undefined } ,
323+ { operation : 'artifact.ingest' , kind : 'chunk' , offset : 0 } ,
324+ { operation : 'artifact.ingest' , kind : 'chunk' , offset : 48 * 1024 } ,
325+ { operation : 'artifact.ingest' , kind : 'commit' , offset : undefined } ,
326+ ] ,
327+ ) ;
328+ const begin = requests [ 0 ] ?. input as { contentSha256 : string ; totalBytes : number } ;
329+ assert . equal ( begin . totalBytes , bytes . byteLength ) ;
330+ assert . match ( begin . contentSha256 , / ^ s h a 2 5 6 : [ a - f 0 - 9 ] { 64 } $ / ) ;
331+ } ) ;
332+
333+ test ( 'aborts an opened Attachment upload when a later chunk fails' , async ( ) => {
334+ const requests : RecordedRequest [ ] = [ ] ;
335+ const connection = {
336+ hostEpoch : 'host-current' ,
337+ request : async < K extends OperationKey > ( operation : K , input : OperationInput < K > ) => {
338+ requests . push ( { operation, input } ) ;
339+ if ( ( input as { kind ?: string } ) . kind === 'begin' ) {
340+ return { kind : 'upload_opened' , nextOffset : 0 } ;
341+ }
342+ if ( ( input as { kind ?: string } ) . kind === 'abort' ) {
343+ return { kind : 'upload_aborted' , uploadId : 'upload-1' } ;
344+ }
345+ throw new Error ( 'chunk failed' ) ;
346+ } ,
347+ close : async ( ) => undefined ,
348+ } as unknown as RuntimeHostConnection ;
349+ const client = new DesktopRuntimeHostClient ( connection ) ;
350+
351+ await assert . rejects (
352+ ( ) =>
353+ client . ingestAttachment ( {
354+ sessionId : 'session-1' ,
355+ uploadId : 'upload-1' ,
356+ name : 'notes.txt' ,
357+ mimeType : 'text/plain' ,
358+ content : new Uint8Array ( [ 1 ] ) ,
359+ } ) ,
360+ / c h u n k f a i l e d / ,
361+ ) ;
362+ assert . deepEqual (
363+ requests . map ( ( { input } ) => ( input as { kind : string } ) . kind ) ,
364+ [ 'begin' , 'chunk' , 'abort' ] ,
365+ ) ;
366+ } ) ;
367+
368+ test ( 'accepts an idempotently committed Attachment upload at begin' , async ( ) => {
369+ const attachment = {
370+ kind : 'file' as const ,
371+ name : 'notes.txt' ,
372+ mimeType : 'text/plain' ,
373+ bytes : 1 ,
374+ ref : {
375+ kind : 'session_file' as const ,
376+ sessionId : 'session-1' ,
377+ relativePath : 'artifact-1' ,
378+ } ,
379+ } ;
380+ const { client, requests } = clientWithResponses ( [
381+ { kind : 'committed' , uploadId : 'upload-1' , attachment } ,
382+ ] ) ;
383+
384+ assert . deepEqual (
385+ await client . ingestAttachment ( {
386+ sessionId : 'session-1' ,
387+ uploadId : 'upload-1' ,
388+ name : 'notes.txt' ,
389+ mimeType : 'text/plain' ,
390+ content : new Uint8Array ( [ 1 ] ) ,
391+ } ) ,
392+ attachment ,
393+ ) ;
394+ assert . equal ( requests . length , 1 ) ;
395+ } ) ;
396+
267397test ( 'fails explicitly when the Host cannot represent a legacy Session' , async ( ) => {
268398 const { client } = clientWithResponses ( [
269399 {
0 commit comments