Load a memory's attestation and classify it against the current source (`inputs`). Prefers the tracked vault entry, falling back to the legacy sidecar. A missing attestation is `None`; a corrupt/malformed one is treated as `None`/skipped with a warning (fail-open, so a read still works on the raw node). Same mechanism as the intent bridge.
(
repo: &Repository,
id_or_path: &str,
inputs: &MemLiftInputs,
)
| 187 | /// as `None`/skipped with a warning (fail-open, so a read still works on the |
| 188 | /// raw node). Same mechanism as the intent bridge. |
| 189 | pub fn load_attestation( |
| 190 | repo: &Repository, |
| 191 | id_or_path: &str, |
| 192 | inputs: &MemLiftInputs, |
| 193 | ) -> CliResult<Attestation> { |
| 194 | // 1) Tracked vault entry (authoritative). The body is the pretty JSON-LD |
| 195 | // node (+ trailing '\n'); parse it straight to a MemoryNode. |
| 196 | let vpath = attestation_vault_path(id_or_path); |
| 197 | if let Some(entry) = repo.vault_retrieve(&vpath).map_err(CliError::Repository)? { |
| 198 | let raw = String::from_utf8_lossy(&entry.content_bytes); |
| 199 | match serde_json::from_str::<MemoryNode>(raw.trim_end()) { |
| 200 | Ok(node) => { |
| 201 | let recorded = serde_json::from_str::<Value>(&entry.frontmatter_json) |
| 202 | .ok() |
| 203 | .and_then(|v| { |
| 204 | v.get("sourceContentHash") |
| 205 | .and_then(Value::as_str) |
| 206 | .map(str::to_owned) |
| 207 | }); |
| 208 | let current = source_content_hash(inputs); |
| 209 | return Ok(match recorded { |
| 210 | Some(h) if h == current => Attestation::Fresh(Box::new(node)), |
| 211 | _ => Attestation::Stale(Box::new(node)), |
| 212 | }); |
| 213 | } |
| 214 | Err(e) => { |
| 215 | eprintln!("warning: ignoring malformed tracked attestation {vpath}: {e}"); |
| 216 | // fall through to the legacy sidecar |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | // 2) Legacy sidecar fallback (pre-upgrade attestations). Memory has no |
| 222 | // PREFIX-N ambiguity, so there is exactly ONE candidate path. |
| 223 | let path = attested_sidecar_path(repo, id_or_path); |
| 224 | if !path.exists() { |
| 225 | return Ok(Attestation::None); |
| 226 | } |
| 227 | let raw = std::fs::read_to_string(&path).map_err(CliError::Io)?; |
| 228 | let artifact: Value = match serde_json::from_str(&raw) { |
| 229 | Ok(v) => v, |
| 230 | Err(e) => { |
| 231 | eprintln!( |
| 232 | "warning: ignoring unreadable attestation sidecar {}: {e}", |
| 233 | path.display() |
| 234 | ); |
| 235 | return Ok(Attestation::None); |
| 236 | } |
| 237 | }; |
| 238 | let node_val = match artifact.get("node") { |
| 239 | Some(n) => n.clone(), |
| 240 | None => { |
| 241 | eprintln!( |
| 242 | "warning: attestation sidecar {} has no 'node' — ignoring", |
| 243 | path.display() |
| 244 | ); |
| 245 | return Ok(Attestation::None); |
| 246 | } |