@@ -460,6 +460,144 @@ TEST(contract_python_relative_import) {
460460 PASS ();
461461}
462462
463+ /* Python: relative aliased from-import must CALLS the real def (execute). */
464+ TEST (contract_python_aliased_from_import_calls ) {
465+ static const LangFile f [] = {
466+ {"gate.py" , "def execute(x):\n return x\n" },
467+ {"router.py" ,
468+ "from .gate import execute as bridge_execute\n\n\n"
469+ "def submit_task(y):\n return bridge_execute(y)\n" }};
470+ LangProj lp ;
471+ cbm_store_t * store = lang_index_files (& lp , f , 2 );
472+ ASSERT_TRUE (store != NULL );
473+ cbm_node_t * nodes = NULL ;
474+ int ncount = 0 ;
475+ ASSERT_EQ (cbm_store_find_nodes_by_name (store , lp .project , "submit_task" , & nodes , & ncount ),
476+ CBM_STORE_OK );
477+ ASSERT_TRUE (ncount >= 1 );
478+ char * * callers = NULL ;
479+ char * * callees = NULL ;
480+ int n_callers = 0 ;
481+ int n_callees = 0 ;
482+ ASSERT_EQ (cbm_store_node_neighbor_names (store , nodes [0 ].id , 32 , & callers , & n_callers , & callees ,
483+ & n_callees ),
484+ 0 );
485+ int saw_execute = 0 ;
486+ int saw_alias_ghost = 0 ;
487+ for (int i = 0 ; i < n_callees ; i ++ ) {
488+ if (callees [i ] && strcmp (callees [i ], "execute" ) == 0 ) {
489+ saw_execute = 1 ;
490+ }
491+ if (callees [i ] && strcmp (callees [i ], "bridge_execute" ) == 0 ) {
492+ saw_alias_ghost = 1 ;
493+ }
494+ }
495+ for (int i = 0 ; i < n_callers ; i ++ ) {
496+ free (callers [i ]);
497+ }
498+ for (int i = 0 ; i < n_callees ; i ++ ) {
499+ free (callees [i ]);
500+ }
501+ free (callers );
502+ free (callees );
503+ cbm_store_free_nodes (nodes , ncount );
504+ lang_cleanup (& lp , store );
505+ ASSERT_TRUE (saw_execute );
506+ ASSERT_FALSE (saw_alias_ghost );
507+ PASS ();
508+ }
509+
510+ /* Python: absolute aliased from-import must CALLS the real def (Yui router shape). */
511+ TEST (contract_python_absolute_aliased_from_import_calls ) {
512+ static const LangFile f [] = {
513+ {"services/satori_bridge/__init__.py" , "" },
514+ {"services/satori_bridge/gate.py" , "def execute(x):\n return x\n" },
515+ {"services/yui_core/__init__.py" , "" },
516+ {"services/yui_core/router.py" ,
517+ "from services.satori_bridge.gate import execute as bridge_execute\n\n\n"
518+ "def submit_task(y):\n return bridge_execute(y)\n" },
519+ {"services/__init__.py" , "" }};
520+ LangProj lp ;
521+ cbm_store_t * store = lang_index_files (& lp , f , 5 );
522+ ASSERT_TRUE (store != NULL );
523+ cbm_node_t * nodes = NULL ;
524+ int ncount = 0 ;
525+ ASSERT_EQ (cbm_store_find_nodes_by_name (store , lp .project , "submit_task" , & nodes , & ncount ),
526+ CBM_STORE_OK );
527+ ASSERT_TRUE (ncount >= 1 );
528+ char * * callers = NULL ;
529+ char * * callees = NULL ;
530+ int n_callers = 0 ;
531+ int n_callees = 0 ;
532+ ASSERT_EQ (cbm_store_node_neighbor_names (store , nodes [0 ].id , 32 , & callers , & n_callers , & callees ,
533+ & n_callees ),
534+ 0 );
535+ int saw_execute = 0 ;
536+ int saw_alias_ghost = 0 ;
537+ for (int i = 0 ; i < n_callees ; i ++ ) {
538+ if (callees [i ] && strcmp (callees [i ], "execute" ) == 0 ) {
539+ saw_execute = 1 ;
540+ }
541+ if (callees [i ] && strcmp (callees [i ], "bridge_execute" ) == 0 ) {
542+ saw_alias_ghost = 1 ;
543+ }
544+ }
545+ for (int i = 0 ; i < n_callers ; i ++ ) {
546+ free (callers [i ]);
547+ }
548+ for (int i = 0 ; i < n_callees ; i ++ ) {
549+ free (callees [i ]);
550+ }
551+ free (callers );
552+ free (callees );
553+ cbm_store_free_nodes (nodes , ncount );
554+ lang_cleanup (& lp , store );
555+ ASSERT_TRUE (saw_execute );
556+ ASSERT_FALSE (saw_alias_ghost );
557+ PASS ();
558+ }
559+
560+ /* Python: unresolvable module + alias must emit no CALLS (no invented edge). */
561+ TEST (contract_python_ghost_module_aliased_from_import_no_calls ) {
562+ static const LangFile f [] = {
563+ {"router.py" ,
564+ "from ghost_module import nope as alias\n\n\n"
565+ "def submit_task(y):\n return alias(y)\n" }};
566+ LangProj lp ;
567+ cbm_store_t * store = lang_index_files (& lp , f , 1 );
568+ ASSERT_TRUE (store != NULL );
569+ cbm_node_t * nodes = NULL ;
570+ int ncount = 0 ;
571+ ASSERT_EQ (cbm_store_find_nodes_by_name (store , lp .project , "submit_task" , & nodes , & ncount ),
572+ CBM_STORE_OK );
573+ ASSERT_TRUE (ncount >= 1 );
574+ char * * callers = NULL ;
575+ char * * callees = NULL ;
576+ int n_callers = 0 ;
577+ int n_callees = 0 ;
578+ ASSERT_EQ (cbm_store_node_neighbor_names (store , nodes [0 ].id , 32 , & callers , & n_callers , & callees ,
579+ & n_callees ),
580+ 0 );
581+ int saw_any_callee = 0 ;
582+ for (int i = 0 ; i < n_callees ; i ++ ) {
583+ if (callees [i ] && callees [i ][0 ]) {
584+ saw_any_callee = 1 ;
585+ }
586+ }
587+ for (int i = 0 ; i < n_callers ; i ++ ) {
588+ free (callers [i ]);
589+ }
590+ for (int i = 0 ; i < n_callees ; i ++ ) {
591+ free (callees [i ]);
592+ }
593+ free (callers );
594+ free (callees );
595+ cbm_store_free_nodes (nodes , ncount );
596+ lang_cleanup (& lp , store );
597+ ASSERT_FALSE (saw_any_callee );
598+ PASS ();
599+ }
600+
463601/* TypeScript: a relative import resolves to a sibling module → IMPORTS edge. */
464602TEST (contract_typescript_relative_import ) {
465603 static const LangFile f [] = {
@@ -1648,6 +1786,9 @@ SUITE(lang_contract) {
16481786 RUN_TEST (contract_java_methods );
16491787 RUN_TEST (contract_kotlin_methods );
16501788 RUN_TEST (contract_python_relative_import );
1789+ RUN_TEST (contract_python_aliased_from_import_calls );
1790+ RUN_TEST (contract_python_absolute_aliased_from_import_calls );
1791+ RUN_TEST (contract_python_ghost_module_aliased_from_import_no_calls );
16511792 RUN_TEST (contract_typescript_relative_import );
16521793
16531794 /* Graph-level breadth across all grammars (P4). */
0 commit comments