diff --git a/epiphany-core/src/admitted_model_direction_consideration.rs b/epiphany-core/src/admitted_model_direction_consideration.rs index 81517375..e5c2d824 100644 --- a/epiphany-core/src/admitted_model_direction_consideration.rs +++ b/epiphany-core/src/admitted_model_direction_consideration.rs @@ -149,26 +149,40 @@ pub fn validate_current_request( cache: &cultcache_rs::CultCache, request: &AdmittedModelDirectionConsiderationRequest, ) -> Result<()> { - validate_request_shape(request)?; - if request_is_superseded(cache, request)? { + validate_request(request)?; + if (crate::EpiphanyRepoModelBasis { + projection_digest: request.model_projection_digest.clone(), + source_documents: request.model_source_documents.clone(), + }) + .validate_against_cache(cache) + .is_err() + { bail!("model direction consideration request is stale"); } + Ok(()) +} + +pub fn validate_request(request: &AdmittedModelDirectionConsiderationRequest) -> Result<()> { + if request.schema_version != REQUEST_SCHEMA + || request.contract != REQUEST_CONTRACT + || request.private_state_included + || request.request_id.trim().is_empty() + || request.runtime_id.trim().is_empty() + || request.thread_id.trim().is_empty() + || request + .previous_terminal_result_id + .as_ref() + .is_some_and(|value| value.trim().is_empty()) + || chrono::DateTime::parse_from_rfc3339(&request.requested_at).is_err() + { + bail!("invalid model direction consideration request"); + } crate::EpiphanyRepoModelBasis { projection_digest: request.model_projection_digest.clone(), source_documents: request.model_source_documents.clone(), } - .validate_against_cache(cache)?; - Ok(()) -} - -fn validate_request_shape(request: &AdmittedModelDirectionConsiderationRequest) -> Result<()> { - if request.schema_version != REQUEST_SCHEMA - || request.contract != REQUEST_CONTRACT - || request.private_state_included - || request.request_id.trim().is_empty() - { - bail!("invalid model direction consideration request"); - } + .validate() + .map_err(|_| anyhow!("invalid model direction consideration request"))?; Ok(()) } @@ -176,7 +190,7 @@ pub fn request_is_superseded( cache: &cultcache_rs::CultCache, request: &AdmittedModelDirectionConsiderationRequest, ) -> Result { - validate_request_shape(request)?; + validate_request(request)?; Ok(crate::EpiphanyRepoModelBasis { projection_digest: request.model_projection_digest.clone(), source_documents: request.model_source_documents.clone(), @@ -245,7 +259,7 @@ mod tests { document_key: crate::REPO_MODEL_IDENTITY_KEY.into(), schema_id: Some("EpiphanyRepoModelIdentityDocument".into()), payload_msgpack: vec![1], - payload_sha256: format!("{:x}", Sha256::digest([1])), + payload_sha256: format!("sha256:{:x}", Sha256::digest([1])), }]; crate::EpiphanyRepoModelBasis { projection_digest: format!( @@ -309,6 +323,21 @@ mod tests { assert!(validate_result(&request, &result).is_err()); } + #[test] + fn intrinsic_request_validation_does_not_claim_current_repo_model_authority() { + let request = request(); + assert!(validate_request(&request).is_ok()); + assert!(validate_current_request(&cultcache_rs::CultCache::new(), &request).is_err()); + + let mut substituted = request.clone(); + substituted.model_source_documents[0].payload_msgpack = vec![2]; + assert!(validate_request(&substituted).is_err()); + + let mut malformed = request; + malformed.requested_at = "not-a-time".into(); + assert!(validate_request(&malformed).is_err()); + } + #[test] fn result_requires_exact_request_model_receipt_and_proposal_only_terminal_authority() { let request = request(); diff --git a/epiphany-core/src/lib.rs b/epiphany-core/src/lib.rs index 5cc509c1..a238f91d 100644 --- a/epiphany-core/src/lib.rs +++ b/epiphany-core/src/lib.rs @@ -71,6 +71,7 @@ pub use admitted_model_direction_consideration::{ render_prompt as render_admitted_model_direction_consideration_prompt, result_id_for_launch as admitted_model_direction_consideration_result_id_for_launch, validate_current_request as validate_current_admitted_model_direction_consideration_request, + validate_request as validate_admitted_model_direction_consideration_request, validate_result as validate_admitted_model_direction_consideration_result, }; pub use agent_launch::EPIPHANY_IMAGINATION_OWNER_ROLE; diff --git a/epiphany-core/src/runtime_spine.rs b/epiphany-core/src/runtime_spine.rs index bac2ad90..df81e43b 100644 --- a/epiphany-core/src/runtime_spine.rs +++ b/epiphany-core/src/runtime_spine.rs @@ -6323,7 +6323,7 @@ fn validate_autonomous_proposal_origin_binding( let worker_launch = cache .get::(&binding.direction_worker_job_id)? .ok_or_else(|| anyhow!("autonomous proposal binding lost its Imagination worker launch"))?; - crate::validate_current_admitted_model_direction_consideration_request(cache, &request)?; + crate::validate_admitted_model_direction_consideration_request(&request)?; crate::validate_admitted_model_direction_consideration_result(&request, &result)?; let result_sha256 = format!("{:x}", Sha256::digest(rmp_serde::to_vec_named(&result)?)); let worker_result_sha256 = format!(