Skip to content

FE-1523: Import and export Petrinaut files as YAML - #9379

Merged
kube merged 5 commits into
mainfrom
claude/fe-1523-yaml-import-export
Aug 28, 2026
Merged

FE-1523: Import and export Petrinaut files as YAML#9379
kube merged 5 commits into
mainfrom
claude/fe-1523-yaml-import-export

Conversation

@kube

@kube kube commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

🌟 What is the purpose of this PR?

Petrinaut saves and loads nets as JSON, where multi-line code fields (transition kernels, lambdas, metrics) serialize as single lines with \n escapes. Exports now default to YAML, and every import path — the editor, the CLI's --model and --optimization files, and the Python bindings that forward paths to the CLI — accepts YAML and JSON interchangeably, detected from the content. Existing JSON files load unchanged, and JSON export stays available in the Export menu.

The same field of the SIR example, before and after:

"lambdaCode": "// Mass-action infection: fires at the configured infection rate whenever a\n// Susceptible and an Infected are both present (the two standard input arcs).\nexport default Lambda((tokens, parameters) => parameters.infection_rate)"
lambdaCode: |-
  // Mass-action infection: fires at the configured infection rate whenever a
  // Susceptible and an Infected are both present (the two standard input arcs).
  export default Lambda((tokens, parameters) => parameters.infection_rate)

The document opens with format metadata, then the sections in dependency order (the Probabilistic Satellite Launcher example):

version: 1
meta:
  generator: Petrinaut
title: Probabilistic Satellite Launcher
parameters: ...
types: ...
differentialEquations: ...
places: ...
transitions: ...
metrics: ...
scenarios: ...

