From 3ab860412be14acdb2846cdc18282a082ef95602 Mon Sep 17 00:00:00 2001 From: debuggingfuture Date: Wed, 7 Oct 2026 00:10:44 +0800 Subject: [PATCH 1/2] Discover gate red checks against the PR base --- runs/contextful-gate.test.ts | 33 ++++++++++++++++++++++++++------- runs/contextful-gate.ts | 8 ++++---- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/runs/contextful-gate.test.ts b/runs/contextful-gate.test.ts index b6911246..fd154699 100644 --- a/runs/contextful-gate.test.ts +++ b/runs/contextful-gate.test.ts @@ -7,6 +7,7 @@ import { contextfulGate, mergeStages, parseStages } from "./contextful-gate"; const SHA = "a".repeat(40); const BASE_SHA = "b".repeat(40); const DISCOVER = "cargo run --locked -q -p contextful-ci -- stages --parts"; +const DISCOVER_HEAD = `${DISCOVER} --base ${BASE_SHA}`; const input = { repo: "fractalboxdev/contextful", sha: SHA, baseSha: BASE_SHA } as const; describe("contextful-gate", () => { @@ -20,9 +21,11 @@ describe("contextful-gate", () => { expect(parseStages("pins\npins\n")).toBeUndefined(); expect(parseStages("pins\nunsafe; command\n")).toBeUndefined(); expect( - parseStages(Array.from({ length: 33 }, (_, i) => `part${i}`).join("\n")), + parseStages(Array.from({ length: 65 }, (_, i) => `part${i}`).join("\n")), ).toBeUndefined(); - expect(parseStages(" ".repeat(2049))).toBeUndefined(); + expect(parseStages(" ".repeat(4097))).toBeUndefined(); + expect(parseStages(Array.from({ length: 48 }, (_, i) => `part${i}`).join("\n"))) + .toHaveLength(48); }); it("keeps base stages when the head removes them", () => { @@ -78,14 +81,17 @@ describe("contextful-gate", () => { it.effect("spawns at most two stages per wave and reports every stage", () => { const { layer, handles } = makeCFRuntimeTest({ - sandboxProgram: { [DISCOVER]: { stdout: "pins\nworkspace.compile\nformal\n", exitCode: 0 } }, + sandboxProgram: { + [DISCOVER_HEAD]: { stdout: "pins\nworkspace.compile\nformal\ntest-first.contextful-core\n", exitCode: 0 }, + [DISCOVER]: { stdout: "pins\nworkspace.compile\nformal\n", exitCode: 0 }, + }, childRuns: { pollFn: (ids) => ids.map((executionId) => ({ executionId, status: "success" })), }, }); return Effect.gen(function* () { const output = yield* contextfulGate.run(input); - expect(output).toEqual({ stages: 3, failed: [] }); + expect(output).toEqual({ stages: 4, failed: [] }); expect(handles.childRuns.spawned.map((spawn) => spawn.input)).toEqual([ expect.objectContaining({ checkLabel: "pins", @@ -99,6 +105,10 @@ describe("contextful-gate", () => { checkLabel: "formal", command: expect.stringContaining(`--stage formal --base ${BASE_SHA}`), }), + expect.objectContaining({ + checkLabel: "test-first.contextful-core", + command: expect.stringContaining(`--stage test-first.contextful-core --base ${BASE_SHA}`), + }), ]); expect(handles.executions.steps.map((entry) => entry.name)).toEqual([ "checkout", @@ -119,7 +129,10 @@ describe("contextful-gate", () => { it.effect("fails its own check when discovery fails", () => { const { layer, handles } = makeCFRuntimeTest({ - sandboxProgram: { [DISCOVER]: { stdout: "", exitCode: 1 } }, + sandboxProgram: { + [DISCOVER_HEAD]: { stdout: "", exitCode: 1 }, + [DISCOVER]: { stdout: "", exitCode: 1 }, + }, }); return Effect.gen(function* () { const exit = yield* Effect.exit(contextfulGate.run(input)); @@ -130,7 +143,10 @@ describe("contextful-gate", () => { it.effect("continues after a red child and makes the parent red", () => { const { layer, handles } = makeCFRuntimeTest({ - sandboxProgram: { [DISCOVER]: { stdout: "pins\nworkspace.compile\nformal\n", exitCode: 0 } }, + sandboxProgram: { + [DISCOVER_HEAD]: { stdout: "pins\nworkspace.compile\nformal\n", exitCode: 0 }, + [DISCOVER]: { stdout: "pins\nworkspace.compile\nformal\n", exitCode: 0 }, + }, childRuns: { pollFn: (ids) => ids.map((executionId) => ({ @@ -151,7 +167,10 @@ describe("contextful-gate", () => { it.effect("makes the parent red when a queued child never settles", () => { const { layer, handles } = makeCFRuntimeTest({ - sandboxProgram: { [DISCOVER]: { stdout: "pins\n", exitCode: 0 } }, + sandboxProgram: { + [DISCOVER_HEAD]: { stdout: "pins\n", exitCode: 0 }, + [DISCOVER]: { stdout: "pins\n", exitCode: 0 }, + }, childRuns: { pollFn: (ids) => ids.map((executionId) => ({ executionId, status: "missing" })), }, diff --git a/runs/contextful-gate.ts b/runs/contextful-gate.ts index 0fbc5924..52c5b204 100644 --- a/runs/contextful-gate.ts +++ b/runs/contextful-gate.ts @@ -16,8 +16,8 @@ import { const REPO = "fractalboxdev/contextful"; const DISCOVER = "cargo run --locked -q -p contextful-ci -- stages --parts"; const STAGE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/; -const MAX_STAGES = 32; -const MAX_STAGE_LIST_BYTES = 2048; +const MAX_STAGES = 64; +const MAX_STAGE_LIST_BYTES = 4096; const STAGE_CONCURRENCY = 2; const Input = Schema.Struct({ @@ -91,7 +91,7 @@ export const contextfulGate = defineRun({ sandbox.exec({ container: ws.container, cwd: ws.dir, - command: DISCOVER, + command: `${DISCOVER} --base ${input.baseSha}`, timeoutSec: 1800, }), ), @@ -138,7 +138,7 @@ export const contextfulGate = defineRun({ return yield* Effect.fail( new AcceptanceFailed({ exitCode: 1, - summaryMd: "The combined head and base gate stage lists exceed 32 parts.", + summaryMd: "The combined head and base gate stage lists exceed 64 parts.", }), ); } From 1c0b9c2541376349a31eea4e084c0c523bea3ce0 Mon Sep 17 00:00:00 2001 From: debuggingfuture Date: Wed, 7 Oct 2026 00:24:55 +0800 Subject: [PATCH 2/2] Retain discovery compatibility with older gate CLIs --- runs/contextful-gate.test.ts | 54 ++++++++++++++++++++++++++++++++++-- runs/contextful-gate.ts | 28 +++++++++++++++---- 2 files changed, 73 insertions(+), 9 deletions(-) diff --git a/runs/contextful-gate.test.ts b/runs/contextful-gate.test.ts index fd154699..ed61609a 100644 --- a/runs/contextful-gate.test.ts +++ b/runs/contextful-gate.test.ts @@ -24,8 +24,9 @@ describe("contextful-gate", () => { parseStages(Array.from({ length: 65 }, (_, i) => `part${i}`).join("\n")), ).toBeUndefined(); expect(parseStages(" ".repeat(4097))).toBeUndefined(); - expect(parseStages(Array.from({ length: 48 }, (_, i) => `part${i}`).join("\n"))) - .toHaveLength(48); + expect(parseStages(Array.from({ length: 48 }, (_, i) => `part${i}`).join("\n"))).toHaveLength( + 48, + ); }); it("keeps base stages when the head removes them", () => { @@ -82,7 +83,10 @@ describe("contextful-gate", () => { it.effect("spawns at most two stages per wave and reports every stage", () => { const { layer, handles } = makeCFRuntimeTest({ sandboxProgram: { - [DISCOVER_HEAD]: { stdout: "pins\nworkspace.compile\nformal\ntest-first.contextful-core\n", exitCode: 0 }, + [DISCOVER_HEAD]: { + stdout: "pins\nworkspace.compile\nformal\ntest-first.contextful-core\n", + exitCode: 0, + }, [DISCOVER]: { stdout: "pins\nworkspace.compile\nformal\n", exitCode: 0 }, }, childRuns: { @@ -141,6 +145,50 @@ describe("contextful-gate", () => { }).pipe(Effect.provide(layer)); }); + it.effect("discovers older heads only when clap rejects the base argument", () => { + const { layer, handles } = makeCFRuntimeTest({ + sandboxProgram: { + [DISCOVER_HEAD]: { + stdout: "", + stderr: "error: unexpected argument '--base' found\nUsage: contextful-ci stages --parts", + exitCode: 2, + }, + [DISCOVER]: { stdout: "pins\n", exitCode: 0 }, + }, + childRuns: { + pollFn: (ids) => ids.map((executionId) => ({ executionId, status: "success" })), + }, + }); + return Effect.gen(function* () { + const output = yield* contextfulGate.run(input); + expect(output).toEqual({ stages: 1, failed: [] }); + expect( + handles.sandbox.execs + .map((exec) => exec.command) + .filter((command) => command.startsWith("cargo ")), + ).toEqual([DISCOVER_HEAD, DISCOVER, DISCOVER]); + }).pipe(Effect.provide(layer)); + }); + + it.effect("keeps a compilation failure red without compatibility discovery", () => { + const { layer, handles } = makeCFRuntimeTest({ + sandboxProgram: { + [DISCOVER_HEAD]: { stdout: "", stderr: "compilation failed", exitCode: 101 }, + [DISCOVER]: { stdout: "pins\n", exitCode: 0 }, + }, + }); + return Effect.gen(function* () { + const output = yield* Effect.exit(contextfulGate.run(input)); + expect(Exit.isFailure(output)).toBe(true); + expect( + handles.sandbox.execs + .map((exec) => exec.command) + .filter((command) => command.startsWith("cargo ")), + ).toEqual([DISCOVER_HEAD, DISCOVER]); + expect(handles.childRuns.spawned).toHaveLength(0); + }).pipe(Effect.provide(layer)); + }); + it.effect("continues after a red child and makes the parent red", () => { const { layer, handles } = makeCFRuntimeTest({ sandboxProgram: { diff --git a/runs/contextful-gate.ts b/runs/contextful-gate.ts index 52c5b204..8761b45e 100644 --- a/runs/contextful-gate.ts +++ b/runs/contextful-gate.ts @@ -88,12 +88,28 @@ export const contextfulGate = defineRun({ () => ensureWorkspace({ current: { container, dir }, repo: input.repo, sha: input.sha }).pipe( Effect.flatMap((ws) => - sandbox.exec({ - container: ws.container, - cwd: ws.dir, - command: `${DISCOVER} --base ${input.baseSha}`, - timeoutSec: 1800, - }), + sandbox + .exec({ + container: ws.container, + cwd: ws.dir, + command: `${DISCOVER} --base ${input.baseSha}`, + timeoutSec: 1800, + }) + .pipe( + Effect.flatMap((result) => + result.exitCode === 2 && + result.stdout.trim() === "" && + /unexpected argument '--base' found/.test(result.stderr) && + /Usage:/.test(result.stderr) + ? sandbox.exec({ + container: ws.container, + cwd: ws.dir, + command: DISCOVER, + timeoutSec: 1800, + }) + : Effect.succeed(result), + ), + ), ), ), { timeoutSec: 1920, retries: 3, retryOn: ["ExecFailed", "StepFailed", "CheckoutFailed"] },