tangle


git clone https://radroots.dev/git/tangle.git
Log | Files | Refs | README | LICENSE

commit a4131641c7f27893b3825254fe58648fbf3aff53
parent f6c6c9f246373789abcdaf60c78964e660a7831f
Author: triesap <tyson@radroots.org>
Date:   Sun, 14 Jun 2026 12:51:37 -0700

nip11: advertise retention contract

- add a typed retention object to relay info
- distinguish visibility gating from physical erasure
- expose no compaction guarantee in NIP-11 JSON
- cover retention fields in runtime and binary tests

Diffstat:
Mcrates/tangle/tests/version.rs | 2++
Mcrates/tangle_runtime/src/nip11.rs | 39+++++++++++++++++++++++++++++++++++++++
Mcrates/tangle_runtime/src/server.rs | 2++
Mcrates/tangle_runtime/tests/base_relay_v2.rs | 6++++++
Mcrates/tangle_runtime/tests/ops_truthfulness.rs | 2++
5 files changed, 51 insertions(+), 0 deletions(-)

diff --git a/crates/tangle/tests/version.rs b/crates/tangle/tests/version.rs @@ -203,6 +203,8 @@ fn tangle_run_starts_server_and_stays_alive_until_shutdown() { assert_eq!(nip11_value["limitation"]["payment_required"], false); assert_eq!(nip11_value["limitation"]["restricted_writes"], true); assert_eq!(nip11_value["limitation"]["default_limit"], 100); + assert_eq!(nip11_value["retention"]["physical_erasure"], false); + assert_eq!(nip11_value["retention"]["compaction_guarantee"], false); assert!( nip11_value["supported_nips"] .as_array() diff --git a/crates/tangle_runtime/src/nip11.rs b/crates/tangle_runtime/src/nip11.rs @@ -111,6 +111,7 @@ impl BaseRelayInfoConfig { restricted_writes: self.restricted_writes, default_limit: self.limits.default_limit(), }, + retention: BaseRelayInfoRetentionDocument::tangle_default(), }) } } @@ -130,6 +131,7 @@ pub struct BaseRelayInfoDocument { pub software: String, pub version: String, pub limitation: BaseRelayInfoLimitationDocument, + pub retention: BaseRelayInfoRetentionDocument, } impl BaseRelayInfoDocument { @@ -154,6 +156,27 @@ pub struct BaseRelayInfoLimitationDocument { pub default_limit: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BaseRelayInfoRetentionDocument { + pub accepted_events: String, + pub relay_generated_events: String, + pub group_visibility: String, + pub physical_erasure: bool, + pub compaction_guarantee: bool, +} + +impl BaseRelayInfoRetentionDocument { + fn tangle_default() -> Self { + Self { + accepted_events: "accepted events are retained in canonical storage without a time-based expiration policy".to_owned(), + relay_generated_events: "relay-generated group state events are retained with their source events".to_owned(), + group_visibility: "private and hidden group policy gates visibility without implying physical deletion".to_owned(), + physical_erasure: false, + compaction_guarantee: false, + } + } +} + pub fn base_relay_info_router(document: BaseRelayInfoDocument) -> Router { Router::new() .route("/", get(base_relay_info)) @@ -261,6 +284,16 @@ mod tests { assert!(!document.limitation.payment_required); assert!(document.limitation.restricted_writes); assert_eq!(document.limitation.default_limit, 100); + assert_eq!( + document.retention.accepted_events, + "accepted events are retained in canonical storage without a time-based expiration policy" + ); + assert_eq!( + document.retention.group_visibility, + "private and hidden group policy gates visibility without implying physical deletion" + ); + assert!(!document.retention.physical_erasure); + assert!(!document.retention.compaction_guarantee); assert_eq!(disabled.supported_nips, vec![1, 11, 42, 45, 70]); assert!(disabled.relay_self().is_none()); } @@ -315,6 +348,12 @@ mod tests { let value = serde_json::from_slice::<serde_json::Value>(&body).expect("json"); assert_eq!(value["name"], document.name); assert!(value["self"].as_str().is_some()); + assert_eq!(value["retention"]["physical_erasure"], false); + assert_eq!(value["retention"]["compaction_guarantee"], false); + assert_eq!( + value["retention"]["group_visibility"], + "private and hidden group policy gates visibility without implying physical deletion" + ); let rejected = base_relay_info_router(document) .oneshot( diff --git a/crates/tangle_runtime/src/server.rs b/crates/tangle_runtime/src/server.rs @@ -482,6 +482,8 @@ mod tests { assert_eq!(nip11_value["limitation"]["payment_required"], false); assert_eq!(nip11_value["limitation"]["restricted_writes"], true); assert_eq!(nip11_value["limitation"]["default_limit"], 100); + assert_eq!(nip11_value["retention"]["physical_erasure"], false); + assert_eq!(nip11_value["retention"]["compaction_guarantee"], false); assert!( nip11_value["supported_nips"] .as_array() diff --git a/crates/tangle_runtime/tests/base_relay_v2.rs b/crates/tangle_runtime/tests/base_relay_v2.rs @@ -104,6 +104,12 @@ fn nip11_integration_reports_group_contracts() { assert!(!document.limitation.payment_required); assert!(document.limitation.restricted_writes); assert_eq!(document.limitation.default_limit, 100); + assert!(!document.retention.physical_erasure); + assert!(!document.retention.compaction_guarantee); + assert_eq!( + document.retention.group_visibility, + "private and hidden group policy gates visibility without implying physical deletion" + ); assert_eq!(disabled.supported_nips, vec![1, 11, 42, 45, 70]); assert!(disabled.relay_self().is_none()); } diff --git a/crates/tangle_runtime/tests/ops_truthfulness.rs b/crates/tangle_runtime/tests/ops_truthfulness.rs @@ -36,6 +36,8 @@ fn operations_surfaces_match_enforced_runtime_contracts() { assert_eq!(document.limitation.max_query_complexity, 2_048); assert_eq!(document.limitation.default_limit, 100); assert!(document.limitation.restricted_writes); + assert!(!document.retention.physical_erasure); + assert!(!document.retention.compaction_guarantee); let redactor = TangleLogRedactor::from_runtime_config(&config); assert_eq!(