diff --git a/validation/api/src/internal.rs b/validation/api/src/internal.rs
index 0c18e1f..b4bf484 100644
--- a/validation/api/src/internal.rs
+++ b/validation/api/src/internal.rs
@@ -31,6 +31,47 @@ impl Context{
 		).await.map_err(Error::Response)?
 		.json().await.map_err(Error::Reqwest)
 	}
+	pub async fn get_scripts<'a>(&self,config:GetScriptsRequest<'a>)->Result<Vec<ScriptResponse>,Error>{
+		let url_raw=format!("{}/scripts",self.0.base_url);
+		let mut url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
+
+		{
+			let mut query_pairs=url.query_pairs_mut();
+			query_pairs.append_pair("Page",config.Page.to_string().as_str());
+			query_pairs.append_pair("Limit",config.Limit.to_string().as_str());
+			if let Some(name)=config.Name{
+				query_pairs.append_pair("Name",name);
+			}
+			if let Some(hash)=config.Hash{
+				query_pairs.append_pair("Hash",hash);
+			}
+			if let Some(source)=config.Source{
+				query_pairs.append_pair("Source",source);
+			}
+			if let Some(submission_id)=config.SubmissionID{
+				query_pairs.append_pair("SubmissionID",submission_id.to_string().as_str());
+			}
+		}
+
+		response_ok(
+			self.0.get(url).await.map_err(Error::Reqwest)?
+		).await.map_err(Error::Response)?
+		.json().await.map_err(Error::Reqwest)
+	}
+	pub async fn get_script_from_hash<'a>(&self,config:HashRequest<'a>)->Result<Option<ScriptResponse>,SingleItemError>{
+		let scripts=self.get_scripts(GetScriptsRequest{
+			Page:1,
+			Limit:2,
+			Hash:Some(config.hash),
+			Name:None,
+			Source:None,
+			SubmissionID:None,
+		}).await.map_err(SingleItemError::Other)?;
+		if 1<scripts.len(){
+			return Err(SingleItemError::DuplicateItems);
+		}
+		Ok(scripts.into_iter().next())
+	}
 	pub async fn create_script<'a>(&self,config:CreateScriptRequest<'a>)->Result<ScriptIDResponse,Error>{
 		let url_raw=format!("{}/scripts",self.0.base_url);
 		let url=reqwest::Url::parse(url_raw.as_str()).map_err(Error::Parse)?;
diff --git a/validation/api/src/types.rs b/validation/api/src/types.rs
index 1be3133..41e282e 100644
--- a/validation/api/src/types.rs
+++ b/validation/api/src/types.rs
@@ -60,9 +60,9 @@ pub async fn response_ok(response:reqwest::Response)->Result<reqwest::Response,R
 	}
 }
 
-#[derive(Clone,Copy,PartialEq,Eq,serde::Serialize,serde::Deserialize)]
+#[derive(Clone,Copy,Debug,PartialEq,Eq,serde::Serialize,serde::Deserialize)]
 pub struct ScriptID(pub(crate)i64);
-#[derive(Clone,Copy,serde::Serialize,serde::Deserialize)]
+#[derive(Clone,Copy,Debug,serde::Serialize,serde::Deserialize)]
 pub struct ScriptPolicyID(pub(crate)i64);
 
 #[allow(nonstandard_style)]
@@ -70,7 +70,7 @@ pub struct GetScriptRequest{
 	pub ScriptID:ScriptID,
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Serialize)]
+#[derive(Clone,Debug,serde::Serialize)]
 pub struct GetScriptsRequest<'a>{
 	pub Page:u32,
 	pub Limit:u32,
@@ -83,11 +83,12 @@ pub struct GetScriptsRequest<'a>{
 	#[serde(skip_serializing_if="Option::is_none")]
 	pub SubmissionID:Option<i64>,
 }
+#[derive(Clone,Copy,Debug)]
 pub struct HashRequest<'a>{
 	pub hash:&'a str,
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Deserialize)]
+#[derive(Clone,Debug,serde::Deserialize)]
 pub struct ScriptResponse{
 	pub ID:ScriptID,
 	pub Name:String,
@@ -96,7 +97,7 @@ pub struct ScriptResponse{
 	pub SubmissionID:i64,
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Serialize)]
+#[derive(Clone,Debug,serde::Serialize)]
 pub struct CreateScriptRequest<'a>{
 	pub Name:&'a str,
 	pub Source:&'a str,
