Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.7.0] - 2026-02-16

### Changed

- **Breaking:** `ExtensionMark::glossary()` now emits `"ref"` instead of `"termId"` to match the spec's `glossaryMark` schema
- **Breaking:** `GlossaryRef.term_id` serializes as `"ref"` instead of `"termId"`
- Deserialization accepts both old `"termId"` and new `"ref"` for backward compatibility

### Added

- `ExtensionMark::get_glossary_ref()` helper supporting both `"ref"` and legacy `"termId"` keys
- `ExtensionMark::normalize_glossary_attrs()` to migrate `"termId"` → `"ref"` in-place
- Backward-compatibility tests for glossary `"termId"` deserialization

## [0.6.0] - 2026-02-16

### Changed
Expand Down Expand Up @@ -296,7 +310,8 @@ Initial release implementing Codex Document Format Specification v0.1.
- `sign_document` - Sign a document with ES256
- `extract_content` - Extract text content from blocks

[Unreleased]: https://github.com/Entrolution/cdx-core/compare/v0.6.0...HEAD
[Unreleased]: https://github.com/Entrolution/cdx-core/compare/v0.7.0...HEAD
[0.7.0]: https://github.com/Entrolution/cdx-core/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/Entrolution/cdx-core/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/Entrolution/cdx-core/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/Entrolution/cdx-core/compare/v0.3.0...v0.4.0
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ thiserror = "2.0"
chrono = { version = "0.4", features = ["serde", "now"], default-features = false }

# Internal crates
cdx-core = { path = "cdx-core", version = "0.6.0" }
cdx-core = { path = "cdx-core", version = "0.7.0" }
4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

| Version | Supported |
|---------|-----------|
| 0.6.x | Yes |
| < 0.6 | No |
| 0.7.x | Yes |
| < 0.7 | No |

Only the latest minor release receives security updates. Earlier versions are not supported.

Expand Down
2 changes: 1 addition & 1 deletion cdx-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cdx-cli"
version = "0.6.0"
version = "0.7.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion cdx-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cdx-core"
version = "0.6.0"
version = "0.7.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true
Expand Down
51 changes: 49 additions & 2 deletions cdx-core/src/content/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,29 @@ impl ExtensionMark {
}
}

/// Get glossary term ref, supporting both `"ref"` and legacy `"termId"`.
///
/// Returns `None` if neither key is present.
#[must_use]
pub fn get_glossary_ref(&self) -> Option<&str> {
self.get_string_attribute("ref")
.or_else(|| self.get_string_attribute("termId"))
}

/// Rewrite legacy `"termId"` → `"ref"` in the attributes map.
///
/// No-op if `"ref"` already exists or `"termId"` is absent.
pub fn normalize_glossary_attrs(&mut self) {
if let Some(obj) = self.attributes.as_object_mut() {
if obj.contains_key("ref") {
return;
}
if let Some(val) = obj.remove("termId") {
obj.insert("ref".to_string(), val);
}
}
}

// ===== Convenience constructors for common extension marks =====

/// Create a citation mark (semantic extension).
Expand Down Expand Up @@ -340,7 +363,7 @@ impl ExtensionMark {
#[must_use]
pub fn glossary(term_id: impl Into<String>) -> Self {
Self::new("semantic", "glossary").with_attributes(serde_json::json!({
"termId": term_id.into()
"ref": term_id.into()
}))
}

Expand Down Expand Up @@ -1176,7 +1199,31 @@ mod tests {
fn test_glossary_convenience() {
let ext = ExtensionMark::glossary("api-term");
assert!(ext.is_type("semantic", "glossary"));
assert_eq!(ext.get_string_attribute("termId"), Some("api-term"));
assert_eq!(ext.get_string_attribute("ref"), Some("api-term"));
assert_eq!(ext.get_glossary_ref(), Some("api-term"));
}

#[test]
fn test_get_glossary_ref_legacy() {
let ext = ExtensionMark::new("semantic", "glossary")
.with_attributes(serde_json::json!({"termId": "api-term"}));
assert_eq!(ext.get_glossary_ref(), Some("api-term"));
}

#[test]
fn test_normalize_glossary_attrs() {
let mut ext = ExtensionMark::new("semantic", "glossary")
.with_attributes(serde_json::json!({"termId": "api-term"}));
ext.normalize_glossary_attrs();
assert_eq!(ext.get_string_attribute("ref"), Some("api-term"));
assert!(ext.get_string_attribute("termId").is_none());
}

#[test]
fn test_normalize_glossary_attrs_noop_when_ref_exists() {
let mut ext = ExtensionMark::glossary("api-term");
ext.normalize_glossary_attrs();
assert_eq!(ext.get_string_attribute("ref"), Some("api-term"));
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion cdx-core/src/extensions/semantic/glossary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,9 @@ impl GlossaryTerm {

/// A reference to a glossary term in the document.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GlossaryRef {
/// ID of the glossary term.
#[serde(rename = "ref", alias = "termId")]
pub term_id: String,

/// Display text (if different from term).
Expand Down
7 changes: 4 additions & 3 deletions cdx-core/tests/extension_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,14 @@ fn schema_entity_emits_uri_and_entity_type() {
// ===== Glossary =====

#[test]
fn schema_glossary_emits_term_id() {
fn schema_glossary_emits_ref() {
let mark = ExtensionMark::glossary("ai");
let json = mark_to_json(&mark);

assert_eq!(json["type"], "semantic:glossary");
assert_string_field(&json, "termId", "glossary");
assert_eq!(json["termId"], "ai");
assert_string_field(&json, "ref", "glossary");
assert_eq!(json["ref"], "ai");
assert_field_absent(&json, "termId", "glossary must not emit legacy 'termId'");
}

// ===== Index =====
Expand Down
Loading