1use super::{
3 CrpMode, ReadMode, ReadOutput, ReadTuning, SessionCache, count_tokens, dedup_hook,
4 handle_with_options_inner, protocol,
5};
6const MAX_RELAY_CONTENT_BYTES: usize = 8192;
7
8fn is_terminal_poll_file(path: &str) -> bool {
15 (path.contains("/.cursor/") || path.contains("\\.cursor\\"))
16 && (path.contains("/terminals/") || path.contains("\\terminals\\"))
17 && std::path::Path::new(path)
18 .extension()
19 .is_some_and(|ext| ext.eq_ignore_ascii_case("txt"))
20}
21
22const RELAY_ELIGIBLE_MODES: &[&str] = &["map", "signatures"];
24
25fn relay_eligible<'a>(mode: &'a str, content: &'a str) -> (Option<&'a str>, Option<&'a str>) {
27 if RELAY_ELIGIBLE_MODES.contains(&mode) && content.len() <= MAX_RELAY_CONTENT_BYTES {
28 (Some(content), Some(mode))
29 } else {
30 (None, None)
31 }
32}
33
34fn relay_variant_matches(
35 mode: &str,
36 stored: &str,
37 crp_mode: CrpMode,
38 tuning: ReadTuning<'_>,
39) -> bool {
40 RELAY_ELIGIBLE_MODES.iter().any(|candidate| {
41 (mode == "auto" || mode == *candidate)
42 && stored
43 == super::compressed_cache_key(
44 candidate,
45 crp_mode,
46 None,
47 tuning.aggressiveness,
48 tuning.protect,
49 )
50 })
51}
52pub fn handle(cache: &mut SessionCache, path: &str, mode: &str, crp_mode: CrpMode) -> String {
54 handle_with_options(cache, path, mode, false, crp_mode, None)
55}
56
57pub fn handle_fresh(cache: &mut SessionCache, path: &str, mode: &str, crp_mode: CrpMode) -> String {
59 handle_with_options(cache, path, mode, true, crp_mode, None)
60}
61
62pub fn handle_with_task(
64 cache: &mut SessionCache,
65 path: &str,
66 mode: &str,
67 crp_mode: CrpMode,
68 task: Option<&str>,
69) -> String {
70 handle_with_task_result(cache, path, mode, crp_mode, task).content
71}
72
73pub fn handle_with_task_result(
76 cache: &mut SessionCache,
77 path: &str,
78 mode: &str,
79 crp_mode: CrpMode,
80 task: Option<&str>,
81) -> ReadOutput {
82 let mut result = handle_with_options_resolved(
83 cache,
84 path,
85 mode,
86 false,
87 crp_mode,
88 task,
89 ReadTuning::resolve(None, &[]),
90 );
91 result.output_tokens = count_tokens(&result.content);
95 result
96}
97
98pub fn handle_with_task_resolved(
100 cache: &mut SessionCache,
101 path: &str,
102 mode: &str,
103 crp_mode: CrpMode,
104 task: Option<&str>,
105) -> ReadOutput {
106 handle_with_options_resolved(
107 cache,
108 path,
109 mode,
110 false,
111 crp_mode,
112 task,
113 ReadTuning::resolve(None, &[]),
114 )
115}
116
117pub fn handle_with_task_resolved_tuned(
121 cache: &mut SessionCache,
122 path: &str,
123 mode: &str,
124 crp_mode: CrpMode,
125 task: Option<&str>,
126 aggressiveness: Option<f64>,
127 protect: &[String],
128) -> ReadOutput {
129 handle_with_options_resolved(
130 cache,
131 path,
132 mode,
133 false,
134 crp_mode,
135 task,
136 ReadTuning::resolve(aggressiveness, protect),
137 )
138}
139
140#[allow(clippy::too_many_arguments)]
143pub fn handle_with_preread(
144 cache: &mut SessionCache,
145 path: &str,
146 mode: &str,
147 fresh: bool,
148 crp_mode: CrpMode,
149 task: Option<&str>,
150 aggressiveness: Option<f64>,
151 protect: &[String],
152 preread: String,
153) -> ReadOutput {
154 handle_with_options_resolved_preread(
155 cache,
156 path,
157 mode,
158 fresh,
159 crp_mode,
160 task,
161 ReadTuning::resolve(aggressiveness, protect),
162 Some(preread),
163 )
164}
165
166pub fn handle_fresh_with_task(
168 cache: &mut SessionCache,
169 path: &str,
170 mode: &str,
171 crp_mode: CrpMode,
172 task: Option<&str>,
173) -> String {
174 handle_fresh_with_task_result(cache, path, mode, crp_mode, task).content
175}
176
177pub fn handle_fresh_with_task_result(
179 cache: &mut SessionCache,
180 path: &str,
181 mode: &str,
182 crp_mode: CrpMode,
183 task: Option<&str>,
184) -> ReadOutput {
185 handle_with_options_resolved(
186 cache,
187 path,
188 mode,
189 true,
190 crp_mode,
191 task,
192 ReadTuning::resolve(None, &[]),
193 )
194}
195
196pub fn handle_fresh_with_task_resolved(
198 cache: &mut SessionCache,
199 path: &str,
200 mode: &str,
201 crp_mode: CrpMode,
202 task: Option<&str>,
203) -> ReadOutput {
204 handle_with_options_resolved(
205 cache,
206 path,
207 mode,
208 true,
209 crp_mode,
210 task,
211 ReadTuning::resolve(None, &[]),
212 )
213}
214
215pub fn handle_fresh_with_task_resolved_tuned(
217 cache: &mut SessionCache,
218 path: &str,
219 mode: &str,
220 crp_mode: CrpMode,
221 task: Option<&str>,
222 aggressiveness: Option<f64>,
223 protect: &[String],
224) -> ReadOutput {
225 handle_with_options_resolved(
226 cache,
227 path,
228 mode,
229 true,
230 crp_mode,
231 task,
232 ReadTuning::resolve(aggressiveness, protect),
233 )
234}
235
236fn handle_with_options(
237 cache: &mut SessionCache,
238 path: &str,
239 mode: &str,
240 fresh: bool,
241 crp_mode: CrpMode,
242 task: Option<&str>,
243) -> String {
244 handle_with_options_resolved(
245 cache,
246 path,
247 mode,
248 fresh,
249 crp_mode,
250 task,
251 ReadTuning::resolve(None, &[]),
252 )
253 .content
254}
255
256pub(crate) fn force_fresh_env() -> bool {
259 static FORCE_FRESH: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
260 *FORCE_FRESH.get_or_init(|| {
261 std::env::var("LEAN_CTX_FORCE_FRESH").is_ok_and(|v| v == "1" || v == "true")
262 })
263}
264
265pub(crate) use lean_ctx::core::conversation::is_subagent_context;
266
267#[allow(clippy::fn_params_excessive_bools)]
270pub(crate) fn effective_fresh_flags(
271 fresh: bool,
272 force_fresh: bool,
273 subagent_context: bool,
274 delivery_for_subagents: bool,
275) -> (bool, bool) {
276 let effective_fresh_for_cache = fresh || force_fresh || subagent_context;
277 let effective_fresh_for_delivery =
278 fresh || force_fresh || (subagent_context && !delivery_for_subagents);
279 (effective_fresh_for_cache, effective_fresh_for_delivery)
280}
281
282pub(crate) fn effective_fresh_for_delivery(fresh: bool) -> bool {
283 let config = lean_ctx::core::config::Config::load();
284 effective_fresh_flags(
285 fresh,
286 force_fresh_env(),
287 is_subagent_context(),
288 config.ocla.delivery.delivery_for_subagents,
289 )
290 .1
291}
292
293fn handle_with_options_resolved(
294 cache: &mut SessionCache,
295 path: &str,
296 mode: &str,
297 fresh: bool,
298 crp_mode: CrpMode,
299 task: Option<&str>,
300 tuning: ReadTuning<'_>,
301) -> ReadOutput {
302 handle_with_options_resolved_preread(cache, path, mode, fresh, crp_mode, task, tuning, None)
303}
304
305fn handle_with_options_resolved_preread(
306 cache: &mut SessionCache,
307 path: &str,
308 mode: &str,
309 fresh: bool,
310 crp_mode: CrpMode,
311 task: Option<&str>,
312 tuning: ReadTuning<'_>,
313 preread: Option<String>,
314) -> ReadOutput {
315 let config = lean_ctx::core::config::Config::load();
318 let (effective_fresh_for_cache, effective_fresh_for_delivery) = effective_fresh_flags(
319 fresh,
320 force_fresh_env(),
321 is_subagent_context(),
322 config.ocla.delivery.delivery_for_subagents,
323 );
324
325 let compress_protected = (mode != "raw"
328 && !mode.starts_with("lines:")
329 && lean_ctx::core::config::Config::load()
330 .proxy
331 .is_path_compress_protected(path))
332 || super::instruction_file_forces_full(path, mode);
333
334 let delivery_metadata = config
337 .ocla
338 .delivery_enabled()
339 .then(|| file_blake3_prefix(path))
340 .flatten();
341
342 if !effective_fresh_for_delivery
343 && !compress_protected
344 && let Some(fp) = delivery_metadata
345 && let Some(stub) = try_cross_agent_stub(path, mode, fp.hash, fp.mtime, crp_mode, tuning)
346 {
347 return stub;
348 }
349
350 if mode == "auto" {
351 let touched: Vec<String> = cache
352 .get_all_entries()
353 .iter()
354 .map(|(p, _)| (*p).clone())
355 .collect();
356 if lean_ctx::core::relevance_gate::should_gate(path, mode, task, &touched) {
357 let meta = std::fs::metadata(path);
358 let byte_count = meta.as_ref().map_or(0, std::fs::Metadata::len);
359 let line_count = preread
360 .as_ref()
361 .map_or(0, |c| bytecount::count(c.as_bytes(), b'\n'));
362 let stub =
363 lean_ctx::core::relevance_gate::irrelevant_stub(path, line_count, byte_count);
364 let stub_tokens = count_tokens(&stub);
365 return ReadOutput {
366 content: stub,
367 resolved_mode: "auto".into(),
368 output_tokens: stub_tokens,
369 is_cache_hit: false,
370 };
371 }
372 }
373
374 if let Ok(mut bt) = lean_ctx::core::bounce_tracker::global().lock() {
375 bt.next_seq();
376 }
377 let mut result = handle_with_options_inner(
378 cache,
379 path,
380 mode,
381 effective_fresh_for_cache,
382 crp_mode,
383 task,
384 tuning,
385 preread,
386 );
387
388 if let Some(entry) = cache.get_mut(path) {
389 entry.last_mode.clone_from(&result.resolved_mode);
390 if matches!(result.resolved_mode.as_str(), "full" | "full-compact")
391 && entry.full_content_delivered
392 && result.is_cache_hit
393 && entry.bump_reread() >= lean_ctx::core::cache::full_degradation_threshold()
394 {
395 entry.full_content_delivered = false;
396 entry.reset_reread_count();
397 lean_ctx::core::auto_mode_resolver::count_source("full_delivery_degraded");
398 }
399 if !matches!(result.resolved_mode.as_str(), "full" | "full-compact")
407 || super::dedup_hook::exceeds_turn_budget(&result.content)
408 {
409 entry.full_content_delivered = false;
410 entry.reset_reread_count();
411 }
412 }
413
414 if !result.is_cache_hit
415 && let Some(fp) = delivery_metadata
416 {
417 record_read_delivery(
418 path,
419 fp,
420 &result.resolved_mode,
421 &result.content,
422 result.output_tokens,
423 crp_mode,
424 tuning,
425 );
426 }
427
428 let dedup_allowed = result
430 .resolved_mode
431 .parse::<ReadMode>()
432 .is_ok_and(|m| m.is_lossy_summary());
433 if dedup_allowed && let Some(deduped) = cache.apply_dedup(path, &result.content) {
434 let new_tokens = count_tokens(&deduped);
435 if new_tokens < result.output_tokens {
436 result.content = deduped;
437 result.output_tokens = new_tokens;
438 }
439 }
440
441 if let Some(stub) = dedup_hook::maybe_dedup(path, &result.content, mode, fresh) {
443 let stub_tokens = count_tokens(&stub);
444 if stub_tokens < result.output_tokens {
445 result.content = stub;
446 result.output_tokens = stub_tokens;
447 result.is_cache_hit = true;
448 }
449 }
450
451 lean_ctx::core::context_kernel::adaptive_hook::update_from_bounce_tracker();
453 if let Ok(mut bt) = lean_ctx::core::bounce_tracker::global().lock() {
454 let original_tokens = cache.get(path).map_or(0, |e| e.original_tokens);
455 bt.record_read(
458 path,
459 &result.resolved_mode,
460 result.output_tokens,
461 original_tokens,
462 );
463 }
464
465 lean_ctx::core::context_gc::maybe_gc(cache);
466
467 result
468}
469
470pub fn try_stub_hit_readonly(cache: &SessionCache, path: &str) -> Option<ReadOutput> {
481 let current_conversation = lean_ctx::core::conversation::current_conversation_id_fresh();
485 try_stub_hit_readonly_scoped(cache, path, current_conversation.as_deref())
486}
487
488pub(crate) fn try_stub_hit_readonly_scoped(
492 cache: &SessionCache,
493 path: &str,
494 current_conversation: Option<&str>,
495) -> Option<ReadOutput> {
496 if !stub_policy_allows() {
497 return None;
498 }
499
500 if let Some(file_ref) = cache.get_file_ref_readonly(path) {
502 let (cached_mtime, cached_hash, line_count, delivered_conv) = {
503 let entry = cache.get(path)?;
504 (
505 entry.stored_mtime,
506 entry.hash.clone(),
507 entry.line_count,
508 entry.delivered_conversation.clone(),
509 )
510 };
511 if lean_ctx::core::cache::is_cache_entry_stale_verified(path, cached_mtime, &cached_hash)
512 || !cache.is_full_delivered(path)
513 {
514 return None;
515 }
516 let is_terminal_poll = is_terminal_poll_file(path);
520
521 if !is_terminal_poll
522 && !lean_ctx::core::conversation::conversation_allows_stub(
523 current_conversation,
524 delivered_conv.as_deref(),
525 )
526 {
527 return None;
528 }
529 let original_tokens = cache.record_cache_hit(path)?.original_tokens;
530 lean_ctx::core::telemetry::global_metrics().record_cache(true);
531 let stub = render_unchanged_stub(&file_ref, path, line_count);
532 lean_ctx::core::stats::record_reread(original_tokens.saturating_sub(stub.output_tokens));
533 return Some(stub);
534 }
535
536 let rec = lean_ctx::core::read_stub_index::lookup(path)?;
542 if lean_ctx::core::cache::is_cache_entry_stale_verified(path, rec.stored_mtime(), &rec.hash) {
543 return None;
544 }
545 if !is_terminal_poll_file(path)
546 && !lean_ctx::core::conversation::conversation_allows_cold_stub(
547 current_conversation,
548 rec.delivered_conversation.as_deref(),
549 )
550 {
551 return None;
552 }
553 Some(render_unchanged_stub(&rec.file_ref, path, rec.line_count))
554}
555
556pub(crate) fn stub_policy_allows() -> bool {
560 let no_deg = lean_ctx::core::config::Config::load().no_degrade_effective();
561 let prof = lean_ctx::core::profiles::active_profile();
562 let force_full = no_deg
563 || (prof.read.default_mode_effective() == "full"
564 && prof.compression.crp_mode.as_deref() == Some("off"));
565 crate::server::compaction_sync::effective_cache_policy() != "safe" && !force_full
566}
567
568fn render_unchanged_stub(file_ref: &str, path: &str, line_count: usize) -> ReadOutput {
576 let short = protocol::shorten_path(path);
577 let out = if lean_ctx::core::protocol::meta_visible() {
578 format!(
579 "{file_ref}={short} [unchanged {line_count}L]\nUnchanged on disk. Use fresh=true to force re-read.",
580 )
581 } else {
582 format!("{file_ref}={short} [unchanged {line_count}L · fresh=true to re-read]")
583 };
584 let out = lean_ctx::core::redaction::redact_text_if_enabled(&out);
585 let sent = count_tokens(&out);
586 ReadOutput {
587 content: out,
588 resolved_mode: "full".into(),
589 output_tokens: sent,
590 is_cache_hit: true,
591 }
592}
593
594pub(crate) fn render_unchanged_variant_stub(file_ref: &str, path: &str, mode: &str) -> ReadOutput {
600 let short = protocol::shorten_path(path);
601 let out = if lean_ctx::core::protocol::meta_visible() {
602 format!(
603 "{file_ref}={short} [unchanged {mode} view]\nSame {mode} output you already received. Use fresh=true to re-emit.",
604 )
605 } else {
606 format!("{file_ref}={short} [unchanged {mode} view · fresh=true to re-emit]")
607 };
608 let out = lean_ctx::core::redaction::redact_text_if_enabled(&out);
609 let sent = count_tokens(&out);
610 ReadOutput {
611 content: out,
612 resolved_mode: mode.to_string(),
613 output_tokens: sent,
614 is_cache_hit: true,
615 }
616}
617
618#[derive(Debug, Clone, PartialEq, Eq)]
621pub struct DeltaExplicitDecision {
622 pub mode: String,
625 pub note: Option<String>,
629}
630
631pub fn resolve_explicit_delta_mode(
653 cache: &SessionCache,
654 path: &str,
655 mode: &str,
656 explicit_mode: bool,
657 fresh: bool,
658 enabled: bool,
659) -> DeltaExplicitDecision {
660 let unchanged = DeltaExplicitDecision {
661 mode: mode.to_string(),
662 note: None,
663 };
664 if fresh
665 || !enabled
666 || !explicit_mode
667 || !(mode == "full" || mode == "full-compact" || mode.starts_with("lines:"))
668 {
669 return unchanged;
670 }
671 let Some(entry) = cache.get(path) else {
672 return unchanged;
674 };
675 let stale =
676 lean_ctx::core::cache::is_cache_entry_stale_verified(path, entry.stored_mtime, &entry.hash);
677 if stale {
678 if entry.content().is_some() {
682 return DeltaExplicitDecision {
683 mode: "diff".to_string(),
684 note: Some(format!(
685 "[delta-explicit] requested mode={mode} served as a diff: the file \
686 changed since your last read and the diff is the new information. \
687 Pass fresh=true if you need the full content re-emitted."
688 )),
689 };
690 }
691 return unchanged;
692 }
693 if mode.starts_with("lines:") && cache.is_full_delivered(path) {
697 return DeltaExplicitDecision {
698 mode: "full".to_string(),
699 note: None,
700 };
701 }
702 unchanged
703}
704
705pub(crate) fn file_blake3_prefix(path: &str) -> Option<DeliveryFingerprint> {
706 let meta = std::fs::metadata(path).ok()?;
707 let mtime = meta
708 .modified()
709 .ok()?
710 .duration_since(std::time::UNIX_EPOCH)
711 .ok()?
712 .as_secs();
713 let bytes = std::fs::read(path).ok()?;
714 let hash = blake3::hash(&bytes);
715 let full = hash.as_bytes();
716 let mut prefix = [0u8; 12];
717 prefix.copy_from_slice(&full[..12]);
718 Some(DeliveryFingerprint {
719 hash: prefix,
720 mtime,
721 line_count: line_count_of(&bytes),
722 })
723}
724
725#[derive(Clone, Copy, Debug, PartialEq, Eq)]
729pub(crate) struct DeliveryFingerprint {
730 pub hash: [u8; 12],
731 pub mtime: u64,
732 pub line_count: u32,
733}
734
735fn line_count_of(bytes: &[u8]) -> u32 {
738 let newlines = bytecount::count(bytes, b'\n');
739 let unterminated = usize::from(bytes.last().is_some_and(|b| *b != b'\n'));
740 u32::try_from(newlines + unterminated).unwrap_or(u32::MAX)
741}
742
743fn delivery_agent(
747 profile: Option<&lean_ctx::core::a2a::task::delivery_authority::DeliverySigningProfileV1>,
748) -> String {
749 profile.map_or_else(
750 || lean_ctx::core::agent_identity::delivery_agent_id().to_string(),
751 |profile| profile.agent_id.clone(),
752 )
753}
754
755pub(crate) fn try_cross_agent_stub(
756 path: &str,
757 mode: &str,
758 hash: [u8; 12],
759 mtime: u64,
760 crp_mode: CrpMode,
761 tuning: ReadTuning<'_>,
762) -> Option<ReadOutput> {
763 if !lean_ctx::core::config::Config::load()
764 .ocla
765 .delivery_enabled()
766 {
767 return None;
768 }
769 if mode != "auto" && !RELAY_ELIGIBLE_MODES.contains(&mode) {
770 return None;
771 }
772 let profile =
773 lean_ctx::core::a2a::task::delivery_authority::DeliverySigningProfileV1::from_environment()
774 .ok()?;
775 let current_agent = delivery_agent(profile.as_ref());
776 let current_conversation = lean_ctx::core::conversation::current_conversation_id()
777 .unwrap_or_else(|| current_agent.clone());
778 let reg = lean_ctx::core::ocla::OclaRegistry::global();
779 let record = if let Some(profile) = profile {
780 lean_ctx::daemon_client::scoped_delivery_check_blocking(
782 &profile,
783 path,
784 hash,
785 ¤t_conversation,
786 )
787 .ok()
788 .flatten()
789 } else {
790 lean_ctx::daemon_client::try_delivery_check_blocking(
791 &hash,
792 mtime,
793 path,
794 Some(¤t_agent),
795 Some(¤t_conversation),
796 )
797 .or_else(|| {
798 reg.delivery_registry.check_delivery(
799 &hash,
800 mtime,
801 path,
802 Some(¤t_agent),
803 Some(¤t_conversation),
804 )
805 })
806 }?;
807
808 render_cross_agent_relay(path, mode, &record, crp_mode, tuning)
809}
810
811fn render_cross_agent_relay(
812 path: &str,
813 mode: &str,
814 record: &lean_ctx::core::ocla::types::DeliveryRecord,
815 crp_mode: CrpMode,
816 tuning: ReadTuning<'_>,
817) -> Option<ReadOutput> {
818 let relay_mode = record.relay_mode.as_deref()?;
819 if !relay_variant_matches(mode, relay_mode, crp_mode, tuning) {
820 return None;
821 }
822 let content = record.relay_content.as_ref()?;
825 let short = protocol::shorten_path(path);
826
827 let header = format!(
828 "{short} [relayed from {} · {relay_mode} · {}L]",
829 record.agent_id, record.line_count,
830 );
831 let body = format!("{header}\n{content}");
832 let tokens = count_tokens(&body);
833 lean_ctx::core::ocla::OclaRegistry::global()
834 .delivery_registry
835 .record_stub_served(record, tokens as u64);
836 Some(ReadOutput {
837 content: body,
838 resolved_mode: "cross-agent-relay".into(),
839 output_tokens: tokens,
840 is_cache_hit: true,
841 })
842}
843
844pub(crate) fn record_read_delivery(
848 path: &str,
849 fingerprint: DeliveryFingerprint,
850 resolved_mode: &str,
851 content: &str,
852 output_tokens: usize,
853 crp_mode: CrpMode,
854 tuning: ReadTuning<'_>,
855) {
856 let relay = relay_eligible(resolved_mode, content);
857 let relay_key = relay.1.map(|mode| {
858 super::compressed_cache_key(mode, crp_mode, None, tuning.aggressiveness, tuning.protect)
859 });
860 record_cross_agent_delivery(
861 path,
862 fingerprint.hash,
863 fingerprint.mtime,
864 fingerprint.line_count,
865 output_tokens,
866 relay.0,
867 relay_key.as_deref(),
868 );
869}
870
871pub(crate) fn record_cross_agent_delivery(
872 path: &str,
873 hash: [u8; 12],
874 mtime: u64,
875 line_count: u32,
876 tokens: usize,
877 relay_content: Option<&str>,
878 relay_mode: Option<&str>,
879) {
880 if !lean_ctx::core::config::Config::load()
881 .ocla
882 .delivery_enabled()
883 {
884 return;
885 }
886 let Ok(profile) =
887 lean_ctx::core::a2a::task::delivery_authority::DeliverySigningProfileV1::from_environment()
888 else {
889 return;
890 };
891 let agent_id = delivery_agent(profile.as_ref());
892 let conversation_id =
893 lean_ctx::core::conversation::current_conversation_id().unwrap_or_else(|| agent_id.clone());
894 let mut entry = lean_ctx::core::ocla::types::DeliveryEntry {
895 access: None,
896 blake3: hash,
897 path: path.into(),
898 line_count,
899 token_count: tokens as u64,
900 agent_id,
901 conversation_id,
902 mtime,
903 relay_content: relay_content
904 .filter(|c| c.len() <= MAX_RELAY_CONTENT_BYTES)
905 .map(str::to_string),
906 relay_mode: relay_mode.map(str::to_string),
907 };
908 if let Some(profile) = profile {
909 let Ok(scope) = lean_ctx_ocla::delivery_scope::DeliveryScopeV1::new(
910 profile.tenant_id.clone(),
911 profile.project_id.clone(),
912 ) else {
913 return;
914 };
915 entry.access = Some(lean_ctx_ocla::delivery_scope::DeliveryAccessV1 {
916 scope,
917 privacy: profile.privacy,
918 });
919 let _ = lean_ctx::daemon_client::scoped_delivery_record_blocking(&profile, entry);
920 return;
921 }
922 lean_ctx::daemon_client::try_delivery_record_blocking(&entry);
923 let reg = lean_ctx::core::ocla::OclaRegistry::global();
924 reg.delivery_registry.record_delivery(entry);
925}
926
927#[cfg(test)]
928mod tests {
929 use super::{
930 CrpMode, ReadOutput, ReadTuning, SessionCache, effective_fresh_flags,
931 is_terminal_poll_file, relay_variant_matches, render_cross_agent_relay,
932 try_cross_agent_stub, try_stub_hit_readonly_scoped,
933 };
934 use std::sync::atomic::Ordering;
935
936 #[test]
937 fn cross_agent_stub_miss_returns_none() {
938 let stub = try_cross_agent_stub(
939 "/nonexistent/file.rs",
940 "auto",
941 [0; 12],
942 0,
943 CrpMode::Off,
944 ReadTuning::default(),
945 );
946 assert!(stub.is_none());
947 }
948
949 #[test]
950 fn relay_variants_preserve_requested_read_semantics() {
951 let defaults = ReadTuning::default();
952 assert!(relay_variant_matches(
953 "map",
954 "map:v2",
955 CrpMode::Off,
956 defaults
957 ));
958 assert!(relay_variant_matches(
959 "auto",
960 "signatures:v2",
961 CrpMode::Off,
962 defaults
963 ));
964 for mode in [
965 "signatures",
966 "lines:1-5",
967 "raw",
968 "full",
969 "task",
970 "reference",
971 ] {
972 assert!(!relay_variant_matches(
973 mode,
974 "map:v2",
975 CrpMode::Off,
976 defaults
977 ));
978 }
979 for stored in ["map", "map:v3", "mapping", "map:v2:tdd", "map:v2:unknown"] {
980 assert!(!relay_variant_matches(
981 "map",
982 stored,
983 CrpMode::Off,
984 defaults
985 ));
986 }
987 assert!(relay_variant_matches(
988 "map",
989 "map:v2:tdd",
990 CrpMode::Tdd,
991 defaults
992 ));
993 let protect = vec!["important".to_owned()];
994 let tuned = ReadTuning {
995 aggressiveness: Some(0.7),
996 protect: &protect,
997 };
998 let key = super::super::compressed_cache_key(
999 "map",
1000 CrpMode::Off,
1001 None,
1002 tuned.aggressiveness,
1003 tuned.protect,
1004 );
1005 assert!(relay_variant_matches("map", &key, CrpMode::Off, tuned));
1006 assert!(!relay_variant_matches("map", &key, CrpMode::Off, defaults));
1007 assert!(!relay_variant_matches("map", "map:v2", CrpMode::Off, tuned));
1008 }
1009
1010 #[test]
1011 fn relay_rendering_requires_matching_usable_payload() {
1012 let _isolated = lean_ctx::core::data_dir::isolated_data_dir();
1013 let mut record = lean_ctx::core::ocla::types::DeliveryRecord {
1014 access: None,
1015 blake3: [81; 12],
1016 path: "variant.rs".into(),
1017 line_count: 12,
1018 token_count: 200,
1019 agent_id: "source-agent".into(),
1020 conversation_id: "source-conversation".into(),
1021 read_at: 1,
1022 mtime: 1,
1023 relay_content: Some("fn shared_context()".into()),
1024 relay_mode: Some("map:v2".into()),
1025 fresh: true,
1026 };
1027 let render = |record: &lean_ctx::core::ocla::types::DeliveryRecord, mode| {
1028 render_cross_agent_relay(
1029 "variant.rs",
1030 mode,
1031 record,
1032 CrpMode::Off,
1033 ReadTuning::default(),
1034 )
1035 };
1036 let output = render(&record, "map").expect("matching relay");
1037 assert!(output.is_cache_hit);
1038 assert_eq!(output.resolved_mode, "cross-agent-relay");
1039 assert!(output.content.contains("fn shared_context()"));
1040 assert_eq!(output.output_tokens, super::count_tokens(&output.content));
1041 for mode in ["signatures", "lines:1-2", "raw", "full"] {
1042 assert!(render(&record, mode).is_none());
1043 }
1044 record.relay_mode = None;
1045 assert!(render(&record, "map").is_none());
1046 record.relay_mode = Some("map:v2".into());
1047 record.relay_content = None;
1048 assert!(render(&record, "map").is_none());
1049 }
1050
1051 #[test]
1052 fn invalid_signing_profile_never_reads_legacy_delivery() {
1053 const CHILD: &str = "LEAN_CTX_TEST_INVALID_DELIVERY_PROFILE";
1054 if std::env::var_os(CHILD).is_none() {
1055 let status = std::process::Command::new(std::env::current_exe().expect("test executable"))
1056 .args(["--exact", "tools::ctx_read::dispatch::tests::invalid_signing_profile_never_reads_legacy_delivery", "--nocapture"])
1057 .env(CHILD, "1")
1058 .env("LEAN_CTX_DELIVERY_PROFILE", "{invalid")
1059 .status().expect("child test");
1060 assert!(status.success());
1061 return;
1062 }
1063 let _isolated = lean_ctx::core::data_dir::isolated_data_dir();
1064 let registry = lean_ctx::core::ocla::OclaRegistry::global();
1065 let path = "/legacy-delivery-profile-regression";
1066 registry
1067 .delivery_registry
1068 .record_delivery(lean_ctx::core::ocla::types::DeliveryEntry {
1069 access: None,
1070 blake3: [93; 12],
1071 path: path.into(),
1072 line_count: 1,
1073 token_count: 100,
1074 agent_id: "legacy-owner".into(),
1075 conversation_id: "legacy-conversation".into(),
1076 mtime: 1,
1077 relay_content: Some("must not leak".into()),
1078 relay_mode: Some("map".into()),
1079 });
1080 assert!(
1081 registry
1082 .delivery_registry
1083 .check_delivery(&[93; 12], 1, path, None, None)
1084 .is_some()
1085 );
1086 assert!(
1087 try_cross_agent_stub(
1088 path,
1089 "auto",
1090 [93; 12],
1091 1,
1092 CrpMode::Off,
1093 ReadTuning::default()
1094 )
1095 .is_none()
1096 );
1097 }
1098
1099 #[test]
1100 fn subagent_delivery_policy_keeps_cache_fresh_but_allows_delivery_by_default() {
1101 let delivery_for_subagents =
1102 lean_ctx::core::config::DeliveryConfig::default().delivery_for_subagents;
1103 assert!(
1104 delivery_for_subagents,
1105 "delivery must default to enabled for subagents"
1106 );
1107 let (cache_fresh, delivery_fresh) =
1108 effective_fresh_flags(false, false, true, delivery_for_subagents);
1109 assert!(cache_fresh, "subagent cache must remain isolated");
1110 assert!(
1111 !delivery_fresh,
1112 "default policy must allow a cross-agent delivery lookup"
1113 );
1114 }
1115
1116 #[test]
1117 fn subagent_delivery_policy_can_force_fresh_delivery() {
1118 let (cache_fresh, delivery_fresh) = effective_fresh_flags(false, false, true, false);
1119 assert!(cache_fresh);
1120 assert!(delivery_fresh, "disabled policy must bypass delivery stubs");
1121 }
1122
1123 #[test]
1124 fn line_count_matches_str_lines_semantics() {
1125 for sample in ["", "a", "a\n", "a\nb", "a\nb\n", "\n\n", "a\r\nb\r\n"] {
1126 assert_eq!(
1127 super::line_count_of(sample.as_bytes()) as usize,
1128 sample.lines().count(),
1129 "line count diverged for {sample:?}"
1130 );
1131 }
1132 }
1133
1134 #[test]
1135 fn fingerprint_carries_real_line_count() {
1136 let dir = tempfile::tempdir().unwrap();
1138 let file = dir.path().join("three.rs");
1139 std::fs::write(&file, "fn a() {}\nfn b() {}\nfn c() {}\n").unwrap();
1140 let fp = super::file_blake3_prefix(file.to_str().unwrap()).unwrap();
1141 assert_eq!(fp.line_count, 3);
1142 }
1143
1144 fn foreign_delivery(
1146 relay: Option<(&str, &str)>,
1147 ) -> (tempfile::TempDir, String, super::DeliveryFingerprint) {
1148 let dir = tempfile::tempdir().unwrap();
1149 let file = dir.path().join("foreign.rs");
1150 std::fs::write(&file, "pub fn foreign() {}\npub fn other() {}\n").unwrap();
1151 let path = file.to_string_lossy().to_string();
1152 let fp = super::file_blake3_prefix(&path).unwrap();
1153 lean_ctx::core::ocla::OclaRegistry::global()
1154 .delivery_registry
1155 .record_delivery(lean_ctx::core::ocla::types::DeliveryEntry {
1156 access: None,
1157 blake3: fp.hash,
1158 path: path.clone(),
1159 line_count: fp.line_count,
1160 token_count: 40,
1161 agent_id: "claude-foreign".into(),
1162 conversation_id: "conv-foreign".into(),
1163 mtime: fp.mtime,
1164 relay_content: relay.map(|(c, _)| c.to_string()),
1165 relay_mode: relay.map(|(_, m)| m.to_string()),
1166 });
1167 (dir, path, fp)
1168 }
1169
1170 fn cross_agent(path: &str, mode: &str, fp: super::DeliveryFingerprint) -> Option<ReadOutput> {
1171 try_cross_agent_stub(
1172 path,
1173 mode,
1174 fp.hash,
1175 fp.mtime,
1176 CrpMode::Off,
1177 ReadTuning::default(),
1178 )
1179 }
1180
1181 #[test]
1182 fn content_free_stub_is_never_served_for_another_agents_read() {
1183 let _isolated = lean_ctx::core::data_dir::isolated_data_dir();
1186 let (_dir, path, fp) = foreign_delivery(None);
1187 for mode in ["auto", "signatures", "map", "aggressive"] {
1188 assert!(
1189 cross_agent(&path, mode, fp).is_none(),
1190 "content-free cross-agent stub leaked for mode={mode}"
1191 );
1192 }
1193 }
1194
1195 #[test]
1196 fn relay_served_only_for_matching_mode() {
1197 let _isolated = lean_ctx::core::data_dir::isolated_data_dir();
1198 let relay = "pub fn foreign() {}";
1199 let (_dir, path, fp) = foreign_delivery(Some((relay, "map:v2")));
1200 let hit = cross_agent(&path, "map", fp).expect("map request must accept a map:v2 relay");
1201 assert!(hit.content.contains(relay), "{}", hit.content);
1202 assert!(hit.content.contains("· 2L]"), "{}", hit.content);
1203 assert!(
1204 cross_agent(&path, "signatures", fp).is_none(),
1205 "a signatures request must not be answered with a map relay"
1206 );
1207 assert!(cross_agent(&path, "lines:1-2", fp).is_none());
1208 }
1209
1210 #[test]
1211 fn warm_stub_hit_records_central_telemetry() {
1212 let dir = tempfile::tempdir().unwrap();
1213 let file = dir.path().join("telemetry-hit.rs");
1214 std::fs::write(&file, "fn telemetry_hit() {}\n").unwrap();
1215 let path = file.to_string_lossy();
1216 let mut cache = SessionCache::new();
1217 cache.store(&path, "fn telemetry_hit() {}\n");
1218 cache.mark_full_delivered(&path);
1219
1220 let metrics = lean_ctx::core::telemetry::global_metrics();
1221 let before = metrics.cache_hits.load(Ordering::Relaxed);
1222 let output = try_stub_hit_readonly_scoped(&cache, &path, None);
1223 let after = metrics.cache_hits.load(Ordering::Relaxed);
1224
1225 assert!(output.is_some(), "warm re-read must use the stub cache");
1226 assert!(
1227 after > before,
1228 "stub cache hit must increment central telemetry"
1229 );
1230 }
1231
1232 #[test]
1233 fn relay_does_not_poison_session_cache() {
1234 let mut cache = SessionCache::new();
1235 let path = "/tmp/test_relay_poison.rs";
1236 cache.store(path, "original content");
1237 assert_eq!(
1238 cache
1239 .get(path)
1240 .map(|e| e.compressed_outputs.contains_key("cross-agent-relay")),
1241 Some(false),
1242 "cross-agent-relay must not exist in session cache"
1243 );
1244 }
1245 #[test]
1246 fn is_terminal_poll_file_matches_cursor_terminal_paths() {
1247 assert!(is_terminal_poll_file(
1248 "/Users/me/.cursor/projects/Users-me-proj/terminals/12345.txt"
1249 ));
1250 assert!(is_terminal_poll_file(
1251 "/home/dev/.cursor/projects/foo/terminals/857272.txt"
1252 ));
1253 assert!(!is_terminal_poll_file("/Users/me/project/src/main.rs"));
1254 assert!(!is_terminal_poll_file(
1255 "/Users/me/.cursor/projects/foo/agent-transcripts/abc.jsonl"
1256 ));
1257 assert!(!is_terminal_poll_file("/Users/me/.cursor/terminals.log"));
1258 }
1259
1260 #[test]
1261 fn terminal_poll_file_stub_bypasses_conversation_gate() {
1262 let dir = tempfile::tempdir().unwrap();
1263 let terminals_dir = dir.path().join(".cursor").join("proj").join("terminals");
1264 std::fs::create_dir_all(&terminals_dir).unwrap();
1265 let file = terminals_dir.join("252028.txt");
1266 std::fs::write(&file, "---\npid: 1234\n---\nterminal output\n").unwrap();
1267 let path = file.to_string_lossy().to_string();
1268
1269 let mut cache = SessionCache::new();
1270 cache.store(&path, "---\npid: 1234\n---\nterminal output\n");
1271 cache.mark_full_delivered(&path);
1272
1273 let output = try_stub_hit_readonly_scoped(&cache, &path, Some("different-conv"));
1275 assert!(
1276 output.is_some(),
1277 "terminal poll file must bypass conversation gate and serve stub"
1278 );
1279 }
1280
1281 #[test]
1282 fn non_terminal_file_respects_conversation_gate() {
1283 if lean_ctx::test_env::run_with_conversation_scope(
1284 "tools::ctx_read::dispatch::tests::non_terminal_file_respects_conversation_gate",
1285 true,
1286 ) {
1287 return;
1288 }
1289 let dir = tempfile::tempdir().unwrap();
1290 let file = dir.path().join("main.rs");
1291 std::fs::write(&file, "fn main() {}\n").unwrap();
1292 let path = file.to_string_lossy().to_string();
1293
1294 let mut cache = SessionCache::new();
1295 cache.store(&path, "fn main() {}\n");
1296 cache.mark_full_delivered(&path);
1297 if let Some(entry) = cache.get_mut(&path) {
1298 entry.delivered_conversation = Some("conv-A".to_string());
1299 }
1300
1301 let output = try_stub_hit_readonly_scoped(&cache, &path, Some("conv-B"));
1303 assert!(
1304 output.is_none(),
1305 "regular file must respect conversation gate"
1306 );
1307 }
1308
1309 #[test]
1312 fn a_read_deposits_no_scent() {
1313 let _data = lean_ctx::core::data_dir::isolated_data_dir();
1314 let project = tempfile::tempdir().unwrap();
1315 let file = project.path().join("scent_marker.rs");
1316 std::fs::write(&file, "pub fn scent_marker() {}\n").unwrap();
1317 let root = project.path().to_str().unwrap();
1318 let mut cache = SessionCache::new();
1319 let out = lean_ctx::core::policy::runtime::REQUEST_PROJECT.sync_scope(
1320 std::cell::RefCell::new(Some(project.path().to_path_buf())),
1321 || super::handle(&mut cache, file.to_str().unwrap(), "full", CrpMode::Off),
1322 );
1323 assert!(out.contains("scent_marker"), "{out}");
1324 assert!(
1325 lean_ctx::core::scent_field::sync_block(root).is_empty(),
1326 "a read must not deposit a scent"
1327 );
1328 }
1329}