fix(code-queue): reject runner skills typo path

This commit is contained in:
Codex
2026-05-23 09:05:23 +00:00
parent a0fb63c098
commit 023509d10a
5 changed files with 167 additions and 8 deletions
@@ -29,6 +29,8 @@ const helpSource = readFileSync("scripts/src/help.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("");
const forbiddenTargetPath = [`/root/${[".ag", "nets"].join("")}`, "skills"].join("/");
const forbiddenSourcePath = [`/home/ubuntu/${[".ag", "nets"].join("")}`, "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");
@@ -74,6 +76,18 @@ assertCondition(missing.blocker === "skills-target-missing", "missing target sho
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");
const typoTarget = collectSkillAvailability({
source: "/home/ubuntu/.agents/skills",
target: forbiddenTargetPath,
requiredSkills: ["docs-spec", "cli-spec"],
});
assertCondition(typoTarget.ok === false, "misspelled target should fail", typoTarget);
assertCondition(typoTarget.degraded === true, "misspelled target should be degraded", typoTarget);
assertCondition(typoTarget.blocker === "forbidden-skills-path-configured", "misspelled target should expose dedicated blocker", typoTarget);
assertCondition(asRecord(typoTarget.pathSpelling, "typoTarget.pathSpelling").forbiddenPathConfigured === true, "misspelled target should mark configured typo", typoTarget.pathSpelling);
assertCondition(JSON.stringify(asRecord(typoTarget.pathSpelling, "typoTarget.pathSpelling").forbiddenPathRoles).includes("target"), "misspelled target should classify target role", typoTarget.pathSpelling);
assertCondition(typoTarget.valuesPrinted === false, "misspelled target report must declare valuesPrinted=false");
const syncDryRun = collectSkillSyncPreflight({
source: "/home/ubuntu/.agents/skills",
target: "/path/that/does/not/exist/for-code-queue-skills-test",
@@ -99,6 +113,17 @@ assertCondition(!JSON.stringify(syncDryRun).includes(forbiddenPathLiteral), "ski
const missingTargetSync = collectSkillSyncPreflight({ target: "/path/that/does/not/exist/for-code-queue-skills-test" });
assertCondition(missingTargetSync.blocker === "unapproved-target", "non-default target must be rejected as unapproved", missingTargetSync);
const typoTargetSync = collectSkillSyncPreflight({ target: forbiddenTargetPath });
assertCondition(typoTargetSync.blocker === "forbidden-skills-path-configured", "misspelled sync target must be rejected as a typo before generic target approval", typoTargetSync);
assertCondition(asRecord(typoTargetSync.pathSpelling, "typoTargetSync.pathSpelling").forbiddenPathConfigured === true, "misspelled sync target should mark configured typo", typoTargetSync.pathSpelling);
assertCondition(JSON.stringify(asRecord(typoTargetSync.pathSpelling, "typoTargetSync.pathSpelling").forbiddenPathRoles).includes("target"), "misspelled sync target should classify target role", typoTargetSync.pathSpelling);
assertCondition(typoTargetSync.valuesPrinted === false, "misspelled sync target must declare valuesPrinted=false");
const typoSourceSync = collectSkillSyncPreflight({ source: forbiddenSourcePath });
assertCondition(typoSourceSync.blocker === "forbidden-skills-path-configured", "misspelled sync source must be rejected as a typo before generic source approval", typoSourceSync);
assertCondition(asRecord(typoSourceSync.pathSpelling, "typoSourceSync.pathSpelling").forbiddenPathConfigured === true, "misspelled sync source should mark configured typo", typoSourceSync.pathSpelling);
assertCondition(JSON.stringify(asRecord(typoSourceSync.pathSpelling, "typoSourceSync.pathSpelling").forbiddenPathRoles).includes("source"), "misspelled sync source should classify source role", typoSourceSync.pathSpelling);
assertCondition(runtimePreflight.includes("skills: SkillAvailabilityReport"), "runtime preflight type must include skills report");
assertCondition(runtimePreflight.includes("skillsSync: SkillSyncPreflightReport"), "runtime preflight type must include skills sync report");
assertCondition(runtimePreflight.includes("collectSkillAvailability"), "runtime preflight must collect skills availability");
@@ -114,9 +139,12 @@ assertCondition(codeQueueCli.includes("Code Queue skills sync dry-run could not
assertCondition(codeQueueCli.includes("compact-skills-sync-control-plane-failure"), "codex skills-sync CLI must keep control-plane failure output compact");
assertCondition(codeQueueCli.includes("compactSkillsSyncStatus"), "codex CLI must compact skills sync output");
assertCondition(codeQueueCli.includes("runner-skills-blocker"), "codex preflight must classify skill lifecycle blockers");
assertCondition(codeQueueCli.includes("forbiddenPathConfigured"), "codex CLI must preserve configured typo classification in compact output");
assertCondition(microserviceCli.includes("compactSkillSync"), "microservice health summary must compact skills sync output");
assertCondition(microserviceCli.includes("forbiddenPathConfigured"), "microservice health summary must preserve configured typo classification");
assertCondition(helpSource.includes("codex skills-sync --dry-run"), "CLI help must document the skills sync dry-run command");
assertCondition(docsReference.includes("codex skills-sync --dry-run"), "reference docs must document the skills sync dry-run command");
assertCondition(docsReference.includes("forbidden-skills-path-configured"), "reference docs must document configured typo blocker");
const skillsPreflightTransport = {
config: null,
@@ -190,6 +218,68 @@ assertCondition(asRecord(preflightSkillsSync.plannedActions, "preflight.skillsSy
assertCondition(preflightSkillsSync.valuesPrinted === false, "full preflight skills sync must declare valuesPrinted=false", preflightSkillsSync);
assertCondition(!JSON.stringify(preflightSkillsSync).includes(forbiddenPathLiteral), "full preflight must not propagate misspelled path literal");
const typoPreflightTransport = {
config: null,
coreFetch: () => ({
ok: true,
status: 200,
body: {
runtimePreflight: {
ok: false,
checkedAt: "2026-05-23T00:00:00.000Z",
cwd: "/workspace/unidesk",
pid: 601,
skills: typoTarget,
skillsSync: typoTargetSync,
ports: {},
pullRequestDelivery: {
ok: true,
checkedAt: "2026-05-23T00:00:00.000Z",
tools: {},
unideskGhCli: { ok: true, path: "/workspace/unidesk/scripts/cli.ts", present: true },
authBroker: { ok: true, configured: true, source: "auth-broker" },
credentials: {
ghTokenPresent: true,
githubTokenPresent: false,
ghHostsConfigPresent: false,
gitCredentialsPresent: false,
},
git: {
insideWorktree: true,
branch: "code-queue/issue-68-runner-skills-lifecycle",
head: "abc1234",
originMaster: "def5678",
remoteOrigin: "git@github.com:pikasTech/unidesk.git",
home: "/root",
homeWritable: true,
knownHostsPresent: true,
privateKeyPresent: true,
},
githubContext: {
host: "github.com",
apiBaseUrl: "https://api.github.com",
repo: "pikasTech/unidesk",
issueProbeNumber: 68,
},
egress: { proxy: {} },
remote: null,
limitations: [],
risks: [],
},
},
},
}),
};
const typoPreflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote"], typoPreflightTransport), "typo preflight summary");
assertCondition(typoPreflightSummary.failureKind === "runner-skills-blocker", "typo preflight should classify configured typo as runner skills blocker", typoPreflightSummary);
assertCondition(typoPreflightSummary.degradedReason === "forbidden-skills-path-configured", "typo preflight degraded reason should preserve configured typo blocker", typoPreflightSummary);
assertCondition(typoPreflightSummary.preflight === undefined, "typo default preflight should remain bounded", typoPreflightSummary);
const typoFullPreflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote", "--full"], typoPreflightTransport), "typo full preflight summary");
const typoFullPreflight = asRecord(typoFullPreflightSummary.preflight, "typoFullPreflightSummary.preflight");
const typoPreflightSkills = asRecord(typoFullPreflight.skills, "typoFullPreflight.skills");
const typoPreflightPathSpelling = asRecord(typoPreflightSkills.pathSpelling, "typoFullPreflight.skills.pathSpelling");
assertCondition(typoPreflightPathSpelling.forbiddenPathConfigured === true, "typo full preflight output must expose configured typo classification", typoPreflightPathSpelling);
const healthSummary = asRecord(summarizeMicroserviceObservation("health", "code-queue", {
ok: true,
status: 200,
@@ -210,6 +300,21 @@ assertCondition(asRecord(healthSkillsSync.counts, "microservice.summary.skillsSy
assertCondition(asRecord(healthSkillsSync.plannedActions, "microservice.summary.skillsSync.plannedActions").copyFromArbitraryPath === false, "compact health must show arbitrary copy is blocked", healthSkillsSync);
assertCondition(!JSON.stringify(healthSkillsSync).includes(forbiddenPathLiteral), "compact health must not propagate misspelled path literal");
const typoHealthSummary = asRecord(summarizeMicroserviceObservation("health", "code-queue", {
ok: true,
status: 200,
body: {
ok: false,
service: "code-queue",
skills: typoTarget,
skillsSync: typoTargetSync,
},
}, []), "microservice typo health summary");
const typoHealthSkills = asRecord(asRecord(asRecord(typoHealthSummary.microservice, "typoHealthSummary.microservice").summary, "typoHealthSummary.microservice.summary").skills, "typoHealthSummary.skills");
const typoHealthPathSpelling = asRecord(typoHealthSkills.pathSpelling, "typoHealthSummary.skills.pathSpelling");
assertCondition(typoHealthSkills.blocker === "forbidden-skills-path-configured", "compact health must preserve configured typo blocker", typoHealthSkills);
assertCondition(typoHealthPathSpelling.forbiddenPathConfigured === true, "compact health must expose configured typo classification", typoHealthPathSpelling);
process.stdout.write(`${JSON.stringify({
ok: true,
checks: [
@@ -218,7 +323,7 @@ process.stdout.write(`${JSON.stringify({
"skills sync dry-run reports source, target, counts, missing skills, permission failures, instructions and no-copy actions",
"runtime-preflight, dev-ready, health and PR preflight use the same structured skill and sync reports",
"default health/preflight summaries expose bounded skills lifecycle evidence and --full expansion",
"misspelled skills paths are only surfaced as a forbidden diagnostic risk",
"misspelled skills paths are rejected with forbidden-skills-path-configured before generic missing/unapproved path blockers",
],
observedRunner: {
source: available.source,
+17 -1
View File
@@ -529,6 +529,15 @@ function asRecord(value: unknown): Record<string, unknown> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
}
function compactObjectFields(record: Record<string, unknown> | null, keys: string[]): Record<string, unknown> | null {
if (record === null) return null;
const selected: Record<string, unknown> = {};
for (const key of keys) {
if (record[key] !== undefined) selected[key] = record[key];
}
return Object.keys(selected).length > 0 ? selected : null;
}
function asArray(value: unknown): unknown[] {
return Array.isArray(value) ? value : [];
}
@@ -4654,7 +4663,14 @@ function compactSkillsStatus(value: unknown): Record<string, unknown> | null {
requiredSkills: Array.isArray(record.requiredSkills) ? record.requiredSkills : [],
missingSkills: Array.isArray(record.missingSkills) ? record.missingSkills : [],
valuesPrinted: record.valuesPrinted ?? false,
pathSpelling: record.pathSpelling ?? null,
pathSpelling: compactObjectFields(asRecord(record.pathSpelling), [
"expectedTarget",
"forbiddenPathChecked",
"forbiddenPathExists",
"forbiddenPathConfigured",
"forbiddenPathRoles",
"forbiddenPathMustNotBeUsed",
]),
repairHint: record.repairHint ?? null,
};
}
+2
View File
@@ -642,6 +642,8 @@ function compactSkillAvailability(value: unknown): Record<string, unknown> | nul
"expectedTarget",
"forbiddenPathChecked",
"forbiddenPathExists",
"forbiddenPathConfigured",
"forbiddenPathRoles",
"forbiddenPathMustNotBeUsed",
]),
repairHint: skills.repairHint ?? null,