Skip to content

FE-629: Compile Petrinaut user code through an HIR to buffer-native programs#8981

Open
kube wants to merge 12 commits into
mainfrom
cf/fe-629-add-an-intermediate-representation-for-petrinaut
Open

FE-629: Compile Petrinaut user code through an HIR to buffer-native programs#8981
kube wants to merge 12 commits into
mainfrom
cf/fe-629-add-an-intermediate-representation-for-petrinaut

Conversation

@kube

@kube kube commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Petrinaut user code (dynamics, firing rates, transition kernels, metrics) was executed by stripping TypeScript with Babel and eval-ing it against per-call decoded token objects. That made the code impossible to analyze statically — e.g. knowing the probability-distribution structure of a kernel, or whether a rate depends on parameters — and slow, since every evaluation decoded packed frame bytes into records and back.

This PR introduces an HIR (high-level intermediate representation): a typed, source-spanned, JSON-serializable expression tree that every user-code surface compiles through. Goals:

  • One analyzable representation for all user code, schema-checked against the net (token attribute types, places, arc weights, parameters).
  • Static analysis for free: distribution DAGs inside kernels (derivation edges, output sinks, shared draws), dependency sets (which parameters/attributes a rate reads), determinism classification — surfaced as LSP diagnostics with exact source ranges.
  • Fast execution: the simulator runs only HIR-compiled buffer programs that read/write the packed frame buffers directly, with all attribute offsets and strides resolved at compile time and baked in as literal constants. No Babel, no per-call token decoding, no eval of raw user text. Simulation worker bundles shrank from ~3 MB to ~40 kB.

And what it enables next: a Petrinaut DSL frontend lowering to the same HIR (TS ⇄ DSL migration becomes pretty-printing), WASM/GPU backends (the buffer ABI is already the right shape: scalars, static offsets, no GC), engine optimizations informed by analyses (e.g. constant-rate detection), and artifact caching.

Note

This HIR is a first version and should not be considered definitive. The node set, the accepted TypeScript subset, the artifact format, and the buffer ABI are all expected to evolve (they already went through three artifact revisions inside this PR). It is deliberately private API: nothing outside petrinaut-core/petrinaut depends on its shape.

How it fits together

flowchart LR
    subgraph authoring["Authoring"]
        TS["TypeScript user code<br/>dynamics · rates · kernels · metrics"]
        DSL["Petrinaut DSL<br/>(future)"]
    end

    subgraph hir["HIR pipeline (this PR — runs in the language worker)"]
        LOWER["Lower + typecheck<br/>against the token schema"]
        IR[("HIR<br/>typed · spanned · JSON")]
        ANALYZE["Analyses<br/>distribution DAG · dependencies · lints"]
        EMIT["Emit buffer programs<br/>offsets/strides baked as constants"]
    end

    subgraph runtime["Simulation workers"]
        ENGINE["Engine hot loops<br/>packed frame buffers (format v2)"]
        FUTURE["WASM / GPU backends<br/>(future)"]
    end

    LSPD["Editor diagnostics<br/>exact source ranges"]

    TS --> LOWER
    DSL -.-> LOWER
    LOWER --> IR
    IR --> ANALYZE --> LSPD
    IR --> EMIT -->|"artifacts (plain JS sources)"| ENGINE
    EMIT -.-> FUTURE

    style DSL stroke-dasharray: 5 5
    style FUTURE stroke-dasharray: 5 5
Loading

Touched by the HIR: the four code surfaces above, the LSP checker/worker (new lints + compile request), the engine hot loops (buffer-ABI execution), and the simulation/experiment start flows (artifact threading). Not touched: the frame format itself, the net editor, scenarios (still sandboxed new Function, run once at start) and visualizers (JSX in the UI) — those are candidates for later HIR/DSL surfaces.

🔗 Related links

