@@ -839,10 +839,65 @@ impl Reader {
839839 claim : & Claim ,
840840 active_claim : & mut Claim ,
841841 ) -> Result < ( ) > {
842+ let mut visited = std:: collections:: HashSet :: new ( ) ;
843+ let mut path = Vec :: new ( ) ;
844+ Self :: collect_ingredient_claims_for_store_impl (
845+ store,
846+ claim,
847+ active_claim,
848+ & mut visited,
849+ & mut path,
850+ )
851+ }
852+
853+ /// Uses the similar cycle detection strategy as Store::get_claim_referenced_manifests_impl
854+ fn collect_ingredient_claims_for_store_impl (
855+ store : & Store ,
856+ claim : & Claim ,
857+ active_claim : & mut Claim ,
858+ visited : & mut std:: collections:: HashSet < String > ,
859+ path : & mut Vec < String > ,
860+ ) -> Result < ( ) > {
861+ Self :: collect_ingredient_claims_impl ( store, claim, active_claim, visited, path)
862+ }
863+
864+ /// Shared implementation for recursively collecting ingredient claims.
865+ ///
866+ /// Uses path-based cycle detection similar to Store::get_claim_referenced_manifests_impl:
867+ /// - `visited`: Claims that have been fully processed (allows DAG convergence)
868+ /// - `path`: Current traversal path to detect and skip cycles
869+ fn collect_ingredient_claims_impl (
870+ store : & Store ,
871+ claim : & Claim ,
872+ active_claim : & mut Claim ,
873+ visited : & mut std:: collections:: HashSet < String > ,
874+ path : & mut Vec < String > ,
875+ ) -> Result < ( ) > {
876+ let claim_label = claim. label ( ) ;
877+
878+ if visited. contains ( claim_label) {
879+ return Ok ( ( ) ) ;
880+ }
881+
882+ // Check for cycle: is this claim already in our current path?
883+ // If so, skip it silently (validation should have already caught this when enabled)
884+ if path. iter ( ) . any ( |p| p == claim_label) {
885+ return Ok ( ( ) ) ;
886+ }
887+
888+ path. push ( claim_label. to_string ( ) ) ;
889+
842890 for ingredient in claim. claim_ingredients ( ) {
843- if let Some ( ingredient_claim) = store. get_claim ( ingredient. label ( ) ) {
844- // First, recursively collect any ingredients this claim references
845- Self :: collect_ingredient_claims_for_store ( store, ingredient_claim, active_claim) ?;
891+ let ingredient_label = ingredient. label ( ) ;
892+
893+ if let Some ( ingredient_claim) = store. get_claim ( ingredient_label) {
894+ Self :: collect_ingredient_claims_impl (
895+ store,
896+ ingredient_claim,
897+ active_claim,
898+ visited,
899+ path,
900+ ) ?;
846901
847902 // Then add this ingredient claim to the primary claim
848903 active_claim. replace_ingredient_or_insert (
@@ -851,6 +906,13 @@ impl Reader {
851906 ) ;
852907 }
853908 }
909+
910+ // Mark as fully processed
911+ visited. insert ( claim_label. to_string ( ) ) ;
912+
913+ // Remove from current path
914+ path. pop ( ) ;
915+
854916 Ok ( ( ) )
855917 }
856918
@@ -997,16 +1059,31 @@ impl Reader {
9971059 builder. definition . instance_id = manifest. instance_id ( ) . to_owned ( ) ;
9981060 builder. definition . thumbnail = manifest. thumbnail_ref ( ) . cloned ( ) ;
9991061 builder. definition . redactions = manifest. redactions . take ( ) ;
1000- let ingredients = std:: mem:: take ( & mut manifest. ingredients ) ; //manifest.ingredients.drain(..).collect::<Vec<_>>();
1062+ let ingredients = std:: mem:: take ( & mut manifest. ingredients ) ;
10011063 for mut ingredient in ingredients {
10021064 if let Some ( active_manifest) = ingredient. active_manifest ( ) {
1003- let ingredient_claim = self . store . remove_claim ( active_manifest) ;
1065+ let ingredient_claim = self . store . get_claim ( active_manifest) ;
10041066 if let Some ( claim) = ingredient_claim {
10051067 // recreate an ingredient store to get the jumbf data
1006- let mut ingredient_store = Store :: new ( ) ;
1007- ingredient_store. commit_claim ( claim. clone ( ) ) ?;
1068+ // ... recursively collect all nested ingredient claims
1069+ let ingredient_store = {
1070+ let mut ingredient_store = Store :: new ( ) ;
1071+ let mut active_claim = claim. clone ( ) ;
1072+
1073+ // Recursion happens here for claims collection - re-embed nested claims from store
1074+ Self :: collect_ingredient_claims_for_store (
1075+ & self . store ,
1076+ claim,
1077+ & mut active_claim,
1078+ ) ?;
1079+
1080+ // Add the main claim with all nested ingredients
1081+ ingredient_store. commit_claim ( active_claim) ?;
1082+ ingredient_store
1083+ } ;
10081084 let jumbf = ingredient_store. to_jumbf_internal ( 0 ) ?;
1009- let manifest_data_ref = manifest. resources_mut ( ) . add_with (
1085+ // Add manifest_data to the ingredient's own resources
1086+ let manifest_data_ref = ingredient. resources_mut ( ) . add_with (
10101087 "manifest_data" ,
10111088 "application/c2pa" ,
10121089 jumbf,
@@ -1059,7 +1136,14 @@ impl Reader {
10591136 let mut active_claim = claim. clone ( ) ;
10601137
10611138 // Recursively collect all ingredient claims and add them to primary_claim
1062- self . collect_ingredient_claims_recursive ( claim, & mut active_claim) ?;
1139+ let mut visited = std:: collections:: HashSet :: new ( ) ;
1140+ let mut path = Vec :: new ( ) ;
1141+ self . collect_ingredient_claims_recursive (
1142+ claim,
1143+ & mut active_claim,
1144+ & mut visited,
1145+ & mut path,
1146+ ) ?;
10631147
10641148 // Add the main claim last
10651149 store. commit_claim ( active_claim) ?;
@@ -1077,20 +1161,10 @@ impl Reader {
10771161 & self ,
10781162 claim : & Claim ,
10791163 active_claim : & mut Claim ,
1164+ visited : & mut std:: collections:: HashSet < String > ,
1165+ path : & mut Vec < String > ,
10801166 ) -> Result < ( ) > {
1081- for ingredient in claim. claim_ingredients ( ) {
1082- if let Some ( ingredient_claim) = self . store . get_claim ( ingredient. label ( ) ) {
1083- // First, recursively collect any ingredients this claim references
1084- self . collect_ingredient_claims_recursive ( ingredient_claim, active_claim) ?;
1085-
1086- // Then add this ingredient claim to the primary claim
1087- active_claim. replace_ingredient_or_insert (
1088- ingredient_claim. label ( ) . to_string ( ) ,
1089- ingredient_claim. clone ( ) ,
1090- ) ;
1091- }
1092- }
1093- Ok ( ( ) )
1167+ Self :: collect_ingredient_claims_impl ( & self . store , claim, active_claim, visited, path)
10941168 }
10951169}
10961170
0 commit comments