fix: expose code queue runner skills contract

This commit is contained in:
Codex
2026-05-24 05:53:24 +00:00
parent 2b66f329ef
commit 13644484a4
3 changed files with 322 additions and 4 deletions
@@ -405,6 +405,12 @@ const skillsPreflightTransport = {
const defaultPreflightSummary = asRecord(codexPrPreflightQueryForTest(["--remote"], skillsPreflightTransport), "default preflight summary");
assertCondition(defaultPreflightSummary.failureKind === "runner-skills-blocker", "missing target should classify as runner skills blocker even when source exists", defaultPreflightSummary);
assertCondition(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").hostRolloutRequired === true, "default preflight should expose host rollout blocker separately", defaultPreflightSummary);
assertCondition(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").source === missing.source, "default preflight should expose skills source in the bounded contract", defaultPreflightSummary.skillsContract);
assertCondition(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").target === missing.target, "default preflight should expose skills target in the bounded contract", defaultPreflightSummary.skillsContract);
assertCondition(Array.isArray(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").requiredSkills), "default preflight should expose requiredSkills in the bounded contract", defaultPreflightSummary.skillsContract);
assertCondition(Array.isArray(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").missingSkills), "default preflight should expose missingSkills in the bounded contract", defaultPreflightSummary.skillsContract);
assertCondition(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").repairHint !== null, "default preflight should expose repairHint in the bounded contract", defaultPreflightSummary.skillsContract);
assertCondition(asRecord(defaultPreflightSummary.skillsContract, "defaultPreflightSummary.skillsContract").valuesPrinted === false, "default preflight skills contract must declare valuesPrinted=false", defaultPreflightSummary.skillsContract);
assertCondition(defaultPreflightSummary.preflight === undefined, "default PR preflight should omit detailed preflight internals", defaultPreflightSummary);
assertCondition(asRecord(defaultPreflightSummary.disclosure, "defaultPreflightSummary.disclosure").fullDetailOmitted === true, "default PR preflight should disclose full detail omission", defaultPreflightSummary.disclosure);
assertCondition(String(asRecord(defaultPreflightSummary.disclosure, "defaultPreflightSummary.disclosure").expandWith ?? "").includes("--full"), "default PR preflight should point to --full expansion", defaultPreflightSummary.disclosure);
@@ -424,6 +430,190 @@ 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 legacyRuntimeSkills = {
ok: false,
runnerUsable: false,
contractOk: false,
path: "/root/.agents/skills",
resolvedPath: "/root/.agents/skills",
resolvedPathSource: null,
resolution: null,
source: "/home/ubuntu/.agents/skills",
target: "/root/.agents/skills",
exists: false,
available: false,
degraded: true,
blocker: "skills-target-missing",
degradedReason: "skills-target-missing",
readonly: false,
skillCount: 0,
requiredSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
missingSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
valuesPrinted: false,
pathSpelling: {
expectedTarget: "/root/.agents/skills",
forbiddenPathChecked: true,
forbiddenPathExists: false,
forbiddenPathConfigured: false,
forbiddenPathRoles: [],
forbiddenPathMustNotBeUsed: true,
},
repairHint: "Mount /home/ubuntu/.agents/skills read-only at /root/.agents/skills, set UNIDESK_SKILLS_PATH=/root/.agents/skills, and remove any forbidden skills path spelling.",
};
const legacyRuntimeSkillsSync = {
ok: false,
degraded: true,
blocker: "skills-target-missing",
checkedAt: "2026-05-23T00:00:00.000Z",
mode: "dry-run",
dryRun: true,
mutation: false,
syncMode: "hostPath-read-only-projection",
source: {
path: "/home/ubuntu/.agents/skills",
approved: true,
exists: true,
directory: true,
readable: true,
writable: true,
readonly: false,
mountPoint: "/home/ubuntu",
symlink: false,
realPath: null,
skillCount: 49,
version: null,
requiredSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
missingSkills: [],
error: null,
},
target: {
path: "/root/.agents/skills",
approved: true,
exists: false,
directory: false,
readable: false,
writable: false,
readonly: false,
mountPoint: "/",
symlink: false,
realPath: null,
skillCount: 0,
version: null,
requiredSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
missingSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
error: null,
},
expected: {
source: "/home/ubuntu/.agents/skills",
target: "/root/.agents/skills",
env: "UNIDESK_SKILLS_PATH",
envValue: "/root/.agents/skills",
mount: "/home/ubuntu/.agents/skills mounted read-only to /root/.agents/skills",
requiredSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
},
counts: {
sourceSkills: 49,
targetSkills: 0,
requiredSkills: 4,
missingSourceSkills: 0,
missingTargetSkills: 4,
},
version: null,
missing: {
sourceSkills: [],
targetSkills: ["docs-spec", "cli-spec", "frontend-design", "playwright-cli"],
},
permissionFailures: [],
pathSpelling: {
expectedTarget: "/root/.agents/skills",
forbiddenPathChecked: true,
forbiddenPathExists: false,
forbiddenPathConfigured: false,
forbiddenPathRoles: [],
forbiddenPathMustNotBeUsed: true,
},
plannedActions: {
copy: false,
writesSource: false,
writesTarget: false,
restartRequired: false,
readsSecrets: false,
copyFromArbitraryPath: false,
},
commands: {
dryRun: "bun scripts/cli.ts codex skills-sync --dry-run",
full: "bun scripts/cli.ts codex skills-sync --dry-run --full",
health: "bun scripts/cli.ts microservice health code-queue",
runtimePreflight: "bun scripts/cli.ts codex pr-preflight --remote",
contractTest: "bun scripts/code-queue-runner-skills-contract-test.ts",
},
valuesPrinted: false,
};
const legacyRuntimePreflightTransport = {
config: null,
coreFetch: () => ({
ok: true,
status: 200,
body: {
runtimePreflight: {
ok: false,
checkedAt: "2026-05-23T00:00:00.000Z",
cwd: "/workspace/unidesk",
pid: 601,
skills: legacyRuntimeSkills,
skillsSync: legacyRuntimeSkillsSync,
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 legacyDefaultPreflight = asRecord(codexPrPreflightQueryForTest(["--remote"], legacyRuntimePreflightTransport), "legacy default preflight");
const legacySkillsContract = asRecord(legacyDefaultPreflight.skillsContract, "legacyDefaultPreflight.skillsContract");
assertCondition(legacyDefaultPreflight.failureKind === "runner-skills-blocker", "legacy runtime shape should still classify missing target as runner skills blocker", legacyDefaultPreflight);
assertCondition(legacySkillsContract.source === "/home/ubuntu/.agents/skills", "legacy runtime shape should expose source path from skillsSync", legacySkillsContract);
assertCondition(legacySkillsContract.target === "/root/.agents/skills", "legacy runtime shape should expose target path from skillsSync", legacySkillsContract);
assertCondition(legacySkillsContract.hostRolloutRequired === true, "legacy runtime shape with source available and target missing should require host rollout", legacySkillsContract);
assertCondition(legacySkillsContract.degradedReason === "skills-target-missing", "legacy runtime shape should keep actionable degraded reason", legacySkillsContract);
assertCondition(Array.isArray(legacySkillsContract.requiredSkills) && legacySkillsContract.requiredSkills.includes("docs-spec"), "legacy runtime shape should expose requiredSkills", legacySkillsContract);
assertCondition(Array.isArray(legacySkillsContract.missingSkills) && legacySkillsContract.missingSkills.includes("docs-spec"), "legacy runtime shape should expose missingSkills", legacySkillsContract);
assertCondition(legacySkillsContract.sourceSkillCount === 49 && legacySkillsContract.targetSkillCount === 0, "legacy runtime shape should expose source/target skill counts", legacySkillsContract);
assertCondition(legacySkillsContract.repairHint !== null, "legacy runtime shape should expose repairHint", legacySkillsContract);
assertCondition(legacySkillsContract.valuesPrinted === false, "legacy runtime shape contract must declare valuesPrinted=false", legacySkillsContract);
const typoPreflightTransport = {
config: null,
coreFetch: () => ({