Resolve `urn:atomic:change: ` evidence URNs against the change store at `repo_root`. One violation per URN that does not resolve. With no repository the check is skipped — the gate still enforces presence; resolution needs a change store. (Ported from PR #102, adapted to the top-level `intent validate`.)
(
evidence: &[(String, String)],
repo_root: Option<&Path>,
)
| 118 | /// the check is skipped — the gate still enforces presence; resolution needs a |
| 119 | /// change store. (Ported from PR #102, adapted to the top-level `intent validate`.) |
| 120 | fn check_evidence_resolution( |
| 121 | evidence: &[(String, String)], |
| 122 | repo_root: Option<&Path>, |
| 123 | ) -> Vec<Violation> { |
| 124 | let mut violations = Vec::new(); |
| 125 | if evidence.is_empty() { |
| 126 | return violations; |
| 127 | } |
| 128 | let Some(root) = repo_root else { |
| 129 | eprintln!("note: evidence resolution skipped — not inside an Atomic repository"); |
| 130 | return violations; |
| 131 | }; |
| 132 | let repo = match Repository::open_readonly(root) { |
| 133 | Ok(repo) => repo, |
| 134 | Err(e) => { |
| 135 | eprintln!("note: evidence resolution skipped — cannot open repository: {e}"); |
| 136 | return violations; |
| 137 | } |
| 138 | }; |
| 139 | for (focus, urn) in evidence { |
| 140 | let Some(hash_str) = urn.strip_prefix("urn:atomic:change:") else { |
| 141 | continue; // non-change URNs are out of scope here |
| 142 | }; |
| 143 | let resolved = Hash::from_base32(hash_str.as_bytes()) |
| 144 | .map(|h| repo.has_change(&h)) |
| 145 | .unwrap_or(false); |
| 146 | if !resolved { |
| 147 | violations.push(Violation { |
| 148 | focus_node: focus.clone(), |
| 149 | shape: "EvidenceResolution".to_string(), |
| 150 | path: Some("evidence".to_string()), |
| 151 | message: format!("evidence {urn} does not resolve to a change in this repository"), |
| 152 | }); |
| 153 | } |
| 154 | } |
| 155 | violations |
| 156 | } |
| 157 | |
| 158 | /// Serialize a [`ValidationReport`] to the documented JSON shape: |
| 159 | /// `{conforms, results: [{focus_node, shape, path, message}]}`. |