@@ -104,12 +105,12 @@ pub struct CreateScriptRequest<'a>{
 	pub SubmissionID:Option<i64>,
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Deserialize)]
+#[derive(Clone,Debug,serde::Deserialize)]
 pub struct ScriptIDResponse{
 	pub ID:ScriptID,
 }
 
-#[derive(PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
+#[derive(Clone,Copy,Debug,PartialEq,Eq,serde_repr::Serialize_repr,serde_repr::Deserialize_repr)]
 #[repr(i32)]
 pub enum Policy{
 	None=0, // not yet reviewed
@@ -120,7 +121,7 @@ pub enum Policy{
 }
 
 #[allow(nonstandard_style)]
-#[derive(serde::Serialize)]
+#[derive(Clone,Debug,serde::Serialize)]
 pub struct GetScriptPoliciesRequest<'a>{
 	pub Page:u32,
 	pub Limit:u32,
@@ -132,7 +133,7 @@ pub struct GetScriptPoliciesRequest<'a>{
 	pub Policy:Option<Policy>,
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Deserialize)]
+#[derive(Clone,Debug,serde::Deserialize)]
 pub struct ScriptPolicyResponse{
 	pub ID:ScriptPolicyID,
 	pub FromScriptHash:String,
@@ -140,20 +141,20 @@ pub struct ScriptPolicyResponse{
 	pub Policy:Policy
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Serialize)]
+#[derive(Clone,Debug,serde::Serialize)]
 pub struct CreateScriptPolicyRequest{
 	pub FromScriptID:ScriptID,
 	pub ToScriptID:ScriptID,
 	pub Policy:Policy,
 }
 #[allow(nonstandard_style)]
-#[derive(serde::Deserialize)]
+#[derive(Clone,Debug,serde::Deserialize)]
 pub struct ScriptPolicyIDResponse{
 	pub ID:ScriptPolicyID,
 }
 
 #[allow(nonstandard_style)]
