(&self)
| 68 | |
| 69 | impl Command for MemoryNew { |
| 70 | fn run(&self) -> CliResult<()> { |
| 71 | // Reject a bad --kind EARLY (before touching the vault) so `new` never |
| 72 | // writes a non-liftable / non-conforming spine. |
| 73 | if !MEMORY_KIND.contains(&self.kind.as_str()) { |
| 74 | return Err(CliError::InvalidArgument { |
| 75 | message: format!( |
| 76 | "unknown memory kind '{}' (one of {:?})", |
| 77 | self.kind, MEMORY_KIND |
| 78 | ), |
| 79 | }); |
| 80 | } |
| 81 | // status is an equally-closed vocabulary — reject a bad one early too, |
| 82 | // so `new` never persists a non-conforming spine (the gate would only |
| 83 | // catch it later at validate/attest). |
| 84 | if !MEMORY_STATUS.contains(&self.status.as_str()) { |
| 85 | return Err(CliError::InvalidArgument { |
| 86 | message: format!( |
| 87 | "unknown memory status '{}' (one of {:?})", |
| 88 | self.status, MEMORY_STATUS |
| 89 | ), |
| 90 | }); |
| 91 | } |
| 92 | if self |
| 93 | .text |
| 94 | .as_ref() |
| 95 | .is_some_and(|text| text.trim().is_empty()) |
| 96 | { |
| 97 | return Err(CliError::InvalidArgument { |
| 98 | message: "memory text cannot be empty".to_string(), |
| 99 | }); |
| 100 | } |
| 101 | |
| 102 | let root = find_repository_root()?; |
| 103 | let repo = Repository::open(&root).map_err(CliError::Repository)?; |
| 104 | |
| 105 | let id = self.id.clone().unwrap_or_else(generate_ulid_lowercased); |
| 106 | let vault_path = bridge::normalize_memory_path(&id); |
| 107 | |
| 108 | // Build the frontmatter SPINE as FLAT SCALAR/array fields only (so it |
| 109 | // survives yaml_frontmatter_to_json round-trip). Do NOT write |
| 110 | // attributedTo/proof/contentHash — attest fills those. createdAt is |
| 111 | // STABLE (load-bearing: it is signed, so every re-lift must reproduce |
| 112 | // byte-identical signing bytes). |
| 113 | let mut spine = Map::new(); |
| 114 | spine.insert("uid".into(), Value::String(id.clone())); |
| 115 | spine.insert("memoryKind".into(), Value::String(self.kind.clone())); |
| 116 | spine.insert("status".into(), Value::String(self.status.clone())); |
| 117 | spine.insert( |
| 118 | "createdAt".into(), |
| 119 | Value::String(chrono::Utc::now().to_rfc3339()), |
| 120 | ); |
| 121 | if !self.about.is_empty() { |
| 122 | spine.insert( |
| 123 | "about".into(), |
| 124 | Value::Array(self.about.iter().cloned().map(Value::String).collect()), |
| 125 | ); |
| 126 | } |
| 127 | if !self.derived_from.is_empty() { |
nothing calls this directly
no test coverage detected