diff --git a/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts b/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts index fdde2d14d90..6bbbe3e7585 100644 --- a/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts +++ b/packages/paperclip-runner/src/drivers/codex/codex-harness-session.ts @@ -886,9 +886,11 @@ export class CodexHarnessSession }; } - async close(input?: { reason: string }): Promise { + // `force` is part of the contract. Codex closes the same way with or without it. + async close(input: { reason: string; force?: boolean }): Promise { this.cancelPendingRequests("session_closed"); this.eventQueue.close(); + // Untyped callers (for example generated scripts) may still omit the input. await this.transport.close(input?.reason); } diff --git a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts index 2f19d3aa461..503ef9cf878 100644 --- a/packages/paperclip-runner/src/live/runnerd-codex-transport.ts +++ b/packages/paperclip-runner/src/live/runnerd-codex-transport.ts @@ -2104,7 +2104,8 @@ export function readRunnerdArtifactBinding(runnerBinaryPath: string): { export interface RetainedRunnerdCleanupProof { readonly binding: Readonly; readonly identity: Readonly; - readonly backend: Readonly<{ kind: string; name: string }>; + /** Must match the quarantine domain, which every production backend builds with kind "runner". */ + readonly backend: Readonly<{ kind: "runner"; name: string }>; readonly providerSessionId: string; readonly activationDirectory: string; readonly sourceFingerprint: string; @@ -2400,7 +2401,7 @@ export interface RetainedRunnerdMaintenanceInput { requestId: string; binding: NativeRunIdentity; identity: DurableRecoveryIdentity; - backend: { kind: string; name: string }; + backend: { kind: "runner"; name: string }; stateDirectory: string; activationDirectory: string; sourceFingerprint: string; diff --git a/packages/paperclip-runner/src/live/runnerd-contract-types.test.ts b/packages/paperclip-runner/src/live/runnerd-contract-types.test.ts new file mode 100644 index 00000000000..09f52dacba0 --- /dev/null +++ b/packages/paperclip-runner/src/live/runnerd-contract-types.test.ts @@ -0,0 +1,22 @@ +import { describe, expectTypeOf, it } from "vitest"; +import type { HarnessSession } from "../contracts/harness-driver.js"; +import type { CodexHarnessSession } from "../drivers/codex/codex-harness-session.js"; +import type { RetainedRunnerdCleanupProof, RetainedRunnerdMaintenanceInput } from "./runnerd-codex-transport.js"; + +// The Runner test typecheck checks these assertions. Vitest only runs them. +describe("Runner contract types", () => { + it("accepts only the runner backend kind for retained cleanup", () => { + // The quarantine domain uses the descriptor kind, which is "runner" for every + // production backend. Another kind would release nothing. + expectTypeOf().toEqualTypeOf<"runner">(); + expectTypeOf().toEqualTypeOf<"runner">(); + + // @ts-expect-error: a backend kind other than "runner" is not allowed. + const remote: RetainedRunnerdMaintenanceInput["backend"] = { kind: "remote", name: "codex_app_server" }; + void remote; + }); + + it("matches the HarnessSession close contract in the Codex session", () => { + expectTypeOf>().toEqualTypeOf>(); + }); +});