fix(code-queue): preflight runner skills availability

fix(code-queue): preflight runner skills availability
This commit is contained in:
Lyon
2026-05-22 23:33:02 +08:00
committed by GitHub
8 changed files with 340 additions and 68 deletions
@@ -0,0 +1,92 @@
import { readFileSync } from "node:fs";
import { collectSkillAvailability } from "../src/components/microservices/code-queue/src/skill-availability";
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, label: string): JsonRecord {
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), `${label} must be an object`, value);
return value as JsonRecord;
}
function countOccurrences(haystack: string, needle: string): number {
return haystack.split(needle).length - 1;
}
const productionManifest = readFileSync("src/components/microservices/k3sctl-adapter/k3s/code-queue.k8s.yaml", "utf8");
const devManifest = readFileSync("src/components/microservices/k3sctl-adapter/k3s/dev/unidesk-dev-code-queue.k8s.yaml", "utf8");
const runtimePreflight = readFileSync("src/components/microservices/code-queue/src/runtime-preflight.ts", "utf8");
const indexSource = readFileSync("src/components/microservices/code-queue/src/index.ts", "utf8");
const skillModule = readFileSync("src/components/microservices/code-queue/src/skill-availability.ts", "utf8");
const promptSource = readFileSync("src/components/microservices/code-queue/src/prompts.ts", "utf8");
const docsReference = readFileSync("docs/reference/code-queue-supervision.md", "utf8");
const forbiddenPathLiteral = [".ag", "nets/skills"].join("");
assertCondition(!productionManifest.includes(forbiddenPathLiteral), "production manifest must not propagate misspelled skills path");
assertCondition(!devManifest.includes(forbiddenPathLiteral), "dev manifest must not propagate misspelled skills path");
assertCondition(!promptSource.includes(forbiddenPathLiteral), "runner prompt must not mention misspelled skills path");
assertCondition(!skillModule.includes(forbiddenPathLiteral), "skill availability implementation must not propagate misspelled skills path literal");
assertCondition(!docsReference.includes(forbiddenPathLiteral), "reference docs must not propagate misspelled skills path literal");
assertCondition(countOccurrences(productionManifest, "name: UNIDESK_SKILLS_PATH") === 3, "production read/write/scheduler must set UNIDESK_SKILLS_PATH", {
count: countOccurrences(productionManifest, "name: UNIDESK_SKILLS_PATH"),
});
assertCondition(countOccurrences(productionManifest, "mountPath: /root/.agents/skills") === 3, "production read/write/scheduler must mount skills target", {
count: countOccurrences(productionManifest, "mountPath: /root/.agents/skills"),
});
assertCondition(countOccurrences(productionManifest, "path: /home/ubuntu/.agents/skills") === 3, "production read/write/scheduler must use hostPath source of truth", {
count: countOccurrences(productionManifest, "path: /home/ubuntu/.agents/skills"),
});
assertCondition(countOccurrences(productionManifest, "name: skills-dir") >= 6, "production manifest must define skills-dir mounts and volumes", {
count: countOccurrences(productionManifest, "name: skills-dir"),
});
assertCondition(devManifest.includes("path: /home/ubuntu/.agents/skills"), "dev manifest should keep the same hostPath source of truth");
const available = collectSkillAvailability({
source: "/home/ubuntu/.agents/skills",
target: "/home/ubuntu/.agents/skills",
requiredSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
});
assertCondition(available.source === "/home/ubuntu/.agents/skills", "skill report must expose source");
assertCondition(available.target === "/home/ubuntu/.agents/skills", "skill report must expose target");
assertCondition(Array.isArray(available.requiredSkills) && available.requiredSkills.includes("docs-spec"), "skill report must expose requiredSkills");
assertCondition(Array.isArray(available.missingSkills), "skill report must expose missingSkills");
assertCondition(available.valuesPrinted === false, "skill report must declare valuesPrinted=false");
assertCondition(asRecord(available.pathSpelling, "pathSpelling").forbiddenPathMustNotBeUsed === true, "skill report must flag misspelled path risk without spreading the literal path");
assertCondition(!JSON.stringify(available).includes(forbiddenPathLiteral), "skill report must not propagate misspelled path literal");
assertCondition(!JSON.stringify(available).includes("GH_TOKEN"), "skill report must not include secret environment names unrelated to skills");
const missing = collectSkillAvailability({
source: "/home/ubuntu/.agents/skills",
target: "/path/that/does/not/exist/for-code-queue-skills-test",
requiredSkills: ["docs-spec", "cli-spec"],
});
assertCondition(missing.ok === false, "missing target should fail");
assertCondition(missing.degraded === true, "missing target should be degraded");
assertCondition(missing.blocker === "skills-target-missing", "missing target should expose blocker", missing);
assertCondition(missing.missingSkills.includes("docs-spec") && missing.missingSkills.includes("cli-spec"), "missing target should list required missing skills", missing);
assertCondition(missing.valuesPrinted === false, "missing report must also declare valuesPrinted=false");
assertCondition(runtimePreflight.includes("skills: SkillAvailabilityReport"), "runtime preflight type must include skills report");
assertCondition(runtimePreflight.includes("collectSkillAvailability"), "runtime preflight must collect skills availability");
assertCondition(runtimePreflight.includes("skills.ok && ports.codex.ok"), "runtime preflight ok must depend on skills.ok");
assertCondition(indexSource.includes("const skillsReady = skills.ok === true"), "dev-ready must gate on structured skills ok");
process.stdout.write(`${JSON.stringify({
ok: true,
checks: [
"production Code Queue mounts /home/ubuntu/.agents/skills read-only at /root/.agents/skills",
"skill availability report exposes source, target, requiredSkills, missingSkills, degraded/blocker and valuesPrinted=false",
"runtime-preflight and dev-ready use the same structured skill report",
"misspelled skills paths are only surfaced as a forbidden diagnostic risk",
],
observedRunner: {
source: available.source,
target: available.target,
ok: available.ok,
missingSkills: available.missingSkills,
valuesPrinted: available.valuesPrinted,
},
}, null, 2)}\n`);
+3
View File
@@ -303,6 +303,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-runner-skills-contract-test.ts"),
fileItem("scripts/code-queue-submit-routing-contract-test.ts"),
fileItem("scripts/code-queue-supervisor-disclosure-contract-test.ts"),
fileItem("scripts/host-codex-commander-skeleton-contract-test.ts"),
@@ -339,6 +340,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:runner-skills-contract", ["bun", "scripts/code-queue-runner-skills-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("code-queue:supervisor-disclosure-contract", ["bun", "scripts/code-queue-supervisor-disclosure-contract-test.ts"], 30_000));
items.push(commandItem("host-codex-commander:skeleton-contract", ["bun", "scripts/host-codex-commander-skeleton-contract-test.ts"], 30_000));
@@ -364,6 +366,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:runner-skills-contract", "Code Queue runner skill availability 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("code-queue:supervisor-disclosure-contract", "Code Queue supervisor disclosure contract is opt-in with script checks", "--scripts-typecheck or --full"));
items.push(skippedItem("host-codex-commander:skeleton-contract", "host Codex commander skeleton contract is opt-in with script checks", "--scripts-typecheck or --full"));
+9 -1
View File
@@ -2593,13 +2593,21 @@ function compactSkillsStatus(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
return {
ok: record.ok ?? false,
path: record.path ?? null,
source: record.source ?? null,
target: record.target ?? null,
mountPoint: record.mountPoint ?? null,
exists: record.exists ?? false,
available: record.available ?? false,
degraded: record.degraded ?? true,
blocker: record.blocker ?? null,
readonly: record.readonly ?? false,
skillCount: record.skillCount ?? 0,
cliSpecAvailable: record.cliSpecAvailable ?? false,
requiredSkills: Array.isArray(record.requiredSkills) ? record.requiredSkills : [],
missingSkills: Array.isArray(record.missingSkills) ? record.missingSkills : [],
valuesPrinted: record.valuesPrinted ?? false,
pathSpelling: record.pathSpelling ?? null,
repairHint: record.repairHint ?? null,
};
}