Skip to content

Commit 9cb32ac

Browse files
committed
refactor: split runtime driver from execution factory
1 parent 5ede2c4 commit 9cb32ac

17 files changed

Lines changed: 213 additions & 41 deletions

File tree

docs-internal/arch/overview.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# Architecture Overview
22

33
```
4-
NodeRuntime → RuntimeDriver → NodeExecutionDriver
5-
(API) (config) (isolated-vm engine)
4+
NodeRuntime → RuntimeDriver + RuntimeExecutionDriverFactory → NodeExecutionDriver
5+
(API) (capability/config + execution wiring) (isolated-vm engine)
66
```
77

88
## NodeRuntime
@@ -14,6 +14,9 @@ Public API. Thin facade — delegates everything to the execution driver.
1414
- `run(code)` — execute as module, get exports back
1515
- `exec(code)` — execute as script, get exit code + stdout/stderr
1616
- `dispose()` / `terminate()`
17+
- Requires both:
18+
- `driver` for runtime capabilities/config
19+
- `executionFactory` for execution-driver construction
1720

1821
## RuntimeDriver
1922

@@ -25,7 +28,12 @@ Config object that bundles what the sandbox can access. Deny-by-default.
2528
- `network` — fetch, DNS, HTTP
2629
- `commandExecutor` — child processes
2730
- `permissions` — per-adapter allow/deny checks
28-
- `runtimeHooks` — factories for isolate + execution driver
31+
32+
## RuntimeExecutionDriverFactory
33+
34+
Factory abstraction for constructing execution drivers from normalized runtime options.
35+
36+
- `createExecutionDriver(options)` — returns a `RuntimeExecutionDriver`
2937

3038
### createNodeDriver()
3139

@@ -36,6 +44,15 @@ Factory that builds a `RuntimeDriver` with Node-native adapters.
3644
- Wraps filesystem in `ModuleAccessFileSystem` (read-only `node_modules` overlay)
3745
- Optionally wires up network and command executor
3846

47+
### createNodeExecutionFactory()
48+
49+
`src/node/driver.ts`
50+
51+
Factory that builds a Node-backed `RuntimeExecutionDriverFactory`.
52+
53+
- Constructs `NodeExecutionDriver` instances
54+
- Owns optional Node-specific isolate creation hook
55+
3956
## NodeExecutionDriver
4057

4158
`src/node/execution-driver.ts`

