Skip to content

refactor(inspector): isolate module setup side effects during project inspection #45

Description

@domutala

Context

runable/inspector deliberately executes runable.config.* files and module setup() hooks while resolving a project.

This preserves fidelity with Runable's real configuration pipeline.

Skipping module setup hooks could result in:

Inspector resolved state ≠ Runable resolved state

The Inspector itself is already isolated from Runable-owned process-global state:

  • it does not generate Runable build artifacts;
  • it does not mutate process.cwd();
  • it does not use or mutate the process-wide loadConfig() cache;
  • each Inspector owns its resolved configuration state.

However, project configuration and module setup() hooks are ordinary user code executed inside the same Node.js process.

They may therefore introduce their own side effects.

Examples

A module can currently do things such as:

setup() {
  process.env.SOME_VALUE = "value";
}

or:

setup() {
  writeFileSync("generated.txt", "...");
}

or mutate:

globalThis
third-party singletons
process-level state
filesystem state

This means two otherwise isolated Inspector instances may still indirectly interfere through arbitrary project code.

Current model

For the initial Inspector and MCP integration, this is an accepted limitation.

The recommended execution model is:

one trusted Runable project
          │
          ▼
one Inspector / MCP process

The Inspector is not a sandbox.

This provides fidelity with the actual Runable configuration pipeline while keeping the implementation simple.

Problem

Future tooling may need stronger isolation.

Examples include:

  • IDE integrations handling multiple workspaces;
  • DevTools inspecting multiple projects;
  • long-running daemons;
  • multi-project MCP servers;
  • hosted tooling.

In those environments, executing arbitrary project configuration and module setup code inside the same Node.js process may not provide sufficient isolation.

Goal

Investigate how Runable can preserve faithful module/configuration resolution while providing stronger isolation for project-defined setup side effects.

Do not solve this by simply skipping setup() hooks.

The Inspector must continue to represent how Runable actually resolves the project.

Possible directions

Investigate approaches such as:

Separate declarative configuration from imperative setup

Allow modules to describe configuration contributions independently from runtime side effects.

Conceptually:

module
 ├── configuration contribution
 └── runtime setup

The Inspector could consume the first while the runtime consumes both.

This would likely require careful API design and backward-compatibility analysis.

Inspection-safe module lifecycle

Explore whether a dedicated inspection/resolution phase could be introduced for modules.

This must not create two incompatible module behaviors.

Process isolation

Execute project configuration resolution in:

  • a worker;
  • a child process;
  • another isolated execution environment.

This could preserve existing module semantics while isolating:

process.env
globalThis
third-party singleton state

Filesystem isolation would require additional consideration.

Explicit metadata

Where appropriate, allow modules to expose metadata/configuration contributions declaratively rather than deriving everything from arbitrary imperative setup code.

Requirements

Any future solution should preserve:

Inspector resolved state ≈ actual Runable resolved state

as closely as possible.

It should not silently produce a simplified project graph just to achieve isolation.

Backward compatibility with existing modules must also be considered.

Acceptance criteria

  • The chosen design preserves fidelity with the configuration Runable actually resolves.
  • Existing module behavior remains backward compatible unless a separately planned breaking change is justified.
  • The isolation boundary is explicitly defined and documented.
  • Inspector consumers can understand which effects are isolated and which remain the project's responsibility.
  • Runtime secrets remain protected by the Inspector public/private boundary.
  • Tests cover the selected isolation model.
  • Concurrent/multi-project behavior is tested where applicable.
  • The solution remains suitable for CLI, MCP, IDE, and DevTools consumers.

Security considerations

The Inspector must continue to treat the inspected project as trusted code unless a future sandboxing model explicitly changes that contract.

In particular, the existence of the Inspector must not imply that:

runable.config.*
module setup()

are safe to execute from an untrusted repository.

Current recommendation

Until stronger isolation is implemented:

Use one Inspector/MCP process per trusted Runable project or workspace.

Do not treat runable/inspector as a sandbox.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions