Skip to content

feat(sandbox): Python runner via Piston + SandboxProvider interface [STORY-007]#14

Merged
khoks merged 1 commit intomainfrom
story/007-python-sandbox-runner
Apr 26, 2026
Merged

feat(sandbox): Python runner via Piston + SandboxProvider interface [STORY-007]#14
khoks merged 1 commit intomainfrom
story/007-python-sandbox-runner

Conversation

@khoks
Copy link
Copy Markdown
Owner

@khoks khoks commented Apr 26, 2026

Summary

  • Adds SandboxProvider interface (single method: run) and PistonSandboxProvider with an injectable PistonTransport shim — same architectural pattern as the LLM gateway (interface + injectable transport + zod boundary + telemetry sink).
  • Adds PistonHttpTransport (real fetch against http://localhost:2000, abortable via AbortController).
  • Wires GET /sandbox and POST /sandbox/run on the API. The POST endpoint validates the body via SandboxRunRequestSchema and returns 400 on bad input, 502 on SandboxRequestError.
  • Adds buildSandboxProvider + loadSandboxConfigFromEnv (PISTON_URL env override + LEARNPRO_SANDBOX_CONFIG JSON).
  • 22 unit/registry tests pass with FakePistonTransport; integration test gated on PISTON_URL; 6 API tests cover the new endpoints.

Hardening verification (no-net, ro rootfs, cgroups, seccomp, non-root) is deferred to STORY-010 by design — that Story owns packages/sandbox/test/breakout/ and exercises the real Piston runtime.

Acceptance criteria covered (STORY-007)

  • SandboxProvider interface defined in packages/sandbox/src/provider.ts (one method: run).
  • Piston-Docker impl runs print('hello') and returns the expected stdout.
  • Wall-clock timeout (default 5s) kills runaway code and reports killed_by: 'timeout'.
  • Output is truncated at 64KB and reports killed_by: 'output-limit' if exceeded.
  • Memory cap (default 128MB) is enforced; OOM reports killed_by: 'memory'.
  • socket.socket().connect((...)) raises a network-blocked error → STORY-010.
  • All hardening checklist items from ADR-0002 verified by automated tests → STORY-010.

Test plan

  • pnpm --filter @learnpro/sandbox test — 22 pass, 2 skipped (integration gate)
  • pnpm --filter @learnpro/api test — 6 pass
  • pnpm typecheck — all green
  • pnpm lint — all green
  • pnpm format:check — all green
  • Manual integration: PISTON_URL=http://localhost:2000 pnpm --filter @learnpro/sandbox test after docker compose -f infra/docker/docker-compose.dev.yaml up -d piston

🤖 Generated with Claude Code

…STORY-007]

- `SandboxProvider` interface (one method: `run`) in `packages/sandbox/src/provider.ts`.
- `PistonSandboxProvider` with injectable `PistonTransport` (clean unit-test seam,
  mirrors the LLM gateway pattern).
- `PistonHttpTransport` — real fetch against a self-hosted Piston (default
  `http://localhost:2000`, abortable via `AbortController`).
- Zod-validated boundary: `SandboxRunRequestSchema` (defaults: 5s timeout,
  128MB memory, 64KB output limit) and `SandboxRunResponseSchema`.
- Output truncation + `killed_by` classifier covers `timeout`, `memory`,
  `output-limit`, and `signal`.
- Telemetry sinks: null + in-memory.
- `buildSandboxProvider` factory + `loadSandboxConfigFromEnv` (`PISTON_URL`
  env override + `LEARNPRO_SANDBOX_CONFIG` JSON).
- API: `GET /sandbox` (provider name) + `POST /sandbox/run` (zod-validated body
  → run result; 502 on `SandboxRequestError`).
- Tests: 22 unit/registry pass with `FakePistonTransport`; integration test
  gated on `PISTON_URL`; 6 API tests cover the new endpoints.

Hardening verification (no-net, ro rootfs, cgroups, seccomp, non-root) is
deferred to STORY-010 by design — it owns `packages/sandbox/test/breakout/`.
@khoks khoks merged commit 5f43946 into main Apr 26, 2026
1 check passed
@khoks khoks deleted the story/007-python-sandbox-runner branch April 26, 2026 20:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant