Add repairable INVALID_PROGRESS_PROTOCOL observations, independent PROGRESS_PROTOCOL_VIOLATED saturation, and controlled release paths. Archive the OpenSpec change after syncing main specs and devflow.
115 lines
5.3 KiB
Java
115 lines
5.3 KiB
Java
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);
|
|
}
|
|
}
|