use crate::contracts::ITangleTypes;
use alloc::borrow::ToOwned;
use alloc::string::{String, ToString};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value, json};
use thiserror::Error;
pub const METADATA_SCHEMA_V1: &str = "tangle.blueprint.metadata.v1";
const EXECUTION_PROFILE_KEY: &str = "execution_profile";
const JOB_PROFILES_BLOB_KEY: &str = "job_profiles_b64_gzip";
#[derive(Debug, Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExecutionProfileError {
#[error("profiling_data must be valid JSON: {message}")]
InvalidJson {
message: String,
},
#[error("profiling_data must be a JSON object")]
MetadataNotObject,
#[error("execution_profile must be an object")]
ExecutionProfileNotObject,
#[error("execution_profile is invalid: {message}")]
InvalidExecutionProfile {
message: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConfidentialityPolicy {
Any,
TeeRequired,
StandardRequired,
TeePreferred,
}
impl Default for ConfidentialityPolicy {
fn default() -> Self {
Self::Any
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GpuPolicy {
None,
Required,
Preferred,
}
impl Default for GpuPolicy {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct GpuRequirements {
#[serde(default)]
pub policy: GpuPolicy,
#[serde(default)]
pub min_count: u32,
#[serde(default)]
pub min_vram_gb: u32,
}
impl GpuRequirements {
#[must_use]
pub fn normalized(mut self) -> Self {
if !matches!(self.policy, GpuPolicy::None) && self.min_count == 0 {
self.min_count = 1;
}
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub struct ExecutionProfile {
#[serde(default)]
pub confidentiality: ConfidentialityPolicy,
#[serde(default)]
pub gpu: GpuRequirements,
}
impl ExecutionProfile {
#[must_use]
pub fn tee_required(self) -> bool {
matches!(self.confidentiality, ConfidentialityPolicy::TeeRequired)
}
#[must_use]
pub fn tee_supported(self) -> bool {
matches!(
self.confidentiality,
ConfidentialityPolicy::Any
| ConfidentialityPolicy::TeeRequired
| ConfidentialityPolicy::TeePreferred
)
}
#[must_use]
pub fn standard_required(self) -> bool {
matches!(
self.confidentiality,
ConfidentialityPolicy::StandardRequired
)
}
#[must_use]
pub fn gpu_required(self) -> bool {
matches!(self.gpu.policy, GpuPolicy::Required)
}
#[must_use]
pub fn gpu_preferred(self) -> bool {
matches!(self.gpu.policy, GpuPolicy::Preferred)
}
#[must_use]
pub fn needs_gpu(self) -> bool {
!matches!(self.gpu.policy, GpuPolicy::None)
}
}
#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
struct RawExecutionProfile {
confidentiality: Option<ConfidentialityPolicy>,
#[serde(default)]
gpu: Option<GpuRequirements>,
}
pub fn resolve_execution_profile(
metadata: &ITangleTypes::BlueprintMetadata,
) -> Result<Option<ExecutionProfile>, ExecutionProfileError> {
resolve_execution_profile_from_profiling_data(metadata.profilingData.as_str())
}
pub fn resolve_execution_profile_from_profiling_data(
profiling_data: &str,
) -> Result<Option<ExecutionProfile>, ExecutionProfileError> {
let trimmed = profiling_data.trim();
if trimmed.is_empty() {
return Ok(None);
}
let root: Value =
serde_json::from_str(trimmed).map_err(|e| ExecutionProfileError::InvalidJson {
message: e.to_string(),
})?;
let Some(root_object) = root.as_object() else {
return Err(ExecutionProfileError::MetadataNotObject);
};
let Some(raw_profile_value) = root_object.get(EXECUTION_PROFILE_KEY) else {
return Ok(None);
};
let Some(raw_profile_object) = raw_profile_value.as_object() else {
return Err(ExecutionProfileError::ExecutionProfileNotObject);
};
let raw_profile: RawExecutionProfile =
serde_json::from_value(Value::Object(raw_profile_object.clone())).map_err(|e| {
ExecutionProfileError::InvalidExecutionProfile {
message: e.to_string(),
}
})?;
Ok(Some(ExecutionProfile {
confidentiality: raw_profile.confidentiality.unwrap_or_default(),
gpu: raw_profile.gpu.unwrap_or_default().normalized(),
}))
}
pub fn resolve_gpu_requirements(
metadata: &ITangleTypes::BlueprintMetadata,
) -> Result<Option<GpuRequirements>, ExecutionProfileError> {
Ok(resolve_execution_profile(metadata)?.map(|profile| profile.gpu))
}
pub fn resolve_confidentiality_policy(
metadata: &ITangleTypes::BlueprintMetadata,
) -> Result<Option<ConfidentialityPolicy>, ExecutionProfileError> {
Ok(resolve_execution_profile(metadata)?.map(|profile| profile.confidentiality))
}
#[must_use]
pub fn inject_execution_profile(profiling_data: &str, profile: ExecutionProfile) -> String {
let trimmed = profiling_data.trim();
if trimmed.is_empty() {
return default_metadata_payload(profile).to_string();
}
if let Ok(mut value) = serde_json::from_str::<Value>(trimmed)
&& let Some(root) = value.as_object_mut()
{
if let Some(schema) = root.get("schema").and_then(Value::as_str)
&& schema != METADATA_SCHEMA_V1
{
return json!({
"schema": METADATA_SCHEMA_V1,
EXECUTION_PROFILE_KEY: profile_to_value(profile),
JOB_PROFILES_BLOB_KEY: trimmed,
})
.to_string();
}
upsert_execution_profile(root, profile);
return value.to_string();
}
json!({
"schema": METADATA_SCHEMA_V1,
EXECUTION_PROFILE_KEY: profile_to_value(profile),
JOB_PROFILES_BLOB_KEY: trimmed,
})
.to_string()
}
#[must_use]
pub fn extract_job_profiles_blob(profiling_data: &str) -> Option<String> {
let trimmed = profiling_data.trim();
if trimmed.is_empty() {
return None;
}
let value: Value = serde_json::from_str(trimmed).ok()?;
value
.as_object()?
.get(JOB_PROFILES_BLOB_KEY)
.and_then(Value::as_str)
.map(ToOwned::to_owned)
}
fn default_metadata_payload(profile: ExecutionProfile) -> Value {
json!({
"schema": METADATA_SCHEMA_V1,
EXECUTION_PROFILE_KEY: profile_to_value(profile),
})
}
fn profile_to_value(profile: ExecutionProfile) -> Value {
let mut obj = Map::new();
obj.insert(
"confidentiality".to_string(),
serde_json::to_value(profile.confidentiality).unwrap_or_default(),
);
if !matches!(profile.gpu.policy, GpuPolicy::None) {
obj.insert(
"gpu".to_string(),
serde_json::to_value(profile.gpu).unwrap_or_default(),
);
}
Value::Object(obj)
}
fn upsert_execution_profile(root: &mut Map<String, Value>, profile: ExecutionProfile) {
root.insert(
"schema".to_string(),
Value::String(METADATA_SCHEMA_V1.to_string()),
);
root.insert(EXECUTION_PROFILE_KEY.to_string(), profile_to_value(profile));
}
#[cfg(test)]
mod tests {
use super::*;
use crate::contracts::ITangleTypes;
#[test]
fn resolves_required_profile() {
let mut metadata: ITangleTypes::BlueprintMetadata = Default::default();
metadata.profilingData =
r#"{"execution_profile":{"confidentiality":"tee_required"}}"#.into();
assert_eq!(
resolve_execution_profile(&metadata).unwrap(),
Some(ExecutionProfile {
confidentiality: ConfidentialityPolicy::TeeRequired,
..Default::default()
})
);
}
#[test]
fn resolves_optional_profile() {
let mut metadata: ITangleTypes::BlueprintMetadata = Default::default();
metadata.profilingData =
r#"{"execution_profile":{"confidentiality":"tee_preferred"}}"#.into();
assert_eq!(
resolve_execution_profile(&metadata).unwrap(),
Some(ExecutionProfile {
confidentiality: ConfidentialityPolicy::TeePreferred,
..Default::default()
})
);
}
#[test]
fn strict_parser_errors_on_non_json_payloads() {
let err =
resolve_execution_profile_from_profiling_data("tee").expect_err("expected parse error");
assert!(matches!(err, ExecutionProfileError::InvalidJson { .. }));
}
#[test]
fn strict_parser_errors_on_non_object_json_payloads() {
let err =
resolve_execution_profile_from_profiling_data("[]").expect_err("expected type error");
assert!(matches!(err, ExecutionProfileError::MetadataNotObject));
}
#[test]
fn strict_parser_errors_on_non_string_confidentiality() {
let err = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"confidentiality":true}}"#,
)
.expect_err("expected type error");
assert!(matches!(
err,
ExecutionProfileError::InvalidExecutionProfile { .. }
));
}
#[test]
fn strict_parser_errors_on_unknown_fields() {
let err = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"confidentiality":"tee_required","bad":true}}"#,
)
.expect_err("expected schema error");
assert!(matches!(
err,
ExecutionProfileError::InvalidExecutionProfile { .. }
));
}
#[test]
fn resolves_gpu_required_profile() {
let profile = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"confidentiality":"any","gpu":{"policy":"required","min_count":2,"min_vram_gb":40}}}"#,
)
.unwrap()
.unwrap();
assert!(profile.gpu_required());
assert_eq!(profile.gpu.min_count, 2);
assert_eq!(profile.gpu.min_vram_gb, 40);
}
#[test]
fn resolves_gpu_preferred_profile() {
let profile = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"gpu":{"policy":"preferred","min_count":1,"min_vram_gb":24}}}"#,
)
.unwrap()
.unwrap();
assert!(profile.gpu_preferred());
assert!(!profile.gpu_required());
assert_eq!(profile.gpu.min_count, 1);
}
#[test]
fn defaults_gpu_to_none_when_absent() {
let profile = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"confidentiality":"tee_required"}}"#,
)
.unwrap()
.unwrap();
assert!(!profile.needs_gpu());
assert_eq!(profile.gpu.policy, GpuPolicy::None);
}
#[test]
fn resolves_combined_tee_and_gpu() {
let profile = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"confidentiality":"tee_required","gpu":{"policy":"required","min_count":1,"min_vram_gb":80}}}"#,
)
.unwrap()
.unwrap();
assert!(profile.tee_required());
assert!(profile.gpu_required());
assert_eq!(profile.gpu.min_vram_gb, 80);
}
#[test]
fn injects_gpu_profile_into_empty_payload() {
let payload = inject_execution_profile(
"",
ExecutionProfile {
confidentiality: ConfidentialityPolicy::Any,
gpu: GpuRequirements {
policy: GpuPolicy::Required,
min_count: 1,
min_vram_gb: 24,
},
},
);
let value: Value = serde_json::from_str(&payload).unwrap();
let gpu = value
.get(EXECUTION_PROFILE_KEY)
.and_then(|v| v.get("gpu"))
.expect("gpu field should be present");
assert_eq!(gpu.get("policy").and_then(Value::as_str), Some("required"));
assert_eq!(gpu.get("min_count").and_then(Value::as_u64), Some(1));
}
#[test]
fn omits_gpu_from_profile_when_none() {
let payload = inject_execution_profile(
"",
ExecutionProfile {
confidentiality: ConfidentialityPolicy::Any,
gpu: GpuRequirements::default(),
},
);
let value: Value = serde_json::from_str(&payload).unwrap();
let profile = value.get(EXECUTION_PROFILE_KEY).unwrap();
assert!(
profile.get("gpu").is_none(),
"gpu field should be omitted when policy is none"
);
}
#[test]
fn injects_into_empty_payload() {
let payload = inject_execution_profile(
"",
ExecutionProfile {
confidentiality: ConfidentialityPolicy::Any,
..Default::default()
},
);
let value: Value = serde_json::from_str(&payload).unwrap();
assert_eq!(
value
.get(EXECUTION_PROFILE_KEY)
.and_then(|v| v.get("confidentiality"))
.and_then(Value::as_str),
Some("any")
);
}
#[test]
fn updates_existing_object_payload() {
let payload = inject_execution_profile(
r#"{"job_profiles_b64_gzip":"abc"}"#,
ExecutionProfile {
confidentiality: ConfidentialityPolicy::TeeRequired,
..Default::default()
},
);
let value: Value = serde_json::from_str(&payload).unwrap();
assert_eq!(
value
.get(EXECUTION_PROFILE_KEY)
.and_then(|v| v.get("confidentiality"))
.and_then(Value::as_str),
Some("tee_required")
);
assert_eq!(
value.get(JOB_PROFILES_BLOB_KEY).and_then(Value::as_str),
Some("abc")
);
}
#[test]
fn wraps_non_json_payload_as_job_profiles_blob() {
let payload = inject_execution_profile(
"H4sIAAAAAAAA/2NgYGBgBGIOAwA6rY+4BQAAAA==",
ExecutionProfile {
confidentiality: ConfidentialityPolicy::TeeRequired,
..Default::default()
},
);
let value: Value = serde_json::from_str(&payload).unwrap();
assert_eq!(
value.get(JOB_PROFILES_BLOB_KEY).and_then(Value::as_str),
Some("H4sIAAAAAAAA/2NgYGBgBGIOAwA6rY+4BQAAAA==")
);
}
#[test]
fn extracts_profiles_blob_from_structured_payload() {
let payload = r#"{"execution_profile":{"confidentiality":"tee_required"},"job_profiles_b64_gzip":"abc"}"#;
assert_eq!(extract_job_profiles_blob(payload), Some("abc".to_string()));
}
#[test]
fn extract_profiles_blob_requires_structured_payload() {
assert_eq!(extract_job_profiles_blob("H4sIAAAAA..."), None);
}
#[test]
fn normalizes_required_with_zero_min_count() {
let profile = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"gpu":{"policy":"required"}}}"#,
)
.unwrap()
.unwrap();
assert!(profile.gpu_required());
assert_eq!(
profile.gpu.min_count, 1,
"Required policy must normalize min_count to at least 1"
);
}
#[test]
fn normalizes_preferred_with_zero_min_count() {
let profile = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"gpu":{"policy":"preferred"}}}"#,
)
.unwrap()
.unwrap();
assert!(profile.gpu_preferred());
assert_eq!(
profile.gpu.min_count, 1,
"Preferred policy must normalize min_count to at least 1"
);
}
#[test]
fn gpu_inject_then_resolve_round_trip() {
let original = ExecutionProfile {
confidentiality: ConfidentialityPolicy::Any,
gpu: GpuRequirements {
policy: GpuPolicy::Required,
min_count: 2,
min_vram_gb: 80,
},
};
let payload = inject_execution_profile("", original);
let resolved = resolve_execution_profile_from_profiling_data(&payload)
.unwrap()
.unwrap();
assert_eq!(resolved, original, "inject then resolve must round-trip");
}
#[test]
fn gpu_rejects_unknown_fields_in_requirements() {
let err = resolve_execution_profile_from_profiling_data(
r#"{"execution_profile":{"gpu":{"policy":"required","unknown_field":true}}}"#,
)
.expect_err("expected error for unknown GPU field");
assert!(matches!(
err,
ExecutionProfileError::InvalidExecutionProfile { .. }
));
}
}