← Files Canonical Memory VerifierARCHIVED FILE
integrations/codex-external-agent-memory-import/codex-import-preflight.patch
10.8 KB · Sep 30, 2026 · 23:13 UTC
diff --git a/codex-rs/external-agent-migration/src/memory_import.rs b/codex-rs/external-agent-migration/src/memory_import.rs
index 92c38aa..f58ac29 100644
--- a/codex-rs/external-agent-migration/src/memory_import.rs
+++ b/codex-rs/external-agent-migration/src/memory_import.rs
@@ -50,6 +50,12 @@ struct ProjectScope<'a> {
cwd: &'a Path,
}
+#[derive(Debug, PartialEq, Eq)]
+struct PreparedMemoryFile {
+ relative_path: PathBuf,
+ content: Vec<u8>,
+}
+
pub(super) async fn import(
codex_home: &Path,
external_agent_home: &Path,
@@ -284,21 +290,45 @@ fn replace_project_resources(
project_cwd: &Path,
memory_files: &[&ExternalMemoryFile],
) -> io::Result<()> {
+ replace_project_resources_with_preflight(
+ codex_home,
+ project_key,
+ project_cwd,
+ memory_files,
+ |_, _, _| Ok(None),
+ )
+ .map(|_| ())
+}
+
+fn replace_project_resources_with_preflight(
+ codex_home: &Path,
+ project_key: &str,
+ project_cwd: &Path,
+ memory_files: &[&ExternalMemoryFile],
+ preflight: impl FnOnce(&str, &Path, &[PreparedMemoryFile]) -> io::Result<Option<String>>,
+) -> io::Result<Option<String>> {
let source_files = memory_files
.iter()
.map(|memory_file| {
- fs::read(&memory_file.source_path)
- .map(|content| (memory_file.relative_path.clone(), content))
+ fs::read(&memory_file.source_path).map(|content| PreparedMemoryFile {
+ relative_path: memory_file.relative_path.clone(),
+ content,
+ })
})
.collect::<io::Result<Vec<_>>>()?;
let scope_content = project_scope_content(project_cwd)?;
+ // A verifier is deliberately called after every source byte is prepared but
+ // before the previous imported project is removed. This keeps ordinary
+ // Markdown imports unchanged while providing a fail-closed integration seam.
+ let conformance_digest = preflight(project_key, project_cwd, &source_files)?;
+
remove_project_resources(codex_home, project_key)?;
let target_root = resources_root(codex_home).join(project_key);
fs::create_dir_all(&target_root)?;
fs::write(target_root.join(PROJECT_SCOPE_FILE), scope_content)?;
- for (relative_path, content) in source_files {
- let target_path = target_root.join(relative_path);
+ for source_file in source_files {
+ let target_path = target_root.join(source_file.relative_path);
let target_parent = target_path.parent().ok_or_else(|| {
invalid_data_error(format!(
"memory target path has no parent: {}",
@@ -306,9 +336,9 @@ fn replace_project_resources(
))
})?;
fs::create_dir_all(target_parent)?;
- fs::write(target_path, content)?;
+ fs::write(target_path, source_file.content)?;
}
- Ok(())
+ Ok(conformance_digest)
}
fn remove_project_resources(codex_home: &Path, project_key: &str) -> io::Result<bool> {
diff --git a/codex-rs/external-agent-migration/src/memory_import_tests.rs b/codex-rs/external-agent-migration/src/memory_import_tests.rs
index 9bf57f3..7d9729c 100644
--- a/codex-rs/external-agent-migration/src/memory_import_tests.rs
+++ b/codex-rs/external-agent-migration/src/memory_import_tests.rs
@@ -1,7 +1,37 @@
use super::*;
use pretty_assertions::assert_eq;
+use std::process::Command;
use tempfile::TempDir;
+fn verify_canonical_fixture(fixture: &str) -> io::Result<String> {
+ let repository_root = std::env::var_os("CMV_REPOSITORY_ROOT")
+ .map(PathBuf::from)
+ .ok_or_else(|| invalid_data_error("CMV_REPOSITORY_ROOT is required"))?;
+ let python = std::env::var_os("CMV_PYTHON").unwrap_or_else(|| "python3".into());
+ let output = Command::new(python)
+ .arg("-B")
+ .arg(repository_root.join("skills/verify-canonical-memory/scripts/cmverify.py"))
+ .arg("verify")
+ .arg(repository_root.join("fixtures").join(fixture))
+ .output()?;
+ let result: serde_json::Value = serde_json::from_slice(&output.stdout)
+ .map_err(|_| invalid_data_error("cmverify did not emit strict JSON"))?;
+ if output.status.success()
+ && result.get("status").and_then(|value| value.as_str()) == Some("VERIFIED")
+ {
+ return result
+ .pointer("/digests/conformance_sha256")
+ .and_then(|value| value.as_str())
+ .map(str::to_string)
+ .ok_or_else(|| invalid_data_error("verified result omitted conformance digest"));
+ }
+ let code = result
+ .pointer("/errors/0/code")
+ .and_then(|value| value.as_str())
+ .unwrap_or("CMV_INTERNAL_FAILURE");
+ Err(invalid_data_error(code))
+}
+
fn write_project_session(project_root: &Path, project_cwd: &Path) {
fs::create_dir_all(project_cwd).expect("create project cwd");
fs::write(
@@ -333,3 +363,154 @@ fn removes_an_existing_unscoped_target_when_cwd_is_unavailable() {
);
assert!(!target_root.exists());
}
+
+#[test]
+fn preflight_failure_preserves_previous_imported_project() {
+ let root = TempDir::new().expect("create tempdir");
+ let codex_home = root.path().join(".codex");
+ let project_cwd = root.path().join("project-a-cwd");
+ fs::create_dir_all(&project_cwd).expect("create project cwd");
+
+ let target_root = resources_root(&codex_home).join("project-a");
+ fs::create_dir_all(&target_root).expect("create previous imported project");
+ fs::write(target_root.join("MEMORY.md"), b"previous verified memory")
+ .expect("write previous memory");
+ fs::write(
+ target_root.join(PROJECT_SCOPE_FILE),
+ project_scope_content(&project_cwd).expect("serialize project scope"),
+ )
+ .expect("write previous scope");
+
+ let source_path = root.path().join("candidate-MEMORY.md");
+ fs::write(&source_path, b"candidate memory").expect("write candidate memory");
+ let memory_file = ExternalMemoryFile {
+ project_key: "project-a".to_string(),
+ project_cwd: Some(project_cwd.clone()),
+ source_path,
+ relative_path: PathBuf::from("MEMORY.md"),
+ };
+
+ let error = replace_project_resources_with_preflight(
+ &codex_home,
+ "project-a",
+ &project_cwd,
+ &[&memory_file],
+ |project_key, checked_cwd, prepared_files| {
+ assert_eq!(project_key, "project-a");
+ assert_eq!(checked_cwd, project_cwd);
+ assert_eq!(
+ prepared_files,
+ &[PreparedMemoryFile {
+ relative_path: PathBuf::from("MEMORY.md"),
+ content: b"candidate memory".to_vec(),
+ }]
+ );
+ Err(invalid_data_error("CMV_SOURCE_HASH_MISMATCH"))
+ },
+ )
+ .expect_err("reject invalid candidate before replacement");
+
+ assert_eq!(error.kind(), io::ErrorKind::InvalidData);
+ assert_eq!(error.to_string(), "CMV_SOURCE_HASH_MISMATCH");
+ assert_eq!(
+ fs::read(target_root.join("MEMORY.md")).expect("read preserved memory"),
+ b"previous verified memory"
+ );
+ assert_eq!(
+ fs::read(target_root.join(PROJECT_SCOPE_FILE)).expect("read preserved scope"),
+ project_scope_content(&project_cwd).expect("serialize expected scope")
+ );
+}
+
+#[test]
+fn canonical_verifier_accepts_valid_bundle_with_stable_digest() {
+ let root = TempDir::new().expect("create tempdir");
+ let codex_home = root.path().join(".codex");
+ let project_cwd = root.path().join("project-a-cwd");
+ fs::create_dir_all(&project_cwd).expect("create project cwd");
+ let source_path = root.path().join("candidate-MEMORY.md");
+ fs::write(&source_path, b"candidate memory").expect("write candidate memory");
+ let memory_file = ExternalMemoryFile {
+ project_key: "project-a".to_string(),
+ project_cwd: Some(project_cwd.clone()),
+ source_path,
+ relative_path: PathBuf::from("MEMORY.md"),
+ };
+ let expected_digest = verify_canonical_fixture("valid/basic").expect("verify valid bundle");
+
+ let actual_digest = replace_project_resources_with_preflight(
+ &codex_home,
+ "project-a",
+ &project_cwd,
+ &[&memory_file],
+ |_, _, _| verify_canonical_fixture("valid/basic").map(Some),
+ )
+ .expect("import verified candidate");
+
+ assert_eq!(actual_digest, Some(expected_digest));
+ assert_eq!(
+ fs::read(resources_root(&codex_home).join("project-a/MEMORY.md"))
+ .expect("read imported candidate"),
+ b"candidate memory"
+ );
+}
+
+#[test]
+fn canonical_verifier_rejections_preserve_previous_import() {
+ let root = TempDir::new().expect("create tempdir");
+ let codex_home = root.path().join(".codex");
+ let project_cwd = root.path().join("project-a-cwd");
+ fs::create_dir_all(&project_cwd).expect("create project cwd");
+ let target_root = resources_root(&codex_home).join("project-a");
+ fs::create_dir_all(&target_root).expect("create previous imported project");
+ let source_path = root.path().join("candidate-MEMORY.md");
+ fs::write(&source_path, b"candidate memory").expect("write candidate memory");
+ let memory_file = ExternalMemoryFile {
+ project_key: "project-a".to_string(),
+ project_cwd: Some(project_cwd.clone()),
+ source_path,
+ relative_path: PathBuf::from("MEMORY.md"),
+ };
+ let cases = [
+ ("invalid/source-tampered", "CMV_SOURCE_HASH_MISMATCH"),
+ ("invalid/ambiguous-heads", "CMV_CURRENT_HEAD_AMBIGUOUS"),
+ (
+ "invalid/approval-true-expired",
+ "CMV_APPROVAL_ELIGIBILITY_VIOLATION",
+ ),
+ (
+ "invalid/approval-true-unexpired",
+ "CMV_APPROVAL_ELIGIBILITY_VIOLATION",
+ ),
+ ];
+
+ for (fixture, expected_code) in cases {
+ fs::write(target_root.join("MEMORY.md"), b"previous verified memory")
+ .expect("restore previous memory");
+ fs::write(
+ target_root.join(PROJECT_SCOPE_FILE),
+ project_scope_content(&project_cwd).expect("serialize project scope"),
+ )
+ .expect("restore previous scope");
+
+ let error = replace_project_resources_with_preflight(
+ &codex_home,
+ "project-a",
+ &project_cwd,
+ &[&memory_file],
+ |_, _, _| verify_canonical_fixture(fixture).map(Some),
+ )
+ .expect_err("reject invalid candidate before replacement");
+
+ assert_eq!(error.kind(), io::ErrorKind::InvalidData);
+ assert_eq!(error.to_string(), expected_code);
+ assert_eq!(
+ fs::read(target_root.join("MEMORY.md")).expect("read preserved memory"),
+ b"previous verified memory"
+ );
+ assert_eq!(
+ fs::read(target_root.join(PROJECT_SCOPE_FILE)).expect("read preserved scope"),
+ project_scope_content(&project_cwd).expect("serialize expected scope")
+ );
+ }
+}
SHA-256: d2ad4cce7c7355b8e6d3425e6bfb465cbdfdf0ed8386589a65282bb4d5fb6adb