🔍 What does this change?

  • HIR pipeline (petrinaut-core/src/hir/, entries ./hir + dependency-free ./hir-runtime): lowering (incl. destructuring, guard ifs, .map/.reduce/.concat, Distribution.*, Uuid.*, string predicates), schema typechecking, analyses, lints, and buffer-program emitters for all four surfaces. Design doc: src/hir/README.md; DSL sketch: src/hir/dsl-sketch.md.
  • Buffer ABI: lambdas read attributes via f64/u64/u8 views at literal offsets (placeBases + baked strides); kernels write per-transition staging with RNG-consuming slots deferred through declaration-order sinks (exact RNG-stream preservation); dynamics compile to allocation-free loops; metrics emit reduction loops over the token region (the per-frame MetricState full decode is gone). Strings resolve via the per-run intern pool; uuids as bigints from two u64 lanes.
  • Engine: CompiledTransition carries only buffer programs + reusable scratch; Babel (@babel/standalone), compile-user-code.ts, compile-metric.ts, and all record decode/encode adapters are deleted.
  • Artifacts: compiled in the LSP worker (sdcpn/compileHirArtifacts request), threaded through simulation/Monte-Carlo configs; items with no artifact fail per-item at build.
  • LSP/UX: HIR lints with codes 99001+ and source: "hir"; out-of-subset or non-compilable code is a blocking error; Play now gates on error-severity diagnostics only (warnings/hints show an amber indicator).
  • Examples are the coverage gate: every dynamics/lambda/kernel/metric of all five shipped models must compile to buffer programs (two pre-existing H-6519 violations in the supply-chain example were fixed along the way).
  • HIR Playground Storybook story: type code + edit the schema, inspect the HIR tree, diagnostics, types, dependency sets, distribution DAG, and both emitted program forms live.

📊 Benchmarks

Measured with libs/@hashintel/petrinaut-core/benches/bench.mjs (yarn build && node --expose-gc benches/bench.mjs), which runs identical scenarios through the public in-process createMonteCarloSimulator API on this branch and on main (bfb99e7570, Babel path). The script feature-detects the HIR entry, so the same file runs on both; a per-run work checksum (RNG state + frames + token bytes) matched on every scenario, i.e. both sides did bit-identical simulation work.

Warm (median of 5 samples after 2 warmups) and cold (fresh process, no warmup — the first-click experience):

Scenario main HIR warm speedup cold speedup
Dynamics — 10k tokens × 200 steps 675 ms 384 ms 1.8× 1.7×
Lambda — 1,000 combinations × 200 steps 75 ms 43 ms 1.8× 1.6×
Kernel — 2 firings/step × 2,000 steps 25 ms 23 ms 1.1× 1.2×
Experiment — 20 runs × 2k tokens × 100 steps, 3 expression metrics 7,213 ms 797 ms 9.1× 7.5×
Experiment — 1,000 runs × 2k tokens × 100 steps, 3 expression metrics 365.7 s 39.7 s 9.2×

(The 1,000-run row is a single sample per branch — at this duration the process is self-warming, so warm/cold no longer differ. The speedup holds steady at experiment scale: ~6 min → ~40 s.)

Memory (1,000-run experiment, via /usr/bin/time -l + forced-GC retained heap):

main HIR
Peak RSS (whole process) 560.5 MB 561.4 MB
Retained heap after run + GC 9.4 MB 6.7 MB

Footprint is unchanged: peak RSS is dominated by the recorded frame buffers, which are byte-identical on both branches (the HIR changes how user code reads/writes frames, not how they are stored). The HIR allocates far less transient garbage per frame (no per-token record objects) — that shows up as the speedup, not as a smaller footprint.

Compilation (10 programs: 1 dynamics + 4 lambdas + 2 kernels + 3 metrics), isolated from simulation:

main (Babel + new Function) HIR (lower + typecheck + emit) speedup
Cold (first compile, fresh process) 24.1 ms 11.2 ms 2.2×
Warm (median of 50) 3.58 ms 0.77 ms 4.7×

Experiments are the headline (the old path decoded every place's tokens into records per metric per frame); dynamics/lambdas gain ~1.8× from removing the record decode/encode; kernels are cold-path (once per firing) so they barely move. There is no compile-time trade-off — the HIR pipeline compiles faster than Babel despite doing typechecking and analysis, on top of the ~3 MB → ~40 kB simulation-worker bundle reduction. Flags: --no-warmup, --samples N, --only <scenario>, --runs N (experiment run count), --compile.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

  • modifies an npm-publishable library and I have added a changeset file(s)

📜 Does this require a change to the docs?

The changes in this PR:

  • require changes to docs which are made as part of this PR
    • petri-net-extensions.md documents the supported code subset (now incl. metrics) and the error/warning split; simulation.md updated for error-only Play gating. Screenshots showing the diagnostics indicator may want refreshing (new amber warnings-only state).

🕸️ Does this require a change to the Turbo Graph?

The changes in this PR:

  • do not affect the execution graph

⚠️ Known issues

  • Behaviour change (deliberate): user TypeScript outside the analyzable subset no longer runs — it surfaces as an error diagnostic with a rewrite hint. The subset is what makes analysis and static-offset compilation possible; it covers all shipped examples and common idioms (const destructuring, guard clauses, .map/.reduce/.concat, Math.*, distributions, Uuid.*).
  • Scenarios keep their sandboxed new Function path (cold, once per run); visualizers keep JSX/Babel in the UI package.
  • A .reduce inside a conditional branch is evaluated eagerly (pure, so semantics hold — just possible wasted work).
  • Static storybook build output fails to render for all stories in this package (pre-existing react/jsx-runtime issue); use yarn dev.

🐾 Next steps

  • Skip materializeEngineFrame in the single-run engine (buffer path needs only offsets/counts + token bytes)
  • Scenario surface on the HIR; then the DSL frontend (src/hir/dsl-sketch.md)
  • WASM backend, GPU dynamics/Monte-Carlo
  • Artifact caching keyed by content hash

🛡 What tests cover this?

  • 707 petrinaut-core + 141 petrinaut tests, including:
    • determinism gate: identical inputs → identical frames over 50 stochastic steps (RNG stream through distribution sampling and uuid generation)
    • coverage gate: every example model item (all four surfaces) compiles to a buffer program
    • unit tests for lowering (spans, destructuring, guards, reduce/concat, metric bodies), schema typechecking, analyses, and all emitters against hand-packed format-v2 buffers (strings, uuids, booleans)
  • The playground story was verified headless (Playwright): all panels populate, surface switching works, schema edits produce live diagnostics.

❓ How to test this?

  1. cd libs/@hashintel/petrinaut && yarn dev → open HIR → Playground: type code across the four surfaces, break it (add a for loop), edit the schema (make an attribute "integer" and feed it a Distribution)
  2. Open the editor, load an example (e.g. Supply Chain), check the Diagnostics tab, run a simulation and a Monte-Carlo experiment with expression metrics
  3. yarn workspace @hashintel/petrinaut-core test:unit run

🤖 Generated with Claude Code

kube and others added 7 commits July 7, 2026 18:36
Introduce a typed, source-spanned, JSON-serializable HIR as the single
compiler for dynamics/lambda/kernel user code:

- TS->HIR lowering (const destructuring, guard if/early-return, .map
  comprehensions, first-class distributions) with positioned diagnostics
- Schema-checked typing against Color/parameter definitions; analyses for
  dependency sets, distribution DAGs (nodes/edges/sinks/shared draws),
  binding usage and constant folding
- Buffer-ABI emitters: lambdas read token attributes at statically
  resolved offsets (tokenValues[slotBases[slot] + attr]); kernels write
  place-major staging floats with deferred distribution sampling ordered
  by output index (exact legacy RNG-stream parity); dynamics compile to
  allocation-free Float64Array loops
- Engine hot loops (single-run + Monte Carlo) prefer buffer programs and
  fall back to object-convention HIR programs per item
- HirArtifacts v2 compiled in the LSP worker (sdcpn/compileHirArtifacts
  request) and threaded through simulation/experiment configs; the
  engine no longer bundles any compiler (simulation workers: ~3MB -> ~40kB)
- HIR semantic lints in the LSP checker (source "hir", codes 99001+);
  out-of-subset code is an error and Play gates on error severity only
- Remove @babel/standalone and compile-user-code.ts; fix H-6519
  violations in the supply-chain example; shipped examples are the
  buffer-ABI coverage gate

Design docs: src/hir/README.md (architecture) and src/hir/dsl-sketch.md
(planned OCaml-flavoured DSL lowering to the same HIR).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Interactive playground for the HIR pipeline: type dynamics/lambda/kernel
TypeScript on the left, and inspect everything it compiles to on the
right — the spanned HIR tree, diagnostics with exact ranges, the inferred
return type, dependency sets, the distribution DAG (derivation edges,
output sinks, shared draws), and both emitted programs (buffer-ABI fast
path and object-convention fallback). The model schema (parameters,
places, attribute types) is editable JSON and re-checks live.

Exposes the compiler via new `@hashintel/petrinaut-core/hir` and
`/hir-runtime` entries (typescript declared as an optional peer — only
the ./hir entry needs it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… the HIR

Rebase onto the FE-769 stack (token format v2, UUID + interned strings) and
extend the compiler:

- New HIR nodes: string literals, string predicates
  (startsWith/endsWith/includes), Uuid.generate()/Uuid.from(...) sentinels
- Typecheck: uuid/string attribute types; kernel outputs accept strings for
  string attributes and uuid/string for uuid attributes (omitted uuid =
  auto-generated); string .length; uuid helpers are kernel-only
- Buffer-ABI v2: lambdas and dynamics compile to packed-struct byte reads
  through the frame's shared f64/u64/u8 views (integers rounded, booleans
  u8, strings resolved via the per-run pool, uuids assembled from two u64
  lanes); slotBases now carry byte offsets. Kernel buffer emission is
  deferred (needs an RNG-ordinal sink design to preserve the stream) — the
  object program runs per firing meanwhile
- Object programs emit uuid sentinels/string literals; analyses track
  generatesUuids; string-equality constant folding
- Replace the deleted Babel compiler in the dev token playground with a
  local evaluator

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove the object-convention path entirely: the engine no longer decodes
token records for user code, and every dynamics/lambda/kernel runs a
buffer program with all dimension offsets and strides resolved at compile
time and baked into the emitted code as literal constants.

- Kernel buffer emitter: static writes into per-transition staging bytes
  at compile-time-constant offsets; RNG-consuming slots (distributions,
  generated/converted UUIDs) defer through sink calls emitted in
  (arc, token, element-declaration) order, preserving the seeded RNG
  stream; strings intern through the per-run pool inline
- Lambda programs now receive (placeBases, indices) with strides baked
  in — the per-slot offset loop is gone; token layouts are computed once
  per arc slot at emit time
- CompiledTransition carries only buffer programs + reusable scratch
  (placeBases/indices/staging views); encode-kernel-token.ts and the
  record-decode adapters are deleted
- Artifacts v3 are buffer-only; shapes that don't scalarize fail
  compilation with hir:not-compilable (all example models compile fully,
  kernels included — enforced by the coverage gate)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Metrics become the fourth HIR surface and lose their new Function path:

- New arrayReduce/arrayConcat HIR nodes (.reduce/.concat lowering); metric
  code lowers as a function body with spans mapped back to the raw text
- Metric buffer programs read frames directly with statically-resolved
  offsets: attribute offsets/strides baked as literals, place ordinals
  resolved once at instantiation, reductions emitted as loops over the
  packed token region (no per-frame MetricState decoding)
- compileHirArtifacts covers sdcpn.metrics; expression metric specs carry
  the compiled artifact; single-run timeline and actual-mode metric paths
  migrated to the same evaluator via SimulationFrameReader.getRawView()
- Delete compile-metric.ts and frames/metric-state.ts; LSP metric
  sessions get HIR lint incl. hir:not-compilable as blocking errors
- All 18 example metrics compile (reduce/concat/guards/strings) —
  enforced by the coverage gate

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 8, 2026 23:49
@vercel

vercel Bot commented Jul 8, 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 9, 2026 8:05pm
petrinaut Ready Ready Preview, Comment Jul 9, 2026 8:05pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hashdotdesign-tokens Ignored Ignored Preview Jul 9, 2026 8:05pm

@cursor

cursor Bot commented Jul 8, 2026

Copy link
Copy Markdown

PR Summary

High Risk
This is a breaking execution path for all user-authored dynamics, rates, kernels, and metrics, with stricter compile-time rejection and deterministic RNG behavior tied to the new buffer ABI—regressions would affect every simulation and experiment.

Overview
Replaces Babel + runtime eval of user TypeScript with a HIR pipeline that lowers a restricted TS subset, typechecks it against the net schema, analyzes it (dependencies, distribution DAGs), and emits v3 buffer programs that read/write packed frame bytes directly. Simulation workers use ./hir-runtime only (no typescript in bundles); full compile lives in ./hir.

Engine & API: Dynamics, lambdas, kernels, and Monte-Carlo expression metrics run only from precompiled hirArtifacts threaded through simulation/Monte-Carlo config—missing or stale artifacts fail per item with diagnostics; there is no Babel fallback. @babel/standalone is removed from petrinaut-core.

Supporting changes: Actual-mode timeline frames gain getRawView() so HIR metrics can run on on-demand packed token buffers. Supply-chain example dynamics are adjusted so they compile under the new subset. Adds benches/bench.mjs for HIR vs baseline timing and checksum parity, plus extensive HIR tests and ABI/docs under src/hir/.

Compatibility: User code outside the supported subset does not run; it is rejected with error diagnostics at compile/LSP time.

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

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) 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 > hash.design Affects the `hash.design` design site (app) labels Jul 8, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces Petrinaut's Babel + eval execution of user code (dynamics, firing rates, transition kernels, metrics) with a new HIR (high-level intermediate representation) pipeline. User code is lowered from a TypeScript subset to a typed, spanned, JSON-serializable expression tree, typechecked against the token schema, analyzed (distribution DAGs, dependency sets, determinism), and emitted as buffer-native programs that read/write packed frame buffers directly with attribute offsets/strides baked in as constants. This removes @babel/standalone and per-call token decode/encode from the simulation hot loops (worker bundles shrink ~3 MB → ~40 kB), surfaces out-of-subset code as blocking LSP error diagnostics, and threads compiled artifacts through the simulation/Monte-Carlo start flows. It fits into the petrinaut-core/petrinaut packages as deliberately private API and lays groundwork for a future DSL frontend and WASM/GPU backends.

