diff --git a/CHANGELOG.md b/CHANGELOG.md index 241e1b7..ce46767 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,26 @@ for reference without a full itemized history. ## [Unreleased] +## [0.14.0] - pending tag + +Additive, non-breaking release. No public API was removed or changed +incompatibly since `v0.13.0`. + +Added: +- `sysml_model`: `ElementId` now documents and recognizes `blake3:` as a + sanctioned content-hash scheme alongside `sha256:` and `git:`. The + newtype itself is unchanged — still `pub struct ElementId(pub String)`, + opaque; this is purely additive. + - `ElementId::HASH_SCHEMES` — the `&'static [&'static str]` of + recognized content-hash / VCS scheme prefixes (`"sha256:"`, + `"blake3:"`, `"git:"`). + - `ElementId::content_hash_scheme()` — the scheme prefix this id + carries if it is content-addressed, else `None`. + - `ElementId::is_content_addressed()` — whether this id is a content / + VCS hash rather than a human-meaningful name. + - The `ElementId` doc comment now lists `blake3:…` among the sanctioned + content / VCS hash forms; the **never numeric** rule is unchanged. + ## [0.13.0] - pending tag Additive, non-breaking release. No public API was removed or changed diff --git a/Cargo.toml b/Cargo.toml index 0f249fa..fd000bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ufo-types" -version = "0.13.0" +version = "0.14.0" edition = "2024" rust-version = "1.85" authors = ["Brian Horakh "] diff --git a/src/sysml_model.rs b/src/sysml_model.rs index 3857d26..6eb6750 100644 --- a/src/sysml_model.rs +++ b/src/sysml_model.rs @@ -38,7 +38,7 @@ use serde::{Deserialize, Serialize}; /// /// MAY be a human-meaningful name (`"bus-3"`, `"agent.foo"`), a KerML /// qualified name (`Pkg::Sub::Item`), or a content / VCS hash -/// (`sha256:…`, `git:`) where an element needs a stable derived +/// (`sha256:…`, `blake3:…`, `git:`) where an element needs a stable derived /// identity. It is **never** a numeric id — a random integer breaks the /// byte-identical, git-diffable artifact model; a derived one is just a less /// legible string (kr0ki `DESIGN-NOTE-typed-model-layer.md` §2.4). @@ -55,6 +55,23 @@ impl ElementId { pub fn as_str(&self) -> &str { &self.0 } + + /// The recognized content-hash / VCS scheme prefixes an `ElementId` may carry. + pub const HASH_SCHEMES: &'static [&'static str] = &["sha256:", "blake3:", "git:"]; + + /// If this id is content-addressed, the scheme prefix it uses + /// (`"sha256:"`, `"blake3:"`, `"git:"`), else `None`. + pub fn content_hash_scheme(&self) -> Option<&'static str> { + Self::HASH_SCHEMES + .iter() + .copied() + .find(|p| self.0.starts_with(p)) + } + + /// Whether this id is a content / VCS hash rather than a name. + pub fn is_content_addressed(&self) -> bool { + self.content_hash_scheme().is_some() + } } impl std::fmt::Display for ElementId { @@ -521,6 +538,26 @@ mod tests { assert_eq!(serde_json::from_str::(&json).unwrap(), id); } + #[test] + fn element_id_content_hash_scheme_detection() { + assert_eq!(eid("blake3:abc123").content_hash_scheme(), Some("blake3:")); + assert!(eid("blake3:abc123").is_content_addressed()); + + assert_eq!( + eid("sha256:deadbeef").content_hash_scheme(), + Some("sha256:") + ); + assert!(eid("sha256:deadbeef").is_content_addressed()); + + assert_eq!(eid("git:deadbeef").content_hash_scheme(), Some("git:")); + assert!(eid("git:deadbeef").is_content_addressed()); + + assert_eq!(eid("bus-3").content_hash_scheme(), None); + assert!(!eid("bus-3").is_content_addressed()); + + assert_eq!(ElementId::HASH_SCHEMES.len(), 3); + } + #[test] fn element_kind_all_is_complete_and_ordered() { // Exhaustive match: a new variant fails to compile until it is also