docs: add code queue minimax routing guard

This commit is contained in:
Codex
2026-05-21 01:14:15 +00:00
parent acc03b10f4
commit a43c24dd88
6 changed files with 387 additions and 10 deletions
@@ -0,0 +1,71 @@
import { codexSubmitRoutingRecommendationForTest } from "./src/code-queue";
type JsonRecord = Record<string, unknown>;
function assertCondition(condition: unknown, message: string, detail: unknown = {}): void {
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
}
function asRecord(value: unknown): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), "expected JSON object", { value });
return value as JsonRecord;
}
const lowRiskPrompt = `
目标:更新 docs/reference/code-queue-supervision.md 中的 MiniMax 派单规则。
范围:只改中文长期文档和一个轻量 dry-run contract test,不触碰 runtime 调度核心。
禁止:不要重启服务,不要读取密钥,不要写数据库,不要部署 prod。
验证:运行 bun scripts/code-queue-submit-routing-contract-test.ts,并在 final response 给出验证证据、commit 和风险。
背景:本 prompt 是完整需求来源,GitHub issue 只能作为辅助引用,不能作为唯一来源。需要 dry-run/preflight 输出帮助指挥官判断 runner/model。请保持改动低风险、可审阅、可回滚,并让指挥官完成后审阅未读任务。
`;
const runtimePrompt = `
目标:修复 Code Queue runtime scheduler 的 active run 状态机。
范围:src/components/microservices/code-queue/src/index.ts 和 runtime-preflight。
禁止:不要部署 prod。
验证:需要证明 scheduler heartbeat、active run、OpenCode session recovery 都正确。
`;
const commanderOnlyPrompt = `
目标:在 production 上 deploy apply 并 restart code-queue,必要时读取 secret token 和写 PostgreSQL 修复任务状态。
验证:live health。
`;
export function runCodeQueueSubmitRoutingContract(): JsonRecord {
const lowRisk = codexSubmitRoutingRecommendationForTest(lowRiskPrompt);
assertCondition(lowRisk.route === "minimax-opencode", "low-risk self-contained prompt should be a MiniMax candidate", lowRisk);
assertCondition(lowRisk.recommendedRunner === "opencode", "MiniMax candidate should recommend OpenCode", lowRisk);
assertCondition(lowRisk.recommendedModel === "minimax-m2.7", "MiniMax candidate should recommend minimax-m2.7", lowRisk);
assertCondition(asRecord(lowRisk.riskControls).promptSelfContained === true, "low-risk prompt should be self-contained", lowRisk);
assertCondition(asRecord(lowRisk.riskControls).issueIsNotOnlySource === true, "issue must not be the only source", lowRisk);
const runtime = codexSubmitRoutingRecommendationForTest(runtimePrompt);
assertCondition(runtime.route === "gpt-5.5-codex", "runtime/core work should stay on GPT-5.5", runtime);
assertCondition(runtime.recommendedRunner === "codex", "runtime/core work should recommend Codex runner", runtime);
assertCondition(runtime.recommendedModel === "gpt-5.5", "runtime/core work should recommend GPT-5.5", runtime);
const commanderOnly = codexSubmitRoutingRecommendationForTest(commanderOnlyPrompt);
assertCondition(commanderOnly.route === "commander-human-only", "prod restart/secrets/DB work should be commander-only", commanderOnly);
assertCondition(commanderOnly.recommendedRunner === "commander", "commander-only work should not recommend a runner", commanderOnly);
assertCondition(commanderOnly.recommendedModel === null, "commander-only work should not recommend a model", commanderOnly);
const explicitGpt = codexSubmitRoutingRecommendationForTest(lowRiskPrompt, "gpt-5.5");
const explicitRequest = asRecord(explicitGpt.explicitRequest);
assertCondition(explicitRequest.runner === "codex", "explicit gpt model should map to Codex", explicitGpt);
assertCondition(String(explicitRequest.note ?? "").includes("differs"), "explicit model mismatch should be visible", explicitGpt);
assertCondition(asRecord(explicitGpt.routingPolicy).doesNotChangeSubmittedPayload === true, "dry-run recommendation must not rewrite payload", explicitGpt);
return {
ok: true,
checks: [
"low-risk self-contained prompts recommend minimax-m2.7/OpenCode",
"runtime/core work recommends GPT-5.5/Codex",
"prod/restart/secret/DB work is commander-only",
"explicit --model mismatch is visible and payload is unchanged",
],
};
}
if (import.meta.main) {
process.stdout.write(`${JSON.stringify(runCodeQueueSubmitRoutingContract(), null, 2)}\n`);
}
+3
View File
@@ -282,6 +282,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
fileItem("scripts/src/code-queue-liveness-fixtures.ts"),
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/src/ci.ts"),
fileItem("scripts/src/e2e.ts"),
fileItem("scripts/deploy-artifact-matrix-contract-test.ts"),
@@ -303,6 +304,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(commandItem("code-queue:issue3-diagnostics-and-image-preflight", ["bun", "scripts/code-queue-issue3-regression-test.ts"], 30_000));
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("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));
@@ -320,6 +322,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
items.push(skippedItem("code-queue:issue3-diagnostics-and-image-preflight", "Code Queue issue #3 regression fixtures are opt-in with script checks", "--scripts-typecheck or --full"));
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("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"));
+276
View File
@@ -11,6 +11,8 @@ const defaultTextPreviewChars = 12_000;
const defaultTasksLimit = 20;
const maxTasksLimit = 100;
const steerPromptPreviewChars = 320;
const minimaxSubmitModel = "minimax-m2.7";
const gptSubmitModel = "gpt-5.5";
const submitLockWaitMs = 60_000;
const submitLockPollMs = 250;
const submitLockStaleMs = 120_000;
@@ -56,6 +58,43 @@ interface CodexSubmitOptions {
dryRun: boolean;
}
type SubmitRoute = "minimax-opencode" | "gpt-5.5-codex" | "commander-human-only";
type SubmitRouteSignalSeverity = "info" | "warning" | "block";
interface SubmitRouteSignal {
id: string;
severity: SubmitRouteSignalSeverity;
matched: boolean;
evidence: string[];
message: string;
}
interface SubmitRoutingRecommendation {
route: SubmitRoute;
recommendedRunner: "opencode" | "codex" | "commander";
recommendedModel: string | null;
confidence: "medium" | "high";
reason: string;
signals: SubmitRouteSignal[];
riskControls: {
promptSelfContained: boolean;
issueIsNotOnlySource: boolean;
noProdRestartSecretOrDbWrite: boolean;
evidenceRequiredByPrompt: boolean;
commanderMustReviewUnread: true;
};
explicitRequest: {
model: string | null;
runner: "opencode" | "codex" | null;
note: string | null;
};
routingPolicy: {
dryRunOnly: true;
doesNotChangeSubmittedPayload: true;
prodMiniMaxAssumedAvailable: false;
};
}
interface CodexSteerOptions {
prompt: string;
dryRun: boolean;
@@ -532,6 +571,222 @@ function compactQueuedReason(value: unknown): Record<string, unknown> | null {
};
}
function normalizeSubmitModel(value: string | null | undefined): string {
const raw = String(value ?? "").trim();
if (raw.length === 0) return raw;
const lower = raw.toLowerCase();
const leaf = lower.includes("/") ? lower.split("/").at(-1) ?? lower : lower;
if (leaf === minimaxSubmitModel || leaf === "m2.7") return minimaxSubmitModel;
return raw;
}
function submitRunnerForModel(model: string | null | undefined): "opencode" | "codex" | null {
const normalized = normalizeSubmitModel(model);
if (normalized.length === 0) return null;
return normalized === minimaxSubmitModel ? "opencode" : "codex";
}
function regexEvidence(text: string, patterns: RegExp[], limit = 6): string[] {
const evidence: string[] = [];
for (const pattern of patterns) {
for (const match of text.matchAll(pattern)) {
const value = String(match[0] ?? "").trim().replace(/\s+/gu, " ");
if (value.length > 0 && !evidence.includes(value)) evidence.push(value);
if (evidence.length >= limit) return evidence;
}
}
return evidence;
}
function regexEvidenceWithoutNegatedContext(text: string, patterns: RegExp[], limit = 6): string[] {
const evidence: string[] = [];
for (const pattern of patterns) {
for (const match of text.matchAll(pattern)) {
const index = match.index ?? 0;
const context = text.slice(Math.max(0, index - 36), Math.min(text.length, index + String(match[0] ?? "").length + 36));
if (/(?:|||||||avoid|forbid|forbidden|do not|don't|must not|no\s+)/iu.test(context)) continue;
const value = String(match[0] ?? "").trim().replace(/\s+/gu, " ");
if (value.length > 0 && !evidence.includes(value)) evidence.push(value);
if (evidence.length >= limit) return evidence;
}
}
return evidence;
}
function routeSignal(id: string, severity: SubmitRouteSignalSeverity, evidence: string[], message: string): SubmitRouteSignal {
return { id, severity, matched: evidence.length > 0, evidence, message };
}
function submitRoutingRecommendation(options: CodexSubmitOptions): SubmitRoutingRecommendation {
const prompt = options.prompt;
const lower = prompt.toLowerCase();
const prodOrStateMutation = regexEvidenceWithoutNegatedContext(lower, [
/\bprod(?:uction)?\b/gu,
/\brestart(?:ing|ed)?\b/gu,
/\brebuild(?:ing|ed)?\b/gu,
/\bdeploy(?:ment|ing|ed)?\b/gu,
/\bserver\s+rebuild\b/gu,
/\bdeploy\s+apply\b/gu,
/\binterrupt\b|\bcancel\b/gu,
/\bsecret\b|\btoken\b|\bapi[_-]?key\b|\bcredential\b/gu,
/\bpostgres(?:ql)?\b|\bpsql\b|\bdatabase\b|\bdb\b|\bsql\b|\bmigration\b/gu,
]);
const runtimeCore = regexEvidence(lower, [
/\bcode[- ]queue\s+(?:runtime|scheduler|backend|execution|runner)\b/gu,
/\bbackend-core\b/gu,
/\bprovider-gateway\b/gu,
/\bk3sctl-adapter\b/gu,
/\bruntime-preflight\b/gu,
/\bactive\s+run\b/gu,
]);
const issueReference = regexEvidence(lower, [
/\bgithub\s+issue\b/gu,
/\bissue\s+#?\d+\b/gu,
/#\d+/gu,
/\bgh\s+issue\s+view\b/gu,
]);
const issueOnly = regexEvidence(lower, [
/\bread\s+(?:the\s+)?issue\b/gu,
/\bsee\s+(?:github\s+)?issue\b/gu,
/\bfrom\s+issue\s+#?\d+\b/gu,
/\bissue\s+(?:has|contains)\s+(?:the\s+)?(?:full|complete)\s+(?:context|requirements?)\b/gu,
/\s*(?:github\s*)?issue/gu,
/\s*(?:github\s*)?issue/gu,
]);
const issueAuxiliaryGuard = regexEvidence(prompt, [
/issue[^\n]*/giu,
/issue[^\n]*/giu,
/issue[^\n]*not[^\n]*only[^\n]*source/giu,
/issue[^\n]*auxiliary[^\n]*reference/giu,
]);
const lowRiskEvidence = regexEvidence(lower, [
/\bdry-run\b/gu,
/\bpreflight\b/gu,
/\bcontract\s+test\b/gu,
/\btypecheck\b/gu,
/\bdocs?\b|\bdocumentation\b/gu,
/\bread[- ]?only\b/gu,
//gu,
//gu,
//gu,
]);
const evidenceRequest = regexEvidence(lower, [
/\bevidence\b/gu,
/\bverification\b|\bverified\b|\bvalidate\b|\bvalidation\b/gu,
/\btest(?:s|ed|ing)?\b/gu,
/\bdry-run\b/gu,
/\bcommit\b/gu,
//gu,
//gu,
//gu,
]);
const selfContainedHints = regexEvidence(prompt, [
/[:]/gu,
/[:]/gu,
/[:]/gu,
/[:]/gu,
/final response/giu,
//gu,
/ prompt/gu,
]);
const destructiveWords = regexEvidenceWithoutNegatedContext(lower, [
/\brm\s+-rf\b/gu,
/\bgit\s+reset\s+--hard\b/gu,
/\bgit\s+checkout\s+--\b/gu,
/\bdrop\s+table\b/gu,
/\btruncate\s+table\b/gu,
/\bdelete\s+from\b/gu,
]);
const crossModule = regexEvidence(lower, [
/\bcross[- ]module\b/gu,
/\barchitecture\b|\barchitectural\b/gu,
/\brelease\/v1\b/gu,
/\bci\/cd\b/gu,
/\brollout\b|\brollback\b/gu,
//gu,
//gu,
/|/gu,
]);
const model = normalizeSubmitModel(options.model);
const explicitRunner = submitRunnerForModel(model);
const promptSelfContained = prompt.length >= 700 || selfContainedHints.length >= 3;
const issueIsNotOnlySource = issueReference.length === 0 || issueAuxiliaryGuard.length > 0 || issueOnly.length === 0 && prompt.length >= 500;
const noProdRestartSecretOrDbWrite = prodOrStateMutation.length === 0 && destructiveWords.length === 0;
const evidenceRequiredByPrompt = evidenceRequest.length > 0;
const signals = [
routeSignal("prod-state-secret-db-write", "block", [...prodOrStateMutation, ...destructiveWords], "Mentions production/state mutation, restart, secrets, DB writes, or destructive commands."),
routeSignal("runtime-core", "warning", runtimeCore, "Touches Code Queue runtime, backend-core, provider-gateway, k3s adapter, or active run behavior."),
routeSignal("issue-source-risk", "warning", issueOnly, "Prompt appears to rely on GitHub issue reading as task context."),
routeSignal("issue-auxiliary-source-guard", "info", issueAuxiliaryGuard, "Prompt explicitly says GitHub issue is auxiliary and not the only source."),
routeSignal("cross-module-release", "warning", crossModule, "Mentions cross-module architecture, CI/CD rollout, release line, or rollback work."),
routeSignal("low-risk-verifiable", "info", lowRiskEvidence, "Mentions low-risk or verifiable work such as docs, read-only checks, dry-run, preflight, or contract tests."),
routeSignal("evidence-requested", "info", evidenceRequest, "Prompt asks for tests, validation, commit, or evidence."),
routeSignal("self-contained-hints", "info", selfContainedHints, "Prompt includes explicit task sections that make it easier to verify without reading an issue."),
];
let route: SubmitRoute = "gpt-5.5-codex";
let recommendedRunner: SubmitRoutingRecommendation["recommendedRunner"] = "codex";
let recommendedModel: string | null = gptSubmitModel;
let confidence: SubmitRoutingRecommendation["confidence"] = "medium";
let reason = "Default to GPT-5.5 when the prompt is not clearly low-risk and self-contained.";
if (prodOrStateMutation.length > 0 || destructiveWords.length > 0) {
route = "commander-human-only";
recommendedRunner = "commander";
recommendedModel = null;
confidence = "high";
reason = "This task mentions production/state mutation, restart, secrets, DB writes, or destructive operations; keep it with the commander or a human.";
} else if (runtimeCore.length > 0 || crossModule.length > 0) {
route = "gpt-5.5-codex";
confidence = "high";
reason = "This task touches runtime/core/cross-module or release-governance surfaces, so it should stay on GPT-5.5.";
} else if (promptSelfContained && issueIsNotOnlySource && evidenceRequiredByPrompt && lowRiskEvidence.length > 0) {
route = "minimax-opencode";
recommendedRunner = "opencode";
recommendedModel = minimaxSubmitModel;
confidence = "high";
reason = "The prompt looks self-contained, low-risk, and asks for verifiable evidence; it is a MiniMax/OpenCode candidate if the runner smoke is currently green.";
} else if (lowRiskEvidence.length > 0 && issueIsNotOnlySource && noProdRestartSecretOrDbWrite) {
route = "minimax-opencode";
recommendedRunner = "opencode";
recommendedModel = minimaxSubmitModel;
confidence = "medium";
reason = "The prompt has low-risk signals, but the commander should tighten self-contained context and evidence requirements before relying on MiniMax.";
}
const explicitNote = model.length === 0
? null
: explicitRunner === recommendedRunner
? "Explicit --model matches the dry-run recommendation."
: "Explicit --model differs from the dry-run recommendation; this dry-run does not rewrite the payload.";
return {
route,
recommendedRunner,
recommendedModel,
confidence,
reason,
signals,
riskControls: {
promptSelfContained,
issueIsNotOnlySource,
noProdRestartSecretOrDbWrite,
evidenceRequiredByPrompt,
commanderMustReviewUnread: true,
},
explicitRequest: {
model: model.length === 0 ? null : model,
runner: explicitRunner,
note: explicitNote,
},
routingPolicy: {
dryRunOnly: true,
doesNotChangeSubmittedPayload: true,
prodMiniMaxAssumedAvailable: false,
},
};
}
function compactSchedulerHeartbeat(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
@@ -2149,6 +2404,21 @@ export function codexPrPreflightQueryForTest(optionArgs: string[], fetcher: Code
return codeQueuePrPreflight(optionArgs, fetcher);
}
export function codexSubmitRoutingRecommendationForTest(prompt: string, model?: string): SubmitRoutingRecommendation {
return submitRoutingRecommendation({
prompt,
model,
queueId: undefined,
providerId: undefined,
cwd: undefined,
reasoningEffort: undefined,
executionMode: undefined,
maxAttempts: undefined,
referenceTaskIds: [],
dryRun: true,
});
}
function codexSubmitTask(args: string[]): unknown {
const options = parseSubmitOptions(args);
const payload = submitPayload(options);
@@ -2156,10 +2426,16 @@ function codexSubmitTask(args: string[]): unknown {
return {
ok: true,
dryRun: true,
routingRecommendation: submitRoutingRecommendation(options),
request: {
...payload,
prompt: textView(options.prompt, true, 3000),
},
commands: {
submitAsRequested: "remove --dry-run to submit exactly this payload",
minimaxCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${minimaxSubmitModel} --dry-run`,
gptCandidate: `bun scripts/cli.ts codex submit --prompt-file <path> --model ${gptSubmitModel} --dry-run`,
},
};
}
const locked = runWithSubmitLock(() => unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/tasks"), { method: "POST", body: payload })));