Changes:

  • New HIR pipeline in petrinaut-core/src/hir/ (lowering, typecheck, analyses, lints, buffer emitters) plus engine execution of buffer programs and deletion of Babel/compile-user-code/compile-metric/record adapters.
  • Artifact compilation moved into the LSP worker (sdcpn/compileHirArtifacts) and threaded through simulation/experiment configs; metric drawers and the timeline now rely on LSP diagnostics + async HIR artifacts instead of synchronous compileMetric.
  • LSP/UX: HIR lints (codes 99001+, source: "hir"), error-only Play gating with an amber warnings-only indicator, and updated user docs.

Reviewed changes

Copilot reviewed 95 out of 96 changed files in this pull request and generated no comments.

Show a summary per file
File Description
yarn.lock Drops @babel/standalone/@types/babel__standalone; adds optional typescript peer dependency for petrinaut-core
.../metrics/view-metric-drawer.tsx Removes synchronous compileMetric compile-check; validation now sourced from metric LSP diagnostics
.../metrics/create-metric-drawer.tsx Same removal of live compileMetric preview compile in the create flow
.../metrics/metric-form.tsx Updates comment to reflect HIR-based compilability rather than compileMetric
.../experiments/create-experiment-drawer.tsx buildMetricSpecs returns ExperimentMetricSpecInput; expression metrics validated for non-empty code, compiled via HIR at experiment start
.../simulation-timeline/use-streaming-data.ts New async useTimelineMetric hook compiling the selected metric via requestHirArtifacts + createHirMetricEvaluator
.../BottomBar/diagnostics-indicator.tsx Adds amber warning status for warnings/hints-only state (Play still allowed)
petrinaut-core/src/hir.ts (+ hir-runtime) New public entry exposing compileHirArtifacts and HIR types
.../simulation/engine/buffer-transition.ts New buffer-ABI transition execution (kernel sinks, UUID lanes, staging)
.../simulation/engine/build-simulation.ts Instantiates buffer programs, validates stale/missing artifacts, allocates reusable scratch
.../simulation/frames/hir-metric.ts New HIR metric evaluator with cached place indices + per-run string-pool rebind
.../simulation/frames/frame-reader.ts, simulation/api.ts getPlaceTokens gains optional color? param (currently ignored)
.../lsp/lib/check-hir.ts HIR diagnostic-code registry and per-item HIR linting (subsetSeverity: "error")
.../simulation-timeline/series-config/metric.ts Timeline metric config switches from places/buildMetricState to an injected evaluator
react/experiments/context Adds ExperimentMetricSpecInput type for pre-artifact metric specs
docs: petri-net-extensions.md, simulation.md Document supported subset (incl. metrics), error/warning split, and error-only Play gating

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 16:57
Comment thread libs/@hashintel/petrinaut-core/src/hir/lint.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 95 out of 96 changed files in this pull request and generated 1 comment.

