204 lines
8.9 KiB
Java
204 lines
8.9 KiB
Java
package com.superbiz.agent.harness.audit;
|
|
|
|
import com.superbiz.agent.harness.contract.DiagnosisDraft;
|
|
import com.superbiz.agent.harness.contract.FallbackType;
|
|
import com.superbiz.agent.harness.contract.IntentType;
|
|
import com.superbiz.agent.harness.contract.ReleaseOutcome;
|
|
import com.superbiz.agent.harness.contract.SemanticVerdict;
|
|
import com.superbiz.agent.harness.core.RunContext;
|
|
import com.superbiz.agent.harness.guard.evidence.EvidenceGuardResult;
|
|
import com.superbiz.agent.harness.guard.evidence.EvidenceViolation;
|
|
import com.superbiz.agent.harness.retry.RetryAttempt;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.LinkedHashMap;
|
|
import java.util.LinkedHashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.regex.Pattern;
|
|
|
|
public final class TraceAuditEvents {
|
|
|
|
private static final Pattern SAFE_TOOL_CALL_ID =
|
|
Pattern.compile("[A-Za-z0-9][A-Za-z0-9._-]{0,127}");
|
|
|
|
private TraceAuditEvents() {
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent runStarted(RunContext context) {
|
|
return event(context, TracePhase.RUN, TraceEventType.RUN_STARTED,
|
|
TraceEventStatus.STARTED, null, null, Map.of());
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent runFinished(
|
|
RunContext context, IntentType intent, ReleaseOutcome outcome, int durationMs) {
|
|
Map<String, Object> details = new LinkedHashMap<>();
|
|
if (intent != null) {
|
|
details.put("intent", intent.name());
|
|
}
|
|
details.put("release_outcome", outcome.name());
|
|
return event(context, TracePhase.RUN, TraceEventType.RUN_FINISHED,
|
|
terminalStatus(outcome), null, durationMs, details);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent routingAttempt(
|
|
RunContext context, RetryAttempt attempt) {
|
|
return retry(context, TracePhase.ROUTING, TraceEventType.ROUTING_ATTEMPT, attempt);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent routingDecision(
|
|
RunContext context, IntentType intent) {
|
|
return event(context, TracePhase.ROUTING, TraceEventType.ROUTING_DECISION,
|
|
TraceEventStatus.SUCCEEDED, null, null, Map.of("intent", intent.name()));
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent agentModelStep(
|
|
String sessionId, String runId, String agentName, int stepIndex,
|
|
int durationMs, Map<String, Object> input, Map<String, Object> output) {
|
|
Map<String, Object> details = new LinkedHashMap<>();
|
|
details.put("agent_name", agentName);
|
|
details.put("step_index", stepIndex);
|
|
details.put("input", input);
|
|
details.put("output", output);
|
|
return new DiagnosisTraceAuditEvent(
|
|
sessionId, runId, TracePhase.AGENT, TraceEventType.AGENT_MODEL_STEP,
|
|
TraceEventStatus.SUCCEEDED, stepIndex + 1, durationMs, details);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent toolInvocation(ToolInvocationAuditEvent tool) {
|
|
Map<String, Object> details = new LinkedHashMap<>();
|
|
details.put("tool_call_id", tool.toolCallId());
|
|
details.put("tool_name", tool.toolName());
|
|
details.put("invocation_status", tool.status().name());
|
|
details.put("evidence_status", tool.evidenceStatus().name());
|
|
details.put("request_bytes", tool.requestBytes());
|
|
details.put("agent_result_bytes", tool.agentResultBytes());
|
|
if (tool.errorCode() != null) {
|
|
details.put("error_code", tool.errorCode());
|
|
}
|
|
return new DiagnosisTraceAuditEvent(
|
|
tool.sessionId(), tool.runId(), TracePhase.TOOL, TraceEventType.TOOL_INVOCATION,
|
|
tool.status() == com.superbiz.agent.harness.contract.InvocationStatus.READY
|
|
? TraceEventStatus.READY : TraceEventStatus.ERROR,
|
|
null, tool.durationMs(), details);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent evidenceValidation(
|
|
RunContext context, TraceEventType type,
|
|
EvidenceGuardResult result, DiagnosisDraft draft) {
|
|
Map<String, Object> details = new LinkedHashMap<>();
|
|
details.put("violation_count", result.violations().size());
|
|
details.put("violations", violations(result.violations()));
|
|
ToolReferences references = toolReferences(draft);
|
|
details.put("referenced_tool_call_ids", references.safeIds());
|
|
details.put("invalid_tool_reference_count", references.invalidCount());
|
|
result.verifiedSnapshot().ifPresent(snapshot -> {
|
|
details.put("verified_analysis_count", snapshot.analyses().size());
|
|
details.put("verified_source_count", snapshot.verifiedSources().size());
|
|
});
|
|
return event(context, TracePhase.EVIDENCE, type,
|
|
result.valid() ? TraceEventStatus.PASSED : TraceEventStatus.REJECTED,
|
|
null, null, details);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent evidenceRepairAttempt(
|
|
RunContext context, RetryAttempt attempt) {
|
|
return retry(context, TracePhase.EVIDENCE,
|
|
TraceEventType.EVIDENCE_REPAIR_ATTEMPT, attempt);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent semanticAttempt(
|
|
RunContext context, RetryAttempt attempt) {
|
|
return retry(context, TracePhase.SEMANTIC,
|
|
TraceEventType.SEMANTIC_GUARD_ATTEMPT, attempt);
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent semanticDecision(
|
|
RunContext context, SemanticVerdict verdict) {
|
|
TraceEventStatus status = verdict == SemanticVerdict.SUPPORTED
|
|
? TraceEventStatus.SUPPORTED : TraceEventStatus.UNSUPPORTED;
|
|
return event(context, TracePhase.SEMANTIC, TraceEventType.SEMANTIC_GUARD_DECISION,
|
|
status, null, null, Map.of("verdict", verdict.name()));
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent semanticUnavailable(RunContext context) {
|
|
return event(context, TracePhase.SEMANTIC, TraceEventType.SEMANTIC_GUARD_DECISION,
|
|
TraceEventStatus.UNAVAILABLE, null, null, Map.of());
|
|
}
|
|
|
|
public static DiagnosisTraceAuditEvent releaseDecision(
|
|
RunContext context, ReleaseOutcome outcome, FallbackType fallbackType) {
|
|
Map<String, Object> details = new LinkedHashMap<>();
|
|
details.put("release_outcome", outcome.name());
|
|
if (fallbackType != null) {
|
|
details.put("fallback_type", fallbackType.name());
|
|
}
|
|
return event(context, TracePhase.RELEASE, TraceEventType.RELEASE_DECISION,
|
|
terminalStatus(outcome), null, null, details);
|
|
}
|
|
|
|
private static DiagnosisTraceAuditEvent retry(
|
|
RunContext context, TracePhase phase, TraceEventType type, RetryAttempt attempt) {
|
|
Map<String, Object> details = new LinkedHashMap<>();
|
|
if (attempt.failure() != null) {
|
|
details.put("failure", attempt.failure().name());
|
|
}
|
|
return event(context, phase, type,
|
|
attempt.success() ? TraceEventStatus.SUCCEEDED : TraceEventStatus.FAILED,
|
|
attempt.attemptNumber(), null, details);
|
|
}
|
|
|
|
private static DiagnosisTraceAuditEvent event(
|
|
RunContext context, TracePhase phase, TraceEventType type, TraceEventStatus status,
|
|
Integer attemptNo, Integer durationMs, Map<String, Object> details) {
|
|
return new DiagnosisTraceAuditEvent(
|
|
context.sessionId(), context.runId(), phase, type, status,
|
|
attemptNo, durationMs, details);
|
|
}
|
|
|
|
private static TraceEventStatus terminalStatus(ReleaseOutcome outcome) {
|
|
return switch (outcome) {
|
|
case SUCCESS -> TraceEventStatus.SUCCEEDED;
|
|
case FALLBACK -> TraceEventStatus.FALLBACK;
|
|
case FAILED -> TraceEventStatus.FAILED;
|
|
case CANCELLED -> TraceEventStatus.CANCELLED;
|
|
};
|
|
}
|
|
|
|
private static List<Map<String, String>> violations(List<EvidenceViolation> violations) {
|
|
List<Map<String, String>> result = new ArrayList<>();
|
|
for (EvidenceViolation violation : violations) {
|
|
Map<String, String> item = new LinkedHashMap<>();
|
|
item.put("code", violation.code().name());
|
|
item.put("target", violation.target());
|
|
result.add(Map.copyOf(item));
|
|
}
|
|
return List.copyOf(result);
|
|
}
|
|
|
|
private static ToolReferences toolReferences(DiagnosisDraft draft) {
|
|
if (draft == null) {
|
|
return new ToolReferences(List.of(), 0);
|
|
}
|
|
Set<String> safeIds = new LinkedHashSet<>();
|
|
int invalid = 0;
|
|
for (DiagnosisDraft.AnalysisItem item : draft.analysis()) {
|
|
if (item == null) {
|
|
continue;
|
|
}
|
|
for (String id : item.toolCallIds()) {
|
|
if (id != null && SAFE_TOOL_CALL_ID.matcher(id).matches()) {
|
|
safeIds.add(id);
|
|
} else {
|
|
invalid++;
|
|
}
|
|
}
|
|
}
|
|
return new ToolReferences(List.copyOf(safeIds), invalid);
|
|
}
|
|
|
|
private record ToolReferences(List<String> safeIds, int invalidCount) {
|
|
}
|
|
}
|