Skip to content

FE-1219: Define and enforce a Scenario HIR for server-side Petrinaut optimization#9060

Open
kube wants to merge 1 commit into
cf/fe-1220-petrinaut-optimization-execution-isolationfrom
cf/fe-1219-petrinaut-scenario-hir
Open

FE-1219: Define and enforce a Scenario HIR for server-side Petrinaut optimization#9060
kube wants to merge 1 commit into
cf/fe-1220-petrinaut-optimization-execution-isolationfrom
cf/fe-1219-petrinaut-scenario-hir

Conversation

@kube

@kube kube commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Closes FE-1219 (part of FE-1217).

Stacked on #9058 (base cf/fe-1220-...).

What

Server-side Petrinaut scenario materialization now goes through the existing restricted HIR pipeline instead of executing manifest-authored strings via sandboxed new Function. This closes the last unrestricted raw-code execution path on the optimizer's server path. The in-editor sandbox path is unchanged.

petrinaut-core

  • Two new HIR surface kinds — scenarioExpression (a single expression over (parameters, scenario) → scalar) and scenarioInit (a statement body → record of place-name → count | token records) — lowered through the generalized wrapped-module machinery, typechecked, and instantiated over compiler-emitted source only (instantiateHirScenarioEvaluator).
  • Scenario orchestration (evaluation order, validation, coercion, rounding, error shapes, and the documented code-mode quirks) extracted to compile-scenario-core.ts, shared by the unchanged sandboxed compileScenario (editor) and the new Node-only compileScenarioProgram (compile once per study, evaluate per trial).
  • Exported only via the Node-only ./compiled-model subpath, so the main entry gains no TypeScript-compiler dependency.
  • Math.random() is a compile error on scenario surfaces (determinism); scenario expressions/init compile at CLI bootstrap, so out-of-subset code fails study startup with the offending manifest path — validation happens before any trial runs.

petrinaut-cli

  • createOptimizationProtocol compiles the scenario program once at bootstrap; per-trial optimization.evaluate runs the pre-instantiated evaluators. The run-with-scenario path compiles once per request. No compileScenario (sandbox) references remain in CLI source.

Security property

Only compiler-emitted source ever reaches new Function; user strings are JSON-quoted by the emitter, and lowering/typecheck reject dynamic import, tagged/template literals, regex, Function, prototype/constructor access, global assignment, and loops. Verified adversarially — no raw-execution path survives.

Review fixes included

  • Reject a const/callback parameter that shadows the scenario/parameters inputs on scenario surfaces (hir:redeclared-parameter), matching the sandbox's SyntaxError so the server no longer accepts what the editor rejects.
  • Documented that server-compiled scenario equality is strict (=====) and can differ from the editor sandbox for mixed-type comparisons (scenarios.md, hir/README.md).

Tests

  • petrinaut-core: 866 vitest
  • petrinaut-cli: 36 vitest
  • petrinaut editor: 150 vitest

Includes a parity suite (HIR program vs sandbox for representative scenarios incl. code-mode colored tokens and the unknown-place-skip quirk), a rejection matrix, and a security test proving zero dynamic evaluation per trial.

Docs

User guide (scenarios.md), OPTIMIZATION_INTEGRATION.md, hir/README.md, the threat model (raw scenario path removed), and a changeset are updated.

🤖 Generated with Claude Code

…optimization

Scenario materialization on the server (Petrinaut CLI) now goes through the
restricted HIR pipeline instead of sandboxed `new Function` on raw manifest
strings:

- New HIR surfaces `scenarioExpression` (parameter overrides, per-place
  initial counts) and `scenarioInit` (code-mode initial state), with
  lowering entry points, surface contexts, typechecking (incl. rejecting
  `Math.random()` for determinism) and object-convention emission.
- `compileScenarioProgram` (Node-only, exported via the `compiled-model`
  subpath) lowers/typechecks/emits/instantiates every scenario surface once
  and returns a program evaluated per run/trial; only compiler-emitted
  source is instantiated.
- The shared orchestration moved to `compile-scenario-core.ts`; the editor's
  sandboxed `compileScenario` is unchanged in behavior and API.
- The CLI compiles the scenario program at bootstrap (manifests with code
  outside the supported subset fail startup listing each offending
  expression path) and evaluates the pre-compiled program per trial; the
  `run` request scenario path uses the same program.
- Docs: HIR README coverage table, scenarios user guide, optimization
  integration guide, and the petrinaut-opt threat model updated.
@kube kube self-assigned this Jul 19, 2026
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hash Ready Ready Preview, Comment Jul 19, 2026 4:10am
petrinaut Ready Ready Preview, Comment Jul 19, 2026 4:10am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Jul 19, 2026 4:10am

@github-actions github-actions Bot added area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps labels Jul 19, 2026
@kube
kube marked this pull request as ready for review July 19, 2026 04:14
@kube
kube requested a review from YannisZa July 19, 2026 04:14
@cursor

cursor Bot commented Jul 19, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Touches the security-critical server path for user-authored optimization manifests; risk is reduced by eliminating raw new Function on manifest text, but correctness now depends on the HIR compiler/emitters and parity with the editor sandbox for edge cases like strict equality.

Overview
Server-side scenario materialization (optimization trials and CLI scenario runs) no longer evaluates manifest strings via sandboxed new Function. Parameter overrides, per-place initial-state expressions, and code-mode initial state are lowered through new HIR surfaces (scenarioExpression, scenarioInit), typechecked, emitted, and compiled once; only compiler-emitted source is instantiated. Per trial/request, the pre-built program runs shared orchestration in compile-scenario-core.ts.

The CLI calls compileScenarioProgram at optimization study bootstrap and once per scenario run request, failing startup with manifest paths when code is outside the supported subset. The in-editor path still uses compileScenario + the existing sandbox.

HIR lowering adds scenario-specific rules (e.g. no Math.random(), no redeclaring scenario/parameters, strict equality). Docs and the optimizer threat model are updated to reflect removal of the raw server execution path.

Reviewed by Cursor Bugbot for commit 718ddcd. Bugbot is set up for automated code reviews on this repo. Configure here.

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

Labels

area/apps area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

2 participants