mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
feat(analytics): add structured run failure evidence (#7534)
* feat(analytics): add structured run failure evidence * fix(daemon): preserve run failure evidence boundaries Generated-By: looper 0.12.0 (runner=fixer, agent=codex) * fix(daemon): correct structured failure attribution Generated-By: looper 0.12.0 (runner=fixer, agent=codex) * fix(daemon): preserve specific failure evidence Generated-By: looper 0.12.0 (runner=fixer, agent=codex)
This commit is contained in:
@@ -2,8 +2,13 @@ import type {
|
||||
TrackingRunCancelOrigin,
|
||||
TrackingRunFailureCategory,
|
||||
TrackingRunFailureDetail,
|
||||
TrackingRunFailureDomain,
|
||||
TrackingRunFailureMechanism,
|
||||
TrackingRunFailureStage,
|
||||
TrackingRunFailureUserAction,
|
||||
TrackingRunEvidenceLevel,
|
||||
TrackingRunAdmissionStatus,
|
||||
TrackingRunRepairOwner,
|
||||
TrackingRunTerminalTrigger,
|
||||
} from '@open-design/contracts/analytics';
|
||||
import {
|
||||
@@ -38,6 +43,12 @@ export interface RunFailureClassification {
|
||||
failure_category: TrackingRunFailureCategory;
|
||||
failure_detail: TrackingRunFailureDetail;
|
||||
failure_stage: TrackingRunFailureStage;
|
||||
failure_mechanism?: TrackingRunFailureMechanism;
|
||||
failure_domain?: TrackingRunFailureDomain;
|
||||
evidence_level?: TrackingRunEvidenceLevel;
|
||||
repair_owner?: TrackingRunRepairOwner;
|
||||
admission_status?: TrackingRunAdmissionStatus;
|
||||
classifier_version?: 'run-failure-v2';
|
||||
retryable: boolean;
|
||||
user_action: TrackingRunFailureUserAction;
|
||||
/** Distinguishes an explicit user stop from lifecycle-driven cancellation. */
|
||||
@@ -245,6 +256,19 @@ function isCliNotInstalledText(text: string): boolean {
|
||||
.test(text);
|
||||
}
|
||||
|
||||
function isBundledBinaryMissingText(text: string): boolean {
|
||||
return /\bbundled (?:OpenCode|agent) binary (?:is )?missing\b/i.test(text);
|
||||
}
|
||||
|
||||
function clientEnvironmentFailureDetail(text: string): TrackingRunFailureDetail | null {
|
||||
if (/\b(Windows Application Control|AppLocker)\b/i.test(text)) return 'host_policy_block';
|
||||
if (/\b(SQLite|WAL).*(?:I\/O|readonly|locked|corrupt|failed)\b/i.test(text)) return 'local_storage_failure';
|
||||
if (/\b(certificate|CERT_|self[- ]signed|unable to verify)\b/i.test(text)) return 'certificate_failure';
|
||||
if (/\b(unsupported proxy protocol|proxy configuration)\b/i.test(text)) return 'proxy_configuration';
|
||||
if (/\b(ECONNREFUSED|ENETUNREACH|network unreachable|local connection failed)\b/i.test(text)) return 'network_configuration';
|
||||
return null;
|
||||
}
|
||||
|
||||
function isGitBashMissingText(text: string): boolean {
|
||||
return /\bClaude Code on Windows requires git-bash\b|\bCLAUDE_CODE_GIT_BASH_PATH\b|\bgit-bash\b/i
|
||||
.test(text);
|
||||
@@ -262,6 +286,10 @@ function isAgentProtocolErrorText(text: string): boolean {
|
||||
/\bfailed to parse request\b/i.test(text);
|
||||
}
|
||||
|
||||
function isAcpFrameTooLargeText(text: string): boolean {
|
||||
return /\bACP input line exceeds maximum size\b/i.test(text);
|
||||
}
|
||||
|
||||
function isFabricatedRoleMarkerText(text: string): boolean {
|
||||
return /\bmodel emitted fabricated role marker\b/i.test(text);
|
||||
}
|
||||
@@ -679,11 +707,126 @@ function classification(
|
||||
failure_stage: TrackingRunFailureStage,
|
||||
retryable: boolean,
|
||||
user_action: TrackingRunFailureUserAction,
|
||||
options: {
|
||||
structuredProviderEvidence?: boolean;
|
||||
evidenceLevel?: TrackingRunEvidenceLevel;
|
||||
} = {},
|
||||
): RunFailureClassification {
|
||||
const policy = [
|
||||
'hard_quota',
|
||||
'model_window_limit',
|
||||
'membership_concurrency_limit',
|
||||
'workspace_credits_exhausted',
|
||||
'amr_insufficient_balance',
|
||||
'amr_tier_upgrade_required',
|
||||
].includes(failure_detail) || failure_category === 'entitlement_required';
|
||||
const localModel = [
|
||||
'cli_version_incompatible',
|
||||
'local_model_not_loaded',
|
||||
].includes(failure_detail);
|
||||
const clientRequest = [
|
||||
'attachment_media_type_unsupported',
|
||||
'tool_schema_invalid',
|
||||
'prompt_tokenization_failed',
|
||||
'provider_resource_not_found',
|
||||
].includes(failure_detail);
|
||||
const transport = !options.structuredProviderEvidence && [
|
||||
'stream_disconnected',
|
||||
'network_error',
|
||||
].includes(failure_detail);
|
||||
const provider = (failure_category === 'model_unavailable' && !localModel)
|
||||
|| (failure_category === 'upstream_unavailable' && !transport && !clientRequest)
|
||||
|| (failure_category === 'rate_limit' && !policy);
|
||||
const environment = [
|
||||
'auth_required', 'stale_profile', 'refresh_token_reused', 'missing_api_key',
|
||||
'invalid_api_key', 'cli_not_installed', 'git_bash_missing',
|
||||
'agent_config_invalid', 'cpu_unsupported', 'host_policy_block',
|
||||
'local_storage_failure', 'certificate_failure', 'proxy_configuration',
|
||||
'network_configuration',
|
||||
].includes(failure_detail) || localModel;
|
||||
const product = [
|
||||
'agent_protocol_error', 'acp_frame_too_large', 'bundled_binary_missing', 'empty_output', 'fabricated_role_marker',
|
||||
'permission_request_not_found', 'plugin_artifact_missing',
|
||||
].includes(failure_detail) || clientRequest || failure_category === 'prompt_too_large';
|
||||
const failure_mechanism: TrackingRunFailureMechanism = policy
|
||||
? 'policy_rejection'
|
||||
: provider
|
||||
? 'provider_rejection'
|
||||
: transport
|
||||
? 'transport_failure'
|
||||
: failure_detail === 'acp_frame_too_large'
|
||||
? 'frame_too_large'
|
||||
: failure_detail === 'agent_protocol_error'
|
||||
? 'protocol_violation'
|
||||
: failure_category === 'empty_output'
|
||||
? 'empty_completion'
|
||||
: failure_category === 'timeout'
|
||||
? failure_detail === 'inactivity_timeout'
|
||||
? 'stream_idle_timeout'
|
||||
: failure_stage === 'post_tool_resume'
|
||||
? 'post_tool_resume_timeout'
|
||||
: 'acp_response_deadline'
|
||||
: failure_category === 'tool_error'
|
||||
? 'tool_execution_failure'
|
||||
: failure_detail === 'interrupted'
|
||||
? 'unknown'
|
||||
: failure_category === 'process_exit'
|
||||
? 'child_exit'
|
||||
: 'unknown';
|
||||
const failure_domain: TrackingRunFailureDomain = policy
|
||||
? 'policy_admission'
|
||||
: provider
|
||||
? 'provider_control_plane'
|
||||
: transport
|
||||
? 'cross_boundary'
|
||||
: environment
|
||||
? 'client_environment'
|
||||
: product
|
||||
? 'client_product'
|
||||
: failure_category === 'timeout' || failure_category === 'process_exit'
|
||||
? 'cross_boundary'
|
||||
: 'unknown';
|
||||
const inferredEvidenceLevel: TrackingRunEvidenceLevel = failure_detail === 'membership_concurrency_limit'
|
||||
? 'structured_code'
|
||||
: failure_detail === 'interrupted'
|
||||
? 'lifecycle_signal'
|
||||
: transport
|
||||
? 'legacy_text'
|
||||
: provider
|
||||
? options.structuredProviderEvidence ? 'structured_code' : 'legacy_text'
|
||||
: failure_detail === 'agent_protocol_error' || failure_detail === 'acp_frame_too_large'
|
||||
? 'protocol_error'
|
||||
: failure_category === 'timeout'
|
||||
? 'lifecycle_signal'
|
||||
: failure_detail === 'bundled_binary_missing' || environment
|
||||
? 'stderr_fallback'
|
||||
: ['fatal_rpc_error', 'stream_error', 'exit_nonzero'].includes(failure_detail)
|
||||
? 'close_reason'
|
||||
: failure_detail === 'unknown'
|
||||
? 'unknown'
|
||||
: 'legacy_text';
|
||||
const evidence_level = options.evidenceLevel ?? inferredEvidenceLevel;
|
||||
const repair_owner: TrackingRunRepairOwner = failure_domain === 'policy_admission'
|
||||
? 'policy_owner'
|
||||
: failure_domain === 'provider_control_plane'
|
||||
? 'provider_owner'
|
||||
: failure_domain === 'client_environment'
|
||||
? 'client_environment'
|
||||
: failure_domain === 'client_product'
|
||||
? 'open_design'
|
||||
: failure_domain === 'cross_boundary'
|
||||
? 'shared_boundary'
|
||||
: 'unknown';
|
||||
return {
|
||||
failure_category,
|
||||
failure_detail,
|
||||
failure_stage,
|
||||
failure_mechanism,
|
||||
failure_domain,
|
||||
evidence_level,
|
||||
repair_owner,
|
||||
admission_status: policy ? 'rejected_policy' : 'admitted',
|
||||
classifier_version: 'run-failure-v2',
|
||||
retryable,
|
||||
user_action,
|
||||
};
|
||||
@@ -699,13 +842,11 @@ function classifyRunFailureBase(
|
||||
return {
|
||||
// Preserve the legacy category/detail for dashboard compatibility.
|
||||
// `cancel_origin` is the authoritative SLO eligibility signal.
|
||||
...classification(
|
||||
'user_cancel',
|
||||
'user_cancelled',
|
||||
inferFailureStageFromEvents(events, 'first_token_wait'),
|
||||
false,
|
||||
'none',
|
||||
),
|
||||
failure_category: 'user_cancel',
|
||||
failure_detail: 'user_cancelled',
|
||||
failure_stage: inferFailureStageFromEvents(events, 'first_token_wait'),
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
cancel_origin: cancelOrigin,
|
||||
terminal_trigger: cancelOrigin,
|
||||
};
|
||||
@@ -723,6 +864,16 @@ function classifyRunFailureBase(
|
||||
text,
|
||||
);
|
||||
|
||||
if (errorCode === 'DAEMON_RESTARTED') {
|
||||
return classification(
|
||||
'process_exit',
|
||||
'interrupted',
|
||||
'finalize',
|
||||
true,
|
||||
'retry',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
errorCode === 'AMR_INSUFFICIENT_BALANCE' ||
|
||||
amrFailure?.code === 'AMR_INSUFFICIENT_BALANCE'
|
||||
@@ -733,6 +884,9 @@ function classifyRunFailureBase(
|
||||
'session_init',
|
||||
false,
|
||||
'recharge',
|
||||
errorCode === 'AMR_INSUFFICIENT_BALANCE'
|
||||
? { evidenceLevel: 'structured_code' }
|
||||
: {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -746,6 +900,9 @@ function classifyRunFailureBase(
|
||||
'session_init',
|
||||
false,
|
||||
'upgrade',
|
||||
errorCode === 'AMR_TIER_UPGRADE_REQUIRED'
|
||||
? { evidenceLevel: 'structured_code' }
|
||||
: {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -761,6 +918,13 @@ function classifyRunFailureBase(
|
||||
'session_init',
|
||||
false,
|
||||
'login',
|
||||
[
|
||||
'AMR_AUTH_REQUIRED',
|
||||
'AGENT_AUTH_REQUIRED',
|
||||
'UNAUTHORIZED',
|
||||
].includes(errorCode ?? '')
|
||||
? { evidenceLevel: 'structured_code' }
|
||||
: {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -772,6 +936,9 @@ function classifyRunFailureBase(
|
||||
'prompt_send',
|
||||
false,
|
||||
'reduce_context',
|
||||
errorCode === 'AGENT_PROMPT_TOO_LARGE'
|
||||
? { evidenceLevel: 'structured_code' }
|
||||
: {},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -788,6 +955,7 @@ function classifyRunFailureBase(
|
||||
: 'model_select',
|
||||
false,
|
||||
'switch_model',
|
||||
{ structuredProviderEvidence: errorCode === 'AMR_MODEL_UNAVAILABLE' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -828,6 +996,33 @@ function classifyRunFailureBase(
|
||||
);
|
||||
}
|
||||
|
||||
if (isBundledBinaryMissingText(text)) {
|
||||
return classification(
|
||||
'process_exit',
|
||||
'bundled_binary_missing',
|
||||
'spawn',
|
||||
false,
|
||||
'none',
|
||||
);
|
||||
}
|
||||
|
||||
const serviceFailure = classifyAgentServiceFailure(text);
|
||||
const environmentDetail = clientEnvironmentFailureDetail(text);
|
||||
const hasStructuredServiceCode = [
|
||||
'RATE_LIMITED',
|
||||
'UPSTREAM_UNAVAILABLE',
|
||||
'AGENT_CONNECTION_DROPPED',
|
||||
].includes(errorCode ?? '');
|
||||
if (environmentDetail && !hasStructuredServiceCode) {
|
||||
return classification(
|
||||
'process_exit',
|
||||
environmentDetail,
|
||||
'spawn',
|
||||
false,
|
||||
'none',
|
||||
);
|
||||
}
|
||||
|
||||
if (isCliNotInstalledText(text)) {
|
||||
return classification(
|
||||
'process_exit',
|
||||
@@ -868,6 +1063,16 @@ function classifyRunFailureBase(
|
||||
);
|
||||
}
|
||||
|
||||
if (isAcpFrameTooLargeText(text)) {
|
||||
return classification(
|
||||
'process_exit',
|
||||
'acp_frame_too_large',
|
||||
inferFailureStageFromEvents(events, 'child_close'),
|
||||
false,
|
||||
'none',
|
||||
);
|
||||
}
|
||||
|
||||
// A protocol failure from AFTER the handshake: a session existed, so the run
|
||||
// may simply have hit a bad moment and the old transient treatment stands.
|
||||
// Handshake-numbered frames (ids 1 and 2) are deliberately NOT claimed here
|
||||
@@ -886,7 +1091,6 @@ function classifyRunFailureBase(
|
||||
);
|
||||
}
|
||||
|
||||
const serviceFailure = classifyAgentServiceFailure(text);
|
||||
if (serviceFailure === 'AGENT_AUTH_REQUIRED' || isAuthDetailText(text)) {
|
||||
return classification(
|
||||
'auth',
|
||||
@@ -938,6 +1142,7 @@ function classifyRunFailureBase(
|
||||
'session_init',
|
||||
retryable,
|
||||
retryable ? 'retry' : workspaceCredits ? 'recharge' : 'none',
|
||||
{ structuredProviderEvidence: errorCode === 'RATE_LIMITED' },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -948,6 +1153,9 @@ function classifyRunFailureBase(
|
||||
isUpstreamDetailText(text) ||
|
||||
byokOpenCodeProviderNotFound
|
||||
) {
|
||||
const structuredProviderEvidence =
|
||||
errorCode === 'UPSTREAM_UNAVAILABLE' ||
|
||||
errorCode === 'AGENT_CONNECTION_DROPPED';
|
||||
const upstreamClientError =
|
||||
byokOpenCodeProviderNotFound || isUpstreamClientErrorText(text);
|
||||
// A provider/SDK 4xx or request-shape rejection will deterministically fail
|
||||
@@ -960,6 +1168,7 @@ function classifyRunFailureBase(
|
||||
inferFailureStageFromEvents(events, 'first_token_wait'),
|
||||
retryable,
|
||||
retryable ? 'retry' : 'none',
|
||||
{ structuredProviderEvidence },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1205,9 +1414,30 @@ export function classifyRunFailure(
|
||||
input: RunFailureClassificationInput,
|
||||
): RunFailureClassification | undefined {
|
||||
const failure = classifyRunFailureBase(input);
|
||||
if (!failure || !input.terminalTrigger) return failure;
|
||||
if (!failure) return failure;
|
||||
const terminalTrigger = input.terminalTrigger ?? failure.terminal_trigger;
|
||||
const failureText = collectFailureText({
|
||||
...input,
|
||||
events: terminalAttemptEvents(input.events),
|
||||
});
|
||||
const failureMechanism = failure.failure_category === 'timeout'
|
||||
? /(?:readiness|ready) deadline[^\n]*(?:timed out|timeout|expired|failed)|(?:readiness failed|failed to become ready|did not become ready|never became ready)/i.test(failureText)
|
||||
? 'startup_readiness_timeout'
|
||||
: terminalTrigger === 'first_output_deadline'
|
||||
? 'first_output_deadline'
|
||||
: terminalTrigger === 'acp_stage_timeout'
|
||||
? failure.failure_stage === 'post_tool_resume'
|
||||
? 'post_tool_resume_timeout'
|
||||
: failure.failure_stage === 'tool_execution' || failure.failure_stage === 'tool_outstanding'
|
||||
? 'tool_execution_failure'
|
||||
: 'acp_response_deadline'
|
||||
: terminalTrigger === 'inactivity_watchdog'
|
||||
? 'stream_idle_timeout'
|
||||
: failure.failure_mechanism
|
||||
: failure.failure_mechanism;
|
||||
return {
|
||||
...failure,
|
||||
terminal_trigger: input.terminalTrigger,
|
||||
...(failureMechanism ? { failure_mechanism: failureMechanism } : {}),
|
||||
...(terminalTrigger ? { terminal_trigger: terminalTrigger } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -32,6 +32,18 @@ import {
|
||||
|
||||
const TERMINAL_STATUSES = new Set(['succeeded', 'failed', 'canceled']);
|
||||
const RECONCILED_STATUS_MESSAGE = 'Run terminal state reconciled after daemon restart.';
|
||||
const STRONGER_TERMINAL_INTEGRITY = new Set([
|
||||
'late',
|
||||
'overwritten',
|
||||
'permanently_missing',
|
||||
'post_terminal_activity',
|
||||
]);
|
||||
|
||||
function reconciledTerminalIntegrity(value: unknown): string {
|
||||
return typeof value === 'string' && STRONGER_TERMINAL_INTEGRITY.has(value)
|
||||
? value
|
||||
: 'reconciled';
|
||||
}
|
||||
|
||||
interface AnalyticsRecovery {
|
||||
context: Record<string, unknown>;
|
||||
@@ -408,24 +420,15 @@ export async function reconcileDurableRunTerminals(
|
||||
: deriveRunErrorCode(state)
|
||||
: undefined;
|
||||
const failure = failed
|
||||
? recoveryReason === 'daemon_restart'
|
||||
? {
|
||||
failure_category: 'process_exit' as const,
|
||||
failure_detail: 'interrupted' as const,
|
||||
failure_stage: 'finalize' as const,
|
||||
retryable: true,
|
||||
user_action: 'retry' as const,
|
||||
terminal_trigger: 'daemon_restart' as const,
|
||||
}
|
||||
: classifyRunFailure({
|
||||
result: runResult,
|
||||
status: state,
|
||||
...(errorCode ? { errorCode } : {}),
|
||||
agentId: state.agentId,
|
||||
cancelOrigin: state.cancelOrigin ?? null,
|
||||
terminalTrigger: state.terminalTrigger ?? null,
|
||||
events,
|
||||
})
|
||||
? classifyRunFailure({
|
||||
result: runResult,
|
||||
status: state,
|
||||
...(errorCode ? { errorCode } : {}),
|
||||
agentId: state.agentId,
|
||||
cancelOrigin: state.cancelOrigin ?? null,
|
||||
terminalTrigger: state.terminalTrigger ?? null,
|
||||
events,
|
||||
})
|
||||
: undefined;
|
||||
const properties: Record<string, unknown> = {
|
||||
...state.analyticsRecovery.properties,
|
||||
@@ -438,6 +441,9 @@ export async function reconcileDurableRunTerminals(
|
||||
total_duration_ms: Math.max(0, state.updatedAt - state.createdAt),
|
||||
langfuse_trace_id: state.id,
|
||||
terminal_reconciled: true,
|
||||
terminal_integrity: reconciledTerminalIntegrity(
|
||||
state.analyticsRecovery.properties.terminal_integrity,
|
||||
),
|
||||
terminal_recovery_reason: recoveryReason,
|
||||
...(errorCode ? { error_code: errorCode } : {}),
|
||||
...(failure ?? {}),
|
||||
|
||||
@@ -961,6 +961,7 @@ export function createRunAnalyticsLifecycle(
|
||||
stable_prompt_changed_sections: run.promptCache?.changedSections ?? undefined,
|
||||
area: isDesignSystemRun ? 'design_system_generation' : 'chat_panel',
|
||||
result,
|
||||
terminal_integrity: 'canonical',
|
||||
...(activationMilestones ? { $set_once: activationMilestones } : {}),
|
||||
model_id: finishedModelId,
|
||||
artifact_count: artifactCount,
|
||||
|
||||
@@ -36,7 +36,11 @@ vi.mock('../src/runtimes/auth.js', () => ({
|
||||
if (value.includes('http 429') || value.includes('too many requests') || value.includes('session limit')) {
|
||||
return 'RATE_LIMITED' as const;
|
||||
}
|
||||
if (value.includes('503 upstream unavailable') || value.includes('upstream unavailable')) {
|
||||
if (
|
||||
value.includes('503 upstream unavailable') ||
|
||||
value.includes('upstream unavailable') ||
|
||||
value.includes('503 service unavailable')
|
||||
) {
|
||||
return 'UPSTREAM_UNAVAILABLE' as const;
|
||||
}
|
||||
return null;
|
||||
@@ -224,6 +228,7 @@ describe('classifyRunFailure', () => {
|
||||
failure_category: 'auth',
|
||||
failure_detail: 'auth_required',
|
||||
failure_stage: 'session_init',
|
||||
evidence_level: 'structured_code',
|
||||
retryable: false,
|
||||
user_action: 'login',
|
||||
});
|
||||
@@ -551,6 +556,14 @@ describe('classifyRunFailure', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('records a direct AMR insufficient-balance code as structured evidence', () => {
|
||||
expect(classify('AMR_INSUFFICIENT_BALANCE')).toMatchObject({
|
||||
failure_category: 'insufficient_balance',
|
||||
failure_detail: 'amr_insufficient_balance',
|
||||
evidence_level: 'structured_code',
|
||||
});
|
||||
});
|
||||
|
||||
it('maps prompt-size failures to reduce-context guidance', () => {
|
||||
expect(classify('AGENT_PROMPT_TOO_LARGE', 'context window exceeded')).toMatchObject({
|
||||
failure_category: 'prompt_too_large',
|
||||
@@ -561,6 +574,14 @@ describe('classifyRunFailure', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('records a direct prompt-too-large code as structured evidence', () => {
|
||||
expect(classify('AGENT_PROMPT_TOO_LARGE')).toMatchObject({
|
||||
failure_category: 'prompt_too_large',
|
||||
failure_detail: 'prompt_too_large',
|
||||
evidence_level: 'structured_code',
|
||||
});
|
||||
});
|
||||
|
||||
it('maps empty output to an explicit retryable category', () => {
|
||||
expect(
|
||||
classify(
|
||||
@@ -619,10 +640,36 @@ describe('classifyRunFailure', () => {
|
||||
).toMatchObject({
|
||||
failure_category: 'timeout',
|
||||
failure_detail: 'inactivity_timeout',
|
||||
failure_mechanism: 'first_output_deadline',
|
||||
failure_domain: 'cross_boundary',
|
||||
terminal_trigger: 'first_output_deadline',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not treat a positive readiness signal as a readiness timeout', () => {
|
||||
const timeoutMessage = 'Agent stalled after the runtime became ready without emitting any new output.';
|
||||
|
||||
expect(
|
||||
classifyRunFailure({
|
||||
result: 'failed',
|
||||
status: {
|
||||
status: 'failed',
|
||||
error: timeoutMessage,
|
||||
signal: 'SIGTERM',
|
||||
exitCode: null,
|
||||
errorCode: 'AGENT_SIGNAL_SIGTERM',
|
||||
},
|
||||
errorCode: 'AGENT_SIGNAL_SIGTERM',
|
||||
terminalTrigger: 'inactivity_watchdog',
|
||||
events: [errorEvent('AGENT_SIGNAL_SIGTERM', timeoutMessage, true)],
|
||||
}),
|
||||
).toMatchObject({
|
||||
failure_category: 'timeout',
|
||||
failure_mechanism: 'stream_idle_timeout',
|
||||
terminal_trigger: 'inactivity_watchdog',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps an explicit watchdog trigger when a provider error supplies the failure bucket', () => {
|
||||
expect(
|
||||
classifyRunFailure({
|
||||
@@ -644,6 +691,40 @@ describe('classifyRunFailure', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps explicit service codes ahead of client-environment text heuristics', () => {
|
||||
expect(
|
||||
classify('UPSTREAM_UNAVAILABLE', 'ECONNREFUSED provider endpoint'),
|
||||
).toMatchObject({
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_mechanism: 'provider_rejection',
|
||||
failure_domain: 'provider_control_plane',
|
||||
evidence_level: 'structured_code',
|
||||
repair_owner: 'provider_owner',
|
||||
});
|
||||
|
||||
expect(
|
||||
classify('RATE_LIMITED', 'Request blocked by account policy'),
|
||||
).toMatchObject({
|
||||
failure_category: 'rate_limit',
|
||||
failure_mechanism: 'provider_rejection',
|
||||
failure_domain: 'provider_control_plane',
|
||||
evidence_level: 'structured_code',
|
||||
repair_owner: 'provider_owner',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps text-only provider failures at legacy-text evidence', () => {
|
||||
expect(
|
||||
classify('AGENT_EXECUTION_FAILED', 'HTTP 503 service unavailable'),
|
||||
).toMatchObject({
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_mechanism: 'provider_rejection',
|
||||
failure_domain: 'provider_control_plane',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'provider_owner',
|
||||
});
|
||||
});
|
||||
|
||||
it('classifies only the terminal attempt after an automatic retry', () => {
|
||||
const timeoutMessage = 'Agent stalled without emitting any new output for 120s.';
|
||||
|
||||
@@ -661,6 +742,7 @@ describe('classifyRunFailure', () => {
|
||||
agentId: 'claude',
|
||||
events: [
|
||||
{ event: 'start', data: { attempt: 1 } },
|
||||
errorEvent('TIMEOUT', 'Runtime readiness deadline timed out.', true),
|
||||
{ event: 'agent', data: { type: 'text_delta', delta: 'Working.' } },
|
||||
{ event: 'agent', data: { type: 'tool_use', id: 'tool-1', name: 'Read' } },
|
||||
errorEvent('UPSTREAM_UNAVAILABLE', '503 upstream unavailable', true),
|
||||
@@ -673,6 +755,7 @@ describe('classifyRunFailure', () => {
|
||||
failure_category: 'timeout',
|
||||
failure_detail: 'inactivity_timeout',
|
||||
failure_stage: 'first_token_wait',
|
||||
failure_mechanism: 'stream_idle_timeout',
|
||||
retryable: true,
|
||||
user_action: 'retry',
|
||||
});
|
||||
@@ -1588,11 +1671,28 @@ describe('execution_failed close-reason refinement', () => {
|
||||
failure_category: 'rate_limit',
|
||||
failure_detail: 'membership_concurrency_limit',
|
||||
failure_stage: 'session_init',
|
||||
failure_mechanism: 'policy_rejection',
|
||||
failure_domain: 'policy_admission',
|
||||
evidence_level: 'structured_code',
|
||||
repair_owner: 'policy_owner',
|
||||
admission_status: 'rejected_policy',
|
||||
classifier_version: 'run-failure-v2',
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not exclude an ordinary provider 429 as a pre-run policy rejection', () => {
|
||||
expect(classifyForAgent('amr', 'RATE_LIMITED', 'HTTP 429: too many requests')).toMatchObject({
|
||||
failure_category: 'rate_limit',
|
||||
failure_detail: 'rate_limit_429',
|
||||
failure_domain: 'provider_control_plane',
|
||||
failure_mechanism: 'provider_rejection',
|
||||
admission_status: 'admitted',
|
||||
repair_owner: 'provider_owner',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a non-AMR membership concurrency envelope retryable', () => {
|
||||
const message =
|
||||
'[code=tier_limit_exceeded] membership concurrency limit exceeded: 3/2 resets 2026-08-25T10:42:00Z';
|
||||
@@ -1609,6 +1709,121 @@ describe('execution_failed close-reason refinement', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('classifies an oversized ACP input frame as a local product protocol failure', () => {
|
||||
const message = 'ACP input line exceeds maximum size (1048576 bytes)';
|
||||
expect(
|
||||
classifyForAgent('amr', 'AGENT_EXECUTION_FAILED', message, [
|
||||
errorEvent('AGENT_EXECUTION_FAILED', message, false),
|
||||
runtimeCloseEvent('fatal_rpc_error'),
|
||||
]),
|
||||
).toMatchObject({
|
||||
failure_category: 'process_exit',
|
||||
failure_detail: 'acp_frame_too_large',
|
||||
failure_mechanism: 'frame_too_large',
|
||||
failure_domain: 'client_product',
|
||||
evidence_level: 'protocol_error',
|
||||
repair_owner: 'open_design',
|
||||
admission_status: 'admitted',
|
||||
classifier_version: 'run-failure-v2',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a wrapped oversized ACP input frame non-retryable without a retry hint', () => {
|
||||
const message =
|
||||
'json-rpc id 4: failed to parse request: ACP input line exceeds maximum size (1048576 bytes)';
|
||||
expect(classifyForAgent('amr', 'AGENT_EXECUTION_FAILED', message, [])).toMatchObject({
|
||||
failure_category: 'process_exit',
|
||||
failure_detail: 'acp_frame_too_large',
|
||||
failure_mechanism: 'frame_too_large',
|
||||
evidence_level: 'protocol_error',
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
});
|
||||
});
|
||||
|
||||
it('classifies a missing bundled OpenCode binary as a local product packaging failure', () => {
|
||||
const message = 'bundled OpenCode binary is missing';
|
||||
expect(classifyForAgent('amr', 'AGENT_EXECUTION_FAILED', message)).toMatchObject({
|
||||
failure_category: 'process_exit',
|
||||
failure_detail: 'bundled_binary_missing',
|
||||
failure_mechanism: 'child_exit',
|
||||
failure_domain: 'client_product',
|
||||
evidence_level: 'stderr_fallback',
|
||||
repair_owner: 'open_design',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps host policy blocks separate from client product failures', () => {
|
||||
const message = 'OpenCode launch was blocked by Windows Application Control policy';
|
||||
expect(classifyForAgent('amr', 'AGENT_EXECUTION_FAILED', message)).toMatchObject({
|
||||
failure_detail: 'host_policy_block',
|
||||
failure_domain: 'client_environment',
|
||||
failure_mechanism: 'child_exit',
|
||||
evidence_level: 'stderr_fallback',
|
||||
repair_owner: 'client_environment',
|
||||
});
|
||||
});
|
||||
|
||||
it('does not treat a text-only account policy rejection as a host policy block', () => {
|
||||
expect(classify('AGENT_EXECUTION_FAILED', 'Request blocked by account policy')).toMatchObject({
|
||||
failure_category: 'process_exit',
|
||||
failure_detail: 'execution_failed',
|
||||
failure_domain: 'cross_boundary',
|
||||
repair_owner: 'shared_boundary',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps CLI version incompatibility client-owned', () => {
|
||||
expect(classify(null, "error: unknown option '--trust'")).toMatchObject({
|
||||
failure_category: 'model_unavailable',
|
||||
failure_detail: 'cli_version_incompatible',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'client_environment',
|
||||
evidence_level: 'stderr_fallback',
|
||||
repair_owner: 'client_environment',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps an unloaded local model client-owned', () => {
|
||||
expect(
|
||||
classify(
|
||||
'AGENT_EXECUTION_FAILED',
|
||||
"No models loaded. Please use the 'lms load' command.",
|
||||
),
|
||||
).toMatchObject({
|
||||
failure_category: 'model_unavailable',
|
||||
failure_detail: 'local_model_not_loaded',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'client_environment',
|
||||
evidence_level: 'stderr_fallback',
|
||||
repair_owner: 'client_environment',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps text-only network errors at the shared transport boundary', () => {
|
||||
expect(
|
||||
classify('AGENT_EXECUTION_FAILED', 'Transport error: network error'),
|
||||
).toMatchObject({
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'network_error',
|
||||
failure_mechanism: 'transport_failure',
|
||||
failure_domain: 'cross_boundary',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'shared_boundary',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps a structured upstream code provider-owned', () => {
|
||||
expect(classify('AGENT_CONNECTION_DROPPED')).toMatchObject({
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'network_error',
|
||||
failure_mechanism: 'provider_rejection',
|
||||
failure_domain: 'provider_control_plane',
|
||||
evidence_level: 'structured_code',
|
||||
repair_owner: 'provider_owner',
|
||||
});
|
||||
});
|
||||
|
||||
it('honors an explicit non-retryable hint on fatal close reasons', () => {
|
||||
const result = classify('AGENT_EXECUTION_FAILED', '', [
|
||||
errorEvent('AGENT_EXECUTION_FAILED', '', false),
|
||||
@@ -1669,6 +1884,7 @@ describe('classifyRunFailure — AMR/vela reclassification out of execution_fail
|
||||
failure_category: 'entitlement_required',
|
||||
failure_detail: 'amr_tier_upgrade_required',
|
||||
failure_stage: 'session_init',
|
||||
evidence_level: 'structured_code',
|
||||
retryable: false,
|
||||
user_action: 'upgrade',
|
||||
});
|
||||
@@ -2279,6 +2495,10 @@ describe('classifyRunFailure — sampled 0.15.1 provider request failures', () =
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'attachment_media_type_unsupported',
|
||||
failure_stage: 'prompt_send',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'client_product',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'open_design',
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
},
|
||||
@@ -2292,6 +2512,10 @@ describe('classifyRunFailure — sampled 0.15.1 provider request failures', () =
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'tool_schema_invalid',
|
||||
failure_stage: 'prompt_send',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'client_product',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'open_design',
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
},
|
||||
@@ -2305,6 +2529,10 @@ describe('classifyRunFailure — sampled 0.15.1 provider request failures', () =
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'prompt_tokenization_failed',
|
||||
failure_stage: 'prompt_send',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'client_product',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'open_design',
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
},
|
||||
@@ -2344,6 +2572,10 @@ describe('classifyRunFailure — sampled 0.15.1 provider request failures', () =
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'provider_resource_not_found',
|
||||
failure_stage: 'prompt_send',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'client_product',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'open_design',
|
||||
retryable: false,
|
||||
user_action: 'none',
|
||||
},
|
||||
@@ -2357,6 +2589,10 @@ describe('classifyRunFailure — sampled 0.15.1 provider request failures', () =
|
||||
failure_category: 'upstream_unavailable',
|
||||
failure_detail: 'stream_disconnected',
|
||||
failure_stage: 'first_token_wait',
|
||||
failure_mechanism: 'transport_failure',
|
||||
failure_domain: 'cross_boundary',
|
||||
evidence_level: 'legacy_text',
|
||||
repair_owner: 'shared_boundary',
|
||||
retryable: true,
|
||||
user_action: 'retry',
|
||||
},
|
||||
|
||||
@@ -104,10 +104,17 @@ describe('durable run terminal reconciliation', () => {
|
||||
failure_category: 'process_exit',
|
||||
failure_detail: 'interrupted',
|
||||
failure_stage: 'finalize',
|
||||
failure_mechanism: 'unknown',
|
||||
failure_domain: 'cross_boundary',
|
||||
evidence_level: 'lifecycle_signal',
|
||||
repair_owner: 'shared_boundary',
|
||||
admission_status: 'admitted',
|
||||
classifier_version: 'run-failure-v2',
|
||||
retryable: true,
|
||||
user_action: 'retry',
|
||||
terminal_trigger: 'daemon_restart',
|
||||
terminal_reconciled: true,
|
||||
terminal_integrity: 'reconciled',
|
||||
terminal_recovery_reason: 'daemon_restart',
|
||||
}),
|
||||
}));
|
||||
@@ -185,6 +192,7 @@ describe('durable run terminal reconciliation', () => {
|
||||
project_id: 'p1',
|
||||
conversation_id: 'c1',
|
||||
run_id: runId,
|
||||
terminal_integrity: 'overwritten',
|
||||
},
|
||||
insertId: 'run-created-analytics-incomplete',
|
||||
},
|
||||
@@ -228,6 +236,7 @@ describe('durable run terminal reconciliation', () => {
|
||||
retryable: false,
|
||||
user_action: 'login',
|
||||
terminal_reconciled: true,
|
||||
terminal_integrity: 'overwritten',
|
||||
terminal_recovery_reason: 'analytics_incomplete',
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { ArtifactOriginEntrySurface, ArtifactOriginStatus } from '../../api
|
||||
import type { AgentDiagnosticReason, AgentDiagnosticSeverity } from '../../api/registry.js';
|
||||
import type { TrackingDesignSystemEditSurface, TrackingDesignSystemKind, TrackingDesignSystemLengthBucket, TrackingDesignSystemOrigin, TrackingDesignSystemRunEntryFrom } from './design-systems.js';
|
||||
import type { TrackingSettingsPage } from './event-names.js';
|
||||
import type { TrackingAmrOpenCodeErrorPhase, TrackingAmrOpenCodeLastEventType, TrackingAmrOpenCodeLastToolKind, TrackingAmrOpenCodeLastToolStatus, TrackingArtifactKind, TrackingArtifactWriteSource, TrackingArtifactWriteStatus, TrackingByokPreflightBlockReason, TrackingByokProviderId, TrackingCliProviderId, TrackingDesignSystemSource, TrackingExecutionMode, TrackingExportFormat, TrackingExportResult, TrackingFeedbackAction, TrackingFeedbackProviderId, TrackingFeedbackRating, TrackingFeedbackRatingWithNone, TrackingFeedbackReasonCode, TrackingFidelity, TrackingFileSizeBucket, TrackingFileType, TrackingFirstModelEventType, TrackingHarness, TrackingLabsItemId, TrackingLabsOptOutReason, TrackingLabsSystemReason, TrackingLabsToggleSource, TrackingLangfuseDeliveryStatus, TrackingLangfuseDropReason, TrackingLangfuseReportResult, TrackingLangfuseReportSkipReason, TrackingProjectKind, TrackingProjectSource, TrackingPublishErrorCode, TrackingResult, TrackingRunCancelOrigin, TrackingRunCloseReason, TrackingRunDiagnosticSource, TrackingRunFailureCategory, TrackingRunFailureDetail, TrackingRunFailureStage, TrackingRunFailureUserAction, TrackingRunLifecyclePhase, TrackingRunPhaseTimingStatus, TrackingRunResult, TrackingRunRetryFinalResult, TrackingRunRetryStrategy, TrackingRunRetrySuppressedReason, TrackingRunTerminalTrigger, TrackingStderrLineCountBucket, TrackingTestResult, TrackingTokenCountSource } from './shared-enums.js';
|
||||
import type { TrackingAmrOpenCodeErrorPhase, TrackingAmrOpenCodeLastEventType, TrackingAmrOpenCodeLastToolKind, TrackingAmrOpenCodeLastToolStatus, TrackingArtifactKind, TrackingArtifactWriteSource, TrackingArtifactWriteStatus, TrackingByokPreflightBlockReason, TrackingByokProviderId, TrackingCliProviderId, TrackingDesignSystemSource, TrackingExecutionMode, TrackingExportFormat, TrackingExportResult, TrackingFeedbackAction, TrackingFeedbackProviderId, TrackingFeedbackRating, TrackingFeedbackRatingWithNone, TrackingFeedbackReasonCode, TrackingFidelity, TrackingFileSizeBucket, TrackingFileType, TrackingFirstModelEventType, TrackingHarness, TrackingLabsItemId, TrackingLabsOptOutReason, TrackingLabsSystemReason, TrackingLabsToggleSource, TrackingLangfuseDeliveryStatus, TrackingLangfuseDropReason, TrackingLangfuseReportResult, TrackingLangfuseReportSkipReason, TrackingProjectKind, TrackingProjectSource, TrackingPublishErrorCode, TrackingResult, TrackingRunAdmissionStatus, TrackingRunCancelOrigin, TrackingRunCloseReason, TrackingRunDiagnosticSource, TrackingRunEvidenceLevel, TrackingRunFailureCategory, TrackingRunFailureDetail, TrackingRunFailureDomain, TrackingRunFailureMechanism, TrackingRunFailureStage, TrackingRunFailureUserAction, TrackingRunLifecyclePhase, TrackingRunPhaseTimingStatus, TrackingRunRepairOwner, TrackingRunResult, TrackingRunRetryFinalResult, TrackingRunRetryStrategy, TrackingRunRetrySuppressedReason, TrackingRunTerminalIntegrity, TrackingRunTerminalTrigger, TrackingStderrLineCountBucket, TrackingTestResult, TrackingTokenCountSource } from './shared-enums.js';
|
||||
import type { ConversationForkAnalyticsContext, TrackingFileVersionSource, TrackingPluginImportSource, TrackingSessionMode, TrackingSettingsArea } from './ui-click.js';
|
||||
// ---- Result events -------------------------------------------------------
|
||||
|
||||
@@ -480,6 +480,14 @@ export interface RunFinishedProps extends Omit<RunCreatedProps, 'area'> {
|
||||
/** v4 name; failure_detail remains during the compatibility window. */
|
||||
failure_reason?: TrackingRunFailureDetail;
|
||||
failure_stage?: TrackingRunFailureStage;
|
||||
/** Versioned causal classification added alongside the legacy category/detail. */
|
||||
failure_mechanism?: TrackingRunFailureMechanism;
|
||||
failure_domain?: TrackingRunFailureDomain;
|
||||
evidence_level?: TrackingRunEvidenceLevel;
|
||||
repair_owner?: TrackingRunRepairOwner;
|
||||
admission_status?: TrackingRunAdmissionStatus;
|
||||
terminal_integrity?: TrackingRunTerminalIntegrity;
|
||||
classifier_version?: 'run-failure-v2';
|
||||
retryable?: boolean;
|
||||
/** v4 name; retryable remains during the compatibility window. */
|
||||
is_automatic_retry_eligible?: boolean;
|
||||
|
||||
@@ -325,6 +325,12 @@ export type TrackingRunFailureDetail =
|
||||
| 'tool_error'
|
||||
| 'plugin_artifact_missing'
|
||||
| 'cli_not_installed'
|
||||
| 'bundled_binary_missing'
|
||||
| 'host_policy_block'
|
||||
| 'local_storage_failure'
|
||||
| 'certificate_failure'
|
||||
| 'proxy_configuration'
|
||||
| 'network_configuration'
|
||||
| 'git_bash_missing'
|
||||
| 'agent_config_invalid'
|
||||
| 'spawn_failed'
|
||||
@@ -333,6 +339,7 @@ export type TrackingRunFailureDetail =
|
||||
| 'spawn_eperm'
|
||||
| 'stdin_write_eof'
|
||||
| 'agent_protocol_error'
|
||||
| 'acp_frame_too_large'
|
||||
| 'session_resume_expired'
|
||||
| 'fabricated_role_marker'
|
||||
| 'permission_request_not_found'
|
||||
@@ -362,6 +369,55 @@ export type TrackingRunFailureStage =
|
||||
| 'artifact_write'
|
||||
| 'child_close'
|
||||
| 'finalize';
|
||||
export type TrackingRunFailureMechanism =
|
||||
| 'policy_rejection'
|
||||
| 'provider_rejection'
|
||||
| 'protocol_violation'
|
||||
| 'frame_too_large'
|
||||
| 'startup_readiness_timeout'
|
||||
| 'first_output_deadline'
|
||||
| 'acp_response_deadline'
|
||||
| 'post_tool_resume_timeout'
|
||||
| 'tool_execution_failure'
|
||||
| 'child_exit'
|
||||
| 'stream_idle_timeout'
|
||||
| 'empty_completion'
|
||||
| 'transport_failure'
|
||||
| 'unknown';
|
||||
export type TrackingRunFailureDomain =
|
||||
| 'client_product'
|
||||
| 'client_environment'
|
||||
| 'provider_control_plane'
|
||||
| 'policy_admission'
|
||||
| 'cross_boundary'
|
||||
| 'unknown';
|
||||
export type TrackingRunEvidenceLevel =
|
||||
| 'structured_error'
|
||||
| 'structured_code'
|
||||
| 'protocol_error'
|
||||
| 'lifecycle_signal'
|
||||
| 'stderr_fallback'
|
||||
| 'close_reason'
|
||||
| 'legacy_text'
|
||||
| 'unknown';
|
||||
export type TrackingRunRepairOwner =
|
||||
| 'open_design'
|
||||
| 'client_environment'
|
||||
| 'provider_owner'
|
||||
| 'policy_owner'
|
||||
| 'shared_boundary'
|
||||
| 'unknown';
|
||||
export type TrackingRunAdmissionStatus =
|
||||
| 'admitted'
|
||||
| 'rejected_policy'
|
||||
| 'unknown';
|
||||
export type TrackingRunTerminalIntegrity =
|
||||
| 'canonical'
|
||||
| 'late'
|
||||
| 'reconciled'
|
||||
| 'overwritten'
|
||||
| 'permanently_missing'
|
||||
| 'post_terminal_activity';
|
||||
export type TrackingRunLifecyclePhase =
|
||||
| 'queued'
|
||||
| 'prompt_build'
|
||||
@@ -604,4 +660,3 @@ export type TrackingLabsOptOutReason =
|
||||
| 'not_what_i_wanted'
|
||||
| 'other'
|
||||
| 'skipped';
|
||||
|
||||
|
||||
@@ -107,6 +107,13 @@ describe('analytics run_finished contract', () => {
|
||||
error_code: 'RATE_LIMITED',
|
||||
failure_category: 'rate_limit',
|
||||
failure_stage: 'session_init',
|
||||
failure_mechanism: 'policy_rejection',
|
||||
failure_domain: 'policy_admission',
|
||||
evidence_level: 'structured_code',
|
||||
repair_owner: 'policy_owner',
|
||||
admission_status: 'rejected_policy',
|
||||
terminal_integrity: 'reconciled',
|
||||
classifier_version: 'run-failure-v2',
|
||||
retryable: true,
|
||||
user_action: 'retry',
|
||||
terminal_reconciled: true,
|
||||
@@ -241,7 +248,9 @@ describe('analytics run_finished contract', () => {
|
||||
|
||||
expect(payload.props.failure_category).toBe('rate_limit');
|
||||
expect(payload.props.conversation_turn_index).toBe(2);
|
||||
expect(payload.props.failure_stage).toBe('session_init');
|
||||
expect(payload.props.failure_stage).toBe('session_init');
|
||||
expect(payload.props.failure_domain).toBe('policy_admission');
|
||||
expect(payload.props.classifier_version).toBe('run-failure-v2');
|
||||
expect(payload.props.terminal_reconciled).toBe(true);
|
||||
expect(payload.props.terminal_recovery_reason).toBe('daemon_restart');
|
||||
expect(payload.props.user_action).toBe('retry');
|
||||
|
||||
Reference in New Issue
Block a user