2222from unplug .core .policy import policy_from_request
2323from unplug .core .privacy import NullPrivacyFilter , PrivacyFilterService
2424from unplug .core .privacy .secrets import SecretsRegistry , SecretsSanitizer
25- from unplug .core .runtime .cache import SafePrefixState , ScanCache , merge_suffix_result
25+ from unplug .core .runtime .cache import (
26+ SafePrefixState ,
27+ ScanCache ,
28+ effective_prefix_skip ,
29+ merge_suffix_result ,
30+ )
2631from unplug .core .runtime .logging import correlation_scope , get_logger
2732from unplug .core .runtime .model_runtime import (
2833 load_active_model_provider ,
@@ -418,6 +423,7 @@ def stream_scanner(
418423 self ,
419424 source = source ,
420425 scan_every_chars = scan_every_chars ,
426+ overlap_chars = self ._config .cache .prefix_overlap_chars ,
421427 document_id = document_id ,
422428 )
423429
@@ -484,6 +490,37 @@ def _request_context(self, request: ScanRequest, *, isolated: bool) -> Execution
484490 def _model_version_for_cache (self ) -> str :
485491 return self ._model_cache_version
486492
493+ def _cache_policy_fingerprint (self , request : ScanRequest ) -> str :
494+ """Fingerprint policy + scanner set so cache entries are not reused across configs."""
495+ policy = self ._config .policy
496+ scanners = request .scanners if request .scanners is not None else list (self ._config .scanners )
497+ parts = [
498+ "," .join (scanners ),
499+ str (
500+ request .block_threshold
501+ if request .block_threshold is not None
502+ else policy .block_threshold
503+ ),
504+ str (
505+ request .redact_threshold
506+ if request .redact_threshold is not None
507+ else policy .redact_threshold
508+ ),
509+ str (
510+ request .review_threshold
511+ if request .review_threshold is not None
512+ else policy .review_threshold
513+ ),
514+ str (
515+ request .block_coverage_ratio
516+ if request .block_coverage_ratio is not None
517+ else policy .block_coverage_ratio
518+ ),
519+ str (request .redact ),
520+ str (request .redaction_mode .value if request .redaction_mode is not None else "" ),
521+ ]
522+ return "|" .join (parts )
523+
487524 def _run_input_with_cache (self , request : ScanRequest , ctx : ExecutionContext ) -> ScanResult :
488525 cache = ctx .scan_cache
489526 if cache is None or not self ._config .cache .enabled :
@@ -498,8 +535,10 @@ def _run_input_with_cache(self, request: ScanRequest, ctx: ExecutionContext) ->
498535 document_id = request .document_id ,
499536 normalizer_version = NORMALIZER_VERSION ,
500537 model_version = self ._model_version_for_cache (),
538+ source = str (request .source ),
539+ policy_fingerprint = self ._cache_policy_fingerprint (request ),
501540 )
502- hit = cache .get_chunk (parts . full_hash )
541+ hit = cache .get_chunk_for_parts (parts )
503542 if hit is not None :
504543 return hit
505544
@@ -508,13 +547,20 @@ def _run_input_with_cache(self, request: ScanRequest, ctx: ExecutionContext) ->
508547 if prefix_state is not None and prefix_state .verify (request .text ):
509548 prefix_len = prefix_state .prefix_len
510549
550+ scan_start = 0
511551 if 0 < prefix_len < len (request .text ):
552+ scan_start = effective_prefix_skip (
553+ prefix_len ,
554+ self ._config .cache .prefix_overlap_chars ,
555+ )
556+
557+ if 0 < scan_start < len (request .text ):
512558 suffix_result = self ._input_pipeline .run (
513- request .text [prefix_len :],
559+ request .text [scan_start :],
514560 source = request .source ,
515561 context = ctx ,
516562 )
517- result = merge_suffix_result (suffix_result , prefix_len )
563+ result = merge_suffix_result (suffix_result , scan_start )
518564 else :
519565 result = self ._input_pipeline .run (
520566 request .text ,
@@ -530,7 +576,7 @@ def _run_input_with_cache(self, request: ScanRequest, ctx: ExecutionContext) ->
530576 parts ,
531577 SafePrefixState .from_text (request .text , len (request .text )),
532578 )
533- cache .set_chunk (parts . full_hash , result )
579+ cache .set_chunk_for_parts (parts , result )
534580 return result
535581
536582 def scan_output (self , text : str | TaintedText ) -> ScanResult :
0 commit comments