@@ -318,3 +318,106 @@ test("coordinator ignores errored tool calls", () => {
318318 ) ;
319319 expect ( rt . currentStep ( ) ?. id ) . toBe ( "a" ) ;
320320} ) ;
321+
322+ test ( "CL-8289 pin: capability skip emits reason and satisfied run lands on gated step" , ( ) => {
323+ const skipped = new WorkflowRuntime ( empty , resolver ) ;
324+ const skippedEvents = collect ( skipped ) ;
325+ skipped . start ( withGatedStep ) ;
326+ skipped . advance ( ) ;
327+ expect ( skipped . currentStep ( ) ?. id ) . toBe ( "c" ) ;
328+ const skip = skippedEvents . find (
329+ ( e ) => e . type === "step-skip" && e . step . id === "needs-ticket" ,
330+ ) ;
331+ expect ( skip ?. type ) . toBe ( "step-skip" ) ;
332+ if ( skip ?. type === "step-skip" ) {
333+ expect ( skip . reason ) . toBe ( "capability not satisfied: ticket-tracker" ) ;
334+ }
335+
336+ const satisfied = new WorkflowRuntime ( ticketTracker , resolver ) ;
337+ satisfied . start ( withGatedStep ) ;
338+ satisfied . advance ( ) ;
339+ expect ( satisfied . currentStep ( ) ?. id ) . toBe ( "needs-ticket" ) ;
340+ } ) ;
341+
342+ test ( "CL-8289 pin: sub-workflow descends parent to child then pops complete to p3" , ( ) => {
343+ const rt = new WorkflowRuntime ( empty , resolver ) ;
344+ const events = collect ( rt ) ;
345+ rt . start ( parent ) ;
346+ expect ( rt . currentStep ( ) ?. id ) . toBe ( "p1" ) ;
347+ rt . advance ( ) ;
348+ expect ( rt . currentStep ( ) ?. id ) . toBe ( "c1" ) ;
349+ rt . advance ( ) ;
350+ expect ( rt . currentStep ( ) ?. id ) . toBe ( "p3" ) ;
351+ expect ( events . map ( ( e ) => e . type ) ) . toEqual ( [
352+ "step-start" ,
353+ "step-complete" ,
354+ "step-start" ,
355+ "step-complete" ,
356+ "step-complete" ,
357+ "step-start" ,
358+ ] ) ;
359+ } ) ;
360+
361+ test ( "CL-8289 pin: optional missing sub-workflow skips with not-found prefix" , ( ) => {
362+ const optionalMissing : Workflow = {
363+ name : "optional-missing" ,
364+ description : "optional absent child" ,
365+ steps : [ { id : "o" , label : "O" , workflow : "nope" , optional : true } ] ,
366+ } ;
367+ const rt = new WorkflowRuntime ( empty , ( n ) =>
368+ n === "optional-missing" ? optionalMissing : undefined ,
369+ ) ;
370+ const events = collect ( rt ) ;
371+ rt . start ( optionalMissing ) ;
372+ expect ( rt . isComplete ( ) ) . toBe ( true ) ;
373+ const skip = events . find ( ( e ) => e . type === "step-skip" ) ;
374+ expect ( skip ?. type ) . toBe ( "step-skip" ) ;
375+ if ( skip ?. type === "step-skip" ) {
376+ expect ( skip . reason . startsWith ( "sub-workflow not found: " ) ) . toBe ( true ) ;
377+ expect ( skip . reason ) . toBe ( "sub-workflow not found: nope" ) ;
378+ }
379+ } ) ;
380+
381+ test ( "CL-8289 pin: required missing sub-workflow throws not-found text" , ( ) => {
382+ const requiredMissing : Workflow = {
383+ name : "required-missing" ,
384+ description : "required absent child" ,
385+ steps : [ { id : "r" , label : "R" , workflow : "nope" } ] ,
386+ } ;
387+ const rt = new WorkflowRuntime ( empty , ( n ) =>
388+ n === "required-missing" ? requiredMissing : undefined ,
389+ ) ;
390+ expect ( ( ) => rt . start ( requiredMissing ) ) . toThrow (
391+ 'Sub-workflow "nope" not found in registry' ,
392+ ) ;
393+ } ) ;
394+
395+ test ( "CL-8289 pin: depth limit throws nesting text" , ( ) => {
396+ const cyclic : Workflow = {
397+ name : "cyclic" ,
398+ description : "calls itself" ,
399+ steps : [ { id : "loop" , label : "Loop" , workflow : "cyclic" } ] ,
400+ } ;
401+ const rt = new WorkflowRuntime ( empty , ( n ) =>
402+ n === "cyclic" ? cyclic : undefined ,
403+ ) ;
404+ expect ( ( ) => rt . start ( cyclic ) ) . toThrow (
405+ 'Workflow nesting exceeded the limit of 3 (at "cyclic")' ,
406+ ) ;
407+ } ) ;
408+
409+ test ( "CL-8289 pin: two-step golden transcript" , ( ) => {
410+ const rt = new WorkflowRuntime ( empty , resolver ) ;
411+ const events = collect ( rt ) ;
412+ rt . start ( simple ) ;
413+ rt . advance ( ) ;
414+ rt . advance ( ) ;
415+ expect ( rt . isComplete ( ) ) . toBe ( true ) ;
416+ expect ( events . map ( ( e ) => e . type ) ) . toEqual ( [
417+ "step-start" ,
418+ "step-complete" ,
419+ "step-start" ,
420+ "step-complete" ,
421+ "workflow-complete" ,
422+ ] ) ;
423+ } ) ;
0 commit comments