1use crate::{
10 lint::lint_markdown_semantics,
11 pcp::{
12 CheckpointState, Contradiction, DosageResult, IssueType, LintIssue, LintResult,
13 PCPCheckpoint, PCPConfig, RecoveryResult, Severity, ValidationIssue, ValidationResult,
14 },
15 AbstractionLevel, Artifact, CellstateError, CellstateResult, EntityIdType, NoteId, Scope,
16 ScopeId, SummarizationPolicy, SummarizationPolicyId, SummarizationTrigger, ValidationError,
17};
18use chrono::Utc;
19use uuid::Uuid;
20
21pub type PCPRuntime = ContextValidator;
23
24#[derive(Debug, Clone)]
26pub struct ContextValidator {
27 config: PCPConfig,
28 checkpoints: Vec<PCPCheckpoint>,
29}
30
31impl ContextValidator {
32 pub fn new(config: PCPConfig) -> CellstateResult<Self> {
34 config.validate()?;
35 Ok(Self {
36 config,
37 checkpoints: Vec::new(),
38 })
39 }
40
41 pub fn config(&self) -> &PCPConfig {
43 &self.config
44 }
45}
46
47impl ContextValidator {
52 pub fn validate_context_integrity(
54 &self,
55 scope: &Scope,
56 artifacts: &[Artifact],
57 current_tokens: i32,
58 ) -> CellstateResult<ValidationResult> {
59 let mut result = ValidationResult::valid();
60 self.check_dosage_limits(&mut result, artifacts.len() as i32, current_tokens);
61 self.check_stale_scope(&mut result, scope);
62 for artifact in artifacts {
63 self.check_artifact_integrity(&mut result, artifact);
64 }
65 Ok(result)
66 }
67
68 fn check_dosage_limits(
69 &self,
70 result: &mut ValidationResult,
71 artifact_count: i32,
72 token_count: i32,
73 ) {
74 if token_count > self.config.dosage.max_tokens_per_scope {
75 result.add_issue(ValidationIssue {
76 severity: Severity::Warning,
77 issue_type: IssueType::DosageExceeded,
78 message: format!(
79 "Token count ({}) exceeds limit ({})",
80 token_count, self.config.dosage.max_tokens_per_scope
81 ),
82 entity_id: None,
83 });
84 }
85 if artifact_count > self.config.dosage.max_artifacts_per_scope {
86 result.add_issue(ValidationIssue {
87 severity: Severity::Warning,
88 issue_type: IssueType::DosageExceeded,
89 message: format!(
90 "Artifact count ({}) exceeds limit ({})",
91 artifact_count, self.config.dosage.max_artifacts_per_scope
92 ),
93 entity_id: None,
94 });
95 }
96 }
97
98 fn check_stale_scope(&self, result: &mut ValidationResult, scope: &Scope) {
99 let now = Utc::now();
100 let age = now.signed_duration_since(scope.created_at);
101 if age.num_hours() > self.config.staleness.stale_hours && scope.is_active {
102 result.add_issue(ValidationIssue {
103 severity: Severity::Warning,
104 issue_type: IssueType::StaleData,
105 message: format!(
106 "Scope {} is {} hours old and still active (threshold: {} hours)",
107 scope.scope_id,
108 age.num_hours(),
109 self.config.staleness.stale_hours
110 ),
111 entity_id: Some(scope.scope_id.as_uuid()),
112 });
113 }
114 }
115
116 fn check_artifact_integrity(&self, result: &mut ValidationResult, artifact: &Artifact) {
117 if self.config.grounding.require_artifact_backing && artifact.embedding.is_none() {
118 result.add_issue(ValidationIssue {
119 severity: Severity::Warning,
120 issue_type: IssueType::MissingReference,
121 message: format!(
122 "Artifact {} is missing embedding (required for grounding)",
123 artifact.artifact_id
124 ),
125 entity_id: Some(artifact.artifact_id.as_uuid()),
126 });
127 }
128 if let Some(confidence) = artifact.provenance.confidence {
129 if confidence < self.config.linting.min_confidence_threshold {
130 result.add_issue(ValidationIssue {
131 severity: Severity::Warning,
132 issue_type: IssueType::MissingReference,
133 message: format!(
134 "Artifact {} has low confidence ({})",
135 artifact.artifact_id, confidence
136 ),
137 entity_id: Some(artifact.artifact_id.as_uuid()),
138 });
139 }
140 }
141 }
142}
143
144impl ContextValidator {
149 pub fn detect_contradictions(
151 &self,
152 artifacts: &[Artifact],
153 ) -> CellstateResult<Vec<Contradiction>> {
154 let mut contradictions = Vec::new();
155 for i in 0..artifacts.len() {
156 for j in (i + 1)..artifacts.len() {
157 let artifact_a = &artifacts[i];
158 let artifact_b = &artifacts[j];
159 let (embedding_a, embedding_b) =
160 match (&artifact_a.embedding, &artifact_b.embedding) {
161 (Some(a), Some(b)) => (a, b),
162 _ => continue,
163 };
164 let similarity = match embedding_a.cosine_similarity(embedding_b) {
165 Ok(s) => s,
166 Err(_) => continue,
167 };
168 if similarity >= self.config.grounding.contradiction_threshold
169 && artifact_a.content != artifact_b.content
170 {
171 contradictions.push(Contradiction {
172 artifact_a: artifact_a.artifact_id,
173 artifact_b: artifact_b.artifact_id,
174 similarity_score: similarity,
175 description: format!(
176 "Artifacts have high similarity ({:.2}) but different content",
177 similarity
178 ),
179 });
180 }
181 }
182 }
183 Ok(contradictions)
184 }
185}
186
187impl ContextValidator {
192 pub fn apply_dosage_limits(
194 &self,
195 artifacts: &[Artifact],
196 current_tokens: i32,
197 ) -> CellstateResult<DosageResult> {
198 let mut result = DosageResult::within_limits();
199 if current_tokens > self.config.dosage.max_tokens_per_scope {
200 result.exceeded = true;
201 result.tokens_trimmed = current_tokens - self.config.dosage.max_tokens_per_scope;
202 result.add_warning(format!(
203 "Token limit exceeded: {} > {}. Need to trim {} tokens.",
204 current_tokens, self.config.dosage.max_tokens_per_scope, result.tokens_trimmed
205 ));
206 }
207 let artifact_count = artifacts.len() as i32;
208 if artifact_count > self.config.dosage.max_artifacts_per_scope {
209 result.exceeded = true;
210 let excess = artifact_count - self.config.dosage.max_artifacts_per_scope;
211 let start_prune = artifacts.len() - excess as usize;
212 for artifact in &artifacts[start_prune..] {
213 result.add_pruned(artifact.artifact_id);
214 }
215 result.add_warning(format!(
216 "Artifact limit exceeded: {} > {}. Pruning {} artifacts.",
217 artifact_count, self.config.dosage.max_artifacts_per_scope, excess
218 ));
219 }
220 Ok(result)
221 }
222
223 pub fn would_exceed_limits(
225 &self,
226 current_artifacts: i32,
227 current_tokens: i32,
228 additional_artifacts: i32,
229 additional_tokens: i32,
230 ) -> bool {
231 let new_artifacts = current_artifacts + additional_artifacts;
232 let new_tokens = current_tokens + additional_tokens;
233 new_artifacts > self.config.dosage.max_artifacts_per_scope
234 || new_tokens > self.config.dosage.max_tokens_per_scope
235 }
236}
237
238impl ContextValidator {
243 pub fn lint_artifact(
245 &self,
246 artifact: &Artifact,
247 existing_artifacts: &[Artifact],
248 ) -> CellstateResult<LintResult> {
249 let mut result = LintResult::passed();
250 self.check_artifact_size(&mut result, artifact);
251 self.check_artifact_duplicates(&mut result, artifact, existing_artifacts);
252 self.check_artifact_embedding(&mut result, artifact);
253 self.check_artifact_confidence(&mut result, artifact);
254 self.check_artifact_semantics(&mut result, artifact);
255 Ok(result)
256 }
257
258 fn check_artifact_size(&self, result: &mut LintResult, artifact: &Artifact) {
259 let max_size = self.config.linting.max_artifact_size;
260 if artifact.content.len() > max_size {
261 result.add_issue(LintIssue {
262 issue_type: crate::pcp::LintIssueType::TooLarge,
263 message: format!(
264 "Artifact content size ({} bytes) exceeds maximum ({} bytes)",
265 artifact.content.len(),
266 max_size
267 ),
268 artifact_id: artifact.artifact_id,
269 });
270 }
271 }
272
273 fn check_artifact_duplicates(
274 &self,
275 result: &mut LintResult,
276 artifact: &Artifact,
277 existing_artifacts: &[Artifact],
278 ) {
279 for existing in existing_artifacts {
280 if existing.artifact_id == artifact.artifact_id {
281 continue;
282 }
283 if existing.content_hash == artifact.content_hash {
284 result.add_issue(LintIssue {
285 issue_type: crate::pcp::LintIssueType::Duplicate,
286 message: format!(
287 "Artifact is a duplicate of existing artifact {}",
288 existing.artifact_id
289 ),
290 artifact_id: artifact.artifact_id,
291 });
292 break;
293 }
294 }
295 }
296
297 fn check_artifact_embedding(&self, result: &mut LintResult, artifact: &Artifact) {
298 if self.config.grounding.require_artifact_backing && artifact.embedding.is_none() {
299 result.add_issue(LintIssue {
300 issue_type: crate::pcp::LintIssueType::MissingEmbedding,
301 message: "Artifact is missing embedding (required for grounding)".to_string(),
302 artifact_id: artifact.artifact_id,
303 });
304 }
305 }
306
307 fn check_artifact_confidence(&self, result: &mut LintResult, artifact: &Artifact) {
308 let min_threshold = self.config.linting.min_confidence_threshold;
309 if let Some(confidence) = artifact.provenance.confidence {
310 if confidence < min_threshold {
311 result.add_issue(LintIssue {
312 issue_type: crate::pcp::LintIssueType::LowConfidence,
313 message: format!(
314 "Artifact confidence ({:.2}) is below threshold ({:.2})",
315 confidence, min_threshold
316 ),
317 artifact_id: artifact.artifact_id,
318 });
319 }
320 }
321 }
322
323 fn check_artifact_semantics(&self, result: &mut LintResult, artifact: &Artifact) {
324 for issue in lint_markdown_semantics(&artifact.content) {
325 result.add_issue(LintIssue {
326 issue_type: issue.issue_type,
327 message: format!("line {}: {}", issue.line, issue.message),
328 artifact_id: artifact.artifact_id,
329 });
330 }
331 }
332
333 pub fn lint_artifacts(&self, artifacts: &[Artifact]) -> CellstateResult<LintResult> {
335 let mut combined_result = LintResult::passed();
336 for artifact in artifacts {
337 let result = self.lint_artifact(artifact, artifacts)?;
338 for issue in result.issues {
339 combined_result.add_issue(issue);
340 }
341 }
342 Ok(combined_result)
343 }
344}
345
346impl ContextValidator {
351 pub fn create_checkpoint(
353 &mut self,
354 scope: &Scope,
355 artifacts: &[Artifact],
356 note_ids: &[NoteId],
357 ) -> CellstateResult<PCPCheckpoint> {
358 if !self.config.recovery.enabled {
359 return Err(CellstateError::Validation(
360 ValidationError::ConstraintViolation {
361 constraint: "recovery.enabled".to_string(),
362 reason: "Recovery is disabled in configuration".to_string(),
363 },
364 ));
365 }
366 let context_snapshot = serde_json::to_vec(scope).map_err(|e| {
367 CellstateError::Validation(ValidationError::InvalidValue {
368 field: "scope".to_string(),
369 reason: format!("Failed to serialize scope: {}", e),
370 })
371 })?;
372 let artifact_ids = artifacts.iter().map(|a| a.artifact_id).collect();
373 let state = CheckpointState {
374 context_snapshot,
375 artifact_ids,
376 note_ids: note_ids.to_vec(),
377 };
378 let checkpoint = PCPCheckpoint {
379 checkpoint_id: Uuid::now_v7(),
380 scope_id: scope.scope_id,
381 state,
382 created_at: Utc::now(),
383 };
384 self.checkpoints.push(checkpoint.clone());
385 self.enforce_checkpoint_limit();
386 Ok(checkpoint)
387 }
388
389 fn enforce_checkpoint_limit(&mut self) {
390 let max = self.config.recovery.max_checkpoints as usize;
391 if self.checkpoints.len() > max {
392 self.checkpoints
393 .sort_by_key(|checkpoint| checkpoint.created_at);
394 let excess = self.checkpoints.len() - max;
395 self.checkpoints.drain(0..excess);
396 }
397 }
398
399 pub fn recover_from_checkpoint(
401 &self,
402 checkpoint: &PCPCheckpoint,
403 ) -> CellstateResult<RecoveryResult> {
404 if !self.config.recovery.enabled {
405 return Ok(RecoveryResult::failure(vec![
406 "Recovery is disabled in configuration".to_string(),
407 ]));
408 }
409 let scope: Scope = match serde_json::from_slice(&checkpoint.state.context_snapshot) {
410 Ok(s) => s,
411 Err(e) => {
412 return Ok(RecoveryResult::failure(vec![format!(
413 "Failed to deserialize scope: {}",
414 e
415 )]));
416 }
417 };
418 Ok(RecoveryResult::success(scope))
419 }
420
421 pub fn get_latest_checkpoint(&self, scope_id: ScopeId) -> Option<&PCPCheckpoint> {
423 self.checkpoints
424 .iter()
425 .filter(|c| c.scope_id == scope_id)
426 .max_by_key(|c| c.created_at)
427 }
428
429 pub fn get_checkpoints_for_scope(&self, scope_id: ScopeId) -> Vec<&PCPCheckpoint> {
431 self.checkpoints
432 .iter()
433 .filter(|c| c.scope_id == scope_id)
434 .collect()
435 }
436
437 pub fn delete_checkpoint(&mut self, checkpoint_id: Uuid) -> bool {
439 let initial_len = self.checkpoints.len();
440 self.checkpoints
441 .retain(|c| c.checkpoint_id != checkpoint_id);
442 self.checkpoints.len() < initial_len
443 }
444
445 pub fn clear_checkpoints_for_scope(&mut self, scope_id: ScopeId) -> usize {
447 let initial_len = self.checkpoints.len();
448 self.checkpoints.retain(|c| c.scope_id != scope_id);
449 initial_len - self.checkpoints.len()
450 }
451}
452
453impl ContextValidator {
458 pub fn check_summarization_triggers(
460 &self,
461 scope: &Scope,
462 turn_count: i32,
463 artifact_count: i32,
464 policies: &[SummarizationPolicy],
465 ) -> CellstateResult<Vec<(SummarizationPolicyId, SummarizationTrigger)>> {
466 let mut triggered = Vec::new();
467 let token_usage_percent = if scope.token_budget > 0 {
468 ((scope.tokens_used as f32 / scope.token_budget as f32) * 100.0) as u8
469 } else {
470 0
471 };
472 for policy in policies {
473 for trigger in &policy.triggers {
474 let should_fire = match trigger {
475 SummarizationTrigger::DosageThreshold { percent } => {
476 token_usage_percent >= *percent
477 }
478 SummarizationTrigger::ScopeClose => !scope.is_active,
479 SummarizationTrigger::TurnCount { count } => {
480 turn_count >= *count && turn_count % *count == 0
481 }
482 SummarizationTrigger::ArtifactCount { count } => {
483 artifact_count >= *count && artifact_count % *count == 0
484 }
485 SummarizationTrigger::Manual => false,
486 };
487 if should_fire {
488 triggered.push((policy.summarization_policy_id, *trigger));
489 }
490 }
491 }
492 Ok(triggered)
493 }
494
495 pub fn get_abstraction_transition(
497 &self,
498 policy: &SummarizationPolicy,
499 ) -> (AbstractionLevel, AbstractionLevel) {
500 (policy.source_level, policy.target_level)
501 }
502
503 pub fn validate_abstraction_transition(
505 &self,
506 source: AbstractionLevel,
507 target: AbstractionLevel,
508 ) -> bool {
509 matches!(
510 (source, target),
511 (AbstractionLevel::Raw, AbstractionLevel::Summary)
512 | (AbstractionLevel::Raw, AbstractionLevel::Principle)
513 | (AbstractionLevel::Summary, AbstractionLevel::Principle)
514 )
515 }
516}
517
518#[cfg(test)]
519mod tests {
520 use super::*;
521 use crate::pcp::*;
522 use crate::{
523 ArtifactType, ConflictResolution, ExtractionMethod, Provenance, ScopeId, TrajectoryId, TTL,
524 };
525
526 fn make_test_pcp_config() -> PCPConfig {
527 PCPConfig {
528 context_dag: ContextDagConfig {
529 max_depth: 10,
530 prune_strategy: PruneStrategy::OldestFirst,
531 },
532 recovery: RecoveryConfig {
533 enabled: true,
534 frequency: RecoveryFrequency::OnScopeClose,
535 max_checkpoints: 5,
536 },
537 dosage: DosageConfig {
538 max_tokens_per_scope: 8000,
539 max_artifacts_per_scope: 100,
540 max_notes_per_trajectory: 500,
541 },
542 anti_sprawl: AntiSprawlConfig {
543 max_trajectory_depth: 5,
544 max_concurrent_scopes: 10,
545 },
546 grounding: GroundingConfig {
547 require_artifact_backing: false,
548 contradiction_threshold: 0.85,
549 conflict_resolution: ConflictResolution::LastWriteWins,
550 },
551 linting: LintingConfig {
552 max_artifact_size: 1024 * 1024,
553 min_confidence_threshold: 0.3,
554 },
555 staleness: StalenessConfig {
556 stale_hours: 24 * 30,
557 },
558 }
559 }
560
561 fn make_test_scope() -> Scope {
562 Scope {
563 scope_id: ScopeId::now_v7(),
564 trajectory_id: TrajectoryId::now_v7(),
565 parent_scope_id: None,
566 name: "Test Scope".to_string(),
567 purpose: Some("Testing".to_string()),
568 is_active: true,
569 created_at: Utc::now(),
570 closed_at: None,
571 checkpoint: None,
572 token_budget: 8000,
573 tokens_used: 0,
574 metadata: None,
575 }
576 }
577
578 fn make_test_artifact(content: &str) -> Artifact {
579 Artifact {
580 artifact_id: crate::ArtifactId::now_v7(),
581 trajectory_id: TrajectoryId::now_v7(),
582 scope_id: ScopeId::now_v7(),
583 artifact_type: ArtifactType::Fact,
584 name: "Test Artifact".to_string(),
585 content: content.to_string(),
586 content_hash: crate::compute_content_hash(content.as_bytes()),
587 embedding: None,
588 provenance: Provenance {
589 source_turn: 1,
590 extraction_method: ExtractionMethod::Explicit,
591 confidence: Some(0.9),
592 },
593 ttl: TTL::Persistent,
594 created_at: Utc::now(),
595 updated_at: Utc::now(),
596 superseded_by: None,
597 metadata: None,
598 }
599 }
600
601 #[test]
602 fn test_context_validator_new() {
603 let config = make_test_pcp_config();
604 let runtime =
605 ContextValidator::new(config).expect("ContextValidator creation should succeed");
606 assert!(runtime.config().recovery.enabled);
607 }
608
609 #[test]
610 fn test_validate_context_integrity() {
611 let config = make_test_pcp_config();
612 let runtime =
613 ContextValidator::new(config).expect("ContextValidator creation should succeed");
614 let scope = make_test_scope();
615 let artifacts = vec![make_test_artifact("test content")];
616 let result = runtime
617 .validate_context_integrity(&scope, &artifacts, 1000)
618 .expect("validate_context_integrity should succeed");
619 assert!(result.valid);
620 }
621
622 #[test]
623 fn test_validate_context_dosage_exceeded() {
624 let mut config = make_test_pcp_config();
625 config.dosage.max_tokens_per_scope = 100;
626 let runtime =
627 ContextValidator::new(config).expect("ContextValidator creation should succeed");
628 let scope = make_test_scope();
629 let result = runtime
630 .validate_context_integrity(&scope, &[], 1000)
631 .expect("validate_context_integrity should succeed");
632 assert!(result
633 .issues
634 .iter()
635 .any(|i| i.issue_type == IssueType::DosageExceeded));
636 }
637
638 #[test]
639 fn test_create_checkpoint() {
640 let config = make_test_pcp_config();
641 let mut runtime =
642 ContextValidator::new(config).expect("ContextValidator creation should succeed");
643 let scope = make_test_scope();
644 let artifacts = vec![make_test_artifact("test")];
645 let note_ids = vec![crate::NoteId::now_v7()];
646 let checkpoint = runtime
647 .create_checkpoint(&scope, &artifacts, ¬e_ids)
648 .expect("create_checkpoint should succeed");
649 assert_eq!(checkpoint.scope_id, scope.scope_id);
650 assert_eq!(checkpoint.state.artifact_ids.len(), 1);
651 assert_eq!(checkpoint.state.note_ids.len(), 1);
652 }
653
654 #[test]
655 fn test_recover_from_checkpoint() {
656 let config = make_test_pcp_config();
657 let mut runtime =
658 ContextValidator::new(config).expect("ContextValidator creation should succeed");
659 let scope = make_test_scope();
660 let checkpoint = runtime
661 .create_checkpoint(&scope, &[], &[])
662 .expect("create_checkpoint should succeed");
663 let result = runtime
664 .recover_from_checkpoint(&checkpoint)
665 .expect("recover_from_checkpoint should succeed");
666 assert!(result.success);
667 assert_eq!(
668 result.recovered_scope.expect("recovered_scope").scope_id,
669 scope.scope_id
670 );
671 }
672
673 #[test]
674 fn test_lint_artifact_passes() {
675 let config = make_test_pcp_config();
676 let runtime =
677 ContextValidator::new(config).expect("ContextValidator creation should succeed");
678 let artifact = make_test_artifact("test content");
679 let result = runtime
680 .lint_artifact(&artifact, &[])
681 .expect("lint_artifact should succeed");
682 assert!(result.passed);
683 }
684
685 #[test]
686 fn test_lint_artifact_duplicate() {
687 let config = make_test_pcp_config();
688 let runtime =
689 ContextValidator::new(config).expect("ContextValidator creation should succeed");
690 let artifact1 = make_test_artifact("same content");
691 let mut artifact2 = make_test_artifact("same content");
692 artifact2.artifact_id = crate::ArtifactId::now_v7();
693 let result = runtime
694 .lint_artifact(&artifact2, &[artifact1])
695 .expect("lint_artifact should succeed");
696 assert!(!result.passed);
697 assert!(result
698 .issues
699 .iter()
700 .any(|i| i.issue_type == LintIssueType::Duplicate));
701 }
702
703 #[test]
704 fn test_lint_artifact_reserved_character_leak() {
705 let config = make_test_pcp_config();
706 let runtime =
707 ContextValidator::new(config).expect("ContextValidator creation should succeed");
708 let artifact = make_test_artifact("Please import @my_skill before execution.");
709 let result = runtime
710 .lint_artifact(&artifact, &[])
711 .expect("lint_artifact should succeed");
712 assert!(!result.passed);
713 assert!(result
714 .issues
715 .iter()
716 .any(|i| i.issue_type == LintIssueType::ReservedCharacterLeak));
717 }
718
719 #[test]
720 fn test_lint_artifact_unterminated_fence() {
721 let config = make_test_pcp_config();
722 let runtime =
723 ContextValidator::new(config).expect("ContextValidator creation should succeed");
724 let artifact = make_test_artifact("```markdown\n# title\nstill open");
725 let result = runtime
726 .lint_artifact(&artifact, &[])
727 .expect("lint_artifact should succeed");
728 assert!(!result.passed);
729 assert!(result
730 .issues
731 .iter()
732 .any(|i| i.issue_type == LintIssueType::SyntaxError));
733 }
734
735 #[test]
736 fn test_get_latest_checkpoint_none() {
737 let config = make_test_pcp_config();
738 let runtime = ContextValidator::new(config).unwrap();
739 assert!(runtime.get_latest_checkpoint(ScopeId::now_v7()).is_none());
740 }
741
742 #[test]
743 fn test_get_checkpoints_for_scope_empty() {
744 let config = make_test_pcp_config();
745 let runtime = ContextValidator::new(config).unwrap();
746 assert!(runtime
747 .get_checkpoints_for_scope(ScopeId::now_v7())
748 .is_empty());
749 }
750
751 #[test]
752 fn test_delete_checkpoint() {
753 let config = make_test_pcp_config();
754 let mut runtime = ContextValidator::new(config).unwrap();
755 let scope = make_test_scope();
756 let cp = runtime.create_checkpoint(&scope, &[], &[]).unwrap();
757 assert!(runtime.delete_checkpoint(cp.checkpoint_id));
758 assert!(!runtime.delete_checkpoint(cp.checkpoint_id));
759 }
760
761 #[test]
762 fn test_clear_checkpoints_for_scope() {
763 let config = make_test_pcp_config();
764 let mut runtime = ContextValidator::new(config).unwrap();
765 let scope = make_test_scope();
766 runtime.create_checkpoint(&scope, &[], &[]).unwrap();
767 runtime.create_checkpoint(&scope, &[], &[]).unwrap();
768 let cleared = runtime.clear_checkpoints_for_scope(scope.scope_id);
769 assert_eq!(cleared, 2);
770 assert!(runtime.get_checkpoints_for_scope(scope.scope_id).is_empty());
771 }
772
773 #[test]
774 fn test_checkpoint_limit_enforcement() {
775 let mut config = make_test_pcp_config();
776 config.recovery.max_checkpoints = 2;
777 let mut runtime = ContextValidator::new(config).unwrap();
778 let scope = make_test_scope();
779 runtime.create_checkpoint(&scope, &[], &[]).unwrap();
780 runtime.create_checkpoint(&scope, &[], &[]).unwrap();
781 runtime.create_checkpoint(&scope, &[], &[]).unwrap();
782 let cps = runtime.get_checkpoints_for_scope(scope.scope_id);
783 assert!(cps.len() <= 2);
784 }
785
786 #[test]
788 fn test_pcp_runtime_alias() {
789 let config = make_test_pcp_config();
790 let _runtime: PCPRuntime = PCPRuntime::new(config).unwrap();
791 }
792}