spike(scripting): JS execution prototype - deterministic Math.random/Date.now#198
Open
ake2l wants to merge 1 commit into
Open
spike(scripting): JS execution prototype - deterministic Math.random/Date.now#198ake2l wants to merge 1 commit into
ake2l wants to merge 1 commit into
Conversation
…Date.now
PROTOTYPE, not wired into the DSL/parser. Evaluates JS via mini-racer
(embedded V8, optional dependency: pip install datamimic_ce[js]) - the
capability {js:...} inline scripts and .js-execute would need, since CE has
no JS engine today.
The part worth prototyping isn't "can we eval JS" - it's that a JS engine
ships nondeterministic builtins (Math.random, Date.now/new Date) inside a
deterministic-first engine (AGENTS.md rule 6: same seed -> same output).
Bridging Math.random through a Python callback per call is possible
(mini-racer's wrap_py_function) but async-only, which doesn't fit CE's
synchronous evaluation model. Instead: derive ONE seed from CE's own RNG
chain (spawn_rng/derive_child_seed, the same helper
BaseDomainGenerator._derive_rng uses) and seed a small pure-JS PRNG
(Mulberry32) with it - no Python<->JS bridge needed per eval call. Date.now
is pinned to the same DETERMINISTIC_ANCHOR every other "now"-derived field in
CE already uses (anchored to UTC explicitly, since the anchor itself is a
naive datetime and .timestamp() on a naive datetime uses local-machine
timezone otherwise).
Verified: same seed -> identical Math.random() sequence across two
separately-constructed engines (both a single draw and a 10-call sequence);
different seeds diverge; Date.now() pins regardless of seed value; an
unseeded engine stays genuinely non-deterministic (matches "no rngSeed =
every run differs, by design").
Known gap, not solved here: new Date() (argument-less) still reads real
wall-clock - only Date.now() is pinned. Fine for a feasibility spike, would
need closing before real use.
Kept as a local, unpushed branch - not a PR. The remaining decision (wire
into the DSL as {js:...}/.js-execute, or not) is a product/security call,
not an engineering one this spike can settle by itself.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Prototype, not a finished feature. Not wired into the DSL/parser - no
{js:...}inline-script support or.js-executeelement yet. This answers one question: can CE run JS deterministically at all, given it's a deterministic-first engine (AGENTS.md rule 6: same seed → same output) and a JS engine ships nondeterministic builtins by default.datamimic_ce/scripting/js_engine.pywraps mini-racer (embedded V8), gated behind a new optional dependency group:pip install datamimic_ce[js]. Not imported anywhere else - zero impact on the base install.Math.random()/Date.now()are real wall-clock/real-random by default. BridgingMath.randomthrough a Python callback per call is possible (mini_racer.wrap_py_function) but async-only, which doesn't fit CE's synchronous evaluation model. Instead: derive one seed from CE's own RNG chain (spawn_rng/derive_child_seed- the same helperBaseDomainGenerator._derive_rngalready uses for every other generator) and seed a small pure-JS PRNG (Mulberry32) with it - no Python↔JS bridge needed per eval call.Date.nowpins to the sameDETERMINISTIC_ANCHORevery other "now"-derived field in CE already uses (explicitly anchored to UTC, since the anchor itself is a naivedatetimeand.timestamp()on a naive datetime uses the local machine's timezone otherwise - would have silently broken cross-machine reproducibility).rngpassed): left alone, nativeMath.random/Date.now- matches "norngSeed= every run differs, by design."Known gap, not solved here: bare
new Date()(argument-less) still reads real wall-clock - onlyDate.now()is pinned. Fine for a feasibility spike, would need closing before real use.Still open, this PR doesn't decide it: whether/how to wire this into the DSL (
{js:...},.js-execute) is a product and security call - executing arbitrary JS is a real surface, and that decision belongs to a human, not this prototype.Test plan
Math.random()sequence across two separately-constructed engines (both a single draw and a 10-call sequence).Date.now()pins regardless of seed value.pytest.importorskip) ifmini-racerisn't installed, so CI without thejsextra is unaffected.