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)
| 168 | /// and non-empty. As everywhere, presence is enforced and content left honest — |
| 169 | /// no rule reads the memory prose. |
| 170 | pub fn validate_memory(node: &MemoryNode) -> ValidationReport { |
| 171 | let mut out = Vec::new(); |
| 172 | let focus = &node.id; |
| 173 | |
| 174 | // memoryKind ∈ closed set, exactly one (single field ⇒ cardinality holds). |
| 175 | if !vocab::is_known_memory_kind(&node.memory_kind) { |
| 176 | out.push(Violation { |
| 177 | focus_node: focus.clone(), |
| 178 | shape: "MemoryShape".into(), |
| 179 | path: Some("memoryKind".into()), |
| 180 | message: format!( |
| 181 | "memoryKind '{}' is not one of {:?}", |
| 182 | node.memory_kind, |
| 183 | vocab::MEMORY_KIND |
| 184 | ), |
| 185 | }); |
| 186 | } |
| 187 | |
| 188 | // status ∈ closed set, exactly one. |
| 189 | if !vocab::is_known_memory_status(&node.status) { |
| 190 | out.push(Violation { |
| 191 | focus_node: focus.clone(), |
| 192 | shape: "MemoryShape".into(), |
| 193 | path: Some("status".into()), |
| 194 | message: format!( |
| 195 | "status '{}' is not one of {:?}", |
| 196 | node.status, |
| 197 | vocab::MEMORY_STATUS |
| 198 | ), |
| 199 | }); |
| 200 | } |
| 201 | |
| 202 | // attributedTo must be present (sh:class prov:Agent deferred — see M0 notes). |
| 203 | if node.attributed_to.as_deref().unwrap_or("").is_empty() { |
| 204 | out.push(Violation { |
| 205 | focus_node: focus.clone(), |
| 206 | shape: "MemoryShape".into(), |
| 207 | path: Some("attributedTo".into()), |
| 208 | message: "author (attributedTo) must be present as a DID".into(), |
| 209 | }); |
| 210 | } |
| 211 | |
| 212 | // proof must be present. |
| 213 | if node.proof.is_none() { |
| 214 | out.push(Violation { |
| 215 | focus_node: focus.clone(), |
| 216 | shape: "MemoryShape".into(), |
| 217 | path: Some("proof".into()), |
| 218 | message: "memory must carry a Data Integrity proof".into(), |
| 219 | }); |
| 220 | } |
| 221 | |
| 222 | // presence-enforced: the memory text must exist (content left honest). |
| 223 | if node.text.trim().is_empty() { |
| 224 | out.push(Violation { |
| 225 | focus_node: focus.clone(), |
| 226 | shape: "MemoryShape".into(), |
| 227 | path: Some("text".into()), |