fix: classify provider runner transient failures

This commit is contained in:
Codex
2026-05-21 02:45:28 +00:00
parent 9e4561e102
commit 71cada25f2
8 changed files with 285 additions and 5 deletions
@@ -0,0 +1,69 @@
import { buildProviderTriageResult, type ProviderTriageSignal } from "./src/provider-triage";
import { classifyRunnerError } from "../src/components/microservices/code-queue/src/runner-error-classifier";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function signal(
id: string,
scope: ProviderTriageSignal["scope"],
status: ProviderTriageSignal["status"],
independentPath = true,
): ProviderTriageSignal {
return {
id,
scope,
status,
independentPath,
observedAt: "2026-05-21T00:00:00.000Z",
summary: `${id}:${scope}:${status}`,
};
}
function assertScope(message: string, expectedScope: string, expectedDisposition: string): JsonRecord {
const classification = classifyRunnerError(message, "D601") as unknown as JsonRecord;
assertCondition(classification.scope === expectedScope, `runner error should classify as ${expectedScope}`, classification);
assertCondition(classification.disposition === expectedDisposition, `runner error disposition should be ${expectedDisposition}`, classification);
assertCondition(classification.globalBlocker === false, "single runner error classification must not be global blocker", classification);
assertCondition(classification.retryable === true, "single runner error classification should remain retryable", classification);
assertCondition(String(classification.recommendedTriageCommand ?? "").includes("provider triage D601"), "classification should include triage command", classification);
return classification;
}
export function runProviderRunnerTriageContract(): JsonRecord {
assertScope("provider is not online: D601", "runner-local", "runner-local-observation-gap");
assertScope("provider-gateway http tunnel failed waiting for request", "provider-gateway", "provider-degraded");
assertScope("artifact registry /v2 manifest HEAD failed on 127.0.0.1:5000", "registry", "service-degraded");
assertScope("kubectl get pods failed: k3s api unavailable", "k3s", "service-degraded");
assertScope("code-queue scheduler active run heartbeat is stale", "scheduler", "retryable-transient");
assertScope("unexpected runner process exited with code 1", "unknown", "retryable-transient");
const singlePath = classifyRunnerError("provider is not online: D601", "D601");
const result = buildProviderTriageResult("D601", [
signal("observed-runner-error", singlePath.scope, "failed", false),
signal("backend-core-node", "provider-gateway", "ok"),
signal("host-ssh-probe", "ssh", "ok"),
signal("code-queue-health", "scheduler", "ok"),
], "2026-05-21T00:00:00.000Z");
assertCondition(result.blockingDisposition === "runner-local-observation-gap", "single path provider offline must stay observation gap", result);
assertCondition(result.decision === "retryable-transient", "single path provider offline should be retryable transient", result);
assertCondition(result.retryable === true, "single path provider offline should be retryable", result);
assertCondition(result.contract.singlePathProviderOfflineIsGlobalBlocker === false, "triage contract should reject single-path global blocker", result);
return {
ok: true,
checks: [
"runner error classifier separates runner-local/provider-gateway/registry/k3s/scheduler/unknown",
"each single runner error classification has globalBlocker=false",
"provider triage keeps single provider is not online as retryable-transient, not global-blocker",
],
};
}
if (import.meta.main) {
process.stdout.write(`${JSON.stringify(runProviderRunnerTriageContract(), null, 2)}\n`);
}
+5
View File
@@ -283,6 +283,9 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("scripts/code-queue-trace-summary-contract-test.ts"),
fileItem("scripts/code-queue-pr-preflight-contract-test.ts"),
fileItem("scripts/code-queue-submit-routing-contract-test.ts"),
fileItem("scripts/provider-runner-triage-contract-test.ts"),
fileItem("scripts/src/provider-triage.ts"),
fileItem("src/components/microservices/code-queue/src/runner-error-classifier.ts"),
fileItem("scripts/src/ci.ts"),
fileItem("scripts/src/e2e.ts"),
fileItem("scripts/deploy-artifact-matrix-contract-test.ts"),
@@ -305,6 +308,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("code-queue:trace-summary-contract", ["bun", "scripts/code-queue-trace-summary-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:pr-preflight-contract", ["bun", "scripts/code-queue-pr-preflight-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:submit-routing-contract", ["bun", "scripts/code-queue-submit-routing-contract-test.ts"], 30_000));
items.push(commandItem("provider:runner-triage-contract", ["bun", "scripts/provider-runner-triage-contract-test.ts"], 30_000));
items.push(commandItem("deploy:artifact-matrix-contract", ["bun", "scripts/deploy-artifact-matrix-contract-test.ts"], 30_000));
items.push(commandItem("code-queue:active-run-heartbeat-visible", ["bun", "scripts/code-queue-liveness-diagnostics-test.ts", "--only", "code-queue:active-run-heartbeat-visible"], 30_000));
items.push(commandItem("code-queue:trace-gap-not-stale", ["bun", "scripts/code-queue-liveness-diagnostics-test.ts", "--only", "code-queue:trace-gap-not-stale"], 30_000));
@@ -323,6 +327,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("code-queue:trace-summary-contract", "Code Queue trace summary contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:pr-preflight-contract", "Code Queue PR preflight contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:submit-routing-contract", "Code Queue submit routing contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("provider:runner-triage-contract", "Provider runner triage contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("deploy:artifact-matrix-contract", "deploy artifact matrix contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("code-queue:liveness-diagnostics-fixtures", "Code Queue liveness diagnostics fixtures are opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("baidu-netdisk:artifact-guard-contract", "Baidu Netdisk artifact guard contract is opt-in with script checks", "--scripts-typecheck or --full"));
+8 -1
View File
@@ -3,6 +3,7 @@ import { coreInternalFetch } from "./microservices";
import { debugDispatch, debugHealth } from "./debug";
import { runArtifactRegistryCommand } from "./artifact-registry";
import { runCodeQueueCommand } from "./code-queue";
import { classifyRunnerError } from "../../src/components/microservices/code-queue/src/runner-error-classifier";
export type ProviderSignalScope =
| "runner-local"
@@ -270,6 +271,12 @@ function codeQueueTasksSignal(response: unknown): ProviderTriageSignal {
}
function classifyErrorMessage(message: string): ProviderSignalScope {
const runnerClassification = classifyRunnerError(message);
if (runnerClassification.scope === "provider-gateway") return "provider-gateway";
if (runnerClassification.scope === "registry") return "registry";
if (runnerClassification.scope === "k3s") return "k3s";
if (runnerClassification.scope === "scheduler") return "scheduler";
if (runnerClassification.scope === "runner-local") return "runner-local";
const normalized = message.toLowerCase();
if (/provider is not online|provider .*offline|provider .*not online/u.test(normalized)) return "runner-local";
if (/ssh|host\.ssh/u.test(normalized)) return "ssh";
@@ -282,7 +289,7 @@ function classifyErrorMessage(message: string): ProviderSignalScope {
}
function observedErrorSignal(message: string, scope: ProviderSignalScope): ProviderTriageSignal {
return signal("observed-error", scope, "failed", message, { message }, scope !== "runner-local");
return signal("observed-error", scope, "failed", message, { message, runnerErrorClassification: classifyRunnerError(message) }, scope !== "runner-local");
}
export function providerTriageRecommendedCrossChecks(providerId: string): string[] {