|
| 1 | +## Context |
| 2 | + |
| 3 | +`secure-exec` runtime architecture is currently optimized for Node-oriented execution drivers and contracts. The current runtime driver naming and shape make Python support ambiguous, while host-facing orchestration needs a stable `exec()` contract across runtimes. |
| 4 | + |
| 5 | +This change introduces an explicit Python runtime path using a Pyodide-backed driver while keeping `SystemDriver` generic and preserving existing permission enforcement behavior. The design must keep warm runtime lifecycle semantics and avoid memory/CPU amplification regressions. |
| 6 | + |
| 7 | +## Goals / Non-Goals |
| 8 | + |
| 9 | +**Goals:** |
| 10 | +- Split runtime-driver interfaces into runtime-specific contracts (`NodeRuntimeDriver`, `PythonRuntimeDriver`) while preserving a shared execution result contract for `exec()`. |
| 11 | +- Add `PythonRuntime` as a user-facing API and implement a concrete Pyodide-backed Python driver. |
| 12 | +- Keep `SystemDriver` runtime-agnostic and reuse existing permissions (`fs`, `network`, `childProcess`, `env`) for Python execution. |
| 13 | +- Define a generic Python `run()` result wrapper that does not expose raw Pyodide-specific objects. |
| 14 | +- Keep warm-runtime behavior for Python execution on a single runtime instance. |
| 15 | +- Add explicit abuse-path coverage (high-volume output and timeout paths). |
| 16 | + |
| 17 | +**Non-Goals:** |
| 18 | +- Dynamic runtime package installation/loading (`micropip`, import-triggered package install) behavior. |
| 19 | +- Non-Pyodide Python backends (for example host subprocess CPython). |
| 20 | +- Full parity between Node `run()` and Python `run()` result semantics. |
| 21 | + |
| 22 | +## Decisions |
| 23 | + |
| 24 | +1. Introduce runtime-specific driver interfaces with a shared execution base contract. |
| 25 | +- Decision: Define separate `NodeRuntimeDriver` and `PythonRuntimeDriver` interfaces, plus a shared `exec` lifecycle contract (`exec`, `dispose`, `terminate`). |
| 26 | +- Rationale: Prevent Node-only and Python-only concerns from leaking into each other while keeping host orchestration uniform. |
| 27 | +- Alternative considered: Keep one generic runtime-driver interface with optional runtime-specific methods. Rejected because it grows ambiguous and weakens type-level guarantees. |
| 28 | + |
| 29 | +2. Keep `SystemDriver` capability-owned and runtime-neutral. |
| 30 | +- Decision: `SystemDriver` remains the single capability/permission boundary for both runtimes. |
| 31 | +- Rationale: Preserves deny-by-default behavior and avoids runtime-specific capability duplication. |
| 32 | +- Alternative considered: Add Python-only capability adapters to `SystemDriver`. Rejected for now to avoid premature surface expansion. |
| 33 | + |
| 34 | +3. Standardize `exec()` parity across Node and Python runtimes. |
| 35 | +- Decision: Node and Python `exec()` return the same base result semantics (`code`, `errorMessage`, timeout/cancel expectations). |
| 36 | +- Rationale: Enables runtime-agnostic host orchestration and simpler control planes. |
| 37 | +- Alternative considered: Runtime-specific `exec()` contracts. Rejected due to host branching and governance complexity. |
| 38 | + |
| 39 | +4. Define Python `run()` as a generic structured wrapper. |
| 40 | +- Decision: `PythonRuntime.run()` returns a structured Python run result wrapper (for example value plus optional selected bindings), not raw Pyodide proxies. |
| 41 | +- Rationale: Keeps API portable and avoids runtime-specific memory/lifecycle leaks through public contracts. |
| 42 | +- Alternative considered: Return raw Pyodide values/proxies. Rejected because it couples API consumers to one backend and increases leak risk. |
| 43 | + |
| 44 | +5. Keep Python runtime warm by default per instance. |
| 45 | +- Decision: Consecutive Python executions on the same `PythonRuntime` share runtime state unless explicitly disposed/terminated. |
| 46 | +- Rationale: Matches current runtime lifecycle expectations and improves performance. |
| 47 | +- Alternative considered: Fresh interpreter per execution. Rejected for now due to startup overhead and changed semantics. |
| 48 | + |
| 49 | +6. Keep package installation/loading out of scope for this change. |
| 50 | +- Decision: Python runtime package installation/loading APIs are not enabled in this change and MUST fail deterministically when invoked. |
| 51 | +- Rationale: Reduces initial attack surface and keeps scope focused on core runtime contracts. |
| 52 | +- Alternative considered: Enable package loading behind permissions in v1. Rejected to avoid mixing supply-chain policy with foundational runtime split. |
| 53 | + |
| 54 | +## Risks / Trade-offs |
| 55 | + |
| 56 | +- [Risk] Warm Python state can leak data across executions on the same instance. → Mitigation: document lifecycle clearly and provide deterministic `dispose`/`terminate` behavior. |
| 57 | +- [Risk] High-volume Python stdout/stderr can amplify host memory/CPU if buffered. → Mitigation: enforce streaming-only output behavior and add exploit-oriented high-volume tests. |
| 58 | +- [Risk] Timeout/cancellation semantics can drift between Node and Python drivers. → Mitigation: codify shared `exec()` parity requirements and cross-runtime contract suites. |
| 59 | +- [Risk] API overfitting to Pyodide backend details. → Mitigation: keep Python runtime public contracts backend-neutral and block direct Pyodide proxy exposure. |
| 60 | + |
| 61 | +## Migration Plan |
| 62 | + |
| 63 | +1. Add new runtime-driver interfaces and type aliases with backwards-compatible export strategy where possible. |
| 64 | +2. Introduce `PythonRuntime` and `PyodideRuntimeDriver` behind additive API exports. |
| 65 | +3. Update Node runtime wiring to use renamed Node-specific driver interface without behavior regressions. |
| 66 | +4. Add shared/runtime-specific contract tests and abuse-path regressions. |
| 67 | +5. Update architecture/docs to include Python runtime components and contracts. |
| 68 | + |
| 69 | +## Open Questions |
| 70 | + |
| 71 | +- Do we need a first-class explicit reset API for warm Python runtime instances in this change or follow-up? |
| 72 | +- Should timeout error text be strictly identical across runtimes or only semantically equivalent under shared `ExecResult` fields? |
0 commit comments