fix: 修复 Code Queue PR preflight fallback

This commit is contained in:
Codex
2026-05-24 03:13:39 +00:00
parent 0159c07652
commit 927ab217ff
2 changed files with 363 additions and 69 deletions
@@ -661,6 +661,135 @@ async function main(): Promise<void> {
assertCondition(githubTransientPreflight.retryable === true, "GitHub transient full preflight should be retryable", githubTransientPreflight);
assertCondition(githubTransientPreflight.commanderAction === "retry-backoff-or-keep-running-if-heartbeat-fresh", "GitHub transient full preflight should expose bounded commander action", githubTransientPreflight);
const k3sTunnelTimeout = {
ok: false,
status: 504,
body: {
ok: false,
error: "provider HTTP tunnel timed out or disconnected",
stage: "http-tunnel-wait",
serviceId: "k3sctl-adapter",
providerId: "D601",
requestId: "req-k3s-timeout",
detail: { retryable: true },
},
};
const fallbackRuntime = rawRuntimePreflightFixture({
pullRequestDelivery: {
...asRecord(rawRuntimePreflightFixture().pullRequestDelivery),
ok: true,
credentials: {
ghTokenPresent: true,
githubTokenPresent: false,
ghHostsConfigPresent: false,
gitCredentialsPresent: false,
},
},
});
const fallbackCalls: string[] = [];
const k3sFallbackRecord = asRecord(await codexPrPreflightQueryForTest(["--remote", "--issue", "20"], {
coreFetch: (path) => {
fallbackCalls.push(path);
if (path.includes("remote=1")) return k3sTunnelTimeout;
return { ok: true, status: 200, body: { runtimePreflight: fallbackRuntime } };
},
}));
assertCondition(fallbackCalls.length === 2, "k3s tunnel timeout should trigger exactly one fallback runtime-preflight fetch", fallbackCalls);
assertCondition(fallbackCalls[0] === "/api/microservices/code-queue/proxy/api/runtime-preflight?remote=1&issue=20", "primary path should request remote runtime probes", fallbackCalls);
assertCondition(fallbackCalls[1] === "/api/microservices/code-queue/proxy/api/runtime-preflight?issue=20", "fallback path should omit remote=1 but keep issue query", fallbackCalls);
assertCondition(k3sFallbackRecord.ok === true, "transport-degraded fallback should preserve observed runtime preflight result", k3sFallbackRecord);
assertCondition(k3sFallbackRecord.failureKind === null, "healthy fallback runtime should not be misclassified as infra-blocked", k3sFallbackRecord);
const k3sFallbackSummary = asRecord(k3sFallbackRecord.summary);
const k3sFallbackTransport = asRecord(k3sFallbackRecord.transportObservation);
const k3sFallbackRuntime = asRecord(k3sFallbackRecord.runtimeObservation);
assertCondition(k3sFallbackSummary.transportState === "transport-degraded", "summary should distinguish degraded transport from runtime observation", k3sFallbackSummary);
assertCondition(k3sFallbackSummary.runtimePreflightState === "runtime-preflight-observed", "summary should expose observed runtime preflight after fallback", k3sFallbackSummary);
assertCondition(k3sFallbackTransport.fallbackUsed === true && k3sFallbackTransport.state === "transport-degraded", "transport observation should mark fallback used", k3sFallbackTransport);
assertCondition(asRecord(k3sFallbackTransport.primary).stage === "http-tunnel-wait", "primary transport summary should retain bounded tunnel stage", k3sFallbackTransport.primary);
assertCondition(asRecord(k3sFallbackTransport.primary).serviceId === "k3sctl-adapter", "primary transport summary should retain service id", k3sFallbackTransport.primary);
assertCondition(k3sFallbackRuntime.source === "fallback-runtime-preflight" && k3sFallbackRuntime.observed === true, "runtime observation should identify fallback source", k3sFallbackRuntime);
assertCondition(k3sFallbackRecord.preflight === undefined, "transport fallback default output should omit detailed preflight", k3sFallbackRecord);
assertCondition(JSON.stringify(k3sFallbackRecord).length < 14000, "transport fallback default output should stay compact", { chars: JSON.stringify(k3sFallbackRecord).length });
const bothPathsFailed = asRecord(await codexPrPreflightQueryForTest(["--remote"], {
coreFetch: (path) => path.includes("remote=1")
? k3sTunnelTimeout
: {
ok: false,
status: 503,
body: {
ok: false,
error: "code-queue runtime unavailable",
stage: "runtime-preflight",
serviceId: "code-queue",
},
},
}));
assertCondition(bothPathsFailed.ok === false, "both unavailable paths should fail", bothPathsFailed);
assertCondition(bothPathsFailed.failureKind === "proxy-gap", "both unavailable paths should remain an infra/proxy observation gap", bothPathsFailed);
assertCondition(bothPathsFailed.degradedReason === "runtime-preflight-transport-unavailable", "both unavailable paths should expose stable degraded reason", bothPathsFailed);
assertCondition(bothPathsFailed.runnerDisposition === "infra-blocked", "both unavailable paths remain infra-blocked", bothPathsFailed);
assertCondition(bothPathsFailed.blockingDisposition === "control-plane-observation-gap", "both unavailable paths should not pretend runner capability was observed", bothPathsFailed);
const bothSummary = asRecord(bothPathsFailed.summary);
const bothTransport = asRecord(bothPathsFailed.transportObservation);
const bothRuntime = asRecord(bothPathsFailed.runtimeObservation);
const bothScheduler = asRecord(bothPathsFailed.schedulerPreflight);
assertCondition(bothSummary.transportState === "transport-blocked", "both paths failed summary should report transport-blocked", bothSummary);
assertCondition(bothSummary.runtimePreflightState === "runtime-preflight-unobserved", "both paths failed summary should report runtime unobserved", bothSummary);
assertCondition(bothSummary.schedulerPreflightAuthReady === null, "transport-blocked should not infer scheduler auth missing", bothSummary);
assertCondition(bothTransport.fallbackUsed === false && bothRuntime.observed === false, "both paths failed should expose fallback attempted but runtime unobserved", { bothTransport, bothRuntime });
assertCondition(bothScheduler.authReady === null && Array.isArray(bothScheduler.missing) && bothScheduler.missing.length === 0, "transport-blocked scheduler preflight should be unknown, not auth-missing", bothScheduler);
const skillsFallbackRecord = asRecord(await codexPrPreflightQueryForTest(["--remote"], {
coreFetch: (path) => path.includes("remote=1")
? k3sTunnelTimeout
: {
ok: true,
status: 200,
body: {
runtimePreflight: rawRuntimePreflightFixture({
ok: false,
skills: {
ok: false,
runnerUsable: false,
contractOk: false,
blocker: "skills-target-missing",
degradedReason: "skills-target-missing",
resolution: { hostRolloutRequired: true },
valuesPrinted: false,
},
skillsSync: {
ok: false,
degraded: true,
blocker: "skills-target-missing",
dryRun: true,
mutation: false,
valuesPrinted: false,
},
pullRequestDelivery: {
...asRecord(rawRuntimePreflightFixture().pullRequestDelivery),
ok: true,
credentials: {
ghTokenPresent: true,
githubTokenPresent: false,
ghHostsConfigPresent: false,
gitCredentialsPresent: false,
},
},
}),
},
},
}));
assertCondition(skillsFallbackRecord.ok === false, "skills blocker observed through fallback should fail preflight", skillsFallbackRecord);
assertCondition(skillsFallbackRecord.failureKind === "runner-skills-blocker", "skills-target-missing should classify as runner capability degraded, not proxy failure", skillsFallbackRecord);
assertCondition(skillsFallbackRecord.degradedReason === "skills-target-missing", "skills degraded reason should preserve skills-target-missing", skillsFallbackRecord);
assertCondition(skillsFallbackRecord.blockingDisposition === "runner-capability-degraded", "skills blocker should expose runner capability degraded disposition", skillsFallbackRecord);
assertCondition(asRecord(skillsFallbackRecord.summary).transportState === "transport-degraded", "skills fallback should still expose degraded transport", skillsFallbackRecord.summary);
assertCondition(asRecord(skillsFallbackRecord.summary).status === "runner-capability-degraded", "skills fallback summary should separate runner capability degradation", skillsFallbackRecord.summary);
assertCondition(asRecord(skillsFallbackRecord.transportObservation).fallbackUsed === true, "skills fallback should mark transport fallback used", skillsFallbackRecord.transportObservation);
assertCondition(asRecord(skillsFallbackRecord.runtimeObservation).observed === true, "skills fallback should report runtime observed", skillsFallbackRecord.runtimeObservation);
assertCondition(asRecord(skillsFallbackRecord.skillsContract).degradedReason === "skills-target-missing", "skills contract should expose missing target", skillsFallbackRecord.skillsContract);
let observedDryRunPath = "";
const dryRunContract = await codexPrPreflightQueryForTest([
"--remote",
@@ -934,6 +1063,9 @@ async function main(): Promise<void> {
"auth missing returns auth-missing with broker/auth-broker-needed",
"proxy failures return proxy-gap",
"GitHub DNS/API failures return github-transient with retry/backoff guidance",
"k3sctl HTTP tunnel 504 falls back to lightweight runtime-preflight observation",
"double runtime-preflight transport failure remains infra-blocked without inferring auth gaps",
"skills-target-missing fallback is runner-capability-degraded, not proxy unavailable",
"git remote failures return git-remote-gap",
"combined push/PR create dry-run contract stays read-only and separates system gh from UniDesk gh CLI",
"broker-issued token, env-token, and missing-token branches expose authBroker source/capability/nextAction",