-#[derive(serde::Serialize)]
+#[derive(Clone,Debug,serde::Serialize)]
 pub struct UpdateScriptPolicyRequest{
 	pub ID:ScriptPolicyID,
 	#[serde(skip_serializing_if="Option::is_none")]
@@ -165,6 +166,7 @@ pub struct UpdateScriptPolicyRequest{
 }
 
 #[allow(nonstandard_style)]
+#[derive(Clone,Debug)]
 pub struct UpdateSubmissionModelRequest{
 	pub SubmissionID:i64,
 	pub ModelID:u64,
@@ -172,15 +174,18 @@ pub struct UpdateSubmissionModelRequest{
 }
 
 #[allow(nonstandard_style)]
+#[derive(Clone,Debug)]
 pub struct ActionSubmissionUploadedRequest{
 	pub SubmissionID:i64,
 	pub TargetAssetID:Option<u64>,
 }
 
 #[allow(nonstandard_style)]
+#[derive(Clone,Debug)]
 pub struct ActionSubmissionAcceptedRequest{
 	pub SubmissionID:i64,
 	pub StatusMessage:String,
 }
 
+#[derive(Clone,Copy,Debug)]
 pub struct SubmissionID(pub i64);
diff --git a/validation/src/validator.rs b/validation/src/validator.rs
index 7b4e437..dcbfd99 100644
--- a/validation/src/validator.rs
+++ b/validation/src/validator.rs
@@ -21,23 +21,33 @@ fn source_has_illegal_keywords(source:&str)->bool{
 	source.find("getfenv").is_some()||source.find("require").is_some()
 }
 
+fn hash_source(source:&str)->String{
+	let mut hasher=siphasher::sip::SipHasher::new();
+	std::hash::Hasher::write(&mut hasher,source.as_bytes());
+	let hash=std::hash::Hasher::finish(&hasher);
+	format!("{:016x}",hash)
+}
+
 #[allow(dead_code)]
 #[derive(Debug)]
 pub enum ValidateError{
-	Flagged,
-	Blocked,
-	NotAllowed,
-	Get(rbx_asset::cookie::GetError),
-	ReadDom(ReadDomError),
-	ApiGetScriptPolicy(submissions_api::types::SingleItemError),
+	ScriptFlaggedIllegalKeyword,
+	ScriptBlocked(Option<submissions_api::types::ScriptID>),
+	ScriptNotYetReviewed(Option<submissions_api::types::ScriptID>),
+	ModelFileDownload(rbx_asset::cookie::GetError),
+	ModelFileDecode(ReadDomError),
+	ApiGetScriptPolicyFromHash(submissions_api::types::SingleItemError),
 	ApiGetScript(submissions_api::Error),
 	ApiCreateScript(submissions_api::Error),
 	ApiCreateScriptPolicy(submissions_api::Error),
+	ApiGetScriptFromHash(submissions_api::types::SingleItemError),
 	ApiUpdateSubmissionModel(submissions_api::Error),
 	ApiActionSubmissionValidate(submissions_api::Error),
-	WriteDom(rbx_binary::EncodeError),
-	Upload(rbx_asset::cookie::UploadError),
-	Create(rbx_asset::cookie::CreateError),
+	ModelFileRootMustHaveOneChild,
+	ModelFileChildRefIsNil,
+	ModelFileEncode(rbx_binary::EncodeError),
+	AssetUpload(rbx_asset::cookie::UploadError),
+	AssetCreate(rbx_asset::cookie::CreateError),
 }
 impl std::fmt::Display for ValidateError{
 	fn fmt(&self,f:&mut std::fmt::Formatter<'_>)->std::fmt::Result{
@@ -89,10 +99,10 @@ impl Validator{
 		let data=self.roblox_cookie.get_asset(rbx_asset::cookie::GetAssetRequest{
 			asset_id:validate_info.ModelID,
 			version:Some(validate_info.ModelVersion),
-		}).await.map_err(ValidateError::Get)?;
+		}).await.map_err(ValidateError::ModelFileDownload)?;
 
 		// decode dom (slow!)
-		let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ReadDom)?;
+		let mut dom=read_dom(&mut std::io::Cursor::new(data)).map_err(ValidateError::ModelFileDecode)?;
 
 		/* VALIDATE MAP */
 
@@ -105,12 +115,12 @@ impl Validator{
 					// check the source for illegal keywords
 					if source_has_illegal_keywords(source){
 						// immediately abort
-						return Err(ValidateError::Flagged);
+						return Err(ValidateError::ScriptFlaggedIllegalKeyword);
 					}
 					// associate a name and policy with the source code
 					// policy will be fetched from the database to replace the default policy
 					script_map.insert(source.clone(),NamePolicy{
-						name:script.name.clone(),
+						name:get_partial_path(&dom,script),
 						policy:Policy::None,
 					});
 				}
@@ -121,14 +131,12 @@ impl Validator{
 		futures::stream::iter(script_map.iter_mut().map(Ok))
 		.try_for_each_concurrent(Some(SCRIPT_CONCURRENCY),|(source,NamePolicy{policy,name})|async{
 			// get the hash
-			let mut hasher=siphasher::sip::SipHasher::new();
-			std::hash::Hasher::write(&mut hasher,source.as_bytes());
-			let hash=std::hash::Hasher::finish(&hasher);
+			let hash=hash_source(source.as_str());
 
 			// fetch the script policy
 			let script_policy=self.api.get_script_policy_from_hash(submissions_api::types::HashRequest{
-				hash:format!("{:016x}",hash).as_str(),
-			}).await.map_err(ValidateError::ApiGetScriptPolicy)?;
+				hash:hash.as_str(),
+			}).await.map_err(ValidateError::ApiGetScriptPolicyFromHash)?;
 
 			// write the policy to the script_map, fetching the replacement code if necessary
 			if let Some(script_policy)=script_policy{
@@ -170,10 +178,22 @@ impl Validator{
 			if let Some(script)=dom.get_by_ref_mut(script_ref){
 				if let Some(rbx_dom_weak::types::Variant::String(source))=script.properties.get_mut("Source"){
 					match script_map.get(source.as_str()).map(|p|&p.policy){
-						Some(Policy::Blocked)=>return Err(ValidateError::Blocked),
+						Some(Policy::Blocked)=>{
+							let hash=hash_source(source.as_str());
+							let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
+								hash:hash.as_str(),
+							}).await.map_err(ValidateError::ApiGetScriptFromHash)?;
+							return Err(ValidateError::ScriptBlocked(script.map(|s|s.ID)));
+						},
 						None
 						|Some(Policy::None)
-						=>return Err(ValidateError::NotAllowed),
+						=>{
+							let hash=hash_source(source.as_str());
+							let script=self.api.get_script_from_hash(submissions_api::types::HashRequest{
+								hash:hash.as_str(),
+							}).await.map_err(ValidateError::ApiGetScriptFromHash)?;
+							return Err(ValidateError::ScriptNotYetReviewed(script.map(|s|s.ID)));
+						},
 						Some(Policy::Allowed)=>(),
 						Some(Policy::Delete)=>{
 							modified=true;
@@ -195,7 +215,10 @@ impl Validator{
 		if modified{
 			// serialize model (slow!)
 			let mut data=Vec::new();
-			rbx_binary::to_writer(&mut data,&dom,dom.root().children()).map_err(ValidateError::WriteDom)?;
+			let &[map_ref]=dom.root().children()else{
+				return Err(ValidateError::ModelFileRootMustHaveOneChild);
+			};
+			rbx_binary::to_writer(&mut data,&dom,&[map_ref]).map_err(ValidateError::ModelFileEncode)?;
 
 			// upload a model lol
 			let model_id=if let Some(model_id)=validate_info.ValidatedModelID{
@@ -207,18 +230,22 @@ impl Validator{
 					ispublic:None,
 					allowComments:None,
 					groupId:None,
-				},data).await.map_err(ValidateError::Upload)?;
+				},data).await.map_err(ValidateError::AssetUpload)?;
 
 				response.AssetId
 			}else{
+				// grab the map instance from the map re
+				let Some(map_instance)=dom.get_by_ref(map_ref)else{
+					return Err(ValidateError::ModelFileChildRefIsNil);
+				};
 				// create new model
 				let response=self.roblox_cookie.create(rbx_asset::cookie::CreateRequest{
-					name:dom.root().name.clone(),
+					name:map_instance.name.clone(),
 					description:"".to_owned(),
 					ispublic:true,
 					allowComments:true,
 					groupId:None,
-				},data).await.map_err(ValidateError::Create)?;
+				},data).await.map_err(ValidateError::AssetCreate)?;
 
 				response.AssetId
 			};
@@ -291,6 +318,25 @@ fn recursive_collect_superclass(objects:&mut std::vec::Vec<rbx_dom_weak::types::
 	}
 }
 
+fn get_partial_path(dom:&rbx_dom_weak::WeakDom,instance:&rbx_dom_weak::Instance)->String{
+	struct ParentIter<'a>{
+		dom:&'a rbx_dom_weak::WeakDom,
+		instance:Option<&'a rbx_dom_weak::Instance>,
+	}
+	impl<'a> Iterator for ParentIter<'a>{
+		type Item=&'a rbx_dom_weak::Instance;
+		fn next(&mut self)->Option<Self::Item>{
+			let parent=self.instance.map(|i|i.parent()).and_then(|p|self.dom.get_by_ref(p));
+			core::mem::replace(&mut self.instance,parent)
+		}
+	}
+
+	let mut tragic:Vec<_>=ParentIter{dom,instance:Some(instance)}.map(|i|i.name.as_str()).collect();
+	tragic.pop();
+	tragic.reverse();
+	tragic.join(".")
+}
+
 fn get_script_refs(dom:&rbx_dom_weak::WeakDom)->Vec<rbx_dom_weak::types::Ref>{
 	let mut scripts=std::vec::Vec::new();
 	recursive_collect_superclass(&mut scripts,dom,dom.root(),"LuaSourceContainer");