use crate::domain::{ScoredNote, WakeupMemoryItem, WakeupPolicy};
#[derive(Debug, Clone)]
pub struct PreparedWakeupItem {
pub item: WakeupMemoryItem,
pub suppressed: bool,
}
#[derive(Debug, Clone)]
pub struct WakeupPolicyState {
max_sensitivity_included: Option<String>,
redactions_applied: bool,
suppressed_note_count: usize,
}
impl WakeupPolicyState {
pub fn new() -> Self {
Self {
max_sensitivity_included: None,
redactions_applied: false,
suppressed_note_count: 0,
}
}
pub fn prepare_item(&mut self, scored: &ScoredNote) -> PreparedWakeupItem {
let note = &scored.note;
let memory_type = note.memory_type().map(ToString::to_string);
let sensitivity = note.sensitivity().map(ToString::to_string);
let source_of_truth = note.source_of_truth();
if sensitivity.as_deref() == Some("secret") {
self.suppressed_note_count += 1;
return PreparedWakeupItem {
item: WakeupMemoryItem {
title: note.title.clone(),
summary: String::new(),
memory_type,
source: note.relative_path.clone(),
sensitivity,
source_of_truth,
confidence: scored.confidence,
},
suppressed: true,
};
}
self.track_max_sensitivity(sensitivity.as_deref());
let summary = self.summarize_excerpt(&scored.excerpt, sensitivity.as_deref());
PreparedWakeupItem {
item: WakeupMemoryItem {
title: note.title.clone(),
summary,
memory_type,
source: note.relative_path.clone(),
sensitivity,
source_of_truth,
confidence: scored.confidence,
},
suppressed: false,
}
}
pub fn build_policy(self) -> WakeupPolicy {
WakeupPolicy {
max_sensitivity_included: self.max_sensitivity_included,
redactions_applied: self.redactions_applied,
suppressed_note_count: self.suppressed_note_count,
policy_mode: "conservative_default".to_string(),
}
}
fn summarize_excerpt(&mut self, excerpt: &str, sensitivity: Option<&str>) -> String {
match sensitivity {
Some("confidential") => {
self.redactions_applied = true;
let summary = excerpt.chars().take(80).collect::<String>();
if summary.is_empty() {
"Confidential content redacted".to_string()
} else {
format!("{} [redacted]", summary)
}
}
_ => excerpt.to_string(),
}
}
fn track_max_sensitivity(&mut self, next: Option<&str>) {
let Some(next) = next else {
return;
};
let next_rank = sensitivity_rank(next);
let current_rank = self
.max_sensitivity_included
.as_deref()
.map(sensitivity_rank)
.unwrap_or(-1);
if next_rank > current_rank {
self.max_sensitivity_included = Some(next.to_string());
}
}
}
fn sensitivity_rank(value: &str) -> i32 {
match value {
"public" => 0,
"internal" => 1,
"confidential" => 2,
"secret" => 3,
_ => -1,
}
}
#[cfg(test)]
mod tests {
use super::WakeupPolicyState;
use crate::domain::{Note, Section};
use serde_json::json;
use std::collections::BTreeMap;
use std::path::PathBuf;
fn make_scored_note(
path: &str,
title: &str,
memory_type: &str,
sensitivity: &str,
body: &str,
) -> crate::domain::ScoredNote {
let note = Note::new(
PathBuf::from(path),
path.to_string(),
title.to_string(),
BTreeMap::from([
("memory_type".to_string(), json!(memory_type)),
("sensitivity".to_string(), json!(sensitivity)),
("source_of_truth".to_string(), json!(true)),
]),
vec![Section {
heading: Some(title.to_string()),
level: 1,
content: body.to_string(),
}],
Vec::new(),
body.to_string(),
);
note.to_scored(10, vec!["matched task token".to_string()])
}
#[test]
fn policy_should_redact_confidential_excerpt() {
let mut state = WakeupPolicyState::new();
let prepared = state.prepare_item(&make_scored_note(
"confidential.md",
"Confidential",
"constraint",
"confidential",
"Highly specific confidential implementation details that should not be exposed verbatim.",
));
assert!(!prepared.suppressed);
assert!(prepared.item.summary.contains("[redacted]"));
let policy = state.build_policy();
assert_eq!(
policy.max_sensitivity_included.as_deref(),
Some("confidential")
);
assert!(policy.redactions_applied);
}
#[test]
fn policy_should_suppress_secret_excerpt() {
let mut state = WakeupPolicyState::new();
let prepared = state.prepare_item(&make_scored_note(
"secret.md",
"Secret",
"constraint",
"secret",
"top secret details",
));
assert!(prepared.suppressed);
let policy = state.build_policy();
assert_eq!(policy.suppressed_note_count, 1);
assert_eq!(policy.max_sensitivity_included, None);
}
#[test]
fn policy_should_track_highest_visible_sensitivity() {
let mut state = WakeupPolicyState::new();
state.prepare_item(&make_scored_note(
"public.md",
"Public",
"project",
"public",
"public body",
));
state.prepare_item(&make_scored_note(
"internal.md",
"Internal",
"project",
"internal",
"internal body",
));
let policy = state.build_policy();
assert_eq!(policy.max_sensitivity_included.as_deref(), Some("internal"));
assert!(!policy.redactions_applied);
assert_eq!(policy.suppressed_note_count, 0);
}
}