mbx_cache_core/agent/
manifest.rs1use super::TASK_ACTION_MANIFEST_VERSION;
2use crate::{CacheDigest, TaskActionManifest};
3use eyre::{Context, Result, bail};
4use log::warn;
5use std::collections::BTreeMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9pub fn is_task_identity(task: &str) -> bool {
15 task.len() == 64
16 && task
17 .bytes()
18 .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
19}
20
21pub(super) fn validate_task_identity(task: &str) -> Result<()> {
22 if !is_task_identity(task) {
23 bail!("invalid task action identity");
24 }
25 Ok(())
26}
27
28pub(super) fn task_manifest_dir(store: &Path) -> PathBuf {
30 store.join("task-manifests").join("v1")
31}
32
33pub fn task_manifest_actions(store: &Path, task: &str) -> Result<Vec<CacheDigest>> {
41 validate_task_identity(task)?;
42 let path = task_manifest_dir(store).join(format!("{task}.json"));
43 let bytes = match fs::read(&path) {
44 Ok(bytes) => bytes,
45 Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
46 Err(error) => {
47 return Err(error).wrap_err_with(|| format!("failed to read {}", path.display()));
48 }
49 };
50 let Ok(manifest) = serde_json::from_slice::<TaskActionManifest>(&bytes) else {
51 return Ok(Vec::new());
52 };
53 if validate_task_manifest(&manifest, task).is_err() {
54 return Ok(Vec::new());
55 }
56 Ok(manifest
57 .predictions
58 .into_iter()
59 .map(|prediction| prediction.action)
60 .collect())
61}
62
63pub(super) fn validate_task_manifest(manifest: &TaskActionManifest, task: &str) -> Result<()> {
64 if manifest.task == task && manifest.validate() {
65 Ok(())
66 } else {
67 bail!("invalid task action manifest")
68 }
69}
70
71pub(super) fn merge_task_manifests(
72 task: &str,
73 base: Option<TaskActionManifest>,
74 update: TaskActionManifest,
75) -> Result<TaskActionManifest> {
76 validate_task_manifest(&update, task)?;
77 let mut predictions = BTreeMap::new();
78 if let Some(base) = base {
79 validate_task_manifest(&base, task)?;
80 predictions.extend(
81 base.predictions
82 .into_iter()
83 .map(|prediction| (prediction.invocation.clone(), prediction)),
84 );
85 }
86 predictions.extend(
87 update
88 .predictions
89 .into_iter()
90 .map(|prediction| (prediction.invocation.clone(), prediction)),
91 );
92 let manifest = TaskActionManifest {
93 version: TASK_ACTION_MANIFEST_VERSION,
94 task: task.to_owned(),
95 predictions: predictions.into_values().collect(),
96 };
97 validate_task_manifest(&manifest, task)?;
98 Ok(manifest)
99}
100
101pub(super) fn merge_remote_task_manifest(
102 task: &str,
103 remote: TaskActionManifest,
104 local: TaskActionManifest,
105) -> (TaskActionManifest, bool) {
106 match merge_task_manifests(task, Some(remote), local.clone()) {
107 Ok(manifest) => (manifest, true),
108 Err(error) => {
109 warn!("remote task action manifest merge failed for {task}: {error}");
110 (local, false)
111 }
112 }
113}