The normalized intent id used to key BOTH the tracked vault attestation path and the legacy `.atomic` sidecar dir, so the dual-read fallback lines up (critic #4). Resolution mirrors the repository's private `normalize_intent_id` closely enough that `attest 1`, `attest pimo-1`, and `attest PIMO-1` all collapse to the SAME ` ` on both the tracked and the legacy paths: 1. A case-insensitiv
(repo: &Repository, id: &str)
| 115 | /// manifest stay as the bare number — the same on both paths, so reconciliation |
| 116 | /// still holds). |
| 117 | pub fn normalized_id(repo: &Repository, id: &str) -> CliResult<String> { |
| 118 | let manifest = repo.vault_manifest().map_err(CliError::Repository)?; |
| 119 | |
| 120 | // (1) direct case-insensitive manifest-key match (PREFIX-N / prefix-n forms). |
| 121 | if let Some(key) = manifest.intents.keys().find(|k| k.eq_ignore_ascii_case(id)) { |
| 122 | return Ok(key.clone()); |
| 123 | } |
| 124 | |
| 125 | // (2) bare number -> expand with the manifest prefix, then confirm against a |
| 126 | // manifest key (case-insensitively) so we return the canonical key form. |
| 127 | if id.parse::<u32>().is_ok() { |
| 128 | let prefix = if manifest.intent_prefix.is_empty() { |
| 129 | "VAULT".to_string() |
| 130 | } else { |
| 131 | manifest.intent_prefix.clone() |
| 132 | }; |
| 133 | let expanded = format!("{prefix}-{id}"); |
| 134 | if let Some(key) = manifest |
| 135 | .intents |
| 136 | .keys() |
| 137 | .find(|k| k.eq_ignore_ascii_case(&expanded)) |
| 138 | { |
| 139 | return Ok(key.clone()); |
| 140 | } |
| 141 | return Ok(expanded); |
| 142 | } |
| 143 | |
| 144 | // (3) fallback: raw arg uppercased. |
| 145 | Ok(id.to_uppercase()) |
| 146 | } |
| 147 | |
| 148 | /// The tracked-vault path for an intent's attestation: |
| 149 | /// `attestations/<sanitized-normalized-id>/attested.md`. |