@@ -710,14 +710,14 @@ impl MonoRepo {
710710 }
711711 } ;
712712
713- // Cedar reviewer auto-assignment for new CL
713+ // Auto-assign reviewers for new CL
714714 if is_new_cl && let Err ( e) = self . assign_system_reviewers ( ) . await {
715- tracing:: warn!( "[Cedar Reviewer] Failed to assign reviewers: {}" , e) ;
715+ tracing:: warn!( "Failed to assign Cedar reviewers: {}" , e) ;
716716 }
717717
718718 // Resync reviewers when existing CL updates policy files
719719 if !is_new_cl && let Err ( e) = self . resync_current_cl_reviewers_if_policy_changed ( ) . await {
720- tracing:: warn!( "[Cedar Reviewer] Failed to resync reviewers: {}" , e) ;
720+ tracing:: warn!( "Failed to resync Cedar reviewers: {}" , e) ;
721721 }
722722
723723 Ok ( ( ) )
@@ -728,22 +728,19 @@ impl MonoRepo {
728728 path. ends_with ( ".cedar/policies.cedar" ) || path. ends_with ( ".cedar\\ policies.cedar" )
729729 }
730730
731- /// Resync reviewers for current CL when policy files are modified in the same push
731+ /// Resync reviewers when policy files are modified in an existing CL.
732732 async fn resync_current_cl_reviewers_if_policy_changed ( & self ) -> Result < ( ) , MegaError > {
733733 let changed_files = self . get_changed_files ( ) . await ?;
734734
735735 if !changed_files. iter ( ) . any ( |f| Self :: is_policy_file ( f) ) {
736736 return Ok ( ( ) ) ;
737737 }
738738
739- tracing:: info!( "[Cedar Reviewer] Policy file modified in CL update, resyncing reviewers" ) ;
740-
741739 let link_guard = self . cl_link . read ( ) . await ;
742740 let cl_link = link_guard
743741 . as_ref ( )
744742 . ok_or_else ( || MegaError :: Other ( "CL link not available" . to_string ( ) ) ) ?;
745743
746- // Collect policies from all changed file paths
747744 let policy_contents = self . collect_policy_contents ( & changed_files) . await ;
748745 if policy_contents. is_empty ( ) {
749746 return Ok ( ( ) ) ;
@@ -754,124 +751,119 @@ impl MonoRepo {
754751 . sync_system_reviewers ( cl_link, & policy_contents, & changed_files)
755752 . await ?;
756753
757- tracing:: info!( "[Cedar Reviewer] Reviewers resynced for CL {}" , cl_link) ;
758754 Ok ( ( ) )
759755 }
760756
761- /// Get list of files changed in this commit
757+ /// Get list of files changed between from_hash and to_hash commits.
758+ /// Returns paths relative to the CL root directory with forward slashes.
762759 async fn get_changed_files ( & self ) -> Result < Vec < String > , MegaError > {
763760 let mono_api_service: MonoApiService = self . into ( ) ;
764761
765- // Get file lists for both commits
766762 let old_files = mono_api_service. get_commit_blobs ( & self . from_hash ) . await ?;
767763 let new_files = mono_api_service. get_commit_blobs ( & self . to_hash ) . await ?;
768-
769- // Compare and get changed files
770764 let changed = mono_api_service. cl_files_list ( old_files, new_files) . await ?;
771765
772- // Extract file paths as strings
766+ // Normalize CL root path to use forward slashes
767+ let cl_root = self . path . to_string_lossy ( ) . replace ( '\\' , "/" ) ;
768+ let cl_root_normalized = cl_root. trim_start_matches ( '/' ) ;
769+
773770 let file_paths: Vec < String > = changed
774771 . iter ( )
775- . map ( |f| f. path ( ) . to_string_lossy ( ) . to_string ( ) )
772+ . map ( |f| {
773+ let full_path = f. path ( ) . to_string_lossy ( ) . replace ( '\\' , "/" ) ;
774+ let full_path_normalized = full_path. trim_start_matches ( '/' ) ;
775+
776+ // Strip CL root prefix to get relative path
777+ if let Some ( rel) = full_path_normalized. strip_prefix ( cl_root_normalized) {
778+ rel. trim_start_matches ( '/' ) . to_string ( )
779+ } else {
780+ full_path. to_string ( )
781+ }
782+ } )
776783 . collect ( ) ;
777784
778785 Ok ( file_paths)
779786 }
780787
781- /// Collect policy files from CL directory and all changed file paths within the CL
782- /// Returns list of (policy_path, content) tuples, ordered from CL root to leaf
783- ///
784- /// # Arguments
785- /// * `changed_files` - List of changed file paths relative to CL root
788+ /// Collect Cedar policy files from directories of all changed files.
789+ /// Returns list of (policy_path, content) tuples, ordered from root to leaf.
786790 async fn collect_policy_contents ( & self , changed_files : & [ String ] ) -> Vec < ( PathBuf , String ) > {
787791 let mono_api_service: MonoApiService = self . into ( ) ;
788792 let mut all_policy_dirs: HashSet < PathBuf > = HashSet :: new ( ) ;
789793
790- // 1. Always include the CL root directory itself
791- all_policy_dirs. insert ( self . path . clone ( ) ) ;
794+ // Always include the CL root directory
795+ all_policy_dirs. insert ( PathBuf :: new ( ) ) ;
792796
793- // 2. Collect directories from all changed files within this CL
797+ // Collect ancestor directories from all changed files
794798 for file_path in changed_files {
795- // Safety: ensure path is relative by removing any leading '/'
796- let relative_path = file_path. trim_start_matches ( '/' ) ;
797- let path = PathBuf :: from ( relative_path) ;
799+ let relative_path = file_path. trim_start_matches ( '/' ) . replace ( '\\' , "/" ) ;
800+ let path = PathBuf :: from ( & relative_path) ;
798801
799- // Get the logical parent directory (skip .cedar directories)
800- // For "servicea/.cedar/policies.cedar" -> we want "servicea"
801- // For "servicea/core/mod.rs" -> we want "servicea/core" and "servicea"
802802 let parent = path. parent ( ) . unwrap_or ( std:: path:: Path :: new ( "" ) ) ;
803803
804- // If parent is .cedar directory, go up one more level
804+ // Skip .cedar directory itself, use its parent
805805 let logical_parent = if parent. file_name ( ) . map ( |n| n == ".cedar" ) . unwrap_or ( false ) {
806806 parent. parent ( ) . unwrap_or ( std:: path:: Path :: new ( "" ) )
807807 } else {
808808 parent
809809 } ;
810810
811- // Add all ancestor directories of the logical parent (within CL scope)
812811 for ancestor in logical_parent. ancestors ( ) {
813- if ancestor. as_os_str ( ) . is_empty ( ) {
814- // Empty path is skipped because CL root (self.path) is already added in step 1
815- continue ;
816- }
817-
818- // Skip any path that contains .cedar component
819812 let ancestor_str = ancestor. to_string_lossy ( ) ;
820813 if ancestor_str. contains ( ".cedar" ) {
821814 continue ;
822815 }
823-
824- // Join with CL path to get the full path within the repository
825- let full_path = self . path . join ( ancestor) ;
826- all_policy_dirs. insert ( full_path) ;
816+ let normalized = PathBuf :: from ( ancestor_str. replace ( '\\' , "/" ) ) ;
817+ all_policy_dirs. insert ( normalized) ;
827818 }
828819 }
829820
830- // 3. Sort directories by depth (CL root to leaf) for correct override semantics
821+ // Sort by depth for correct override semantics (root policies first)
831822 let mut sorted_dirs: Vec < PathBuf > = all_policy_dirs. into_iter ( ) . collect ( ) ;
832823 sorted_dirs. sort_by_key ( |p| p. components ( ) . count ( ) ) ;
833824
834- // 4. Collect policy contents from all directories
835825 let mut policy_contents: Vec < ( PathBuf , String ) > = Vec :: new ( ) ;
836- let mut seen_policies: HashSet < PathBuf > = HashSet :: new ( ) ;
826+ let mut seen_policies: HashSet < String > = HashSet :: new ( ) ;
837827
838828 for dir in sorted_dirs {
839- let policy_path = dir. join ( ".cedar/policies.cedar" ) ;
829+ let policy_relative_path = if dir. as_os_str ( ) . is_empty ( ) {
830+ ".cedar/policies.cedar" . to_string ( )
831+ } else {
832+ let dir_str = dir. to_string_lossy ( ) . replace ( '\\' , "/" ) ;
833+ format ! ( "{}/.cedar/policies.cedar" , dir_str)
834+ } ;
840835
841- // Skip if we've already processed this policy file
842- if seen_policies. contains ( & policy_path) {
836+ if seen_policies. contains ( & policy_relative_path) {
843837 continue ;
844838 }
845839
840+ let lookup_path = PathBuf :: from ( & policy_relative_path) ;
841+ let self_path_str = self . path . to_string_lossy ( ) . replace ( '\\' , "/" ) ;
842+ let full_policy_path_str = format ! ( "{}/{}" , self_path_str, policy_relative_path) ;
843+ let full_policy_path = PathBuf :: from ( & full_policy_path_str) ;
844+
846845 if let Ok ( Some ( content) ) = mono_api_service
847- . get_blob_as_string ( policy_path . clone ( ) , Some ( & self . to_hash ) )
846+ . get_blob_as_string ( lookup_path , Some ( & self . to_hash ) )
848847 . await
849848 {
850- tracing:: debug!(
851- "[Cedar Reviewer] Found policy file: {}" ,
852- policy_path. display( )
853- ) ;
854- seen_policies. insert ( policy_path. clone ( ) ) ;
855- policy_contents. push ( ( policy_path, content) ) ;
849+ seen_policies. insert ( policy_relative_path) ;
850+ policy_contents. push ( ( full_policy_path, content) ) ;
856851 }
857852 }
858853
859854 policy_contents
860855 }
861856
862- /// Auto-assign system required reviewers based on .cedar/policies.cedar
863- /// Collects policies from all changed file paths, not just CL root ancestors
857+ /// Auto-assign system required reviewers based on Cedar policy files.
864858 async fn assign_system_reviewers ( & self ) -> Result < ( ) , MegaError > {
865859 let link_guard = self . cl_link . read ( ) . await ;
866860 let cl_link = link_guard
867861 . as_ref ( )
868862 . ok_or_else ( || MegaError :: Other ( "CL link not available" . to_string ( ) ) ) ?;
869863
870- // Get all changed files to match against policy rules
871- let changed_files = self . get_changed_files ( ) . await . unwrap_or_default ( ) ;
872-
873- // Collect policies from all changed file paths
864+ let changed_files = self . get_changed_files ( ) . await ?;
874865 let policy_contents = self . collect_policy_contents ( & changed_files) . await ;
866+
875867 if policy_contents. is_empty ( ) {
876868 return Ok ( ( ) ) ;
877869 }
0 commit comments