@@ -1050,6 +1050,146 @@ describe("Schema", () => {
10501050 expect ( paths ) . not . toContain ( "chain.next.next.next.next.next.next.value" ) ;
10511051 } ) ;
10521052
1053+ it ( 'unblocks std::list::push item once the list is provided, even as an empty literal' , ( ) => {
1054+ // std::list::push → <T>(list: LIST<T>, item: T): NUMBER
1055+ // `item` shares the type parameter T with `list`, so it starts out
1056+ // blockedBy [0]. Providing the `list` argument satisfies that dependency,
1057+ // so `item` becomes unblocked — an empty list literal `[]` counts as a
1058+ // provided value just like a concrete list does.
1059+ const buildPushFlow = ( listValue : any ) : Flow => ( {
1060+ id : "gid://sagittarius/Flow/1" ,
1061+ startingNodeId : "gid://sagittarius/NodeFunction/1" ,
1062+ signature : "(): void" ,
1063+ nodes : {
1064+ nodes : [
1065+ {
1066+ id : "gid://sagittarius/NodeFunction/1" ,
1067+ functionDefinition : { identifier : "std::list::push" } ,
1068+ parameters : {
1069+ nodes : [
1070+ { value : listValue } ,
1071+ { value : null } ,
1072+ ] ,
1073+ } ,
1074+ } ,
1075+ ] ,
1076+ } ,
1077+ } ) ;
1078+
1079+ // With no list provided, `item` is blocked by the list parameter (index 0).
1080+ const blockedResult = getSignatureSchema (
1081+ buildPushFlow ( null ) ,
1082+ DATA_TYPES ,
1083+ FUNCTION_SIGNATURES ,
1084+ "gid://sagittarius/NodeFunction/1" ,
1085+ ) ;
1086+ expect ( blockedResult . parameters [ 1 ] . blockedBy ) . toEqual ( [ 0 ] ) ;
1087+
1088+ // Providing an empty list literal `[]` satisfies the dependency, so the
1089+ // `item` parameter is now unblocked.
1090+ const emptyResult = getSignatureSchema (
1091+ buildPushFlow ( { __typename : "LiteralValue" , value : [ ] } ) ,
1092+ DATA_TYPES ,
1093+ FUNCTION_SIGNATURES ,
1094+ "gid://sagittarius/NodeFunction/1" ,
1095+ ) ;
1096+ const [ emptyList , emptyItem ] = emptyResult . parameters ;
1097+
1098+ expect ( emptyList . schema . input ) . toBe ( "list" ) ;
1099+ expect ( emptyList . blockedBy ) . toEqual ( [ ] ) ;
1100+ expect ( emptyItem . blockedBy ) . toEqual ( [ ] ) ;
1101+ } ) ;
1102+
1103+ it ( 'keeps std::list::push item blocked while the list has no value' , ( ) => {
1104+ // std::list::push → <T>(list: LIST<T>, item: T): NUMBER
1105+ // As long as the `list` parameter (index 0) carries no value, T cannot be
1106+ // pinned, so `item` (index 1) must stay blockedBy [0] and fall back to a
1107+ // generic input. This holds whether the list slot is `null` or an explicit
1108+ // null-typed value.
1109+ const buildPushFlow = ( listValue : any ) : Flow => ( {
1110+ id : "gid://sagittarius/Flow/1" ,
1111+ startingNodeId : "gid://sagittarius/NodeFunction/1" ,
1112+ signature : "(): void" ,
1113+ nodes : {
1114+ nodes : [
1115+ {
1116+ id : "gid://sagittarius/NodeFunction/1" ,
1117+ functionDefinition : { identifier : "std::list::push" } ,
1118+ parameters : {
1119+ nodes : [
1120+ { value : listValue } ,
1121+ { value : null } ,
1122+ ] ,
1123+ } ,
1124+ } ,
1125+ ] ,
1126+ } ,
1127+ } ) ;
1128+
1129+ // No parameter object at all for the list slot.
1130+ const nullResult = getSignatureSchema (
1131+ buildPushFlow ( null ) ,
1132+ DATA_TYPES ,
1133+ FUNCTION_SIGNATURES ,
1134+ "gid://sagittarius/NodeFunction/1" ,
1135+ ) ;
1136+ const [ nullList , nullItem ] = nullResult . parameters ;
1137+
1138+ // The list itself is never blocked — nothing feeds it.
1139+ expect ( nullList . blockedBy ) . toEqual ( [ ] ) ;
1140+ // item stays blocked by the list and has no concrete type yet.
1141+ expect ( nullItem . blockedBy ) . toEqual ( [ 0 ] ) ;
1142+ expect ( nullItem . schema . input ) . toBe ( "generic" ) ;
1143+ } ) ;
1144+
1145+ it ( 'keeps a later std::list::push item blocked when its list references an earlier node' , ( ) => {
1146+ // Two-node flow. Node 1 (std::list::push) returns NUMBER. Node 2 is another
1147+ // std::list::push whose `list` parameter is still empty, so its `item`
1148+ // parameter must stay blockedBy [0] — an unrelated, already-configured
1149+ // predecessor node does not pin node 2's T.
1150+ const flow : Flow = {
1151+ id : "gid://sagittarius/Flow/1" ,
1152+ startingNodeId : "gid://sagittarius/NodeFunction/1" ,
1153+ signature : "(): void" ,
1154+ nodes : {
1155+ nodes : [
1156+ {
1157+ id : "gid://sagittarius/NodeFunction/1" ,
1158+ functionDefinition : { identifier : "std::list::push" } ,
1159+ nextNodeId : "gid://sagittarius/NodeFunction/2" ,
1160+ parameters : {
1161+ nodes : [
1162+ { value : { __typename : "LiteralValue" , value : [ 1 , 2 , 3 ] } } ,
1163+ { value : { __typename : "LiteralValue" , value : 4 } } ,
1164+ ] ,
1165+ } ,
1166+ } ,
1167+ {
1168+ id : "gid://sagittarius/NodeFunction/2" ,
1169+ functionDefinition : { identifier : "std::list::push" } ,
1170+ parameters : {
1171+ nodes : [
1172+ { value : null } ,
1173+ { value : null } ,
1174+ ] ,
1175+ } ,
1176+ } ,
1177+ ] ,
1178+ } ,
1179+ } ;
1180+
1181+ const { parameters : [ list , item ] } = getSignatureSchema (
1182+ flow ,
1183+ DATA_TYPES ,
1184+ FUNCTION_SIGNATURES ,
1185+ "gid://sagittarius/NodeFunction/2" ,
1186+ ) ;
1187+
1188+ expect ( list . blockedBy ) . toEqual ( [ ] ) ;
1189+ expect ( item . blockedBy ) . toEqual ( [ 0 ] ) ;
1190+ expect ( item . schema . input ) . toBe ( "generic" ) ;
1191+ } ) ;
1192+
10531193 describe ( "return schema" , ( ) => {
10541194 // Single-node flow calling `identifier` with the given parameters, probed at that node.
10551195 const singleNode = ( identifier : string , params : any [ ] ) : Flow => ( {
@@ -1167,6 +1307,90 @@ describe("Schema", () => {
11671307 expect ( result . return ) . toEqual ( { input : "generic" , type : "void" } ) ;
11681308 } ) ;
11691309
1310+ // A trigger is analyzed at the flow level (no nodeId). The flow's own
1311+ // signature carries the return type, and its `settings` supply the
1312+ // arguments the generic return is instantiated from. This mirrors the
1313+ // REST trigger: <T>(input_schema: TYPE<T>, ...): REST_ADAPTER_INPUT<T>.
1314+ const restTrigger = ( inputSchema : any ) : Flow => ( {
1315+ id : "gid://sagittarius/Flow/1" ,
1316+ startingNodeId : "gid://sagittarius/NodeFunction/1" ,
1317+ signature :
1318+ "<T>(input_schema: TYPE<T>, httpURL: HTTP_URL, httpMethod: HTTP_METHOD): REST_ADAPTER_INPUT<T>" ,
1319+ settings : {
1320+ nodes : [
1321+ { value : inputSchema } ,
1322+ { value : "/users" } ,
1323+ { value : "GET" } ,
1324+ ] ,
1325+ } ,
1326+ nodes : { nodes : [ ] } ,
1327+ } as Flow ) ;
1328+
1329+ it ( "resolves a trigger's return schema at the flow level (no nodeId) from its settings" , ( ) => {
1330+ const result = getSignatureSchema (
1331+ restTrigger ( { name : "text" } ) ,
1332+ DATA_TYPES ,
1333+ FUNCTION_SIGNATURES ,
1334+ ) ;
1335+
1336+ // Flow-level probe → no target node.
1337+ expect ( result . nodeId ) . toBeUndefined ( ) ;
1338+
1339+ // REST_ADAPTER_INPUT<T> is an object → data input with its four fields.
1340+ const ret = result . return as {
1341+ input : string ;
1342+ properties : Record < string , any > ;
1343+ required : string [ ] ;
1344+ } ;
1345+ expect ( ret . input ) . toBe ( "data" ) ;
1346+ expect ( Object . keys ( ret . properties ) ) . toEqual (
1347+ expect . arrayContaining ( [
1348+ "payload" ,
1349+ "headers" ,
1350+ "query_params" ,
1351+ "path_params" ,
1352+ ] ) ,
1353+ ) ;
1354+ expect ( ret . required ) . toEqual (
1355+ expect . arrayContaining ( [
1356+ "payload" ,
1357+ "headers" ,
1358+ "query_params" ,
1359+ "path_params" ,
1360+ ] ) ,
1361+ ) ;
1362+
1363+ // T is bound from the input_schema setting ({name: TEXT}), so the
1364+ // payload keeps that concrete shape.
1365+ const payload = ret . properties . payload ;
1366+ expect ( payload . input ) . toBe ( "data" ) ;
1367+ expect ( Object . keys ( payload . properties ) ) . toEqual ( [ "name" ] ) ;
1368+ expect ( payload . properties . name . input ) . toBe ( "text" ) ;
1369+
1370+ // The remaining REST fields are open objects.
1371+ expect ( ret . properties . headers . input ) . toBe ( "data" ) ;
1372+ expect ( ret . properties . query_params . input ) . toBe ( "data" ) ;
1373+ expect ( ret . properties . path_params . input ) . toBe ( "data" ) ;
1374+
1375+ // A return type describes an output → no suggestions anywhere.
1376+ expectNoSuggestionsAnywhere ( ret ) ;
1377+ } ) ;
1378+
1379+ it ( "instantiates the trigger's generic return payload from a primitive input_schema setting" , ( ) => {
1380+ const result = getSignatureSchema (
1381+ restTrigger ( 42 ) ,
1382+ DATA_TYPES ,
1383+ FUNCTION_SIGNATURES ,
1384+ ) ;
1385+
1386+ expect ( result . nodeId ) . toBeUndefined ( ) ;
1387+
1388+ // input_schema = 42 → T = NUMBER → payload is a number input.
1389+ const ret = result . return as { properties : Record < string , any > } ;
1390+ expect ( ret . properties . payload ) . toEqual ( { input : "number" , type : "number" } ) ;
1391+ expectNoSuggestionsAnywhere ( ret ) ;
1392+ } ) ;
1393+
11701394 it ( "never carries suggestions, whatever the return type" , ( ) => {
11711395 // Primitive, object, list and generic returns all stay suggestion-free.
11721396 expectNoSuggestionsAnywhere (
0 commit comments