@@ -521,44 +521,6 @@ test("process interruption after claiming leaves inspectable execution state and
521521 assert . deepEqual ( ( await readdir ( stateRoot ( project . root ) ) ) . filter ( ( name ) => name . startsWith ( ".lifecycle" ) ) , [ ] ) ;
522522} ) ;
523523
524- test ( "abort retains an undelivered blocked notification until acknowledgement" , async ( t ) => {
525- const project = await setupProject ( t ) ;
526- const graph = parseDeliveryGraph ( { ...graphInput , issues : [ graphInput . issues [ 2 ] ] } ) ;
527- const initial = createInitialRunState ( {
528- run_id : RUN_ID ,
529- graph,
530- source_commit : await git ( project . root , "rev-parse" , "HEAD" ) ,
531- main_worktree : project . root ,
532- integration_branch : "integration" ,
533- default_branch : "main" ,
534- created_at : "2026-08-09T00:00:00.000Z" ,
535- main_pane : "main-pane" ,
536- workspace_id : "main-workspace" ,
537- } ) ;
538- await createRun ( project . root , initial , ( ) => "create" ) ;
539- await writeRunState ( project . root , {
540- ...initial ,
541- phase : "blocked" ,
542- block_reason : "API needs a decision." ,
543- tasks : {
544- ...initial . tasks ,
545- api : { status : "blocked" , attempts : 1 , block_reason : "Choose a protocol." , blocked_role : "implementer" } ,
546- } ,
547- } , ( ) => "blocked" ) ;
548- const pending = ( await readRunState ( project . root , RUN_ID ) ) ! . notifications [ 0 ] ;
549- const lifecycle = createCoreLifecycle ( { uuid : ( ) => "lifecycle" , now : ( ) => "2026-08-09T01:00:00.000Z" } ) ;
550- const aborted = await lifecycle . abort ( project . root , "Cancelled by user" ) ;
551- assert . equal ( aborted . phase , "aborted" ) ;
552- assert . equal ( aborted . abort_cleanup_complete , true ) ;
553- assert . equal ( await readActiveRunId ( project . root ) , RUN_ID ) ;
554- const resumed = await lifecycle . resume ( project . root ) ;
555- assert . equal ( resumed . phase , "aborted" ) ;
556- assert . equal ( resumed . notifications . find ( ( { event_id } ) => event_id === pending . event_id ) ?. delivered_at , undefined ) ;
557- assert . equal ( await readActiveRunId ( project . root ) , RUN_ID ) ;
558- await lifecycle . acknowledgeNotification ( project . root , pending . event_id ) ;
559- assert . equal ( await readActiveRunId ( project . root ) , undefined ) ;
560- } ) ;
561-
562524test ( "terminal settlement retains an aborted lock without cleanup proof" , async ( t ) => {
563525 const project = await setupProject ( t ) ;
564526 const graph = parseDeliveryGraph ( { ...graphInput , issues : [ graphInput . issues [ 2 ] ] } ) ;
@@ -579,49 +541,7 @@ test("terminal settlement retains an aborted lock without cleanup proof", async
579541 assert . equal ( await readActiveRunId ( project . root ) , RUN_ID ) ;
580542} ) ;
581543
582- test ( "terminal settlement releases a completed lock after acknowledgement persistence" , async ( t ) => {
583- const project = await setupProject ( t ) ;
584- const graph = parseDeliveryGraph ( { ...graphInput , issues : [ graphInput . issues [ 2 ] ] } ) ;
585- const initial = createInitialRunState ( {
586- run_id : RUN_ID ,
587- graph,
588- source_commit : await git ( project . root , "rev-parse" , "HEAD" ) ,
589- main_worktree : project . root ,
590- integration_branch : "integration" ,
591- default_branch : "main" ,
592- created_at : "2026-08-09T00:00:00.000Z" ,
593- main_pane : "main-pane" ,
594- workspace_id : "main-workspace" ,
595- } ) ;
596- await createRun ( project . root , initial , ( ) => "create" ) ;
597- await writeRunState ( project . root , {
598- ...initial ,
599- phase : "completed" ,
600- tasks : Object . fromEntries ( Object . entries ( initial . tasks ) . map ( ( [ id , task ] ) => [ id , { status : "completed" , attempts : task . attempts } ] ) ) ,
601- pr : {
602- number : 42 ,
603- url : "https://example.test/pull/42" ,
604- head_ref : "integration" ,
605- base_ref : "main" ,
606- head_oid : initial . integration_head ,
607- } ,
608- } , ( ) => "completed" ) ;
609- const completed = ( await readRunState ( project . root , RUN_ID ) ) ! ;
610- await assert . rejects ( createCoreLifecycle ( ) . abort ( project . root , "Too late" ) , / C a n n o t a b o r t a c o m p l e t e d r u n / ) ;
611- assert . equal ( ( await readRunState ( project . root , RUN_ID ) ) ! . phase , "completed" ) ;
612- await writeRunState ( project . root , {
613- ...completed ,
614- notifications : completed . notifications . map ( ( notification ) => ( {
615- ...notification ,
616- delivered_at : "2026-08-09T01:00:00.000Z" ,
617- } ) ) ,
618- } , ( ) => "delivered" ) ;
619- assert . equal ( await readActiveRunId ( project . root ) , RUN_ID ) ;
620- await createCoreLifecycle ( ) . settleTerminal ( project . root ) ;
621- assert . equal ( await readActiveRunId ( project . root ) , undefined ) ;
622- } ) ;
623-
624- test ( "durable blocked/completed notifications retain stable IDs and release completion only after ack" , async ( t ) => {
544+ test ( "durable blocked/completed notifications retain stable IDs and settle delivered completion" , async ( t ) => {
625545 const project = await setupProject ( t ) ;
626546 const graph = parseDeliveryGraph ( { ...graphInput , issues : [ graphInput . issues [ 2 ] ] } ) ;
627547 const initial = createInitialRunState ( {
@@ -703,10 +623,17 @@ test("durable blocked/completed notifications retain stable IDs and release comp
703623 const completedRoundTrip = ( await readRunState ( project . root , RUN_ID ) ) ! ;
704624 assert . equal ( completedRoundTrip . notifications . find ( ( { kind } ) => kind === "completed" ) ! . event_id , completedId ) ;
705625 assert . equal ( completedRoundTrip . notifications . length , 3 ) ;
706-
707- const delivered = await lifecycle . acknowledgeNotification ( project . root , completedId ) ;
626+ await assert . rejects ( lifecycle . abort ( project . root , "Too late" ) , / C a n n o t a b o r t a c o m p l e t e d r u n / ) ;
627+ assert . deepEqual ( await readRunState ( project . root , RUN_ID ) , completedRoundTrip ) ;
628+ await writeRunState ( project . root , {
629+ ...completedRoundTrip ,
630+ notifications : completedRoundTrip . notifications . map ( ( notification ) => notification . kind === "completed"
631+ ? { ...notification , delivered_at : "2026-08-09T01:00:00.000Z" }
632+ : notification ) ,
633+ } , ( ) => "completed-delivered" ) ;
634+ assert . equal ( await readActiveRunId ( project . root ) , RUN_ID ) ;
635+ await lifecycle . settleTerminal ( project . root ) ;
708636 assert . equal ( await readActiveRunId ( project . root ) , undefined ) ;
709- assert . deepEqual ( await lifecycle . acknowledgeNotification ( project . root , completedId ) , delivered ) ;
710637} ) ;
711638
712639test ( "followUp delivery is fire-and-forget: failed or unacknowledged dispatch stays pending until explicit acknowledgement" , async ( t ) => {
@@ -731,8 +658,15 @@ test("followUp delivery is fire-and-forget: failed or unacknowledged dispatch st
731658 tasks : { ...initial . tasks , api : { status : "blocked" , attempts : 1 , block_reason : "Choose a protocol." , blocked_role : "implementer" } } ,
732659 } , ( ) => "blocked" ) ;
733660 const lifecycle = createCoreLifecycle ( { uuid : ( ) => "lifecycle" , now : ( ) => "2026-08-09T01:00:00.000Z" } ) ;
734- await lifecycle . abort ( project . root , "Cancelled by user" ) ;
735661 const eventId = ( await readRunState ( project . root , RUN_ID ) ) ! . notifications [ 0 ] . event_id ;
662+ const aborted = await lifecycle . abort ( project . root , "Cancelled by user" ) ;
663+ assert . equal ( aborted . phase , "aborted" ) ;
664+ assert . equal ( aborted . abort_cleanup_complete , true ) ;
665+ assert . equal ( await readActiveRunId ( project . root ) , RUN_ID ) ;
666+ assert . equal ( ( await lifecycle . resume ( project . root ) ) . phase , "aborted" ) ;
667+ const pending = ( await readRunState ( project . root , RUN_ID ) ) ! . notifications [ 0 ] ;
668+ assert . equal ( pending . event_id , eventId ) ;
669+ assert . equal ( pending . delivered_at , undefined ) ;
736670
737671 let failSend = true ;
738672 const sent : string [ ] = [ ] ;
0 commit comments