An optimization manifest (the CLI's --optimization input) reads the same way — the checked-in supply-chain fixture, with most of the embedded model and all but one binding elided. scenario.id and objective.metricId refer to the scenario and metric inside model.definition:

kind: petrinaut-optimization
version: 1
name: Supply Chain Profit model optimization
model:
  title: Supply Chain Profit model
  definition:
    places: ...
    transitions: ...
    parameters: ...
    scenarios:
      - id: scenario_baseline_supply_chain_with_stock
        name: Baseline with enough stock
        scenarioParameters: ...
    metrics:
      - id: metric_profit
        name: Profit
        code: |-
          const soldOrders = state.places.SoldOrders.count;
          ...
scenario:
  id: scenario_baseline_supply_chain_with_stock
  parameterBindings:
    production_rate:
      kind: optimize
      domain:
        kind: continuous
        minimum: 20
        maximum: 250
        scale: linear
    ...
objective:
  metricId: metric_profit
  direction: maximize
execution:
  seed: 1234
  dt: 0.1
  maxTime: 36.5
study:
  trials: 1000
  sampler: tpe

🔗 Related links

🔍 What does this change?

  • @hashintel/petrinaut-core gains file-format/document-text.ts. parseDocumentText tries JSON.parse first, so JSON documents keep their exact semantics, and falls back to js-yaml with CORE_SCHEMA, so on/yes stay strings and timestamps stay strings. serializeDocument encodes either format; its YAML output writes multi-line strings as literal block scalars, never folds long lines, and writes repeated objects in full instead of anchor/alias references.
  • parseSDCPNDocument(text) is the text-level counterpart to parseSDCPNFile(data), and serializeSDCPN takes a format option that defaults to "yaml". A consumer that re-parses serializeSDCPN output with JSON.parse must now pass format: "json".
  • The editor's Export menu offers YAML, YAML without visual info, JSON, JSON without visual info, and TikZ. Import accepts .yaml, .yml, and .json. Actual mode's Export Net follows the YAML default.
  • Both formats write their keys in the fixed order shown above, where each section references only sections above it by id; subnets and componentInstances, absent from that example, come after differentialEquations and places respectively. Subnet keys are ordered the same way, a compile-time check fails when SDCPN gains a field without an export position, and import accepts any key order.
  • Import accepts a versioned file without meta, so a hand-written file can declare version: 1 without generator boilerplate. Exports keep writing meta.generator: older importers require it on versioned files, so removing it would make new exports unreadable there.
  • The CLI's JSON-lines protocol and its stdin sources still read one JSON line; only file loading gained YAML. Actual-mode stream recordings and clipboard payloads stay JSON.
  • The user guide, the CLI README, the arch-docs usage manual, and the Python bindings' docstrings and README describe the new formats.

Pre-Merge Checklist 🚀

🚢 Has this modified a publishable library?

This PR:

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

Patch changesets for @hashintel/petrinaut-core and @hashintel/petrinaut.

📜 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

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

The changes in this PR:

  • do not affect the execution graph

🛡 What tests cover this?

  • document-text.test.ts covers format detection, JSON-first parsing, error selection, core-schema scalars, block scalars, and the throw on values YAML cannot represent.
  • serialize-sdcpn.test.ts round-trips serialize → parse in both formats, with and without visual info.
  • The CLI tests load a hand-written YAML model file and a YAML optimization manifest.

❓ How to test this?

  1. Run yarn workspace @hashintel/petrinaut dev and load an example net.
  2. Open the hamburger menu → ExportYAML; the downloaded file's code fields are block scalars.
  3. Menu → Import with that file, then with an older .json export; both load.
  4. For the CLI: node libs/@hashintel/petrinaut-cli/dist/cli.js serve --model <file>.yaml --stdio answers {"id":1,"method":"metadata"}.

🤖 Generated with Claude Code

Nets and optimization manifests now load from YAML as well as JSON,
detected from the content rather than the file name. The editor's
export menu and the core serializer default to YAML, which writes
multi-line code fields as block scalars; JSON export remains available.
The CLI's JSON-lines protocol and stdin sources are unchanged.
@kube kube self-assigned this Aug 27, 2026
@vercel

vercel Bot commented Aug 27, 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 Aug 28, 2026 9:31am
hashdotdesign-tokens Ready Ready Preview Aug 28, 2026 9:31am
petrinaut Ready Ready Preview Aug 28, 2026 9:31am
petrinaut-docs Ready Ready Preview Aug 28, 2026 9:31am

Request Review

@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 type/eng > backend Owned by the @backend team labels Aug 27, 2026
Externalize js-yaml in the core build like the other runtime
dependencies, and drop skipInvalid so a value YAML cannot represent
throws instead of being dropped silently. Export the codec's result
type, deduplicate the CLI's ImportResult unwrapping, anchor the
visual-info test assertions to line starts, and match the docs list
to the menu order.
@cursor

cursor Bot commented Aug 27, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
Default export format and new parsing path affect all file I/O for models/manifests; JSON-first vs YAML fallback semantics must stay stable for existing JSON workflows.

Overview
Adds content-detected YAML and JSON for Petrinaut net files and optimization manifests across core, editor, and CLI. Exports default to YAML (block scalars for multi-line code); JSON remains available from the Export menu.

@hashintel/petrinaut-core introduces parseDocumentText / serializeDocument (js-yaml, JSON-first parse) and parseSDCPNDocument for raw file text. serializeSDCPN gains format (default "yaml") and writes version, meta, title, then net sections in dependency order; import still accepts any key order. Versioned imports may omit meta.

The editor Export menu lists YAML/JSON (with/without visual info); import accepts .yaml, .yml, .json. CLI --model and --optimization file paths use the same parsing; stdio JSON-lines protocol is unchanged.

Docs and Python binding docstrings describe YAML as the editor default.

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

@kube
kube requested a review from CiaranMn August 27, 2026 16:49
@kube
kube requested a review from YannisZa August 27, 2026 16:50
@kube
kube enabled auto-merge August 27, 2026 16:50
…order

Both formats now open with version, meta, and title, followed by the
net sections ordered so each references only sections above it by id:
parameters, types, differentialEquations, subnets, places,
componentInstances, transitions, metrics, scenarios. Subnet keys are
ordered the same way. Compile-time checks fail when SDCPN or Subnet
gains a field without an export position, so new fields cannot be
dropped from exports silently. Import accepts any key order.
A hand-written file can declare version: 1 without the meta.generator
boilerplate. Exports keep writing meta so older importers, which
require it on versioned files, still accept new exports.
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 60.66%. Comparing base (3b2d8e3) to head (b4b620d).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9379      +/-   ##
==========================================
- Coverage   60.74%   60.66%   -0.09%     
==========================================
  Files        1440     1439       -1     
  Lines      143458   143120     -338     
  Branches     6662     6654       -8     
==========================================
- Hits        87141    86817     -324     
+ Misses      55225    55212      -13     
+ Partials     1092     1091       -1     
Flag Coverage Δ
apps.hash-api 14.68% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kube
kube added this pull request to the merge queue Aug 28, 2026
Merged via the queue into main with commit f38be04 Aug 28, 2026
74 checks passed
@kube
kube deleted the claude/fe-1523-yaml-import-export branch August 28, 2026 10:44
@hash-release hash-release Bot mentioned this pull request Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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 > backend Owned by the @backend team type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

2 participants