Validate a canonical Memory node against `MemoryShape`. Mirrors the doc's shape: `memoryKind` and `status` are closed value sets (exactly one each), `attributedTo` and `proof` must be present, and the `text` must be present and non-empty. As everywhere, presence is enforced and content left honest — no rule reads the memory prose.
(node: &MemoryNode)
| 243 | /// and non-empty. As everywhere, presence is enforced and content left honest — |
| 244 | /// no rule reads the memory prose. |
| 245 | pub fn validate_memory(node: &MemoryNode) -> ValidationReport { |
| 246 | let mut out = Vec::new(); |
| 247 | let focus = &node.id; |
| 248 | |
| 249 | // memoryKind ∈ closed set, exactly one (single field ⇒ cardinality holds). |
| 250 | if !vocab::is_known_memory_kind(&node.memory_kind) { |
| 251 | out.push(Violation { |
| 252 | focus_node: focus.clone(), |
| 253 | shape: "MemoryShape".into(), |
| 254 | path: Some("memoryKind".into()), |
| 255 | message: format!( |
| 256 | "memoryKind '{}' is not one of {:?}", |
| 257 | node.memory_kind, |
| 258 | vocab::MEMORY_KIND |
| 259 | ), |
| 260 | }); |
| 261 | } |
| 262 | |
| 263 | // status ∈ closed set, exactly one. |
| 264 | if !vocab::is_known_memory_status(&node.status) { |
| 265 | out.push(Violation { |
| 266 | focus_node: focus.clone(), |
| 267 | shape: "MemoryShape".into(), |
| 268 | path: Some("status".into()), |
| 269 | message: format!( |
| 270 | "status '{}' is not one of {:?}", |
| 271 | node.status, |
| 272 | vocab::MEMORY_STATUS |
| 273 | ), |
| 274 | }); |
| 275 | } |
| 276 | |
| 277 | // attributedTo must be present (sh:class prov:Agent deferred — see M0 notes). |
| 278 | if node.attributed_to.as_deref().unwrap_or("").is_empty() { |
| 279 | out.push(Violation { |
| 280 | focus_node: focus.clone(), |
| 281 | shape: "MemoryShape".into(), |
| 282 | path: Some("attributedTo".into()), |
| 283 | message: "author (attributedTo) must be present as a DID".into(), |
| 284 | }); |
| 285 | } |
| 286 | |
| 287 | // proof must be present. |
| 288 | if node.proof.is_none() { |
| 289 | out.push(Violation { |
| 290 | focus_node: focus.clone(), |
| 291 | shape: "MemoryShape".into(), |
| 292 | path: Some("proof".into()), |
| 293 | message: "memory must carry a Data Integrity proof".into(), |
| 294 | }); |
| 295 | } |
| 296 | |
| 297 | // presence-enforced: the memory text must exist (content left honest). |
| 298 | if node.text.trim().is_empty() { |
| 299 | out.push(Violation { |
| 300 | focus_node: focus.clone(), |
| 301 | shape: "MemoryShape".into(), |
| 302 | path: Some("text".into()), |