Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/scripts/runners.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ def normalize_mode(raw_mode):
return "default"


def resolve_contract(mode):
def is_enabled(raw_value):
return (raw_value or "").strip().lower() in {"1", "true", "yes", "on", "hosted"}


def resolve_contract(mode, control_fallback=False):
general_medium = BLACKSMITH_4V if mode == "performance" else GITHUB_HOSTED
hot_path = GITHUB_HOSTED if mode == "economic" else BLACKSMITH_4V
control = CONTABO_CONTROL if mode == "default" else GITHUB_HOSTED
# Escape hatch: route control-profile jobs to GitHub-hosted runners when the
# self-hosted pool is unavailable, without changing runner mode.
if control_fallback:
control = GITHUB_HOSTED

return {
"runs_on": {
Expand All @@ -40,12 +48,16 @@ def resolve_contract(mode):
"decision": {
"schema_version": 1,
"mode": mode,
"control_fallback": control_fallback,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding decision.control_fallback changes the runners.py output schema, but the owning workflow contract test still asserts exact equality on that object in e2e/tests/packaged-smoke-workflow.test.ts:1221, :1250, and :1261. On this PR head, python3 .github/scripts/runners.py now emits decision={"schema_version":1,"mode":"default","control_fallback":false}, which no longer matches the existing { schema_version: 1, mode: "default" } expectation, so this PR introduces a concrete regression in the current runner-profile test suite and still leaves the new fallback path unverified. Please update that test section to include the new field in the expected decision payload and add a truthy OD_CI_CONTROL_FALLBACK case that proves only runs_on.control flips to ubuntu-24.04 while the other runner tiers remain mode-driven.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

},
}


def main():
contract = resolve_contract(normalize_mode(os.environ.get("OD_CI_RUNNER_MODE")))
contract = resolve_contract(
normalize_mode(os.environ.get("OD_CI_RUNNER_MODE")),
is_enabled(os.environ.get("OD_CI_CONTROL_FALLBACK")),
)
output_path = os.environ.get("GITHUB_OUTPUT")
lines = [
f"{key}={value if isinstance(value, str) else compact_json(value)}"
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ jobs:
id: runners
env:
OD_CI_RUNNER_MODE: ${{ vars.OD_CI_RUNNER_MODE }}
OD_CI_CONTROL_FALLBACK: ${{ vars.OD_CI_CONTROL_FALLBACK }}
run: python3 .github/scripts/runners.py

scopes:
Expand Down
51 changes: 45 additions & 6 deletions e2e/tests/packaged-smoke-workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,21 @@ if (${JSON.stringify(changedFiles.length > 0)}) process.stdout.write("\\n");
}
}

