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:
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!(