Retention was keyed on comment type, so public and post-key-signed comments carried a randomized 30-365d expiry inside their signed digest. Under the ruling neither should ever expire; only the private, non-post-key-signed (open-slot/greeting) channel gets an automatic TTL, since that is the throwaway-identity retirement mechanism. New crates/core/src/comment_ttl.rs is the single authority: CommentClass (Public / PostKeySigned / OpenSlot / Unverifiable) x CommentTtlRule (Never | Window | UnknownParent), with draw_expiry (writer) and ttl_ok (holder). expires_at_ms == 0 is the never-expires sentinel, honored by the sweep, the ingest gate, and the store_comment upsert. - Post.comment_ttl: Option<CommentTtlPolicy> — a GENERIC per-post policy, so an author-set TTL is a future config surface, not a redesign. Registry posts declare a flat 30d policy that binds EVERY comment on them (registrations, duplicate reports, anything else), replacing the registration-only rule. - OpenSlotDecl.max_comments: author-declarable cap on private PK-unsigned comments, enforced holder-side (clamped to the holder default), replacing the hardcoded per-bio greeting cap. Refusal remains "declare no slot". Node::set_greetings_max + `greetings-max` CLI command to write it. - Holder enforcement rejects TTLs contradicting the parent's policy in both directions; a comment naming a different post than its envelope is rejected. - UnknownParent rule: bounded TTLs accepted from unheld parents (self-heal), never-expires refused — permanence is not granted on unseen evidence. Also fixed while here: five Post-reconstructing queries silently dropped comment_ttl AND the pre-existing fof_gating (shipped in v0.8.0-alpha), so any gated or policy-carrying post failed BLAKE3 verification on sync/export and was discarded with no diagnostic. All hydration now goes through one POST_COLUMNS/post_from_row path; export/import round-trips the policy. Registry frozen bytes regenerated for the policy field; REGISTRY_POST_ID is now 10a1be3383efb2977607fe45c4a7b3f1b5e626e81d0ac1af9c0f3d7eb9864d32. design.html section 21 rewritten to the corrected taxonomy. 250 core tests (was 228); a3 integration 12/12 (new step 6 asserts registry comments hold exactly 30d while greetings randomize); c_topology 33/33. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LGiPD2cF75mnvneSCjdDC5
77 lines
2.5 KiB
Rust
77 lines
2.5 KiB
Rust
pub mod activity;
|
|
#[cfg(target_os = "android")]
|
|
pub mod android_wifi;
|
|
pub mod blob;
|
|
pub mod comment_ttl;
|
|
pub mod connection;
|
|
pub mod content;
|
|
pub mod control;
|
|
pub mod crypto;
|
|
pub mod group_key_distribution;
|
|
pub mod http;
|
|
pub mod export;
|
|
pub mod fof;
|
|
pub mod identity;
|
|
pub mod import;
|
|
pub mod announcement;
|
|
pub mod network;
|
|
pub mod node;
|
|
pub mod profile;
|
|
pub mod protocol;
|
|
pub mod registry;
|
|
pub mod storage;
|
|
pub mod stun;
|
|
pub mod types;
|
|
pub mod upnp;
|
|
pub mod web;
|
|
|
|
// Re-export iroh types needed by consumers
|
|
pub use iroh::{EndpointAddr, EndpointId};
|
|
|
|
use types::NodeId;
|
|
|
|
/// Posting-identity public key of the bootstrap anchor. Only announcements
|
|
/// authored by this key are accepted by `control::receive_post` for the
|
|
/// `VisibilityIntent::Announcement` intent. Hardcoded so clients cannot be
|
|
/// tricked into accepting a forged network-wide announcement.
|
|
pub const DEFAULT_ANCHOR_POSTING_ID: NodeId = [
|
|
0x17, 0xaf, 0x14, 0x19, 0x56, 0xae, 0x0b, 0x50,
|
|
0xdc, 0x1c, 0xb9, 0x24, 0x8c, 0xad, 0xf5, 0xfc,
|
|
0xa3, 0x71, 0xea, 0x2d, 0x85, 0x31, 0xac, 0x9a,
|
|
0xdd, 0x3c, 0x03, 0xca, 0xff, 0xc6, 0x14, 0x41,
|
|
];
|
|
|
|
/// Parse a connect string "nodeid_hex@ip:port" or "nodeid_hex@host:port" or bare "nodeid_hex"
|
|
/// into (NodeId, EndpointAddr). Supports DNS hostnames via `ToSocketAddrs`.
|
|
/// Shared utility used by CLI, Tauri, and bootstrap.
|
|
pub fn parse_connect_string(s: &str) -> anyhow::Result<(NodeId, EndpointAddr)> {
|
|
use std::net::ToSocketAddrs;
|
|
if let Some((id_hex, addr_str)) = s.split_once('@') {
|
|
let nid = parse_node_id_hex(id_hex)?;
|
|
let endpoint_id = EndpointId::from_bytes(&nid)?;
|
|
let all_addrs: Vec<std::net::SocketAddr> = addr_str
|
|
.to_socket_addrs()?
|
|
.collect();
|
|
if all_addrs.is_empty() {
|
|
anyhow::bail!("could not resolve address: {}", addr_str);
|
|
}
|
|
let mut addr = EndpointAddr::from(endpoint_id);
|
|
for sock_addr in all_addrs {
|
|
addr = addr.with_ip_addr(sock_addr);
|
|
}
|
|
Ok((nid, addr))
|
|
} else {
|
|
let nid = parse_node_id_hex(s)?;
|
|
let endpoint_id = EndpointId::from_bytes(&nid)?;
|
|
Ok((nid, EndpointAddr::from(endpoint_id)))
|
|
}
|
|
}
|
|
|
|
/// Parse a hex-encoded node ID string into NodeId bytes.
|
|
pub fn parse_node_id_hex(hex_str: &str) -> anyhow::Result<NodeId> {
|
|
let bytes = hex::decode(hex_str)?;
|
|
let id: NodeId = bytes
|
|
.try_into()
|
|
.map_err(|v: Vec<u8>| anyhow::anyhow!("expected 32 bytes, got {}", v.len()))?;
|
|
Ok(id)
|
|
}
|