diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d204d..23102e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to WASM-OJ are recorded here. Releases follow [Semantic Versioning](https://semver.org/) and the contract/package versioning policy in [the versioning policy](docs/versioning.md). +## Unreleased + +- Accept runtime-bundle interactors (for example CPython) in `Runner.interact` on the server + and in the browser runner Worker. Either side of an interactive session may now be a + standalone Wasm module or a runtime bundle that provides streaming fd 0. + ## 0.2.3 - 2026-10-05 - Download pinned browser toolchain assets before the compiler timeout starts, so slow diff --git a/docs/library-contract.md b/docs/library-contract.md index 6531588..214a5fa 100644 --- a/docs/library-contract.md +++ b/docs/library-contract.md @@ -241,7 +241,8 @@ set/multiset, output-file sets, and standalone sandboxed Wasm checkers. Interactive cases start contestant and interactor concurrently with full-duplex pipes, independent resource policies, process-local deterministic clocks, and secret inputs mounted only on the -interactor side. Runtime bundles that cannot provide streaming fd 0 are rejected for interaction. +interactor side. Either side may be a standalone Wasm module or a runtime bundle such as CPython; +runtime bundles that cannot provide streaming fd 0 are rejected for interaction. Each case executes under the broad hard policy once. Correct output and the same normalized metrics are evaluated against ordered cumulative `baseline`, `efficient`, and `optimal` policies. The diff --git a/src/runtime/runner.worker.ts b/src/runtime/runner.worker.ts index 6041f9b..ebeb2f8 100644 --- a/src/runtime/runner.worker.ts +++ b/src/runtime/runner.worker.ts @@ -412,9 +412,6 @@ async function interactArtifacts( ): Promise { const started = performance.now(); if (!runtimeDrivers) throw new Error("The WASM-OJ runtime-driver registry is not initialized."); - if (request.interactor.kind !== "wasm") { - throw new Error("Interactive judge artifacts must be standalone Wasm modules."); - } progress(request.requestId, "loading-toolchain", "Resolving contestant and interactor runtimes", 0.1); const [contestant, interactor] = await Promise.all([ prepareArtifactInteraction( diff --git a/src/server/judge.integration.test.ts b/src/server/judge.integration.test.ts index 5b7c69e..c333d74 100644 --- a/src/server/judge.integration.test.ts +++ b/src/server/judge.integration.test.ts @@ -130,6 +130,46 @@ describe.skipIf(!enabled)("real server judge contracts", () => { }); }); + it("runs a CPython interactor against a compiled contestant", { timeout: 300_000 }, async () => { + const contestant = await compileC("bundle-contestant", [ + "#include ", + "int main(void) {", + " int challenge = 0;", + " if (scanf(\"%d\", &challenge) != 1) return 2;", + " printf(\"%d\\n\", challenge + 1);", + " fflush(stdout);", + " return 0;", + "}", + ].join("\n")); + const built = await engine.compile({ + projectId: "judge-integration:python-interactor", + name: "python-interactor", + language: "python", + entry: "main.py", + files: { + "main.py": [ + "import sys", + "target = int(open(sys.argv[1]).read())", + "print(target - 1, flush=True)", + "sys.exit(0 if int(input()) == target else 1)", + ].join("\n") + "\n", + }, + }, { cache: false }); + expect(built.success).toBe(true); + expect(built.artifact?.kind).toBe("runtime-bundle"); + + const result = await engine.interact(contestant, built.artifact!, { + interactor: { args: ["/judge/input.txt"], files: { "/judge/input.txt": "42\n" } }, + }); + + expect(result).toMatchObject({ + contestantToInteractor: "42\n", + interactorToContestant: "41\n", + contestant: { code: 0, termination: "exited" }, + interactor: { code: 0, termination: "exited" }, + }); + }); + async function compileC(name: string, source: string): Promise { const entry = `src/${name}.c`; const input: CompileInput = { diff --git a/src/server/server-runner.ts b/src/server/server-runner.ts index 6eaa268..c8f0f35 100644 --- a/src/server/server-runner.ts +++ b/src/server/server-runner.ts @@ -303,9 +303,6 @@ export class ServerRunner implements Runner { if (this.activeOperation || this.cacheClearActive) { throw new Error("ServerRunner accepts one active operation at a time."); } - if (interactorArtifact.kind !== "wasm") { - throw new Error("Interactive judge artifacts must be standalone Wasm modules."); - } const operation = createServerRunOperation(this.generation); this.activeOperation = operation; this.inFlightRuns.add(operation);