package com.superbiz.agent.harness.progress; import com.superbiz.agent.harness.contract.EvidenceStatus; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; class DiagnosisProgressTrackerTest { @Test void gainedResetsConsecutiveNoGainAndConsumesPendingCallOnce() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(2); tracker.recordDuplicateScope(); tracker.recordCompleted(call("call-1", "scope-1"), EvidenceStatus.EVIDENCE_FOUND); tracker.applyPreviousObservation(new PreviousObservation("call-1", InformationGain.GAINED)); DiagnosisProgressSnapshotState state = tracker.snapshot(); assertEquals(0, state.consecutiveNoGain()); assertEquals(DiagnosisCollectionState.COLLECTING, state.collectionState()); assertThrows(IllegalArgumentException.class, () -> tracker.applyPreviousObservation( new PreviousObservation("call-1", InformationGain.NO_GAIN))); } @Test void rejectsMissingOrOutOfOrderEvaluation() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(2); tracker.recordCompleted(call("call-1", "scope-1"), EvidenceStatus.EVIDENCE_FOUND); assertThrows(IllegalArgumentException.class, () -> tracker.applyPreviousObservation(null)); assertThrows(IllegalArgumentException.class, () -> tracker.applyPreviousObservation( new PreviousObservation("call-other", InformationGain.GAINED))); assertEquals("call-1", tracker.snapshot().pendingToolCallId()); } @Test void deterministicNoEvidenceSaturatesAndStopInstructionIsClaimedOnce() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(2); tracker.recordCompleted(call("call-1", "scope-1"), EvidenceStatus.NO_EVIDENCE); tracker.recordCompleted(call("call-2", "scope-2"), EvidenceStatus.NO_EVIDENCE); DiagnosisProgressSnapshotState state = tracker.snapshot(); assertEquals(DiagnosisCollectionState.SATURATED, state.collectionState()); assertEquals(DiagnosisStopReason.INFORMATION_SATURATED, state.stopReason()); assertTrue(tracker.claimStopInstruction()); assertFalse(tracker.claimStopInstruction()); } @Test void onlySuccessfulCompletedScopesParticipateInDuplicateDetection() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(3); assertFalse(tracker.isDuplicate("lookup_knowledge", "scope-1")); tracker.recordCompleted(call("call-1", "scope-1"), EvidenceStatus.EVIDENCE_FOUND); assertTrue(tracker.isDuplicate("lookup_knowledge", "scope-1")); assertFalse(tracker.isDuplicate("query_logs", "scope-1")); assertFalse(tracker.isDuplicate("lookup_knowledge", "scope-2")); } @Test void progressProtocolViolationsAccumulateIndependentlyFromNoGain() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(3, 2); tracker.recordDuplicateScope(); DiagnosisProgressSnapshotState first = tracker.recordProgressProtocolViolation(); assertEquals(1, first.consecutiveProgressProtocolViolations()); assertEquals(1, first.consecutiveNoGain()); assertEquals(DiagnosisCollectionState.COLLECTING, first.collectionState()); DiagnosisProgressSnapshotState second = tracker.recordProgressProtocolViolation(); assertEquals(2, second.consecutiveProgressProtocolViolations()); assertEquals(1, second.consecutiveNoGain()); assertEquals(DiagnosisCollectionState.SATURATED, second.collectionState()); assertEquals(DiagnosisStopReason.PROGRESS_PROTOCOL_VIOLATED, second.stopReason()); assertTrue(tracker.claimStopInstruction()); assertFalse(tracker.claimStopInstruction()); } @Test void validEvaluationClearsProtocolViolationCount() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(2, 3); tracker.recordCompleted(call("call-1", "scope-1"), EvidenceStatus.EVIDENCE_FOUND); tracker.recordProgressProtocolViolation(); tracker.applyPreviousObservation(new PreviousObservation("call-1", InformationGain.GAINED)); DiagnosisProgressSnapshotState state = tracker.snapshot(); assertEquals(0, state.consecutiveProgressProtocolViolations()); assertEquals(0, state.consecutiveNoGain()); assertEquals(DiagnosisCollectionState.COLLECTING, state.collectionState()); } @Test void rejectsUnexpectedPreviousObservationWithTypedViolation() { DiagnosisProgressTracker tracker = new DiagnosisProgressTracker(2, 2); ProgressProtocolViolationException failure = assertThrows( ProgressProtocolViolationException.class, () -> tracker.applyPreviousObservation( new PreviousObservation("call-x", InformationGain.GAINED))); assertEquals(ProgressProtocolViolationType.UNEXPECTED_PREVIOUS_OBSERVATION, failure.violationType()); assertEquals("previous_observation", failure.missingField()); } private CompletedToolCall call(String id, String scope) { return new CompletedToolCall(id, "lookup_knowledge", scope); } }