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)
| 364 | /// and non-empty. As everywhere, presence is enforced and content left honest — |
| 365 | /// no rule reads the memory prose. |
| 366 | pub fn validate_memory(node: &MemoryNode) -> ValidationReport { |
| 367 | let mut out = Vec::new(); |
| 368 | let focus = &node.id; |
| 369 | |
| 370 | // memoryKind ∈ closed set, exactly one (single field ⇒ cardinality holds). |
| 371 | if !vocab::is_known_memory_kind(&node.memory_kind) { |
| 372 | out.push(Violation { |
| 373 | focus_node: focus.clone(), |
| 374 | shape: "MemoryShape".into(), |
| 375 | path: Some("memoryKind".into()), |
| 376 | message: format!( |
| 377 | "memoryKind '{}' is not one of {:?}", |
| 378 | node.memory_kind, |
| 379 | vocab::MEMORY_KIND |
| 380 | ), |
| 381 | }); |
| 382 | } |
| 383 | |
| 384 | // status ∈ closed set, exactly one. |
| 385 | if !vocab::is_known_memory_status(&node.status) { |
| 386 | out.push(Violation { |
| 387 | focus_node: focus.clone(), |
| 388 | shape: "MemoryShape".into(), |
| 389 | path: Some("status".into()), |
| 390 | message: format!( |
| 391 | "status '{}' is not one of {:?}", |
| 392 | node.status, |
| 393 | vocab::MEMORY_STATUS |
| 394 | ), |
| 395 | }); |
| 396 | } |
| 397 | |
| 398 | // attributedTo must be present (sh:class prov:Agent deferred — see M0 notes). |
| 399 | if node.attributed_to.as_deref().unwrap_or("").is_empty() { |
| 400 | out.push(Violation { |
| 401 | focus_node: focus.clone(), |
| 402 | shape: "MemoryShape".into(), |
| 403 | path: Some("attributedTo".into()), |
| 404 | message: "author (attributedTo) must be present as a DID".into(), |
| 405 | }); |
| 406 | } |
| 407 | |
| 408 | // proof must be present. |
| 409 | if node.proof.is_none() { |
| 410 | out.push(Violation { |
| 411 | focus_node: focus.clone(), |
| 412 | shape: "MemoryShape".into(), |
| 413 | path: Some("proof".into()), |
| 414 | message: "memory must carry a Data Integrity proof".into(), |
| 415 | }); |
| 416 | } |
| 417 | |
| 418 | // presence-enforced: the memory text must exist (content left honest). |
| 419 | if node.text.trim().is_empty() { |
| 420 | out.push(Violation { |
| 421 | focus_node: focus.clone(), |
| 422 | shape: "MemoryShape".into(), |
| 423 | path: Some("text".into()), |