async function runRunners(mode?: string): Promise<Record<string, string>> {
async function runRunners(
mode?: string,
options: { controlFallback?: boolean } = {},
): Promise<Record<string, string>> {
const env = { ...process.env };
if (mode === undefined) {
delete env.OD_CI_RUNNER_MODE;
} else {
env.OD_CI_RUNNER_MODE = mode;
}
if (options.controlFallback) {
env.OD_CI_CONTROL_FALLBACK = "1";
} else {
delete env.OD_CI_CONTROL_FALLBACK;
}
delete env.GITHUB_OUTPUT;

const { stdout } = await execFileAsync("python3", [runnersScriptPath], {
Expand Down Expand Up @@ -268,8 +276,14 @@ function runnerOutput(profiles: Record<string, string>, key: string): string {
return value;
}

function runnerDecision(profiles: Record<string, string>): { schema_version: number; mode: string } {
return JSON.parse(runnerOutput(profiles, "decision")) as { schema_version: number; mode: string };
function runnerDecision(
profiles: Record<string, string>,
): { schema_version: number; mode: string; control_fallback: boolean } {
return JSON.parse(runnerOutput(profiles, "decision")) as {
schema_version: number;
mode: string;
control_fallback: boolean;
};
}

function runnerRunsOn(profiles: Record<string, string>): Record<string, string[]> {
Expand Down Expand Up @@ -1218,7 +1232,7 @@ process.stdin.on("end", () => {
it("[P2] resolves CI runner profiles by mode", async () => {
const defaultProfiles = await runRunners();
const defaultRunsOn = runnerRunsOn(defaultProfiles);
expect(runnerDecision(defaultProfiles)).toEqual({ schema_version: 1, mode: "default" });
expect(runnerDecision(defaultProfiles)).toEqual({ schema_version: 1, mode: "default", control_fallback: false });
expect(Object.keys(defaultRunsOn).sort()).toEqual([
"control",
"general_medium",
Expand Down Expand Up @@ -1247,7 +1261,7 @@ process.stdin.on("end", () => {

const performanceProfiles = await runRunners("performance");
const performanceRunsOn = runnerRunsOn(performanceProfiles);
expect(runnerDecision(performanceProfiles)).toEqual({ schema_version: 1, mode: "performance" });
expect(runnerDecision(performanceProfiles)).toEqual({ schema_version: 1, mode: "performance", control_fallback: false });
expect(performanceRunsOn.control).toEqual(["ubuntu-24.04"]);
expect(performanceRunsOn.general_medium).toEqual(["blacksmith-4vcpu-ubuntu-2404"]);
expect(performanceRunsOn.workspace_unit).toEqual(["ubuntu-24.04"]);
Expand All @@ -1258,7 +1272,7 @@ process.stdin.on("end", () => {

const economicProfiles = await runRunners("economic");
const economicRunsOn = runnerRunsOn(economicProfiles);
expect(runnerDecision(economicProfiles)).toEqual({ schema_version: 1, mode: "economic" });
expect(runnerDecision(economicProfiles)).toEqual({ schema_version: 1, mode: "economic", control_fallback: false });
expect(economicRunsOn.control).toEqual(["ubuntu-24.04"]);
expect(economicRunsOn.general_medium).toEqual(["ubuntu-24.04"]);
expect(economicRunsOn.workspace_unit).toEqual(["ubuntu-24.04"]);
Expand All @@ -1268,6 +1282,31 @@ process.stdin.on("end", () => {
expect(economicRunsOn.visual_hot).toEqual(["ubuntu-24.04"]);
});

it("[P2] control fallback routes only the control profile to GitHub-hosted", async () => {
const fallbackProfiles = await runRunners(undefined, { controlFallback: true });
const fallbackRunsOn = runnerRunsOn(fallbackProfiles);
expect(runnerDecision(fallbackProfiles)).toEqual({
schema_version: 1,
mode: "default",
control_fallback: true,
});
expect(fallbackRunsOn.control).toEqual(["ubuntu-24.04"]);
expect(fallbackRunsOn.general_medium).toEqual(["ubuntu-24.04"]);
expect(fallbackRunsOn.workspace_unit).toEqual(["ubuntu-24.04"]);
expect(fallbackRunsOn.windows_tools).toEqual(["windows-latest"]);
expect(fallbackRunsOn.js_hot).toEqual(["blacksmith-4vcpu-ubuntu-2404"]);
expect(fallbackRunsOn.ui_hot).toEqual(["blacksmith-4vcpu-ubuntu-2404"]);
expect(fallbackRunsOn.visual_hot).toEqual(["blacksmith-4vcpu-ubuntu-2404"]);

// A non-default mode still only flips control; the other tiers stay mode-driven.
const performanceFallbackRunsOn = runnerRunsOn(
await runRunners("performance", { controlFallback: true }),
);
expect(performanceFallbackRunsOn.control).toEqual(["ubuntu-24.04"]);
expect(performanceFallbackRunsOn.general_medium).toEqual(["blacksmith-4vcpu-ubuntu-2404"]);
expect(performanceFallbackRunsOn.js_hot).toEqual(["blacksmith-4vcpu-ubuntu-2404"]);
});

it("[P2] routes CI follow-ons through generic handoff workflows", async () => {
const [ciWorkflow, commentWorkflow, autofixWorkflow, reportWorkflow, handoffScript] = await Promise.all([
readFile(ciWorkflowPath, "utf8"),
Expand Down
Loading