use std::cmp::Reverse;
#[cfg(test)]
use std::collections::BTreeSet;
use std::sync::OnceLock;
use serde::{Deserialize, Serialize};
use crate::mesh::auto_enrollment_safety::{IntendedLanePolicy, LaneDecision};
use crate::models::TrustClass;
pub const LANE_GRANT_PREVIEW_SCHEMA_V2: &str = "ee.mesh.lane_grant_preview.v2";
pub const LANE_GRANT_PREVIEW_COPY_VERSION: &str = "ee.mesh.lane_grant_preview.copy.v2";
pub const LANE_GRANT_MEMORY_CANDIDATE_KIND: &str = "memory";
pub const LANE_GRANT_MESH_LEDGER_EVENT_CANDIDATE_KIND: &str = "mesh_ledger_event";
#[must_use]
pub fn lane_grant_redaction_scanner_generation() -> &'static str {
static GENERATION: OnceLock<String> = OnceLock::new();
GENERATION
.get_or_init(|| {
let mut hasher = blake3::Hasher::new();
hasher.update(b"ee.mesh.lane_grant.redaction_scanner_generation.v1");
hasher.update(include_bytes!("../policy/mod.rs"));
format!("redscan1_{}", hasher.finalize().to_hex())
})
.as_str()
}
pub const LANE_GRANT_TARGET_ADAPTER_VERSION: &str = "ee.mesh.grant_target.v1";
pub const LANE_GRANT_PREVIEW_PEER_NOT_IN_GROUP_CODE: &str = "lane_grant_preview_peer_not_in_group";
pub const LANE_GRANT_PREVIEW_LANE_ALREADY_GRANTED_CODE: &str =
"lane_grant_preview_lane_already_granted";
pub const LANE_GRANT_PREVIEW_DEFAULT_LIMIT: usize = 25;
pub const LANE_GRANT_PREVIEW_MAX_LIMIT: usize = 500;
pub const LANE_GRANT_PREVIEW_CONTENT_PREVIEW_CHARS: usize = 100;
pub const LANE_GRANT_PREVIEW_LARGE_VOLUME_THRESHOLD: u64 = 1000;
pub const SENSITIVE_TAGS: &[&str] = &["secret", "private", "personal", "internal"];
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Lane {
Metadata,
Body,
Embedding,
GraphLink,
CurationSignal,
RevisionNotice,
}
impl Lane {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Metadata => "metadata",
Self::Body => "body",
Self::Embedding => "embedding",
Self::GraphLink => "graph_link",
Self::CurationSignal => "curation_signal",
Self::RevisionNotice => "revision_notice",
}
}
#[must_use]
pub fn decision_in(self, policy: &IntendedLanePolicy) -> LaneDecision {
match self {
Self::Metadata => policy.metadata,
Self::Body => policy.body,
Self::Embedding => policy.embedding,
Self::GraphLink => policy.graph_link,
Self::CurationSignal => policy.curation_signal,
Self::RevisionNotice => policy.revision_notice,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SampleStrategy {
Random,
HighestTrust,
MostRecent,
}
impl SampleStrategy {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::Random => "random",
Self::HighestTrust => "highest-trust",
Self::MostRecent => "most-recent",
}
}
}
fn trust_score(trust_class: TrustClass) -> u8 {
match trust_class {
TrustClass::HumanExplicit => 6,
TrustClass::PeerHumanAttested => 5,
TrustClass::AgentValidated => 4,
TrustClass::AgentAssertion => 3,
TrustClass::CassEvidence => 2,
TrustClass::LegacyImport => 1,
}
}
fn is_high_trust(trust_class: TrustClass) -> bool {
matches!(trust_class, TrustClass::HumanExplicit)
}
#[derive(Clone, Copy, Debug)]
pub struct MemoryView<'a> {
pub memory_id: &'a str,
pub level: &'a str,
pub kind: &'a str,
pub content: &'a str,
pub tags: &'a [String],
pub trust_class: TrustClass,
pub redacted_fields: &'a [String],
pub created_at_secs: i64,
pub is_tombstoned: bool,
pub blocked_by_redaction_class: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct LaneGrantPreviewInput<'a> {
pub peer_node_key: &'a str,
pub peer_in_group: bool,
pub lane: Lane,
pub workspace_id: &'a str,
pub current_policy: IntendedLanePolicy,
pub proposed_policy: IntendedLanePolicy,
pub memories: &'a [MemoryView<'a>],
pub sample_strategy: SampleStrategy,
pub limit: usize,
pub redaction_rules: &'a [String],
pub sample_random_seed: u64,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PolicySnapshot {
pub generation: String,
pub lane: String,
pub decision: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GrantTargetSnapshot {
pub adapter_version: String,
pub peer_id: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct MeshLedgerEventCandidateView<'a> {
pub event_id: &'a str,
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CandidateRevisionPin {
pub candidate_kind: String,
pub candidate_id: String,
pub revision_id: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct LaneGrantApprovalContext<'a> {
pub target_peer_id: &'a str,
pub grant_generation: u64,
pub candidate_revision_generation: u64,
pub current_policy_generation: &'a str,
pub proposed_policy_generation: &'a str,
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ApprovalTokenProjection {
pub schema: String,
pub value: String,
pub expires_at: String,
pub handling: String,
}
impl std::fmt::Debug for ApprovalTokenProjection {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ApprovalTokenProjection")
.field("schema", &self.schema)
.field("value", &"<redacted>")
.field("expires_at", &self.expires_at)
.field("handling", &self.handling)
.finish()
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct PreviewRow {
#[serde(rename = "memoryId")]
pub memory_id: String,
#[serde(rename = "revisionId")]
pub revision_id: String,
pub level: String,
pub kind: String,
#[serde(rename = "contentPreview")]
pub content_preview: String,
pub tags: Vec<String>,
#[serde(rename = "trustClass")]
pub trust_class: String,
#[serde(rename = "hasSensitiveTags")]
pub has_sensitive_tags: bool,
#[serde(rename = "redactedFields")]
pub redacted_fields: Vec<String>,
#[serde(rename = "wouldExposeUnderProposedPolicy")]
pub would_expose_under_proposed_policy: bool,
}
pub mod caution_kinds {
pub const HIGH_TRUST_CLASS_EXPOSURE: &str = "high_trust_class_exposure";
pub const LARGE_VOLUME_EXPOSURE: &str = "large_volume_exposure";
pub const SENSITIVE_TAGS_IN_EXPOSURE: &str = "sensitive_tags_in_exposure";
pub const TOMBSTONED_IN_EXPOSURE: &str = "tombstoned_in_exposure";
pub const REDACTION_ACTIVE: &str = "redaction_active";
pub const PEER_NOT_IN_GROUP: &str = "peer_not_in_group";
pub const LANE_ALREADY_GRANTED: &str = "lane_already_granted";
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct Caution {
pub kind: String,
pub message: String,
pub severity: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LaneGrantPreview {
pub schema: &'static str,
#[serde(rename = "copyVersion")]
pub copy_version: &'static str,
#[serde(rename = "workspaceId")]
pub workspace_id: String,
pub target: GrantTargetSnapshot,
pub lane: String,
#[serde(rename = "grantGeneration")]
pub grant_generation: u64,
#[serde(rename = "currentPolicy")]
pub current_policy: PolicySnapshot,
#[serde(rename = "proposedPolicy")]
pub proposed_policy: PolicySnapshot,
#[serde(rename = "candidateSet")]
pub candidate_set: Vec<CandidateRevisionPin>,
#[serde(rename = "affectedMemoryCount")]
pub affected_memory_count: u64,
#[serde(rename = "affectedLedgerEventCount")]
pub affected_ledger_event_count: u64,
#[serde(rename = "redactedFromExposureCount")]
pub redacted_from_exposure_count: u64,
#[serde(rename = "previewSampleStrategy")]
pub preview_sample_strategy: String,
#[serde(rename = "previewSampleLimit")]
pub preview_sample_limit: usize,
#[serde(rename = "previewSample")]
pub preview_sample: Vec<PreviewRow>,
#[serde(rename = "redactionRulesApplied")]
pub redaction_rules_applied: Vec<String>,
#[serde(rename = "redactionScannerGeneration")]
pub redaction_scanner_generation: String,
#[serde(rename = "cautionCodes")]
pub caution_codes: Vec<String>,
pub cautions: Vec<Caution>,
#[serde(rename = "approvalToken", skip_serializing_if = "Option::is_none")]
pub approval_token: Option<ApprovalTokenProjection>,
}
impl LaneGrantPreview {
pub fn canonical_approval_snapshot_bytes(&self) -> Result<Vec<u8>, serde_json::Error> {
let mut snapshot = self.clone();
snapshot.approval_token = None;
serde_json::to_vec(&snapshot)
}
}
#[must_use]
pub fn compute_lane_grant_preview(input: &LaneGrantPreviewInput<'_>) -> LaneGrantPreview {
compute_lane_grant_preview_with_context(
input,
&LaneGrantApprovalContext {
target_peer_id: input.peer_node_key,
grant_generation: 0,
candidate_revision_generation: 0,
current_policy_generation: "policy:unspecified",
proposed_policy_generation: "policy:unspecified",
},
)
}
#[must_use]
pub fn compute_lane_grant_preview_with_context(
input: &LaneGrantPreviewInput<'_>,
context: &LaneGrantApprovalContext<'_>,
) -> LaneGrantPreview {
compute_lane_grant_preview_with_context_and_ledger_candidates(input, context, &[])
}
#[must_use]
pub fn compute_lane_grant_preview_with_context_and_ledger_candidates(
input: &LaneGrantPreviewInput<'_>,
context: &LaneGrantApprovalContext<'_>,
ledger_candidates: &[MeshLedgerEventCandidateView<'_>],
) -> LaneGrantPreview {
let effective_limit = effective_limit(input.limit);
let current_decision = input.lane.decision_in(&input.current_policy);
let proposed_decision = input.lane.decision_in(&input.proposed_policy);
let proposed_allows = proposed_decision == LaneDecision::Allow;
let mut would_expose: Vec<&MemoryView<'_>> = Vec::with_capacity(input.memories.len());
let mut tombstoned_blocked = 0_u64;
let mut redacted_blocked = 0_u64;
for memory in input.memories {
let exposable =
proposed_allows && !memory.is_tombstoned && !memory.blocked_by_redaction_class;
if exposable {
would_expose.push(memory);
continue;
}
if proposed_allows && memory.is_tombstoned {
tombstoned_blocked += 1;
}
if proposed_allows && memory.blocked_by_redaction_class {
redacted_blocked += 1;
}
}
let affected_memory_count = would_expose.len() as u64;
sort_sample(
&mut would_expose,
input.sample_strategy,
input.sample_random_seed,
);
let sample_rows: Vec<PreviewRow> = would_expose
.iter()
.take(effective_limit)
.map(|memory| build_preview_row(memory, true, context.candidate_revision_generation))
.collect();
let cautions = collect_cautions(
input,
current_decision,
proposed_decision,
affected_memory_count,
tombstoned_blocked,
redacted_blocked,
)
.into_iter()
.map(sanitize_preview_caution)
.collect::<Vec<_>>();
let mut candidate_set = input
.memories
.iter()
.map(|memory| CandidateRevisionPin {
candidate_kind: LANE_GRANT_MEMORY_CANDIDATE_KIND.to_owned(),
candidate_id: sanitize_preview_text(memory.memory_id),
revision_id: memory_candidate_revision_id(
memory.memory_id,
context.candidate_revision_generation,
),
})
.collect::<Vec<_>>();
candidate_set.extend(
ledger_candidates
.iter()
.map(|candidate| CandidateRevisionPin {
candidate_kind: LANE_GRANT_MESH_LEDGER_EVENT_CANDIDATE_KIND.to_owned(),
candidate_id: sanitize_preview_text(candidate.event_id),
revision_id: mesh_ledger_event_revision_id(candidate.event_id),
}),
);
candidate_set.sort();
let caution_codes = cautions.iter().map(|item| item.kind.clone()).collect();
LaneGrantPreview {
schema: LANE_GRANT_PREVIEW_SCHEMA_V2,
copy_version: LANE_GRANT_PREVIEW_COPY_VERSION,
workspace_id: sanitize_preview_text(input.workspace_id),
target: GrantTargetSnapshot {
adapter_version: LANE_GRANT_TARGET_ADAPTER_VERSION.to_owned(),
peer_id: sanitize_preview_text(context.target_peer_id),
},
lane: input.lane.as_str().to_owned(),
grant_generation: context.grant_generation,
current_policy: PolicySnapshot {
generation: sanitize_preview_text(context.current_policy_generation),
lane: input.lane.as_str().to_owned(),
decision: current_decision.as_str().to_owned(),
},
proposed_policy: PolicySnapshot {
generation: sanitize_preview_text(context.proposed_policy_generation),
lane: input.lane.as_str().to_owned(),
decision: proposed_decision.as_str().to_owned(),
},
candidate_set,
affected_memory_count,
affected_ledger_event_count: ledger_candidates.len() as u64,
redacted_from_exposure_count: redacted_blocked,
preview_sample_strategy: input.sample_strategy.as_str().to_owned(),
preview_sample_limit: effective_limit,
preview_sample: sample_rows,
redaction_rules_applied: input
.redaction_rules
.iter()
.map(|rule| sanitize_preview_text(rule))
.collect(),
redaction_scanner_generation: lane_grant_redaction_scanner_generation().to_owned(),
caution_codes,
cautions,
approval_token: None,
}
}
fn effective_limit(requested: usize) -> usize {
let baseline = if requested == 0 {
LANE_GRANT_PREVIEW_DEFAULT_LIMIT
} else {
requested
};
baseline.min(LANE_GRANT_PREVIEW_MAX_LIMIT)
}
fn build_preview_row(
memory: &MemoryView<'_>,
would_expose: bool,
candidate_revision_generation: u64,
) -> PreviewRow {
PreviewRow {
memory_id: sanitize_preview_text(memory.memory_id),
revision_id: memory_candidate_revision_id(memory.memory_id, candidate_revision_generation),
level: sanitize_preview_text(memory.level),
kind: sanitize_preview_text(memory.kind),
content_preview: sanitize_preview_content(
memory.content,
LANE_GRANT_PREVIEW_CONTENT_PREVIEW_CHARS,
),
tags: memory
.tags
.iter()
.map(|tag| sanitize_preview_text(tag))
.collect(),
trust_class: memory.trust_class.as_str().to_owned(),
has_sensitive_tags: memory_has_sensitive_tag(memory),
redacted_fields: memory
.redacted_fields
.iter()
.map(|field| sanitize_preview_text(field))
.collect(),
would_expose_under_proposed_policy: would_expose,
}
}
fn memory_candidate_revision_id(memory_id: &str, candidate_revision_generation: u64) -> String {
let mut hasher = blake3::Hasher::new();
hasher.update(b"ee.mesh.lane_grant.candidate_revision.v1");
hasher.update(&(memory_id.len() as u64).to_le_bytes());
hasher.update(memory_id.as_bytes());
hasher.update(&candidate_revision_generation.to_le_bytes());
format!("revwg1_{}", hasher.finalize().to_hex())
}
fn mesh_ledger_event_revision_id(event_id: &str) -> String {
let mut hasher = blake3::Hasher::new();
hasher.update(b"ee.mesh.lane_grant.mesh_ledger_event_revision.v1");
hasher.update(&(event_id.len() as u64).to_le_bytes());
hasher.update(event_id.as_bytes());
format!("revme1_{}", hasher.finalize().to_hex())
}
fn truncate_chars(value: &str, max_chars: usize) -> String {
if value.chars().count() <= max_chars {
return value.to_owned();
}
value.chars().take(max_chars).collect()
}
fn sanitize_preview_text(value: &str) -> String {
value.chars().map(sanitize_preview_character).collect()
}
fn sanitize_preview_content(value: &str, max_chars: usize) -> String {
truncate_chars(value, max_chars)
.chars()
.map(sanitize_preview_character)
.collect()
}
fn sanitize_preview_character(character: char) -> char {
if is_preview_format_hazard(character) {
'\u{FFFD}'
} else {
character
}
}
fn is_preview_format_hazard(character: char) -> bool {
character.is_control()
|| matches!(
character,
'\u{00AD}'
| '\u{180E}'
| '\u{200B}'
| '\u{2060}'
| '\u{FEFF}'
| '\u{061C}'
| '\u{200E}'
| '\u{200F}'
| '\u{202A}'..='\u{202E}'
| '\u{2066}'..='\u{206F}'
| '\u{2028}'
| '\u{2029}'
| '\u{FFF9}'..='\u{FFFB}'
| '\u{E0000}'..='\u{E007F}'
)
}
fn sanitize_preview_caution(mut caution: Caution) -> Caution {
caution.message = sanitize_preview_text(&caution.message);
caution
}
fn memory_has_sensitive_tag(memory: &MemoryView<'_>) -> bool {
memory.tags.iter().any(|tag| tag_has_sensitive_token(tag))
}
fn tag_has_sensitive_token(tag: &str) -> bool {
tag.split(|ch: char| !ch.is_ascii_alphanumeric())
.filter(|token| !token.is_empty())
.any(|token| {
SENSITIVE_TAGS
.iter()
.any(|sensitive| token.eq_ignore_ascii_case(sensitive))
})
}
fn sort_sample(items: &mut [&MemoryView<'_>], strategy: SampleStrategy, seed: u64) {
match strategy {
SampleStrategy::HighestTrust => {
items
.sort_by_key(|memory| (Reverse(trust_score(memory.trust_class)), memory.memory_id));
}
SampleStrategy::MostRecent => {
items.sort_by_key(|memory| (Reverse(memory.created_at_secs), memory.memory_id));
}
SampleStrategy::Random => {
items.sort_by_key(|memory| deterministic_random_key(memory.memory_id, seed));
}
}
}
fn deterministic_random_key(memory_id: &str, seed: u64) -> [u8; 16] {
let mut hasher = blake3::Hasher::new();
hasher.update(&seed.to_le_bytes());
hasher.update(memory_id.as_bytes());
let mut out = [0_u8; 16];
out.copy_from_slice(&hasher.finalize().as_bytes()[..16]);
out
}
fn collect_cautions(
input: &LaneGrantPreviewInput<'_>,
current_decision: LaneDecision,
_proposed_decision: LaneDecision,
affected_memory_count: u64,
tombstoned_blocked: u64,
redacted_blocked: u64,
) -> Vec<Caution> {
let mut cautions = Vec::new();
if !input.peer_in_group {
cautions.push(Caution {
kind: caution_kinds::PEER_NOT_IN_GROUP.to_owned(),
message: format!(
"peer {} is enrolled but is not included in this workspace's peer-group bindings; if that membership is intended, add it, then review and freshly approve the lane because membership alone does not grant a denied lane",
input.peer_node_key
),
severity: "info".to_owned(),
});
}
if current_decision == LaneDecision::Allow {
cautions.push(Caution {
kind: caution_kinds::LANE_ALREADY_GRANTED.to_owned(),
message: format!(
"lane '{}' is already granted in the current policy; this preview shows what is currently exposed",
input.lane.as_str()
),
severity: "info".to_owned(),
});
}
let mut high_trust_exposure_count: u64 = 0;
let mut sensitive_tag_exposure_count: u64 = 0;
let proposed_allows = input.lane.decision_in(&input.proposed_policy) == LaneDecision::Allow;
for memory in input.memories {
let would_expose =
proposed_allows && !memory.is_tombstoned && !memory.blocked_by_redaction_class;
if !would_expose {
continue;
}
if is_high_trust(memory.trust_class) {
high_trust_exposure_count += 1;
}
if memory_has_sensitive_tag(memory) {
sensitive_tag_exposure_count += 1;
}
}
if high_trust_exposure_count > 0 {
cautions.push(Caution {
kind: caution_kinds::HIGH_TRUST_CLASS_EXPOSURE.to_owned(),
message: format!(
"{high_trust_exposure_count} memor{plural} with trust_class=human_explicit would be exposed; these are the user's directly-authored rules",
plural = if high_trust_exposure_count == 1 { "y" } else { "ies" }
),
severity: "warning".to_owned(),
});
}
if affected_memory_count > LANE_GRANT_PREVIEW_LARGE_VOLUME_THRESHOLD {
cautions.push(Caution {
kind: caution_kinds::LARGE_VOLUME_EXPOSURE.to_owned(),
message: format!(
"{affected_memory_count} memories would be exposed (>{LANE_GRANT_PREVIEW_LARGE_VOLUME_THRESHOLD}); the workspace may be larger than expected"
),
severity: "warning".to_owned(),
});
}
if sensitive_tag_exposure_count > 0 {
cautions.push(Caution {
kind: caution_kinds::SENSITIVE_TAGS_IN_EXPOSURE.to_owned(),
message: format!(
"{sensitive_tag_exposure_count} memor{plural} tagged secret/private/personal/internal would be exposed; tag-driven scope filtering is the user's main lever to hide things",
plural = if sensitive_tag_exposure_count == 1 { "y" } else { "ies" }
),
severity: "warning".to_owned(),
});
}
if tombstoned_blocked > 0 {
cautions.push(Caution {
kind: caution_kinds::TOMBSTONED_IN_EXPOSURE.to_owned(),
message: format!(
"{tombstoned_blocked} tombstoned memor{plural} would not be exposed; tombstoned status is honored",
plural = if tombstoned_blocked == 1 { "y" } else { "ies" }
),
severity: "info".to_owned(),
});
}
let field_redacted_memory_count = input
.memories
.iter()
.filter(|memory| !memory.redacted_fields.is_empty())
.count() as u64;
if redacted_blocked > 0 || field_redacted_memory_count > 0 {
let message = match (redacted_blocked, field_redacted_memory_count) {
(blocked, 0) => format!(
"{blocked} memor{plural} would not be exposed because existing redaction-class rules block that lane",
plural = if blocked == 1 { "y" } else { "ies" }
),
(0, field_redacted) => format!(
"{field_redacted} memor{plural} had sensitive fields redacted before preview or exposure; the listed redaction rules remain active",
plural = if field_redacted == 1 { "y" } else { "ies" }
),
(blocked, field_redacted) => format!(
"{blocked} memor{blocked_plural} would not be exposed because redaction-class rules block the lane, and {field_redacted} memor{field_plural} had sensitive fields redacted before preview or exposure",
blocked_plural = if blocked == 1 { "y" } else { "ies" },
field_plural = if field_redacted == 1 { "y" } else { "ies" },
),
};
cautions.push(Caution {
kind: caution_kinds::REDACTION_ACTIVE.to_owned(),
message,
severity: "info".to_owned(),
});
}
cautions
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mesh::auto_enrollment_safety::IntendedLanePolicy;
fn tags(values: &[&str]) -> Vec<String> {
values.iter().map(|s| (*s).to_owned()).collect()
}
fn empty_strings() -> Vec<String> {
Vec::new()
}
fn assert_json_strings_are_terminal_safe(value: &serde_json::Value) {
match value {
serde_json::Value::String(text) => assert!(
!text.chars().any(is_preview_format_hazard),
"snapshot string retained a terminal/control hazard: {text:?}",
),
serde_json::Value::Array(items) => {
for item in items {
assert_json_strings_are_terminal_safe(item);
}
}
serde_json::Value::Object(fields) => {
for field in fields.values() {
assert_json_strings_are_terminal_safe(field);
}
}
serde_json::Value::Null | serde_json::Value::Bool(_) | serde_json::Value::Number(_) => {
}
}
}
fn body_grant_proposed() -> IntendedLanePolicy {
let mut policy = IntendedLanePolicy::conservative_default();
policy.body = LaneDecision::Allow;
policy
}
fn build_memory<'a>(
memory_id: &'a str,
trust_class: TrustClass,
tag_storage: &'a [String],
created_at_secs: i64,
is_tombstoned: bool,
blocked_by_redaction_class: bool,
redacted_field_storage: &'a [String],
) -> MemoryView<'a> {
MemoryView {
memory_id,
level: "memory",
kind: "fact",
content: "example content body that the peer would see if body lane is granted",
tags: tag_storage,
trust_class,
redacted_fields: redacted_field_storage,
created_at_secs,
is_tombstoned,
blocked_by_redaction_class,
}
}
#[test]
fn approval_token_debug_redacts_secret_value() {
let token = ApprovalTokenProjection {
schema: "ee.mesh.approval_token.v1".to_owned(),
value: "eeap1_secret-bearer-material".to_owned(),
expires_at: "2026-08-04T08:15:00Z".to_owned(),
handling: "secret".to_owned(),
};
let rendered = format!("{token:?}");
assert!(rendered.contains("<redacted>"));
assert!(!rendered.contains("secret-bearer-material"));
}
#[test]
fn redaction_scanner_generation_is_stable_and_source_derived() {
let first = lane_grant_redaction_scanner_generation();
let second = lane_grant_redaction_scanner_generation();
assert_eq!(first, second);
assert!(first.starts_with("redscan1_"));
assert_eq!(first.len(), "redscan1_".len() + 64);
assert!(first.strip_prefix("redscan1_").is_some_and(|suffix| {
suffix
.bytes()
.all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
}));
}
#[test]
fn lane_decision_lookup_matches_policy_fields() {
let policy = IntendedLanePolicy {
metadata: LaneDecision::Allow,
body: LaneDecision::Quarantine,
embedding: LaneDecision::Deny,
graph_link: LaneDecision::Deny,
curation_signal: LaneDecision::Allow,
revision_notice: LaneDecision::Allow,
};
assert_eq!(Lane::Metadata.decision_in(&policy), LaneDecision::Allow);
assert_eq!(Lane::Body.decision_in(&policy), LaneDecision::Quarantine);
assert_eq!(Lane::Embedding.decision_in(&policy), LaneDecision::Deny);
assert_eq!(Lane::GraphLink.decision_in(&policy), LaneDecision::Deny);
assert_eq!(
Lane::CurationSignal.decision_in(&policy),
LaneDecision::Allow
);
assert_eq!(
Lane::RevisionNotice.decision_in(&policy),
LaneDecision::Allow
);
}
#[test]
fn effective_limit_falls_back_to_default_when_zero() {
assert_eq!(effective_limit(0), LANE_GRANT_PREVIEW_DEFAULT_LIMIT);
}
#[test]
fn effective_limit_honors_requested_below_max() {
assert_eq!(effective_limit(50), 50);
}
#[test]
fn effective_limit_clamps_to_max() {
assert_eq!(effective_limit(usize::MAX), LANE_GRANT_PREVIEW_MAX_LIMIT);
}
#[test]
fn truncate_chars_short_returns_input_unchanged() {
assert_eq!(truncate_chars("hello", 100), "hello");
}
#[test]
fn truncate_chars_long_truncates_to_char_count_not_byte_count() {
let s = "🚀🚀🚀🚀🚀🚀";
assert_eq!(truncate_chars(s, 5).chars().count(), 5);
}
#[test]
fn preview_content_neutralizes_terminal_controls_before_snapshot_construction() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let hostile =
"safe\u{1b}[31mRED\u{1b}[0m|\n|\0|\u{202E}rtl\u{202C}|\u{2066}iso\u{2069}|\u{200B}end";
let base = build_memory(
"hostile",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
);
let memories = [MemoryView {
content: hostile,
..base
}];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::MostRecent,
limit: 1,
redaction_rules: &redaction_rules,
sample_random_seed: 0,
});
let expected = "safe\u{FFFD}[31mRED\u{FFFD}[0m|\u{FFFD}|\u{FFFD}|\u{FFFD}rtl\u{FFFD}|\u{FFFD}iso\u{FFFD}|\u{FFFD}end";
assert_eq!(preview.preview_sample[0].content_preview, expected);
assert!(
!preview.preview_sample[0]
.content_preview
.chars()
.any(is_preview_format_hazard),
"the constructed row must contain no terminal or Unicode formatting hazards",
);
let canonical = preview.canonical_approval_snapshot_bytes().unwrap();
let canonical_text = std::str::from_utf8(&canonical).unwrap();
for encoded_hazard in [
r"\u001b", r"\n", r"\u0000", r"\u202e", r"\u202c", r"\u2066", r"\u2069", r"\u200b",
] {
assert!(
!canonical_text.contains(encoded_hazard),
"canonical approval snapshot retained {encoded_hazard:?}",
);
}
assert!(!canonical_text.chars().any(is_preview_format_hazard));
let decoded: serde_json::Value = serde_json::from_slice(&canonical).unwrap();
assert_eq!(
decoded
.pointer("/previewSample/0/contentPreview")
.and_then(serde_json::Value::as_str),
Some(expected),
);
}
#[test]
fn preview_content_preserves_ordinary_unicode_exactly() {
let ordinary = "Café; cafe\u{0301}; עברית; العربية; 👨\u{200D}👩\u{200D}👧\u{200D}👦; 中文";
assert_eq!(
sanitize_preview_content(ordinary, LANE_GRANT_PREVIEW_CONTENT_PREVIEW_CHARS),
ordinary,
);
let no_tags = empty_strings();
let no_redacted = empty_strings();
let base = build_memory(
"ordinary-unicode",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
);
let memories = [MemoryView {
content: ordinary,
..base
}];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::MostRecent,
limit: 1,
redaction_rules: &redaction_rules,
sample_random_seed: 0,
});
assert_eq!(preview.preview_sample[0].content_preview, ordinary);
let canonical = preview.canonical_approval_snapshot_bytes().unwrap();
let decoded: serde_json::Value = serde_json::from_slice(&canonical).unwrap();
assert_eq!(
decoded
.pointer("/previewSample/0/contentPreview")
.and_then(serde_json::Value::as_str),
Some(ordinary),
);
}
#[test]
fn every_caller_derived_snapshot_string_is_terminal_safe() {
let hostile_tags = tags(&[
"private\u{202E}tag",
"family-👨\u{200D}👩\u{200D}👧\u{200D}👦",
]);
let hostile_redacted_fields = tags(&["body\nsecret", "nom-Café"]);
let memories = [MemoryView {
memory_id: "memory\u{1B}[31m-red-👨\u{200D}👩",
level: "episodic\r-Café",
kind: "fact\u{200B}-中文",
content: "body\t-Café",
tags: &hostile_tags,
trust_class: TrustClass::HumanExplicit,
redacted_fields: &hostile_redacted_fields,
created_at_secs: 1,
is_tombstoned: false,
blocked_by_redaction_class: false,
}];
let hostile_redaction_rules = tags(&["api\u{009D}key", "règle"]);
let input = LaneGrantPreviewInput {
peer_node_key: "node\nkey-עברית",
peer_in_group: false,
lane: Lane::Body,
workspace_id: "workspace\u{1B}[2J-Café",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::MostRecent,
limit: 1,
redaction_rules: &hostile_redaction_rules,
sample_random_seed: 0,
};
let context = LaneGrantApprovalContext {
target_peer_id: "peer\u{202E}spoof\u{202C}-עברית",
grant_generation: 4,
candidate_revision_generation: 9,
current_policy_generation: "current\n-Café",
proposed_policy_generation: "proposed\u{2066}iso\u{2069}-中文",
};
let ledger_candidates = [MeshLedgerEventCandidateView {
event_id: "event\u{202E}rtl\u{202C}-العربية",
}];
let preview = compute_lane_grant_preview_with_context_and_ledger_candidates(
&input,
&context,
&ledger_candidates,
);
assert_eq!(preview.workspace_id, "workspace\u{FFFD}[2J-Café");
assert_eq!(preview.target.peer_id, "peer\u{FFFD}spoof\u{FFFD}-עברית");
assert_eq!(preview.current_policy.generation, "current\u{FFFD}-Café");
assert_eq!(
preview.proposed_policy.generation,
"proposed\u{FFFD}iso\u{FFFD}-中文",
);
let memory_pin = preview
.candidate_set
.iter()
.find(|candidate| candidate.candidate_kind == LANE_GRANT_MEMORY_CANDIDATE_KIND)
.expect("memory pin");
assert_eq!(
memory_pin.candidate_id,
"memory\u{FFFD}[31m-red-👨\u{200D}👩"
);
let ledger_pin = preview
.candidate_set
.iter()
.find(|candidate| {
candidate.candidate_kind == LANE_GRANT_MESH_LEDGER_EVENT_CANDIDATE_KIND
})
.expect("ledger-event pin");
assert_eq!(ledger_pin.candidate_id, "event\u{FFFD}rtl\u{FFFD}-العربية",);
let row = &preview.preview_sample[0];
assert_eq!(row.memory_id, "memory\u{FFFD}[31m-red-👨\u{200D}👩");
assert_eq!(row.level, "episodic\u{FFFD}-Café");
assert_eq!(row.kind, "fact\u{FFFD}-中文");
assert_eq!(row.content_preview, "body\u{FFFD}-Café");
assert_eq!(
row.tags,
[
"private\u{FFFD}tag".to_owned(),
"family-👨\u{200D}👩\u{200D}👧\u{200D}👦".to_owned(),
],
);
assert_eq!(
row.redacted_fields,
["body\u{FFFD}secret".to_owned(), "nom-Café".to_owned()],
);
assert_eq!(
preview.redaction_rules_applied,
["api\u{FFFD}key".to_owned(), "règle".to_owned()],
);
let peer_caution = preview
.cautions
.iter()
.find(|caution| caution.kind == caution_kinds::PEER_NOT_IN_GROUP)
.expect("peer-not-in-group caution");
assert!(peer_caution.message.contains("node\u{FFFD}key-עברית"));
let rendered = serde_json::to_value(&preview).unwrap();
assert_json_strings_are_terminal_safe(&rendered);
let canonical = preview.canonical_approval_snapshot_bytes().unwrap();
let decoded: serde_json::Value = serde_json::from_slice(&canonical).unwrap();
assert_json_strings_are_terminal_safe(&decoded);
}
#[test]
fn body_deny_to_allow_with_non_tombstoned_non_blocked_memories_exposes_all() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [
build_memory(
"m1",
TrustClass::AgentAssertion,
&no_tags,
1_000_000,
false,
false,
&no_redacted,
),
build_memory(
"m2",
TrustClass::AgentAssertion,
&no_tags,
2_000_000,
false,
false,
&no_redacted,
),
];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
assert_eq!(preview.affected_memory_count, 2);
assert_eq!(preview.redacted_from_exposure_count, 0);
assert_eq!(preview.preview_sample.len(), 2);
assert_eq!(preview.lane, "body");
assert_eq!(preview.current_policy.decision, "deny");
assert_eq!(preview.proposed_policy.decision, "allow");
assert!(
preview
.preview_sample
.iter()
.all(|row| row.would_expose_under_proposed_policy)
);
}
#[test]
fn workspace_generation_revision_pin_fences_unsampled_candidate_mutation() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [
build_memory(
"sampled",
TrustClass::AgentAssertion,
&no_tags,
2,
false,
false,
&no_redacted,
),
build_memory(
"unsampled",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
),
];
let redaction_rules = empty_strings();
let input = LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::MostRecent,
limit: 1,
redaction_rules: &redaction_rules,
sample_random_seed: 0,
};
let before = compute_lane_grant_preview_with_context(
&input,
&LaneGrantApprovalContext {
target_peer_id: "peer-1",
grant_generation: 3,
candidate_revision_generation: 41,
current_policy_generation: "policy-current",
proposed_policy_generation: "policy-proposed",
},
);
let after_unsampled_mutation = compute_lane_grant_preview_with_context(
&input,
&LaneGrantApprovalContext {
target_peer_id: "peer-1",
grant_generation: 3,
candidate_revision_generation: 42,
current_policy_generation: "policy-current",
proposed_policy_generation: "policy-proposed",
},
);
assert_eq!(before.preview_sample.len(), 1);
assert_eq!(before.preview_sample[0].memory_id, "sampled");
let before_unsampled = before
.candidate_set
.iter()
.find(|candidate| {
candidate.candidate_kind == LANE_GRANT_MEMORY_CANDIDATE_KIND
&& candidate.candidate_id == "unsampled"
})
.expect("complete candidate set includes unsampled memory");
let after_unsampled = after_unsampled_mutation
.candidate_set
.iter()
.find(|candidate| {
candidate.candidate_kind == LANE_GRANT_MEMORY_CANDIDATE_KIND
&& candidate.candidate_id == "unsampled"
})
.expect("complete candidate set still includes unsampled memory");
assert_ne!(before_unsampled.revision_id, after_unsampled.revision_id);
assert!(before_unsampled.revision_id.starts_with("revwg1_"));
assert!(!before_unsampled.revision_id.contains("unsampled"));
assert_ne!(
before.canonical_approval_snapshot_bytes().unwrap(),
after_unsampled_mutation
.canonical_approval_snapshot_bytes()
.unwrap(),
"an unsampled source mutation must stale the authenticated snapshot",
);
}
#[test]
fn immutable_ledger_event_pins_are_generic_deterministic_and_snapshot_bound() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [build_memory(
"memory-candidate",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let input = LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::GraphLink,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: IntendedLanePolicy {
graph_link: LaneDecision::Allow,
..IntendedLanePolicy::conservative_default()
},
memories: &memories,
sample_strategy: SampleStrategy::MostRecent,
limit: 1,
redaction_rules: &redaction_rules,
sample_random_seed: 0,
};
let context = LaneGrantApprovalContext {
target_peer_id: "peer-1",
grant_generation: 3,
candidate_revision_generation: 41,
current_policy_generation: "policy-current",
proposed_policy_generation: "policy-proposed",
};
let first_event = [MeshLedgerEventCandidateView {
event_id: "mesh_evt_immutable_1",
}];
let first = compute_lane_grant_preview_with_context_and_ledger_candidates(
&input,
&context,
&first_event,
);
let repeated = compute_lane_grant_preview_with_context_and_ledger_candidates(
&input,
&context,
&first_event,
);
let second_event = [
MeshLedgerEventCandidateView {
event_id: "mesh_evt_immutable_1",
},
MeshLedgerEventCandidateView {
event_id: "mesh_evt_immutable_2",
},
];
let after_insert = compute_lane_grant_preview_with_context_and_ledger_candidates(
&input,
&context,
&second_event,
);
assert_eq!(first, repeated);
assert_eq!(first.affected_ledger_event_count, 1);
assert_eq!(after_insert.affected_ledger_event_count, 2);
let event_pin = first
.candidate_set
.iter()
.find(|candidate| {
candidate.candidate_kind == LANE_GRANT_MESH_LEDGER_EVENT_CANDIDATE_KIND
})
.expect("ledger event is in the complete candidate set");
assert_eq!(event_pin.candidate_id, "mesh_evt_immutable_1");
assert!(event_pin.revision_id.starts_with("revme1_"));
assert!(!event_pin.revision_id.contains("mesh_evt_immutable_1"));
assert_ne!(
first.canonical_approval_snapshot_bytes().unwrap(),
after_insert.canonical_approval_snapshot_bytes().unwrap(),
"a ledger insert must stale the snapshot without a workspace-generation change",
);
}
#[test]
fn every_public_canonical_field_is_bound_but_bearer_projection_is_not() {
let sensitive_tags = tags(&["private"]);
let redacted_fields = tags(&["content:api_key"]);
let memories = [build_memory(
"m1",
TrustClass::HumanExplicit,
&sensitive_tags,
1,
false,
false,
&redacted_fields,
)];
let redaction_rules = tags(&["api_key"]);
let already_allowed = body_grant_proposed();
let base = compute_lane_grant_preview_with_context(
&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: false,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: already_allowed,
proposed_policy: already_allowed,
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 1,
redaction_rules: &redaction_rules,
sample_random_seed: 7,
},
&LaneGrantApprovalContext {
target_peer_id: "peer-1",
grant_generation: 3,
candidate_revision_generation: 41,
current_policy_generation: "policy-current",
proposed_policy_generation: "policy-proposed",
},
);
let canonical = base.canonical_approval_snapshot_bytes().unwrap();
macro_rules! assert_field_drift {
($label:literal, $mutation:expr) => {{
let mut changed = base.clone();
$mutation(&mut changed);
assert_ne!(
changed.canonical_approval_snapshot_bytes().unwrap(),
canonical,
"{} must be authenticated by the canonical snapshot",
$label,
);
}};
}
assert_field_drift!("schema", |value: &mut LaneGrantPreview| value.schema =
"ee.mesh.lane_grant_preview.test");
assert_field_drift!("copyVersion", |value: &mut LaneGrantPreview| value
.copy_version =
"ee.mesh.lane_grant_preview.copy.test");
assert_field_drift!("workspaceId", |value: &mut LaneGrantPreview| value
.workspace_id
.push('x'));
assert_field_drift!("target.adapterVersion", |value: &mut LaneGrantPreview| {
value.target.adapter_version.push('x')
});
assert_field_drift!("target.peerId", |value: &mut LaneGrantPreview| value
.target
.peer_id
.push('x'));
assert_field_drift!("lane", |value: &mut LaneGrantPreview| value.lane.push('x'));
assert_field_drift!("grantGeneration", |value: &mut LaneGrantPreview| value
.grant_generation +=
1);
assert_field_drift!(
"currentPolicy.generation",
|value: &mut LaneGrantPreview| value.current_policy.generation.push('x')
);
assert_field_drift!("currentPolicy.lane", |value: &mut LaneGrantPreview| value
.current_policy
.lane
.push('x'));
assert_field_drift!("currentPolicy.decision", |value: &mut LaneGrantPreview| {
value.current_policy.decision.push('x')
});
assert_field_drift!(
"proposedPolicy.generation",
|value: &mut LaneGrantPreview| value.proposed_policy.generation.push('x')
);
assert_field_drift!("proposedPolicy.lane", |value: &mut LaneGrantPreview| value
.proposed_policy
.lane
.push('x'));
assert_field_drift!("proposedPolicy.decision", |value: &mut LaneGrantPreview| {
value.proposed_policy.decision.push('x')
});
assert_field_drift!(
"candidateSet.candidateKind",
|value: &mut LaneGrantPreview| { value.candidate_set[0].candidate_kind.push('x') }
);
assert_field_drift!(
"candidateSet.candidateId",
|value: &mut LaneGrantPreview| { value.candidate_set[0].candidate_id.push('x') }
);
assert_field_drift!("candidateSet.revisionId", |value: &mut LaneGrantPreview| {
value.candidate_set[0].revision_id.push('x')
});
assert_field_drift!("affectedMemoryCount", |value: &mut LaneGrantPreview| {
value.affected_memory_count += 1
});
assert_field_drift!(
"affectedLedgerEventCount",
|value: &mut LaneGrantPreview| { value.affected_ledger_event_count += 1 }
);
assert_field_drift!(
"redactedFromExposureCount",
|value: &mut LaneGrantPreview| value.redacted_from_exposure_count += 1
);
assert_field_drift!("previewSampleStrategy", |value: &mut LaneGrantPreview| {
value.preview_sample_strategy.push('x')
});
assert_field_drift!("previewSampleLimit", |value: &mut LaneGrantPreview| {
value.preview_sample_limit += 1
});
assert_field_drift!("previewSample.memoryId", |value: &mut LaneGrantPreview| {
value.preview_sample[0].memory_id.push('x')
});
assert_field_drift!(
"previewSample.revisionId",
|value: &mut LaneGrantPreview| value.preview_sample[0].revision_id.push('x')
);
assert_field_drift!("previewSample.level", |value: &mut LaneGrantPreview| value
.preview_sample[0]
.level
.push('x'));
assert_field_drift!("previewSample.kind", |value: &mut LaneGrantPreview| value
.preview_sample[0]
.kind
.push('x'));
assert_field_drift!(
"previewSample.contentPreview",
|value: &mut LaneGrantPreview| value.preview_sample[0].content_preview.push('x')
);
assert_field_drift!("previewSample.tags", |value: &mut LaneGrantPreview| value
.preview_sample[0]
.tags
.push("extra".to_owned()));
assert_field_drift!(
"previewSample.trustClass",
|value: &mut LaneGrantPreview| value.preview_sample[0].trust_class.push('x')
);
assert_field_drift!(
"previewSample.hasSensitiveTags",
|value: &mut LaneGrantPreview| {
let row = &mut value.preview_sample[0];
row.has_sensitive_tags = !row.has_sensitive_tags;
}
);
assert_field_drift!(
"previewSample.redactedFields",
|value: &mut LaneGrantPreview| value.preview_sample[0]
.redacted_fields
.push("tag:jwt".to_owned())
);
assert_field_drift!(
"previewSample.wouldExposeUnderProposedPolicy",
|value: &mut LaneGrantPreview| value.preview_sample[0]
.would_expose_under_proposed_policy = false
);
assert_field_drift!("redactionRulesApplied", |value: &mut LaneGrantPreview| {
value.redaction_rules_applied.push("jwt".to_owned())
});
assert_field_drift!(
"redactionScannerGeneration",
|value: &mut LaneGrantPreview| value.redaction_scanner_generation.push('x')
);
assert_field_drift!("cautionCodes", |value: &mut LaneGrantPreview| value
.caution_codes
.push("extra".to_owned()));
assert_field_drift!("cautions.kind", |value: &mut LaneGrantPreview| value
.cautions[0]
.kind
.push('x'));
assert_field_drift!("cautions.message", |value: &mut LaneGrantPreview| value
.cautions[0]
.message
.push('x'));
assert_field_drift!("cautions.severity", |value: &mut LaneGrantPreview| value
.cautions[0]
.severity
.push('x'));
let mut projected = base.clone();
projected.approval_token = Some(ApprovalTokenProjection {
schema: "ee.mesh.approval_token.v1".to_owned(),
value: "eeap1_redacted-test-bearer".to_owned(),
expires_at: "2026-08-04T08:15:00Z".to_owned(),
handling: "secret".to_owned(),
});
assert_eq!(
projected.canonical_approval_snapshot_bytes().unwrap(),
canonical,
"the bearer projection must not recursively authenticate itself",
);
}
#[test]
fn tombstoned_and_redaction_blocked_memories_are_excluded_from_exposure() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [
build_memory(
"live",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
),
build_memory(
"tomb",
TrustClass::AgentAssertion,
&no_tags,
1,
true,
false,
&no_redacted,
),
build_memory(
"blocked",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
true,
&no_redacted,
),
];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
assert_eq!(preview.affected_memory_count, 1);
assert_eq!(preview.redacted_from_exposure_count, 1);
assert!(
preview
.preview_sample
.iter()
.all(|row| row.memory_id == "live")
);
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(kinds.contains(caution_kinds::TOMBSTONED_IN_EXPOSURE));
assert!(kinds.contains(caution_kinds::REDACTION_ACTIVE));
let redaction_caution = preview
.cautions
.iter()
.find(|caution| caution.kind == caution_kinds::REDACTION_ACTIVE)
.expect("redaction_active caution present");
assert!(redaction_caution.message.contains("would not be exposed"));
assert!(
redaction_caution
.message
.contains("redaction-class rules block that lane")
);
}
#[test]
fn field_level_redaction_emits_redaction_active_without_blocking_exposure() {
let no_tags = empty_strings();
let redacted_fields = tags(&["content:api_key"]);
let memories = [build_memory(
"redacted",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&redacted_fields,
)];
let redaction_rules = tags(&["api_key"]);
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
assert_eq!(preview.affected_memory_count, 1);
assert_eq!(preview.redacted_from_exposure_count, 0);
assert_eq!(preview.preview_sample[0].redacted_fields, redacted_fields);
let caution = preview
.cautions
.iter()
.find(|caution| caution.kind == caution_kinds::REDACTION_ACTIVE)
.expect("field redaction must emit redaction_active");
assert!(caution.message.contains("had sensitive fields redacted"));
}
#[test]
fn high_trust_exposure_caution_fires_for_human_explicit() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [build_memory(
"explicit-1",
TrustClass::HumanExplicit,
&no_tags,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::HighestTrust,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(kinds.contains(caution_kinds::HIGH_TRUST_CLASS_EXPOSURE));
}
#[test]
fn human_explicit_exposure_caution_excludes_peer_human_attested() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [build_memory(
"peer-attested-1",
TrustClass::PeerHumanAttested,
&no_tags,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::HighestTrust,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
assert!(
preview
.cautions
.iter()
.all(|caution| caution.kind != caution_kinds::HIGH_TRUST_CLASS_EXPOSURE)
);
}
#[test]
fn sensitive_tag_exposure_caution_fires_for_canonical_tags() {
for sensitive_tag in SENSITIVE_TAGS {
let tag_storage = tags(&[sensitive_tag]);
let no_redacted = empty_strings();
let memories = [build_memory(
"m1",
TrustClass::AgentAssertion,
&tag_storage,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(
kinds.contains(caution_kinds::SENSITIVE_TAGS_IN_EXPOSURE),
"tag {sensitive_tag} should fire sensitive caution",
);
assert!(preview.preview_sample[0].has_sensitive_tags);
}
}
#[test]
fn sensitive_tag_exposure_caution_fires_for_case_and_scoped_tags() {
let variants = [
"Secret",
"security:secret",
"private-data",
"personal_data",
"INTERNAL",
];
for variant in variants {
let tag_storage = tags(&[variant]);
let no_redacted = empty_strings();
let memories = [build_memory(
"m1",
TrustClass::AgentAssertion,
&tag_storage,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(
kinds.contains(caution_kinds::SENSITIVE_TAGS_IN_EXPOSURE),
"tag {variant} should fire sensitive caution",
);
assert!(
preview.preview_sample[0].has_sensitive_tags,
"tag {variant} should mark the preview row sensitive",
);
}
}
#[test]
fn sensitive_tag_exposure_caution_does_not_match_embedded_words() {
let tag_storage = tags(&["nonsecret", "privately", "personality", "internalized"]);
let no_redacted = empty_strings();
let memories = [build_memory(
"m1",
TrustClass::AgentAssertion,
&tag_storage,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(!kinds.contains(caution_kinds::SENSITIVE_TAGS_IN_EXPOSURE));
assert!(!preview.preview_sample[0].has_sensitive_tags);
}
#[test]
fn peer_not_in_group_caution_fires_with_explicit_severity() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [build_memory(
"m1",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:stranger",
peer_in_group: false,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let caution = preview
.cautions
.iter()
.find(|c| c.kind == caution_kinds::PEER_NOT_IN_GROUP)
.expect("peer_not_in_group caution present");
assert_eq!(caution.severity, "info");
assert!(caution.message.contains("nodekey:stranger"));
assert!(caution.message.contains("peer-group bindings"));
}
#[test]
fn lane_already_granted_caution_fires_when_current_is_allow() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [build_memory(
"m1",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
)];
let already_allow = body_grant_proposed();
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: already_allow,
proposed_policy: already_allow,
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let caution = preview
.cautions
.iter()
.find(|c| c.kind == caution_kinds::LANE_ALREADY_GRANTED)
.expect("lane_already_granted caution present");
assert_eq!(caution.severity, "info");
}
#[test]
fn highest_trust_strategy_orders_peer_attestation_between_human_and_agent_validation() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [
build_memory(
"agent",
TrustClass::AgentAssertion,
&no_tags,
1,
false,
false,
&no_redacted,
),
build_memory(
"explicit",
TrustClass::HumanExplicit,
&no_tags,
1,
false,
false,
&no_redacted,
),
build_memory(
"peer-attested",
TrustClass::PeerHumanAttested,
&no_tags,
1,
false,
false,
&no_redacted,
),
build_memory(
"validated",
TrustClass::AgentValidated,
&no_tags,
1,
false,
false,
&no_redacted,
),
build_memory(
"external",
TrustClass::CassEvidence,
&no_tags,
1,
false,
false,
&no_redacted,
),
];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::HighestTrust,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let order: Vec<&str> = preview
.preview_sample
.iter()
.map(|row| row.memory_id.as_str())
.collect();
assert_eq!(
order,
vec![
"explicit",
"peer-attested",
"validated",
"agent",
"external"
]
);
}
#[test]
fn most_recent_strategy_orders_newest_first() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [
build_memory(
"old",
TrustClass::AgentAssertion,
&no_tags,
100,
false,
false,
&no_redacted,
),
build_memory(
"new",
TrustClass::AgentAssertion,
&no_tags,
999_999,
false,
false,
&no_redacted,
),
build_memory(
"middle",
TrustClass::AgentAssertion,
&no_tags,
5_000,
false,
false,
&no_redacted,
),
];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::MostRecent,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let order: Vec<&str> = preview
.preview_sample
.iter()
.map(|row| row.memory_id.as_str())
.collect();
assert_eq!(order, vec!["new", "middle", "old"]);
}
#[test]
fn random_strategy_is_deterministic_for_fixed_seed() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories: Vec<MemoryView<'_>> = (0..20)
.map(|i| {
let id: &'static str = match i {
0 => "m00",
1 => "m01",
2 => "m02",
3 => "m03",
4 => "m04",
5 => "m05",
6 => "m06",
7 => "m07",
8 => "m08",
9 => "m09",
10 => "m10",
11 => "m11",
12 => "m12",
13 => "m13",
14 => "m14",
15 => "m15",
16 => "m16",
17 => "m17",
18 => "m18",
_ => "m19",
};
build_memory(
id,
TrustClass::AgentAssertion,
&no_tags,
i,
false,
false,
&no_redacted,
)
})
.collect();
let redaction_rules = empty_strings();
let preview_a = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 5,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let preview_b = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 5,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
let ids_a: Vec<&str> = preview_a
.preview_sample
.iter()
.map(|row| row.memory_id.as_str())
.collect();
let ids_b: Vec<&str> = preview_b
.preview_sample
.iter()
.map(|row| row.memory_id.as_str())
.collect();
assert_eq!(ids_a, ids_b, "same seed must produce same ordering");
}
#[test]
fn random_strategy_different_seed_produces_different_ordering() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories: Vec<MemoryView<'_>> = (0..20)
.map(|i| {
let id: &'static str = match i {
0 => "m00",
1 => "m01",
2 => "m02",
3 => "m03",
4 => "m04",
5 => "m05",
6 => "m06",
7 => "m07",
8 => "m08",
9 => "m09",
10 => "m10",
11 => "m11",
12 => "m12",
13 => "m13",
14 => "m14",
15 => "m15",
16 => "m16",
17 => "m17",
18 => "m18",
_ => "m19",
};
build_memory(
id,
TrustClass::AgentAssertion,
&no_tags,
i,
false,
false,
&no_redacted,
)
})
.collect();
let redaction_rules = empty_strings();
let preview_a = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 20,
redaction_rules: &redaction_rules,
sample_random_seed: 1,
});
let preview_b = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 20,
redaction_rules: &redaction_rules,
sample_random_seed: 2,
});
let ids_a: Vec<&str> = preview_a
.preview_sample
.iter()
.map(|row| row.memory_id.as_str())
.collect();
let ids_b: Vec<&str> = preview_b
.preview_sample
.iter()
.map(|row| row.memory_id.as_str())
.collect();
assert_ne!(ids_a, ids_b, "different seeds should rarely match");
}
#[test]
fn large_volume_exposure_caution_fires_above_threshold() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories: Vec<MemoryView<'_>> = (0..1500)
.map(|i| {
let id_ref: &'static str = Box::leak(format!("m{i:04}").into_boxed_str());
build_memory(
id_ref,
TrustClass::AgentAssertion,
&no_tags,
i,
false,
false,
&no_redacted,
)
})
.collect();
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: body_grant_proposed(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 7,
});
assert_eq!(preview.affected_memory_count, 1500);
assert_eq!(preview.preview_sample.len(), 25);
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(kinds.contains(caution_kinds::LARGE_VOLUME_EXPOSURE));
}
#[test]
fn proposed_deny_yields_zero_exposure_and_minimal_cautions() {
let no_tags = empty_strings();
let no_redacted = empty_strings();
let memories = [build_memory(
"m1",
TrustClass::HumanExplicit,
&no_tags,
1,
false,
false,
&no_redacted,
)];
let redaction_rules = empty_strings();
let preview = compute_lane_grant_preview(&LaneGrantPreviewInput {
peer_node_key: "nodekey:test",
peer_in_group: true,
lane: Lane::Body,
workspace_id: "ws-1",
current_policy: IntendedLanePolicy::conservative_default(),
proposed_policy: IntendedLanePolicy::conservative_default(),
memories: &memories,
sample_strategy: SampleStrategy::Random,
limit: 25,
redaction_rules: &redaction_rules,
sample_random_seed: 42,
});
assert_eq!(preview.affected_memory_count, 0);
assert_eq!(preview.preview_sample.len(), 0);
let kinds: BTreeSet<&str> = preview.cautions.iter().map(|c| c.kind.as_str()).collect();
assert!(!kinds.contains(caution_kinds::HIGH_TRUST_CLASS_EXPOSURE));
assert!(!kinds.contains(caution_kinds::LARGE_VOLUME_EXPOSURE));
}
#[test]
fn schema_constant_is_documented_version() {
assert_eq!(
LANE_GRANT_PREVIEW_SCHEMA_V2,
"ee.mesh.lane_grant_preview.v2"
);
}
}