Comment on lines 318 to 321
return {
store,
metricError: compiledMetric.error,
metricError: timelineMetric.error,
};
Runs scenarios (dynamics, lambda combinations, kernel firings, Monte-Carlo
experiment with expression metrics) through the public in-process
createMonteCarloSimulator API. Feature-detects the HIR entry so the same
script runs on pre-HIR baselines; emits per-scenario build/run medians,
heap growth, and a work checksum that must match across variants.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 17:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 96 out of 97 changed files in this pull request and generated 1 comment.

Comment on lines +137 to +146
useEffect(() => {
if (!metric) {
// No compile needed; callers key results off the metric identity, so
// stale state is simply ignored.
return;
}

let cancelled = false;
const key = `${metric.id}\u0000${metric.code}`;
requestHirArtifacts(
--no-warmup/--samples/--only for fresh-process cold runs; --compile
isolates user-code compilation (HIR pipeline vs Babel per-item).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 17:31

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b4b537e. Configure here.

const selected = ONLY === null ? SCENARIOS : [SCENARIOS[Number(ONLY)]];
for (const scenario of selected) {
for (let index = 0; index < WARMUPS; index += 1) {
sample(scenario);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bench --only invalid index crashes

Low Severity

When --only is passed a non-numeric or out-of-range index, SCENARIOS[Number(ONLY)] is undefined, yet the harness still calls sample(scenario) and dereferences scenario fields, which throws instead of reporting a clear usage error.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b4b537e. Configure here.

Comment thread libs/@hashintel/petrinaut-core/src/actual-mode/timeline.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 96 out of 97 changed files in this pull request and generated 3 comments.

Comment on lines +137 to +142
useEffect(() => {
if (!metric) {
// No compile needed; callers key results off the metric identity, so
// stale state is simply ignored.
return;
}
Comment on lines +145 to +149
const key = `${metric.id}\u0000${metric.code}`;
requestHirArtifacts(
{ ...petriNetDefinition, metrics: [metric] },
extensions,
)
Comment on lines +46 to 49
const hasErrors = !!formError || hasLspErrors;
const totalErrorCount = (formError ? 1 : 0) + lspErrorCount;
const firstError = formError ?? firstLspMessage ?? undefined;
const canSave = canSubmit && !hasErrors && !isSubmitting && !isDefaultValue;
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 96 out of 97 changed files in this pull request and generated no new comments.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 96 out of 97 changed files in this pull request and generated 9 comments.

Comment on lines +429 to +437
// Metric token counts are dynamic, so any scalar index is allowed;
// metric code is expected to guard with `.length` (out-of-range
// reads land in other places' token bytes or zeroed capacity).
const index = this.scalar(this.eval(expr.index, env));
const stride = this.layoutOf(target.elements).strideBytes;
const strideCode = stride === 0 ? "0" : `(${index}) * ${stride}`;
return {
kind: "token",
baseCode: `placeOffsets[__places[${target.ordinal}]] + ${strideCode}`,
Comment on lines +114 to +119
export type HirArtifacts = {
version: 3;
dynamics: Record<string, HirDynamicsArtifact>;
lambdas: Record<string, HirLambdaArtifact>;
kernels: Record<string, HirKernelArtifact>;
metrics: Record<string, HirMetricArtifact>;
Comment on lines +138 to +142
if (!metric) {
// No compile needed; callers key results off the metric identity, so
// stale state is simply ignored.
return;
}
Comment on lines +144 to +149
let cancelled = false;
const key = `${metric.id}\u0000${metric.code}`;
requestHirArtifacts(
{ ...petriNetDefinition, metrics: [metric] },
extensions,
)
Comment on lines +113 to 115
// Live validation (TypeScript + HIR semantic/compilability checks) comes
// from the metric LSP session diagnostics.
const values = useStore(form.store, (state) => state.values);
Comment on lines +11 to +18
- `authoring/metric/` and `authoring/scenario/` compile metric/scenario
inputs (`new Function` with same-realm hardening from
`authoring/sandbox.ts`). Dynamics/lambda/kernel code is compiled solely
through the HIR pipeline (`src/hir/README.md`) into
`SimulationInput.hirArtifacts` — produced off-engine (LSP worker) and
instantiated dependency-free via `src/hir-runtime.ts`. Buffer-ABI programs
read/write packed token bytes directly (`engine/buffer-transition.ts`);
unsupported HIR shapes are compile errors.
@@ -0,0 +1,10 @@
---
"@hashintel/petrinaut": patch
"@hashintel/petrinaut-core": patch
Comment on lines +130 to +132
if (code.trim() === "") {
return [];
}
Comment on lines +379 to +388
// Compile the net's user code to HIR artifacts in the language worker —
// the simulation engine has no compiler of its own.
const { artifacts } = await requestHirArtifacts(
simulationSdcpn,
extensionsRef.current,
);
sim = await createSimulation({
sdcpn: simulationSdcpn,
extensions: extensionsRef.current,
hirArtifacts: artifacts,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps > hash.design Affects the `hash.design` design site (app) area/deps Relates to third-party dependencies (area) 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