<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Side-Effect Free Ephemeral Preview & Plugin Whitelisting</title>
<style>
body {
font-family: sans-serif;
line-height: 1.4;
margin: 1.5rem;
}
h1, h2, h3 {
color: #333;
}
code {
background: #f2f2f2;
padding: 0.1rem 0.3rem;
border-radius: 4px;
font-family: Consolas, "Courier New", monospace;
}
.pure {
color: #218c74;
font-weight: bold;
}
.side-effect {
color: #b33939;
font-weight: bold;
}
.maybe {
color: #f39c12;
font-weight: bold;
}
.section {
margin-bottom: 2rem;
}
.fn-list li {
margin: 0.3rem 0;
}
pre code {
white-space: pre;
}
hr {
margin: 2rem 0;
}
</style>
</head>
<body>
<h1>Side-Effect Free Ephemeral Preview & Plugin Whitelisting</h1>
<p>
This document describes how we want to implement an <strong>ephemeral “preview” mode</strong> in the Lava REPL,
where a user’s typed expression is evaluated in a <em>temporary clone</em> of the interpreter state,
to show the result but <strong>without</strong> producing any real side effects.
</p>
<p>
Additionally, we plan to adopt a <strong>default deny</strong> approach:
<em>no bridging function</em> is allowed in ephemeral mode unless it is explicitly declared “preview safe.”
As we progress, we will refine or add “stub” logic for certain side-effectful calls if needed.
</p>
<hr/>
<h2>Plan for Safe Ephemeral Preview with Whitelisting</h2>
<ol>
<li>
<strong>Default Deny:</strong> By default, any plugin function that does not explicitly declare itself
as <em>ephemeral-safe</em> will raise an error or be blocked in the ephemeral environment.
This ensures we do not accidentally run side effects, spawn tasks, or do I/O in the “dry run.”
</li>
<li>
<strong>Opt-In “Preview-Safe” Declarations:</strong> Each bridging call that is purely functional
(e.g., <code>math:plus</code>, <code>string:upper</code>) can be <em>whitelisted</em> so ephemeral calls
proceed normally in the cloned environment. They either do not cause side effects at all, or we
have carefully verified that ephemeral calls are harmless.
</li>
<li>
<strong>Side-Effectful Functions => Forbid or Stub:</strong>
If a function definitely does real I/O or changes global state (like <code>file:write</code> or <code>math:srand</code>),
ephemeral calls by default are <em>forbidden</em>.
Over time, if we want ephemeral usage for certain calls, we can define a “stub” method.
For example, <code>math:srand</code> might do nothing in ephemeral mode, returning a dummy result.
The plugin itself <em>provides</em> that stub if needed.
</li>
<li>
<strong>Use <code>math</code> Plugin as Pilot:</strong>
Because <code>math</code> is mostly pure, we can easily mark <code>math:plus</code>, <code>math:pow</code>,
<code>math:sum</code>, etc. as ephemeral-safe. Meanwhile, we forbid ephemeral usage of <code>math:srand</code>.
We then test ephemeral previews in real code, verifying that:
<ul>
<li>Ephemeral calls to <code>math:plus</code> in the cloned interpreter produce correct results but do not alter the real environment.</li>
<li>Calls to <code>math:srand</code> in ephemeral mode are blocked or stubbed, so we do not truly re-seed in the preview context.</li>
</ul>
This pilot step ensures the system logic works before broadening the approach to all plugins.
</li>
<li>
<strong>Then, Tackle Each Plugin One by One:</strong>
For each plugin:
<ul>
<li>List every bridging function.</li>
<li>Decide if it is <span class="pure">pure</span>, <span class="side-effect">side-effectful</span>,
or <span class="maybe">conditionally side-effectful</span> (e.g., if it calls a user-provided callback that might do I/O).</li>
<li>If side-effectful, consider whether providing a “stub” is beneficial (like returning a dummy result).
If so, the plugin must provide that stub logic. Otherwise, ephemeral calls remain forbidden by default.</li>
<li>Mark each function with the appropriate preview safety classification, so ephemeral mode either calls it, stubs it, or forbids it.</li>
</ul>
</li>
<li>
<strong>Extra Considerations:</strong>
<ul>
<li>We may also need to handle user-supplied callbacks that might have side effects.
For example, <code>array:map</code> is “pure” from the bridging perspective but the user’s function might do printing or file I/O.
The ephemeral logic can skip or block side-effectful callbacks unless we stub them too.</li>
<li>We can eventually refine or enhance “stub” usage so ephemeral calls produce partial or indicative results
(e.g., <code>file:read</code> ephemeral might return <code>"(mock file content)"</code> or an empty string
so the user sees a “shape” of the result, not real I/O).</li>
<li>In many cases, just forbidding ephemeral usage is simpler than stubbing.
Then, if the user tries <code>file:write</code> while typing, ephemeral mode says
<code>"Cannot call file:write in ephemeral mode."</code> which is safer than doing real writes.</li>
</ul>
</li>
</ol>
<hr/>
<h2>Repeating the Explanation: “PreviewSafety” Flag</h2>
<p>
We previously described a pattern for each bridging function to define a metadata field, for example:
</p>
<pre><code>enum PreviewSafety {
Safe, // no side effects -> ephemeral calls are allowed
Forbid, // definitely has side effects -> ephemeral calls blocked
Stub, // ephemeral calls do a custom stub method
}
</code></pre>
<p>
Under a <strong>default deny</strong> approach, if a function does not set <code>preview_safety = Safe</code> or <code>Stub</code>,
it becomes <em>Forbid</em> by default. This ensures no bridging function can slip through ephemeral mode
without an explicit “yes, we are safe” from the plugin developer.
</p>
<hr/>
<h2>List of Functions by Plugin – Purity Classification</h2>
<div class="section">
<h3>Math Plugin</h3>
<ul class="fn-list">
<li>
<code>math:plus</code>, <code>math:pow</code>, <code>math:sqrt</code>, <code>math:sum</code>, etc.
→ <span class="pure">Pure</span> — ephemeral calls are fully safe.
</li>
<li>
<code>math:rand</code> → can be <span class="maybe">maybe pure</span> if it uses a local RNG
that doesn't persist. But if it changes global seeds or a global RNG state,
ephemeral calls must be <span class="side-effect">forbidden or stubbed</span>.
</li>
<li>
<code>math:srand</code> → definitely <span class="side-effect">Side-effectful</span> (reseeding).
Mark as <code>Forbid</code> in ephemeral mode or provide a no-op <em>Stub</em>.
</li>
</ul>
</div>
<div class="section">
<h3>String Plugin</h3>
<ul class="fn-list">
<li>
<code>string:lower</code>, <code>string:upper</code>, <code>string:length</code>, <code>string:concat</code>, <code>string:to_string</code>
→ <span class="pure">Pure</span>.
</li>
</ul>
</div>
<div class="section">
<h3>Array Plugin</h3>
<ul class="fn-list">
<li><code>array:lower</code>, <code>array:upper</code>, <code>array:keys</code>, <code>array:length</code>, <code>array:join</code>
→ <span class="pure">Pure</span> bridging logic.
</li>
<li><code>array:filter</code>, <code>array:map</code>, <code>array:reduce</code>, <code>array:each</code> →
<span class="maybe">Maybe</span> because side effects can happen if the user’s callback is side-effectful.
The bridging itself is pure, but ephemeral calls might need to block or stub user callbacks that do I/O.
</li>
<li><code>array:assoc</code>, <code>array:nth</code>, <code>array:range</code>, <code>array:tail</code>
→ <span class="pure">Pure</span>.
</li>
</ul>
</div>
<div class="section">
<h3>File Plugin</h3>
<ul class="fn-list">
<li><code>file:read</code>, <code>file:write</code>, <code>file:stream</code>
→ <span class="side-effect">Side-effectful I/O</span>.
We forbid ephemeral usage or create stubs that return dummy data if needed.
</li>
</ul>
</div>
<div class="section">
<h3>Net Plugin</h3>
<ul class="fn-list">
<li><code>net:fetch</code>, <code>net:ping</code>, <code>net:stop</code>, <code>poll_events</code>, <code>block_until_no_tasks</code>, <code>embedded_ping</code>
→ <span class="side-effect">Side-effectful</span> (networking, threads).
Typically <code>Forbid</code> ephemeral usage or stub it.
</li>
</ul>
</div>
<div class="section">
<h3>Process Plugin</h3>
<ul class="fn-list">
<li><code>process:info</code>
→ <span class="maybe">Potentially safe</span> (it only reads system info).
We might mark it <code>Safe</code> if we consider reading system info “pure enough.”
</li>
<li><code>process:check_tasks</code>
→ <span class="side-effect">Side-effectful</span>, manipulates background tasks or polls them,
so ephemeral usage might be <code>Forbid</code>.
</li>
</ul>
</div>
<div class="section">
<h3>Test Plugin</h3>
<ul class="fn-list">
<li>
<code>describe</code>, <code>it</code>, <code>expect_equal</code>, <code>expect_not_equal</code>, etc.
→ <span class="side-effect">Side-effectful</span>, storing test state or printing.
Typically forbid ephemeral usage or stub with no test state changes.
</li>
</ul>
</div>
<div class="section">
<h3>Event Plugin</h3>
<ul class="fn-list">
<li><code>event:timeout</code>, <code>event:interval</code>, <code>event:stop</code>, <code>event:check_tasks</code>
→ <span class="side-effect">Side-effectful</span> scheduling tasks.
Ephemeral usage is normally <code>Forbid</code> or maybe <em>Stub</em> to produce dummy handles.
</li>
</ul>
</div>
<div class="section">
<h3>Date Plugin</h3>
<ul class="fn-list">
<li><code>date:timestamp</code>, <code>date:ms</code>, <code>date:micro</code>
→ <span class="pure">Generally pure enough</span> (just reading current time).
We can allow ephemeral calls that simply read the system clock in the ephemeral environment.
</li>
</ul>
</div>
<hr/>
<h2>Conclusion</h2>
<p>
By default, we disallow ephemeral calls on all bridging functions except for those explicitly marked as <code>PreviewSafety::Safe</code> or <code>Stub</code>.
We use the <strong>math plugin</strong> first as a test bed, since <code>math:plus</code> and others are pure numeric transforms.
We’ll see ephemeral previews returning correct results with zero side effects.
Functions like <code>math:srand</code> or <code>file:write</code> remain disallowed until/unless we create stub logic for ephemeral usage or decide to forgo ephemeral calls entirely.
</p>
<p>
Then, one plugin at a time, we’ll document each bridging function as purely functional, side-effectful, or conditionally so.
If side-effectful but we want ephemeral usage, the plugin <em>must provide</em> a <strong>stub function</strong>
(e.g., returning a dummy success) for ephemeral mode. Otherwise ephemeral calls remain blocked.
</p>
<p>
This approach ensures minimal rewriting of existing code,
and a <strong>safe default deny</strong> policy so no new bridging calls are inadvertently run with side effects in ephemeral.
</p>
</body>
</html>