docs/quickstart.mdx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,12 @@ description: Get secure-exec running from TypeScript in a few minutes.
1111
</Step>
1212
<Step title="Execute code">
1313
```ts
14-
import { NodeRuntime, createNodeDriver } from "secure-exec";
14+
import { NodeRuntime, createNodeDriver, createNodeExecutionFactory } from "secure-exec";
1515

16-
const proc = new NodeRuntime({ driver: createNodeDriver({}) });
16+
const proc = new NodeRuntime({
17+
driver: createNodeDriver({}),
18+
executionFactory: createNodeExecutionFactory(),
19+
});
1720
const logs: string[] = [];
1821
const result = await proc.exec("console.log('hello from secure-exec')", {
1922
onStdio: (event) => {

examples/hono/loader/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
NodeFileSystem,
99
NodeRuntime,
1010
createNodeDriver,
11+
createNodeExecutionFactory,
1112
} from "../../../../packages/secure-exec/src/index.ts";
1213
import {
1314
LOOPBACK_HOST,
@@ -33,6 +34,7 @@ function createProcess(runnerRoot: string, runnerEntry: string): NodeRuntime {
3334

3435
return new NodeRuntime({
3536
driver,
37+
executionFactory: createNodeExecutionFactory(),
3638
});
3739
}
3840

examples/just-bash/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
allowAll,
44
NodeRuntime,
55
createNodeDriver,
6+
createNodeExecutionFactory,
67
type CommandExecutor,
78
type SpawnedProcess,
89
type VirtualFileSystem,
@@ -92,7 +93,10 @@ async function main(): Promise<void> {
9293
permissions: allowAll,
9394
});
9495

95-
const proc = new NodeRuntime({ driver });
96+
const proc = new NodeRuntime({
97+
driver,
98+
executionFactory: createNodeExecutionFactory(),
99+
});
96100
const result = await proc.exec(`
97101
const { execSync } = require('child_process');
98102
const output = execSync('echo hello from bash');
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-03-02
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Context
2+
3+
`RuntimeDriver` currently carries both capability configuration (filesystem/network/child-process/permissions/runtime config) and execution-construction hooks (`runtimeHooks`). This mixes data ownership with engine-construction concerns and makes the generic driver type depend on Node-specific execution wiring.
4+
5+
The goal is to preserve runtime behavior while clarifying boundaries: capability data stays on the driver, and execution construction moves to a separate factory contract.
6+
7+
## Goals / Non-Goals
8+
9+
**Goals:**
10+
- Separate capability/config ownership from execution-engine construction.
11+
- Keep `runtime.process` and `runtime.os` sourced from the driver and injected into execution.
12+
- Preserve deny-by-default semantics and existing run/exec behavior.
13+
- Provide a clean extension point for non-Node execution engines without hook leakage.
14+
15+
**Non-Goals:**
16+
- Reworking module loading/bridge semantics.
17+
- Changing permission policy behavior.
18+
- Restoring browser runtime support in this change.
19+
20+
## Decisions
21+
22+
1. Introduce explicit execution factory contract.
23+
- Decision: Add a dedicated runtime execution factory/provider type and remove `runtimeHooks` from `RuntimeDriver`.
24+
- Rationale: keeps `RuntimeDriver` as pure capability/config surface and removes implicit, optional hook coupling.
25+
- Alternative considered: keep `runtimeHooks` but make fields required. Rejected because it still blends concerns and keeps generic driver tied to execution internals.
26+
27+
2. Update `NodeRuntime` constructor to accept both capability driver and execution factory.
28+
- Decision: `NodeRuntime` requires explicit factory wiring at construction time.
29+
- Rationale: dependency is explicit at API boundary and independent of driver data shape.
30+
- Alternative considered: global/default factory fallback. Rejected because hidden defaults reduce clarity and complicate multi-runtime composition.
31+
32+
3. Keep runtime config injection path unchanged in intent.
33+
- Decision: `process`/`os` config remains on driver and is normalized by `NodeRuntime` before creating execution driver.
34+
- Rationale: preserves current ownership expectations and avoids duplicating runtime config across surfaces.
35+
- Alternative considered: move runtime config into factory options only. Rejected because driver is the canonical capability/config bundle.
36+
37+
4. Keep Node-owned heavy lifting in Node factory/driver modules.
38+
- Decision: Node-specific isolate wiring and execution driver construction stays in Node implementation modules, not generic runtime types.
39+
- Rationale: aligns with driver-owned specialization and reduces generic runtime coupling.
40+
41+
## Risks / Trade-offs
42+
43+
- [Breaking API surface] Constructor/type signatures change for hosts creating `NodeRuntime` directly. → Mitigation: update exports/types in one change and cover with type tests.
44+
- [Migration confusion] Existing code may still expect `driver.runtimeHooks`. → Mitigation: remove hook references in runtime/docs and update Node factory helpers to provide the new execution factory directly.
45+
- [Behavior drift risk] Refactor could accidentally alter deny-by-default behavior. → Mitigation: run targeted permissions/runtime tests and preserve adapter wrapping/stubs paths.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Why
2+
3+
`RuntimeDriver` currently mixes two responsibilities: capability/config data and execution-engine construction via `runtimeHooks`. This coupling leaks Node execution concerns into generic driver typing and makes alternate runtimes harder to add or reason about.
4+
5+
## What Changes
6+
7+
- Remove execution-construction hooks from `RuntimeDriver`.
8+
- Introduce a separate execution-factory contract for constructing `RuntimeExecutionDriver` instances.
9+
- Update `NodeRuntime` construction to require both a capability driver and an execution factory/provider.
10+
- Keep runtime/process/os config sourced from the driver and injected into execution construction.
11+
- Preserve deny-by-default behavior for omitted adapters/permissions.
12+
- Update Node driver assembly so Node-specific execution construction is owned by Node factory code rather than generic driver hooks.
13+
- Update internal architecture docs to reflect the split.
14+
15+
## Capabilities
16+
17+
### New Capabilities
18+
- None.
19+
20+
### Modified Capabilities
21+
- `node-runtime`: Runtime construction contract changes from hook-based execution creation on `RuntimeDriver` to explicit execution-factory wiring while preserving runtime behavior.
22+
23+
## Impact
24+
25+
- Affected code:
26+
- `packages/secure-exec/src/runtime-driver.ts`
27+
- `packages/secure-exec/src/index.ts`
28+
- `packages/secure-exec/src/node/driver.ts`
29+
- `packages/secure-exec/src/node/execution-driver.ts`
30+
- related tests and docs
31+
- API impact:
32+
- **BREAKING**: `NodeRuntime` constructor options and runtime-driver typing change.
33+
- Dependencies:
34+
- No new external dependencies expected.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
## MODIFIED Requirements
2+
3+
### Requirement: Driver-Based Capability Composition
4+
Runtime capabilities SHALL be composed through host-provided drivers so filesystem, network, and child-process behavior are controlled by configured adapters rather than hardcoded runtime behavior. `NodeRuntime` construction SHALL require both a capability driver and an execution factory.
5+
6+
#### Scenario: Node runtime uses configured adapters with explicit execution factory
7+
- **WHEN** `NodeRuntime` is created with a driver that defines filesystem, network, and command-execution adapters and with an execution factory
8+
- **THEN** sandboxed operations MUST route through those adapters for capability access and execution MUST be created through the provided factory
9+
10+
#### Scenario: Missing permissions deny capability access by default
11+
- **WHEN** a driver is configured without explicit permission allowance for a capability domain
12+
- **THEN** operations in that capability domain MUST be denied by default
13+
14+
#### Scenario: Omitted capability remains unavailable
15+
- **WHEN** a capability adapter is omitted from runtime configuration
16+
- **THEN** corresponding sandbox operations MUST be unavailable or denied by the runtime contract
17+
18+
#### Scenario: Runtime process/os config remains driver-owned
19+
- **WHEN** a caller provides runtime `process` and `os` configuration on the driver
20+
- **THEN** `NodeRuntime` MUST source and inject that configuration into execution creation
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## 1. Type and API Surface Split
2+
3+
- [x] 1.1 Remove `runtimeHooks` from `RuntimeDriver` and define a dedicated runtime execution factory/provider contract in `runtime-driver.ts`.
4+
- [x] 1.2 Update `NodeRuntimeOptions` and `NodeRuntime` construction to require both `driver` (capabilities/config) and execution factory/provider.
5+
- [x] 1.3 Update exports and re-exported types so consumers can use the new contracts without importing internal paths.
6+
7+
## 2. Node Implementation Wiring
8+
9+
- [x] 2.1 Refactor Node driver assembly to return pure capability/config driver data and separate Node execution factory wiring.
10+
- [x] 2.2 Update Node execution-driver options/types to consume the new construction contract instead of `driver.runtimeHooks`.
11+
- [x] 2.3 Remove obsolete hook-based wiring and keep runtime process/os config injection behavior unchanged.
12+
13+
## 3. Validation and Docs
14+
15+
- [x] 3.1 Update tests to cover the new constructor/factory wiring and preserve deny-by-default semantics.
16+
- [x] 3.2 Update architecture documentation to describe the split between capability driver and execution factory.
17+
- [x] 3.3 Run targeted typecheck/tests and mark tasks complete.

openspec/specs/node-runtime/spec.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ The project SHALL provide a stable Node sandbox execution interface, with `NodeR
3131
- **THEN** the result's `exports` field MUST be an object containing both the `default` property and all named export properties
3232

3333
### Requirement: Driver-Based Capability Composition
34-
Runtime capabilities SHALL be composed through host-provided drivers so filesystem, network, and child-process behavior are controlled by configured adapters rather than hardcoded runtime behavior. `NodeRuntime` construction SHALL require a driver.
34+
Runtime capabilities SHALL be composed through host-provided drivers so filesystem, network, and child-process behavior are controlled by configured adapters rather than hardcoded runtime behavior. `NodeRuntime` construction SHALL require both a capability driver and an execution factory.
3535

36-
#### Scenario: Node process uses configured adapters
37-
- **WHEN** `NodeRuntime` is created with a driver that defines filesystem, network, and command-execution adapters
38-
- **THEN** sandboxed operations MUST route through those adapters for capability access
36+
#### Scenario: Node runtime uses configured adapters with explicit execution factory
37+
- **WHEN** `NodeRuntime` is created with a driver that defines filesystem, network, and command-execution adapters and with an execution factory
38+
- **THEN** sandboxed operations MUST route through those adapters for capability access and execution MUST be created through the provided factory
3939

4040
#### Scenario: Missing permissions deny capability access by default
4141
- **WHEN** a driver is configured without explicit permission allowance for a capability domain
@@ -45,6 +45,10 @@ Runtime capabilities SHALL be composed through host-provided drivers so filesyst
4545
- **WHEN** a capability adapter is omitted from runtime configuration
4646
- **THEN** corresponding sandbox operations MUST be unavailable or denied by the runtime contract
4747

48+
#### Scenario: Runtime process/os config remains driver-owned
49+
- **WHEN** a caller provides runtime `process` and `os` configuration on the driver
50+
- **THEN** `NodeRuntime` MUST source and inject that configuration into execution creation
51+
4852
### Requirement: Active Handle Completion for Async Operations
4953
The Node runtime SHALL wait for tracked active handles before finalizing execution results so callback-driven asynchronous work can complete.
5054

0 commit comments

Comments
 (0)