FE-1485: Synthesize ad-hoc scenarios from an inline definition - #9284
FE-1485: Synthesize ad-hoc scenarios from an inline definition#9284kube wants to merge 7 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
21c1cd9 to
a25badb
Compare
a25badb to
c83cb44
Compare
c018875 to
e43bd4a
Compare
|
Semgrep found 8
RegExp() called with a View Dataflow Graphflowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L597 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 597] state</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L597 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 597] state</a>"]
v3["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L642 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 642] list</a>"]
v4["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L649 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 649] ?.</a>"]
v5["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L649 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 649] oldName</a>"]
v6["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L671 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 671] rewriteReferencesForRename</a>"]
v7["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L544 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 544] oldName</a>"]
v8["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L549 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 549] rewriteAdHocReference</a>"]
v9["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L467 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 467] oldName</a>"]
v10["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L470 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 470] escapeForPattern</a>"]
v11["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L456 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 456] name</a>"]
v12["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L470 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 470] escaped</a>"]
v13["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L481 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 481] `</a>"]
end
v2 --> v3
v3 --> v4
v4 --> v5
v5 --> v6
v6 --> v7
v7 --> v8
v8 --> v9
v9 --> v10
v10 --> v11
v11 --> v12
v12 --> v13
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/hashintel/hash/blob/e43bd4aac3d83ed36b1e9600bb0dd907c5d111fe/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc-actions.ts#L481 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 481] new RegExp(String.raw`(?<![.\w$])${escaped}(?![A-Za-z0-9_$])`, "g")</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
|
An ad-hoc scenario is an initial-state + parameters definition made inline — per-place token spreadsheets whose cells are expressions, a Static or Dynamic count per place, and named Variables at two scopes — compiled into a Scenario value at run time and never persisted into the net file. Synthesis emits a code-mode scenario, the one initial-state mode whose expressions the existing compileScenario already evaluates in its hardened sandbox, so the compiler is untouched. synthesizeAdHocScenario ignores Optimize toggles and serves quick simulation and plain experiments. synthesizeAdHocOptimization turns every Optimize selection (cell, Variable, dynamic count, net parameter) into a generated scenario parameter with a deterministic source-derived name — the join key from optimization results back to what the user selected — and returns the manifest parameterBindings for them. Bounds are expressions that must resolve to constants at study creation; an optimized bound may not reference an optimized entity; per-place Variables evaluate per row, so they may read i. The tests round-trip every fabricated scenario through the real compileScenario.
Top-level Variables are referenced as scenario.<name>, matching the scenario code editor. Synthesis emits AdHocSynthesisOutput (scenario + optimized fields with targets, labels, and domains) with adHocOptimizationBindings as the manifest transform. Adds slot keys and target labels for error and diagnostic attribution, the row-kind gutter cycle, place totals, and an ad-hoc LSP session that type-checks every value slot.
The override wrapper captures the compiler's scenario object before the Variables shadow it, so an override reading an optimized Variable no longer evaluates to undefined. Synthesis rejects empty required expressions at their slot, two participating places sharing a name (the generated initial state is keyed by name), integer steps that cannot reach the maximum, and logarithmic integer steps other than 1; integer preview defaults round; a share whose element no longer exists is ignored rather than fatal. Slot keys escape ids without percent signs or underscores so Monaco's URI normalization cannot break the round-trip, and a place identified "net" cannot collide with the top-level scope. The LSP scope of a place variable's documents covers only earlier siblings, mirroring the generated declaration order, and reserved names are kept out of ambient declarations. The gutter cycle can skip its count-Optimized stage for consumers without Optimize.
… cross-scope names
152c069 to
f6d051e
Compare
|
Semgrep found 8
RegExp() called with a View Dataflow Graphflowchart LR
classDef invis fill:white, stroke: none
classDef default fill:#e7f5ff, color:#1c7fd6, stroke: none
subgraph File0["<b>libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts</b>"]
direction LR
%% Source
subgraph Source
direction LR
v0["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L699 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 699] state</a>"]
end
%% Intermediate
subgraph Traces0[Traces]
direction TB
v2["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L699 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 699] state</a>"]
v3["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L749 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 749] list</a>"]
v4["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L756 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 756] ?.</a>"]
v5["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L756 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 756] oldName</a>"]
v6["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L771 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 771] rewriteReferencesForRename</a>"]
v7["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L643 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 643] oldName</a>"]
v8["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L648 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 648] rewriteAdHocReference</a>"]
v9["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L542 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 542] oldName</a>"]
v10["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L545 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 545] escapeForPattern</a>"]
v11["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L531 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 531] name</a>"]
v12["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L545 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 545] escaped</a>"]
v13["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L561 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 561] `</a>"]
end
v2 --> v3
v3 --> v4
v4 --> v5
v5 --> v6
v6 --> v7
v7 --> v8
v8 --> v9
v9 --> v10
v10 --> v11
v11 --> v12
v12 --> v13
%% Sink
subgraph Sink
direction LR
v1["<a href=https://github.com/hashintel/hash/blob/f6d051e2fc8d626103b1bf089cc975472ba42e1d/libs/@hashintel/petrinaut-core/src/simulation/authoring/scenario/ad-hoc/ad-hoc-actions.ts#L561 target=_blank style='text-decoration:none; color:#1c7fd6'>[Line: 561] new RegExp(String.raw`(?<![.\w$])${escaped}(?![A-Za-z0-9_$])`, "g")</a>"]
end
end
%% Class Assignment
Source:::invis
Sink:::invis
Traces0:::invis
File0:::invis
%% Connections
Source --> Traces0
Traces0 --> Sink
|
| new RegExp( | ||
| String.raw`(?<![.\w$])(scenario\s*\.\s*)${escaped}(?![A-Za-z0-9_$])`, | ||
| "g", | ||
| ), |
| new RegExp( | ||
| String.raw`(?<![.\w$])(scenario\s*\.\s*)${escaped}(?![A-Za-z0-9_$])`, | ||
| "g", | ||
| ), |
| new RegExp( | ||
| String.raw`(?<![.\w$])(scenario\s*\.\s*)${escaped}(?![A-Za-z0-9_$])`, | ||
| "g", | ||
| ), |
| new RegExp( | ||
| String.raw`(?<![.\w$])(scenario\s*\.\s*)${escaped}(?![A-Za-z0-9_$])`, | ||
| "g", | ||
| ), |
| ); | ||
| } | ||
| return expression.replace( | ||
| new RegExp(String.raw`(?<![.\w$])${escaped}(?![A-Za-z0-9_$])`, "g"), |
| ); | ||
| } | ||
| return expression.replace( | ||
| new RegExp(String.raw`(?<![.\w$])${escaped}(?![A-Za-z0-9_$])`, "g"), |
| ); | ||
| } | ||
| return expression.replace( | ||
| new RegExp(String.raw`(?<![.\w$])${escaped}(?![A-Za-z0-9_$])`, "g"), |
| ); | ||
| } | ||
| return expression.replace( | ||
| new RegExp(String.raw`(?<![.\w$])${escaped}(?![A-Za-z0-9_$])`, "g"), |
🌟 What is the purpose of this PR?
The foundation of the ad-hoc scenario feature (FE-778): the shapes, the synthesis, and the expression type-checking infrastructure, all in
petrinaut-core. Bottom of stack #9286. The design follows the ratified prototype (the "shared column values" variation handed to the designers) and the decisions from the #lib-petrinaut thread of 2026-08-06.🔗 Related links
🔍 What does this change?
Three shapes, in pipeline order (
simulation/authoring/scenario/ad-hoc-scenario.ts):AdHocScenarioState— the form's editing state: per-place token spreadsheets whose every cell is an expression, rows that are Fixed or Dynamic and mix freely, shared column values, and Variables at two scopes. Rows cycle Fixed → Dynamic → count-Optimized throughcycleAdHocRowKind, retaining the count (bounds included) across the cycle.AdHocSynthesisOutput— what synthesis emits: a generated code-modeScenario(never persisted;compileScenariois untouched) plus oneAdHocOptimizedFieldper enabled Optimize toggle, carrying the deterministicadhoc.*parameter name, the source's attribution label (Space › item 0 › x), its target path, and its typed domain.output.scenariofeedscompileScenariodirectly (plain runs);adHocOptimizationBindings(output.optimizedFields)is the transform to aPetrinautOptimizationManifest'sparameterBindings(the manifest schema itself is unchanged).Expression vocabulary matches the scenario code editor: top-level Variables are written
scenario.<name>(they stand in for scenario parameters, exactly as in the Satellites Launcher example), per-place Variables are bare names, net parameters areparameters.<name>, andi/countare per row. Net parameter overrides may read top-level Variables.Slots (
AdHocSlot= target + part, with stable path-safeadHocSlotKeystrings) are the join key between synthesis errors, LSP diagnostics, and the form's rendering of both. Every synthesis error carries its slot.Place totals:
resolveAdHocPlaceTotalsums row counts — a number when everything resolves, the unresolved expressions printed otherwise — for the table's bottom line.Actions and the pure reducer (
ad-hoc-actions.ts): the form's editing model as serializable actions, mirroring the net editor's mutation actions — a zod input schema per action (adHocActionInputSchemas), theAdHocActionunion, andapplyAdHocAction(state, context, action), a pure reducer that returns the same reference when an action changes nothing. The vocabulary covers value edits (setExpression,setDomainField,toggleSelection— count targets pick the count defaults), Variables (add with generated unique names, rename with an optional reference rewrite —scenario.<name>across the whole state for top-level Variables, bare identifiers within the owning place for per-place ones, bounds included — retype, delete, duplicate with name dedup), token rows (add with type-appropriate default cells, delete, duplicate, set-kind through the retention transitions), and shared columns.adHocActionCoalescingKeynames the actions that collapse into one undo entry when dispatched consecutively (typing bursts, slot-keyed viaadHocSlotKey). The form (#9285) dispatches these exclusively and builds its redux-like undo on top.LSP: a new ad-hoc session kind mirrors scenario sessions end to end (protocol notifications
temp/adhoc/*, virtual-file generation per non-empty value slot with scope-correct ambient declarations, service sync, worker handling, client methods). Every cell, count, Variable, net parameter override, and optimize bound gets a virtual TypeScript document type-checked as the slot's type; bounds see only the constant scope, soiin a bound is a type error.Pre-Merge Checklist 🚀
🚢 Has this modified a publishable library?
This PR:
patch).📜 Does this require a change to the docs?
The changes in this PR:
🕸️ Does this require a change to the Turbo Graph?
The changes in this PR:
🛡 What tests cover this?
24 synthesis tests — every fabricated scenario round-trips through the real
compileScenario, including thescenario.<name>vocabulary, optimize suggestions applied to every generated parameter kind, shared-column supersession, the gutter cycle's retention, place totals, slot keys, and attribution labels. 5 LSP-session tests pin scope-correct diagnostics (valid expressions acceptparameters.*/scenario.*/i/count/place variables; type mismatches and unknown identifiers report;iis out of scope in bounds; empty expressions produce no document; kill removes the session's files).❓ How to test this?
turbo run test:unit --filter @hashintel/petrinaut-core🐾 Next steps
FE-1486 (#9285) builds the form component on these shapes.
🤖 Generated with Claude Code