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
+130 -4
View File
@@ -6382,6 +6382,118 @@ function compactSkillsSyncStatus(value: unknown, full = false): Record<string, u
return compact;
}
function compactStringArray(value: unknown): string[] {
return Array.from(new Set(asArray(value).map((item) => String(item ?? "")).filter((item) => item.length > 0)));
}
function firstStringField(...values: unknown[]): string | null {
for (const value of values) {
if (typeof value === "string" && value.length > 0) return value;
}
return null;
}
function firstNumberField(...values: unknown[]): number | null {
for (const value of values) {
if (typeof value === "number" && Number.isFinite(value)) return value;
}
return null;
}
function mergeStringArrays(...values: unknown[]): string[] {
return Array.from(new Set(values.flatMap((value) => compactStringArray(value))));
}
function compactPrPreflightSkillsContract(
skills: Record<string, unknown> | null,
skillsSync: Record<string, unknown> | null,
existing: Record<string, unknown> | null = null,
): Record<string, unknown> | null {
if (skills === null && skillsSync === null && existing === null) return null;
const resolution = asRecord(skills?.resolution);
const sourceReport = asRecord(skillsSync?.source);
const targetReport = asRecord(skillsSync?.target);
const expected = asRecord(skillsSync?.expected);
const counts = asRecord(skillsSync?.counts);
const missing = asRecord(skillsSync?.missing);
const existingResolution = asRecord(existing?.resolution);
const source = firstStringField(existing?.source, skills?.source, sourceReport?.path, expected?.source);
const target = firstStringField(existing?.target, skills?.target, targetReport?.path, expected?.target, skills?.path, skills?.resolvedPath);
const resolvedPath = firstStringField(existing?.resolvedPath, skills?.resolvedPath, skills?.path, resolution?.resolvedPath, target);
const sourceMissingSkills = mergeStringArrays(existing?.sourceMissingSkills, skills?.sourceMissingSkills, missing?.sourceSkills, sourceReport?.missingSkills);
const targetMissingSkills = mergeStringArrays(existing?.targetMissingSkills, skills?.targetMissingSkills, missing?.targetSkills, targetReport?.missingSkills);
const requiredSkills = mergeStringArrays(existing?.requiredSkills, skills?.requiredSkills, expected?.requiredSkills, sourceReport?.requiredSkills, targetReport?.requiredSkills);
const missingSkills = mergeStringArrays(existing?.missingSkills, skills?.missingSkills, targetMissingSkills);
const sourceSkillCount = firstNumberField(existing?.sourceSkillCount, skills?.sourceSkillCount, counts?.sourceSkills, sourceReport?.skillCount);
const targetSkillCount = firstNumberField(existing?.targetSkillCount, skills?.targetSkillCount, counts?.targetSkills, targetReport?.skillCount, skills?.skillCount);
const blocker = firstStringField(existing?.degradedReason, existing?.blocker, skills?.degradedReason, skills?.blocker, skillsSync?.blocker);
const contractOk = existing?.ok === true || skills?.contractOk === true || skills?.runnerUsable === true || skills?.ok === true && skillsSync?.ok !== false || skillsSync?.ok === true && skills === null;
const degraded = !contractOk;
const forbiddenPathBlocker = blocker === "forbidden-skills-path-configured" || blocker === "forbidden-skills-path-present";
const sourceHasRequiredSkills = sourceMissingSkills.length === 0
&& (sourceReport?.exists === true && sourceReport?.readable === true || typeof sourceSkillCount === "number" && sourceSkillCount > 0);
const targetProjectionMissing = !forbiddenPathBlocker
&& (
blocker === "skills-target-missing"
|| blocker === "skills-target-not-directory"
|| blocker === "skills-target-permission-failed"
|| blocker === "skills-target-not-readonly"
|| blocker === "required-target-skills-missing"
|| targetMissingSkills.length > 0
|| targetReport?.exists === false
|| skills?.resolvedPathSource === "missing"
);
const hostRolloutRequired = existing?.hostRolloutRequired === true
|| existingResolution?.hostRolloutRequired === true
|| resolution?.hostRolloutRequired === true
|| degraded && sourceHasRequiredSkills && targetProjectionMissing;
const resolvedPathSource = firstStringField(existing?.resolvedPathSource, skills?.resolvedPathSource, resolution?.resolvedPathSource)
?? (targetProjectionMissing ? "missing" : null);
const pathSpelling = compactObjectFields(asRecord(existing?.pathSpelling) ?? asRecord(skills?.pathSpelling) ?? asRecord(skillsSync?.pathSpelling), [
"expectedTarget",
"forbiddenPathChecked",
"forbiddenPathExists",
"forbiddenPathConfigured",
"forbiddenPathRoles",
"forbiddenPathMustNotBeUsed",
]);
const repairHint = firstStringField(existing?.repairHint, skills?.repairHint)
?? (degraded
? hostRolloutRequired && source !== null && target !== null
? `Source ${source} has the required skills; restore the read-only ${source} -> ${target} projection before starting runners.`
: source !== null && target !== null
? `Mount ${source} read-only at ${target}, set UNIDESK_SKILLS_PATH=${target}, and remove any forbidden skills path spelling.`
: "Restore the approved runner skills source/target projection before starting runners."
: null);
return {
ok: contractOk,
degraded,
source,
target,
resolvedPath,
resolvedPathSource,
hostRolloutRequired,
degradedReason: blocker,
blocker,
requiredSkills,
missingSkills,
sourceSkillCount,
targetSkillCount,
sourceMissingSkills,
targetMissingSkills,
sourceHasRequiredSkills,
targetProjected: !targetProjectionMissing && !degraded,
repairHint,
valuesPrinted: false,
...(pathSpelling === null ? {} : { pathSpelling }),
note: degraded
? hostRolloutRequired
? "runner skills source is available, but the read-only target projection is missing and needs controlled host rollout repair"
: "runner skills target projection contract is degraded; use repairHint before starting runners"
: null,
};
}
function codeQueueDevReady(): unknown {
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/dev-ready")));
const devReady = asRecord(response.body.devReady) ?? {};
@@ -6903,7 +7015,19 @@ function compactRecommendedActions(record: Record<string, unknown>): Record<stri
}
function compactPrPreflightCommanderView(record: Record<string, unknown>, options: CodexPrPreflightOptions): Record<string, unknown> {
if (options.full) return record;
const preflight = asRecord(record.preflight);
const normalizedSkillsContract = compactPrPreflightSkillsContract(
compactSkillsStatus(record.skills ?? preflight?.skills),
compactSkillsSyncStatus(record.skillsSync ?? preflight?.skillsSync, options.full),
asRecord(record.skillsContract) ?? asRecord(preflight?.skillsContract),
);
if (options.full) {
return normalizedSkillsContract === null ? record : {
...record,
skillsContract: normalizedSkillsContract,
...(preflight === null ? {} : { preflight: { ...preflight, skillsContract: normalizedSkillsContract } }),
};
}
const tokenCoverage = prPreflightTokenCoverage(record);
const authBroker = prPreflightAuthBroker(record);
@@ -6969,7 +7093,7 @@ function compactPrPreflightCommanderView(record: Record<string, unknown>, option
capabilitySource: authBrokerCapability?.source ?? tokenCoverage?.credentialSource ?? null,
},
},
skillsContract: record.skillsContract ?? undefined,
skillsContract: normalizedSkillsContract ?? undefined,
activeRunnerPrCapability: {
scope: activeRunnerDevContainer.scope ?? "current-cli-process",
tokenCandidatePresent: activeRunnerTokenCandidatePresent,
@@ -7329,6 +7453,7 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
const githubTransient = githubTransientEvidence(pull);
const skillsBlocked = skills !== null && skills.runnerUsable !== true && skills.ok !== true;
const skillsContractDegraded = skills !== null && skills.contractOk !== true;
const skillsContract = compactPrPreflightSkillsContract(skills, skillsSync);
const failureKind = !tokenCoverage.ok
? "auth-missing"
: skillsBlocked
@@ -7375,12 +7500,13 @@ function compactPrRuntimePreflight(preflight: Record<string, unknown>, options:
},
skills,
skillsSync,
skillsContract: {
skillsContract: skillsContract ?? {
ok: !skillsContractDegraded,
degraded: skillsContractDegraded,
hostRolloutRequired: asRecord(skills?.resolution)?.hostRolloutRequired === true,
degradedReason: skills?.degradedReason ?? skills?.blocker ?? skillsSync?.blocker ?? null,
note: skillsContractDegraded ? "runner skills are usable through the resolved path, but the read-only target projection still needs host rollout repair" : null,
valuesPrinted: false,
note: skillsContractDegraded ? "runner skills target projection contract is degraded; use repairHint before starting runners" : null,
},
tokenCoverage,
authBroker: authBrokerNeededStatus(tokenCoverage, authBrokerRuntime, systemGhBinary, unideskGhCli),