boulder 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (303) hide show
  1. boulder-0.6.1/.cruft.json +17 -0
  2. boulder-0.6.1/.cursor/skills/boulder-gui/SKILL.md +162 -0
  3. boulder-0.6.1/.cursor/skills/boulder-gui/reference.md +89 -0
  4. boulder-0.6.1/.cursorignore +8 -0
  5. boulder-0.6.1/.cursorrules +21 -0
  6. boulder-0.6.1/.github/workflows/on-push.yml +193 -0
  7. boulder-0.6.1/.github/workflows/release.yml +88 -0
  8. boulder-0.6.1/.gitignore +563 -0
  9. boulder-0.6.1/.pre-commit-config-cruft.yaml +7 -0
  10. boulder-0.6.1/.pre-commit-config.yaml +42 -0
  11. boulder-0.6.1/AGENTS.md +166 -0
  12. boulder-0.6.1/ARCHITECTURE.md +528 -0
  13. boulder-0.6.1/FMI_FMU_EXPORT.md +226 -0
  14. boulder-0.6.1/LICENSE +21 -0
  15. boulder-0.6.1/Makefile +28 -0
  16. boulder-0.6.1/PKG-INFO +249 -0
  17. boulder-0.6.1/README.md +197 -0
  18. boulder-0.6.1/SPECIFICATIONS.md +32 -0
  19. boulder-0.6.1/STONE_SPECIFICATIONS.md +1303 -0
  20. boulder-0.6.1/boulder/__init__.py +46 -0
  21. boulder-0.6.1/boulder/_frontend/assets/SankeyTab-D_7pX8ns.js +1 -0
  22. boulder-0.6.1/boulder/_frontend/assets/ThermoReportTab-B1AsQ0LC.js +1 -0
  23. boulder-0.6.1/boulder/_frontend/assets/index-4YpGN-6Y.js +4117 -0
  24. boulder-0.6.1/boulder/_frontend/assets/index-CxKeOVn3.css +1 -0
  25. boulder-0.6.1/boulder/_frontend/assets/index-HWgOWN26.js +11 -0
  26. boulder-0.6.1/boulder/_frontend/index.html +14 -0
  27. boulder-0.6.1/boulder/_frontend/vite.svg +1 -0
  28. boulder-0.6.1/boulder/api/__init__.py +1 -0
  29. boulder-0.6.1/boulder/api/main.py +348 -0
  30. boulder-0.6.1/boulder/api/routes/__init__.py +1 -0
  31. boulder-0.6.1/boulder/api/routes/configs.py +231 -0
  32. boulder-0.6.1/boulder/api/routes/graph.py +38 -0
  33. boulder-0.6.1/boulder/api/routes/gui_actions.py +143 -0
  34. boulder-0.6.1/boulder/api/routes/mechanisms.py +21 -0
  35. boulder-0.6.1/boulder/api/routes/plugins.py +94 -0
  36. boulder-0.6.1/boulder/api/routes/scenarios.py +204 -0
  37. boulder-0.6.1/boulder/api/routes/simulations.py +424 -0
  38. boulder-0.6.1/boulder/api/routes/sweep.py +251 -0
  39. boulder-0.6.1/boulder/api/routes/ui.py +49 -0
  40. boulder-0.6.1/boulder/api/sse.py +94 -0
  41. boulder-0.6.1/boulder/bindings.py +315 -0
  42. boulder-0.6.1/boulder/cantera_converter.py +2342 -0
  43. boulder-0.6.1/boulder/cli.py +805 -0
  44. boulder-0.6.1/boulder/config.py +2814 -0
  45. boulder-0.6.1/boulder/ctutils.py +285 -0
  46. boulder-0.6.1/boulder/data/sample_config.yaml +45 -0
  47. boulder-0.6.1/boulder/download_script_emitter.py +840 -0
  48. boulder-0.6.1/boulder/export.py +145 -0
  49. boulder-0.6.1/boulder/gui_actions.py +120 -0
  50. boulder-0.6.1/boulder/lagrangian.py +334 -0
  51. boulder-0.6.1/boulder/live_simulation.py +85 -0
  52. boulder-0.6.1/boulder/network_plugin.py +164 -0
  53. boulder-0.6.1/boulder/output_pane_plugins.py +194 -0
  54. boulder-0.6.1/boulder/output_summary.py +224 -0
  55. boulder-0.6.1/boulder/parser/__init__.py +13 -0
  56. boulder-0.6.1/boulder/parser/py_to_yaml.py +266 -0
  57. boulder-0.6.1/boulder/payload_store.py +485 -0
  58. boulder-0.6.1/boulder/reactor_energy.py +89 -0
  59. boulder-0.6.1/boulder/reactor_sizing.py +83 -0
  60. boulder-0.6.1/boulder/result_cache.py +836 -0
  61. boulder-0.6.1/boulder/runner.py +847 -0
  62. boulder-0.6.1/boulder/sankey.py +689 -0
  63. boulder-0.6.1/boulder/schema_registry.py +326 -0
  64. boulder-0.6.1/boulder/scopes.py +269 -0
  65. boulder-0.6.1/boulder/signals.py +365 -0
  66. boulder-0.6.1/boulder/sim2stone.py +1140 -0
  67. boulder-0.6.1/boulder/sim2stone_ast.py +794 -0
  68. boulder-0.6.1/boulder/sim2stone_cli.py +233 -0
  69. boulder-0.6.1/boulder/sim2stone_trace.py +157 -0
  70. boulder-0.6.1/boulder/simulation_result.py +190 -0
  71. boulder-0.6.1/boulder/simulation_worker.py +632 -0
  72. boulder-0.6.1/boulder/spatial_inference.py +176 -0
  73. boulder-0.6.1/boulder/stage_network.py +63 -0
  74. boulder-0.6.1/boulder/staged_network.py +127 -0
  75. boulder-0.6.1/boulder/staged_solver.py +1483 -0
  76. boulder-0.6.1/boulder/stone2sim_cli.py +89 -0
  77. boulder-0.6.1/boulder/styles.py +246 -0
  78. boulder-0.6.1/boulder/summary_builder.py +237 -0
  79. boulder-0.6.1/boulder/tests/test_build_isolated_reactor.py +63 -0
  80. boulder-0.6.1/boulder/tests/test_reactor_energy.py +53 -0
  81. boulder-0.6.1/boulder/utils.py +650 -0
  82. boulder-0.6.1/boulder/validation.py +536 -0
  83. boulder-0.6.1/boulder/verbose_utils.py +81 -0
  84. boulder-0.6.1/boulder/version.py +2 -0
  85. boulder-0.6.1/boulder/yaml_unit_map.py +321 -0
  86. boulder-0.6.1/boulder.egg-info/PKG-INFO +249 -0
  87. boulder-0.6.1/boulder.egg-info/SOURCES.txt +301 -0
  88. boulder-0.6.1/boulder.egg-info/dependency_links.txt +1 -0
  89. boulder-0.6.1/boulder.egg-info/entry_points.txt +4 -0
  90. boulder-0.6.1/boulder.egg-info/requires.txt +15 -0
  91. boulder-0.6.1/boulder.egg-info/scm_file_list.json +289 -0
  92. boulder-0.6.1/boulder.egg-info/scm_version.json +8 -0
  93. boulder-0.6.1/boulder.egg-info/top_level.txt +1 -0
  94. boulder-0.6.1/ci/environment-ci.yml +24 -0
  95. boulder-0.6.1/ci/environment-integration.yml +13 -0
  96. boulder-0.6.1/configs/README.md +147 -0
  97. boulder-0.6.1/configs/cstr_methane_air.yaml +80 -0
  98. boulder-0.6.1/configs/default.yaml +58 -0
  99. boulder-0.6.1/configs/grouped_nodes.yaml +63 -0
  100. boulder-0.6.1/configs/mix_react_streams.yaml +58 -0
  101. boulder-0.6.1/configs/sample_configs2.yaml +61 -0
  102. boulder-0.6.1/configs/staged_psr_pfr.yaml +116 -0
  103. boulder-0.6.1/docs/Makefile +39 -0
  104. boulder-0.6.1/docs/api.rst +8 -0
  105. boulder-0.6.1/docs/boulder.png +0 -0
  106. boulder-0.6.1/docs/boulder_logo_small.png +0 -0
  107. boulder-0.6.1/docs/cantera_examples/.gitignore +3 -0
  108. boulder-0.6.1/docs/cantera_examples/combustor.py +120 -0
  109. boulder-0.6.1/docs/cantera_examples/nanosecond_pulse_discharge.py +145 -0
  110. boulder-0.6.1/docs/cantera_examples/reactor2.py +147 -0
  111. boulder-0.6.1/docs/cantera_upstream_examples.rst +184 -0
  112. boulder-0.6.1/docs/conf.py +127 -0
  113. boulder-0.6.1/docs/index.rst +43 -0
  114. boulder-0.6.1/docs/installation.rst +10 -0
  115. boulder-0.6.1/docs/mix_streams_example.png +0 -0
  116. boulder-0.6.1/docs/plans/2026-06-07-wait-for-server-before-browser-open.md +118 -0
  117. boulder-0.6.1/docs/stone.rst +125 -0
  118. boulder-0.6.1/docs/usage.rst +94 -0
  119. boulder-0.6.1/environment.yml +23 -0
  120. boulder-0.6.1/examples/GALLERY_HEADER.rst +22 -0
  121. boulder-0.6.1/examples/launch_from_cli.py +10 -0
  122. boulder-0.6.1/examples/launch_from_python.py +6 -0
  123. boulder-0.6.1/examples/mix1.py +96 -0
  124. boulder-0.6.1/examples/mix1.yaml +108 -0
  125. boulder-0.6.1/examples/plot_mixed_reactor_stream_example.py +100 -0
  126. boulder-0.6.1/examples/plot_staged_solve_from_yaml.py +66 -0
  127. boulder-0.6.1/examples/plot_staged_solve_progressive_draw.py +145 -0
  128. boulder-0.6.1/examples/plugin_example.py +277 -0
  129. boulder-0.6.1/frontend/.gitignore +24 -0
  130. boulder-0.6.1/frontend/README.md +73 -0
  131. boulder-0.6.1/frontend/SPECIFICATIONS.md +198 -0
  132. boulder-0.6.1/frontend/eslint.config.js +23 -0
  133. boulder-0.6.1/frontend/index.html +13 -0
  134. boulder-0.6.1/frontend/package-lock.json +7906 -0
  135. boulder-0.6.1/frontend/package.json +54 -0
  136. boulder-0.6.1/frontend/playwright.config.ts +25 -0
  137. boulder-0.6.1/frontend/public/vite.svg +1 -0
  138. boulder-0.6.1/frontend/src/App.css +42 -0
  139. boulder-0.6.1/frontend/src/App.tsx +21 -0
  140. boulder-0.6.1/frontend/src/api/client.ts +27 -0
  141. boulder-0.6.1/frontend/src/api/configs.ts +88 -0
  142. boulder-0.6.1/frontend/src/api/guiActions.ts +46 -0
  143. boulder-0.6.1/frontend/src/api/mechanisms.ts +10 -0
  144. boulder-0.6.1/frontend/src/api/plugins.ts +21 -0
  145. boulder-0.6.1/frontend/src/api/resultCache.ts +48 -0
  146. boulder-0.6.1/frontend/src/api/scenarios.ts +50 -0
  147. boulder-0.6.1/frontend/src/api/simulations.ts +31 -0
  148. boulder-0.6.1/frontend/src/api/sweep.ts +38 -0
  149. boulder-0.6.1/frontend/src/assets/react.svg +1 -0
  150. boulder-0.6.1/frontend/src/components/graph/ReactorGraph.tsx +1463 -0
  151. boulder-0.6.1/frontend/src/components/layout/AppShell.test.tsx +147 -0
  152. boulder-0.6.1/frontend/src/components/layout/AppShell.tsx +360 -0
  153. boulder-0.6.1/frontend/src/components/layout/paneControls.tsx +73 -0
  154. boulder-0.6.1/frontend/src/components/modals/AddMFCModal.tsx +148 -0
  155. boulder-0.6.1/frontend/src/components/modals/AddReactorModal.tsx +135 -0
  156. boulder-0.6.1/frontend/src/components/modals/ConfirmDeleteNodeModal.tsx +51 -0
  157. boulder-0.6.1/frontend/src/components/modals/YAMLEditorModal.tsx +173 -0
  158. boulder-0.6.1/frontend/src/components/panels/EditNetworkCard.tsx +85 -0
  159. boulder-0.6.1/frontend/src/components/panels/PropertiesPanel.test.tsx +145 -0
  160. boulder-0.6.1/frontend/src/components/panels/PropertiesPanel.tsx +319 -0
  161. boulder-0.6.1/frontend/src/components/panels/RunControl.tsx +232 -0
  162. boulder-0.6.1/frontend/src/components/panels/ScenarioPane.tsx +145 -0
  163. boulder-0.6.1/frontend/src/components/panels/SimulateCard.test.tsx +256 -0
  164. boulder-0.6.1/frontend/src/components/panels/SimulateCard.tsx +388 -0
  165. boulder-0.6.1/frontend/src/components/panels/SolverDetailsModal.tsx +185 -0
  166. boulder-0.6.1/frontend/src/components/panels/solverShared.ts +37 -0
  167. boulder-0.6.1/frontend/src/components/results/ConvergenceTab.tsx +265 -0
  168. boulder-0.6.1/frontend/src/components/results/ErrorTab.tsx +19 -0
  169. boulder-0.6.1/frontend/src/components/results/PlotsTab.test.tsx +123 -0
  170. boulder-0.6.1/frontend/src/components/results/PlotsTab.tsx +461 -0
  171. boulder-0.6.1/frontend/src/components/results/PluginTab.tsx +166 -0
  172. boulder-0.6.1/frontend/src/components/results/ResultsTabs.tsx +204 -0
  173. boulder-0.6.1/frontend/src/components/results/SankeyTab.test.tsx +78 -0
  174. boulder-0.6.1/frontend/src/components/results/SankeyTab.tsx +107 -0
  175. boulder-0.6.1/frontend/src/components/results/SummaryTab.tsx +50 -0
  176. boulder-0.6.1/frontend/src/components/results/ThermoReportTab.tsx +162 -0
  177. boulder-0.6.1/frontend/src/components/simulation/SimulationOverlay.tsx +80 -0
  178. boulder-0.6.1/frontend/src/components/ui/Button.tsx +57 -0
  179. boulder-0.6.1/frontend/src/hooks/useKeyboardShortcuts.ts +19 -0
  180. boulder-0.6.1/frontend/src/hooks/useScenarioFocus.ts +37 -0
  181. boulder-0.6.1/frontend/src/hooks/useSimulationSSE.ts +114 -0
  182. boulder-0.6.1/frontend/src/index.css +97 -0
  183. boulder-0.6.1/frontend/src/lib/cn.ts +10 -0
  184. boulder-0.6.1/frontend/src/lib/cytoscapeNodeColor.test.ts +32 -0
  185. boulder-0.6.1/frontend/src/lib/cytoscapeNodeColor.ts +81 -0
  186. boulder-0.6.1/frontend/src/lib/plotAxis.test.ts +28 -0
  187. boulder-0.6.1/frontend/src/lib/plotAxis.ts +18 -0
  188. boulder-0.6.1/frontend/src/lib/units.ts +104 -0
  189. boulder-0.6.1/frontend/src/main.tsx +10 -0
  190. boulder-0.6.1/frontend/src/stores/configStore.ts +106 -0
  191. boulder-0.6.1/frontend/src/stores/layoutStore.ts +74 -0
  192. boulder-0.6.1/frontend/src/stores/resultsTabStore.ts +12 -0
  193. boulder-0.6.1/frontend/src/stores/scenarioStore.ts +73 -0
  194. boulder-0.6.1/frontend/src/stores/selectionStore.ts +18 -0
  195. boulder-0.6.1/frontend/src/stores/simulationStore.ts +73 -0
  196. boulder-0.6.1/frontend/src/stores/themeStore.ts +73 -0
  197. boulder-0.6.1/frontend/src/types/config.ts +32 -0
  198. boulder-0.6.1/frontend/src/types/guiAction.ts +7 -0
  199. boulder-0.6.1/frontend/src/types/plugin.ts +23 -0
  200. boulder-0.6.1/frontend/src/types/simulation.ts +96 -0
  201. boulder-0.6.1/frontend/tests/e2e/boulder.spec.ts +215 -0
  202. boulder-0.6.1/frontend/tests/unit/stores/configStore.test.ts +92 -0
  203. boulder-0.6.1/frontend/tsconfig.app.json +28 -0
  204. boulder-0.6.1/frontend/tsconfig.json +7 -0
  205. boulder-0.6.1/frontend/tsconfig.node.json +26 -0
  206. boulder-0.6.1/frontend/vite.config.ts +28 -0
  207. boulder-0.6.1/frontend/vitest.config.ts +16 -0
  208. boulder-0.6.1/mypy.ini +3 -0
  209. boulder-0.6.1/pyproject.toml +109 -0
  210. boulder-0.6.1/run.py +6 -0
  211. boulder-0.6.1/setup.cfg +4 -0
  212. boulder-0.6.1/tests/README.md +169 -0
  213. boulder-0.6.1/tests/configs/mix_react_streams.yaml +97 -0
  214. boulder-0.6.1/tests/configs/mix_react_streams_highT.yaml +15 -0
  215. boulder-0.6.1/tests/conftest.py +10 -0
  216. boulder-0.6.1/tests/fixtures/stone_v2/invalid/01_v1_nodes_connections.yaml +11 -0
  217. boulder-0.6.1/tests/fixtures/stone_v2/invalid/02_mixed_network_and_stages.yaml +11 -0
  218. boulder-0.6.1/tests/fixtures/stone_v2/invalid/03_missing_dynamic_block.yaml +7 -0
  219. boulder-0.6.1/tests/fixtures/stone_v2/invalid/04_undeclared_dynamic_block.yaml +10 -0
  220. boulder-0.6.1/tests/fixtures/stone_v2/invalid/05_source_only_connection.yaml +11 -0
  221. boulder-0.6.1/tests/fixtures/stone_v2/invalid/06_target_only_connection.yaml +11 -0
  222. boulder-0.6.1/tests/fixtures/stone_v2/invalid/07_duplicate_node_id.yaml +21 -0
  223. boulder-0.6.1/tests/fixtures/stone_v2/invalid/08_reserved_stage_name.yaml +12 -0
  224. boulder-0.6.1/tests/fixtures/stone_v2/invalid/09_unknown_top_level_key.yaml +12 -0
  225. boulder-0.6.1/tests/fixtures/stone_v2/invalid/10_unknown_item_key.yaml +12 -0
  226. boulder-0.6.1/tests/fixtures/stone_v2/invalid/11_intra_stage_logical_connection.yaml +22 -0
  227. boulder-0.6.1/tests/fixtures/stone_v2/invalid/12_downstream_operating_state.yaml +29 -0
  228. boulder-0.6.1/tests/fixtures/stone_v2/invalid/13_inline_inlet_port.yaml +13 -0
  229. boulder-0.6.1/tests/fixtures/stone_v2/invalid/14_over_specified_sizing.yaml +11 -0
  230. boulder-0.6.1/tests/fixtures/stone_v2/invalid/15_ambiguous_mfc_conservation.yaml +36 -0
  231. boulder-0.6.1/tests/fixtures/stone_v2/invalid/16_stages_and_network_coexist.yaml +16 -0
  232. boulder-0.6.1/tests/fixtures/stone_v2/invalid/17_stage_dag_cycle.yaml +31 -0
  233. boulder-0.6.1/tests/fixtures/stone_v2/invalid/18_outlet_sink_as_source.yaml +19 -0
  234. boulder-0.6.1/tests/fixtures/stone_v2/invalid/19_advance_time_without_advance.yaml +16 -0
  235. boulder-0.6.1/tests/fixtures/stone_v2/invalid/20_advance_missing_advance_time.yaml +15 -0
  236. boulder-0.6.1/tests/fixtures/stone_v2/invalid/21_mixed_composition_bases.yaml +12 -0
  237. boulder-0.6.1/tests/fixtures/stone_v2/invalid/22_reservoir_missing_state.yaml +10 -0
  238. boulder-0.6.1/tests/fixtures/stone_v2/invalid/23_pressure_controller_no_master.yaml +42 -0
  239. boulder-0.6.1/tests/fixtures/stone_v2/valid/01_network_single_stage.yaml +22 -0
  240. boulder-0.6.1/tests/fixtures/stone_v2/valid/02_staged_logical_handoff.yaml +36 -0
  241. boulder-0.6.1/tests/fixtures/stone_v2/valid/03_staged_logical_handoff_flow.yaml +40 -0
  242. boulder-0.6.1/tests/fixtures/stone_v2/valid/04_batch_reactor_initial.yaml +14 -0
  243. boulder-0.6.1/tests/fixtures/stone_v2/valid/05_const_pressure_psr.yaml +38 -0
  244. boulder-0.6.1/tests/fixtures/stone_v2/valid/06_outlet_sink.yaml +29 -0
  245. boulder-0.6.1/tests/fixtures/stone_v2/valid/07_empty_mfc_conservation.yaml +31 -0
  246. boulder-0.6.1/tests/fixtures/stone_v2/valid/08_mechanism_switch.yaml +41 -0
  247. boulder-0.6.1/tests/fixtures/stone_v2/valid/09_phase_alias.yaml +25 -0
  248. boulder-0.6.1/tests/test_api.py +812 -0
  249. boulder-0.6.1/tests/test_bindings_resolver.py +234 -0
  250. boulder-0.6.1/tests/test_bindings_runtime.py +82 -0
  251. boulder-0.6.1/tests/test_cantera_script_emitter.py +149 -0
  252. boulder-0.6.1/tests/test_cli_browser.py +129 -0
  253. boulder-0.6.1/tests/test_cli_headless.py +261 -0
  254. boulder-0.6.1/tests/test_cli_import.py +56 -0
  255. boulder-0.6.1/tests/test_composite_yaml_roundtrip.py +393 -0
  256. boulder-0.6.1/tests/test_config_output_block.py +131 -0
  257. boulder-0.6.1/tests/test_config_validation.py +301 -0
  258. boulder-0.6.1/tests/test_data/invalid/duplicate_node_id.yaml +8 -0
  259. boulder-0.6.1/tests/test_data/invalid/missing_properties_node.yaml +7 -0
  260. boulder-0.6.1/tests/test_data/invalid/unknown_connection_ref.yaml +11 -0
  261. boulder-0.6.1/tests/test_default_boulder_logging.py +61 -0
  262. boulder-0.6.1/tests/test_export.py +139 -0
  263. boulder-0.6.1/tests/test_flow_conservation.py +243 -0
  264. boulder-0.6.1/tests/test_gui_actions_api.py +202 -0
  265. boulder-0.6.1/tests/test_network_plugin.py +179 -0
  266. boulder-0.6.1/tests/test_normalize_signals.py +137 -0
  267. boulder-0.6.1/tests/test_output_summary.py +132 -0
  268. boulder-0.6.1/tests/test_output_summary_integration.py +55 -0
  269. boulder-0.6.1/tests/test_package_import.py +8 -0
  270. boulder-0.6.1/tests/test_payload_store.py +259 -0
  271. boulder-0.6.1/tests/test_phase_b_transient.py +426 -0
  272. boulder-0.6.1/tests/test_phase_c_continuation.py +395 -0
  273. boulder-0.6.1/tests/test_plugin_example.py +347 -0
  274. boulder-0.6.1/tests/test_ports_conflict.py +226 -0
  275. boulder-0.6.1/tests/test_ports_expansion.py +137 -0
  276. boulder-0.6.1/tests/test_pressure_controller_connection.py +226 -0
  277. boulder-0.6.1/tests/test_pressure_defaults.py +246 -0
  278. boulder-0.6.1/tests/test_psr_volume_sizing.py +148 -0
  279. boulder-0.6.1/tests/test_result_cache.py +554 -0
  280. boulder-0.6.1/tests/test_runner.py +413 -0
  281. boulder-0.6.1/tests/test_sankey_if_no_species.py +25 -0
  282. boulder-0.6.1/tests/test_sankey_links_for_api.py +75 -0
  283. boulder-0.6.1/tests/test_scenario_focus.py +89 -0
  284. boulder-0.6.1/tests/test_scopes.py +364 -0
  285. boulder-0.6.1/tests/test_script_emitter_class_dispatch.py +139 -0
  286. boulder-0.6.1/tests/test_signals_registry.py +319 -0
  287. boulder-0.6.1/tests/test_sim2stone/test_fixture_scripts_sim2stone.py +392 -0
  288. boulder-0.6.1/tests/test_sim2stone_ast.py +403 -0
  289. boulder-0.6.1/tests/test_sim2stone_mechanisms.py +54 -0
  290. boulder-0.6.1/tests/test_sim_to_yaml.py +454 -0
  291. boulder-0.6.1/tests/test_simulation_cleanup.py +283 -0
  292. boulder-0.6.1/tests/test_smart_detection.py +454 -0
  293. boulder-0.6.1/tests/test_solver_dispatch.py +644 -0
  294. boulder-0.6.1/tests/test_solver_mode.py +247 -0
  295. boulder-0.6.1/tests/test_staged_solver.py +1261 -0
  296. boulder-0.6.1/tests/test_staged_viz_flow.py +777 -0
  297. boulder-0.6.1/tests/test_stone_v2_fixtures.py +43 -0
  298. boulder-0.6.1/tests/test_sweep_runset.py +39 -0
  299. boulder-0.6.1/tests/test_transient_capture.py +73 -0
  300. boulder-0.6.1/tests/test_unfold.py +302 -0
  301. boulder-0.6.1/tests/test_unit.py +267 -0
  302. boulder-0.6.1/tests/test_yaml_comment_system.py +852 -0
  303. boulder-0.6.1/tests/test_yaml_unit_map.py +237 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "template": "https://github.com/spark-cleantech/package-template",
3
+ "commit": "02767a57f4444fc8413d0e3265a5a0f92d586794",
4
+ "checkout": null,
5
+ "context": {
6
+ "cookiecutter": {
7
+ "project_name": "boulder",
8
+ "repo_name": "boulder",
9
+ "project_author": "Erwan Pannier",
10
+ "project_short_description": "Cantera Reactor Net Simulations",
11
+ "copyright_year": "2025",
12
+ "mypy_strict": false,
13
+ "_template": "https://github.com/spark-cleantech/package-template"
14
+ }
15
+ },
16
+ "directory": null
17
+ }
@@ -0,0 +1,162 @@
1
+ ______________________________________________________________________
2
+
3
+ ## name: boulder-gui description: >- Start, drive, and verify the Boulder web GUI (FastAPI + React/Cytoscape) for STONE YAML simulations. Use when the user asks to run a simulation in the browser, test the UI, click nodes, read Thermo/Summary tabs, preload a YAML, or automate E2E checks against localhost:8050. Covers server startup, config loading, graph interaction, results tabs, and API fallbacks when browser canvas clicks fail.
4
+
5
+ # Boulder GUI
6
+
7
+ Guide for agents verifying STONE simulations through the web UI or its HTTP API.
8
+
9
+ ## When to use what
10
+
11
+ | Goal | Prefer |
12
+ |------|--------|
13
+ | Assert outlet T, stream points, staged solve | **HTTP API** (reproducible, no canvas coords) |
14
+ | Visual layout, UX, tab wiring | **Browser MCP** |
15
+ | Custom reactor kinds via plugins | `BOULDER_PLUGINS` + registered plugin package |
16
+
17
+ Always rebuild frontend after UI changes: `cd frontend && npm run build`.
18
+
19
+ ## Start the server
20
+
21
+ Default URL: `http://127.0.0.1:8050`. Health: `GET /api/health`.
22
+
23
+ ```bash
24
+ conda activate boulder
25
+ cd frontend && npm run build && cd ..
26
+ boulder path/to/config.yaml --no-open
27
+ # or: boulder --no-open # empty / default config
28
+ ```
29
+
30
+ Optional plugins (entry points or `BOULDER_PLUGINS` env var) load at startup.
31
+
32
+ After changing the YAML on disk, **restart the server** — preloaded config is read once at startup.
33
+
34
+ ## UI layout
35
+
36
+ ```
37
+ ┌─────────────────────────────────────────────────────────────┐
38
+ │ Header: [filename.yaml] Light/Dark │
39
+ ├──────────────┬──────────────────────────────────────────────┤
40
+ │ Edit Network │ Reactor graph (Cytoscape canvas) │
41
+ │ Simulate │ Click nodes/edges to select │
42
+ │ Properties │ │
43
+ ├──────────────┴──────────────────────────────────────────────┤
44
+ │ Results: Plots | Sankey | Thermo | Summary | Convergence │
45
+ │ (+ plugin tabs e.g. Network) │
46
+ └─────────────────────────────────────────────────────────────┘
47
+ ```
48
+
49
+ - **Header filename button** — opens YAML editor (Save/Cancel).
50
+ - **Upload Config** — load `.yaml` / `.yml` / `.py` from disk.
51
+ - **Run Simulation (Ctrl+Enter)** — disabled when config has no nodes or a run is in progress.
52
+ - **Properties panel** (left) — selected node/edge fields; stream points and terminal sinks show computed Material Stream (read-only after solve).
53
+ - **Graph** — nodes are circles (reactors) or octagons (reservoirs/sinks); edges are flow devices.
54
+
55
+ ## Standard agent workflow (browser)
56
+
57
+ Copy and track:
58
+
59
+ ```
60
+ GUI check:
61
+ - [ ] Server up (/api/health)
62
+ - [ ] Preloaded config matches expected YAML (/api/configs/preloaded)
63
+ - [ ] Page shows correct filename (not untitled.yaml)
64
+ - [ ] Run Simulation → completes (Download Python button appears)
65
+ - [ ] Select target node → Thermo tab shows expected T/P/composition
66
+ ```
67
+
68
+ 1. Navigate to `http://127.0.0.1:8050/` (hard refresh if assets 404 or MIME errors after a frontend rebuild).
69
+ 1. Confirm header shows the expected `*.yaml` name. If it shows `untitled.yaml`, wait for preload toast or reload; click filename to verify YAML content.
70
+ 1. Click **Run Simulation** (or wait until enabled).
71
+ 1. Wait for **Download Python** and result tabs — not "Running...".
72
+ 1. Open **Thermo** tab.
73
+ 1. **Select a node** on the graph (see canvas clicks below).
74
+ 1. Read `reactor_report` / thermo text in the tab; cross-check via API if critical.
75
+
76
+ ### Graph selection (browser automation)
77
+
78
+ - The graph is a **Cytoscape `<canvas>`** — node ids are **not** exposed to `browser_search`.
79
+ - **Single click** on a node → selects it (Properties + Thermo).
80
+ - **Double click** on a node within ~300 ms → also switches to **Thermo** tab.
81
+ - **Single click** on a **MassFlowController** edge → selects it and opens **Thermo** (shows source reactor T/P/X and mdot).
82
+
83
+ **Reliable click pattern (cursor-ide-browser):**
84
+
85
+ 1. `browser_snapshot` with `take_screenshot_afterwards: true`.
86
+ 1. Immediately `browser_mouse_click_xy` using **screenshot coordinates** on the node center.
87
+ 1. Target must be `<canvas>`, not `#graph-container` or the sidebar grid.
88
+ 1. If click fails, take a **fresh** screenshot before retrying (cache invalidates).
89
+
90
+ Clicks are fragile at narrow viewports — resize browser wider if needed. When automation cannot select a node, use the **API verification** below; do not loop on coordinates.
91
+
92
+ ## Results tabs
93
+
94
+ | Tab | Needs selection? | Content |
95
+ |-----|------------------|---------|
96
+ | **Plots** | No | Time series after transient runs |
97
+ | **Sankey** | No | Species / energy flows (plugin may customize) |
98
+ | **Thermo** | **Yes** — node or MFC | `reactor_report`, full Cantera `thermo_report` |
99
+ | **Summary** | No | Elapsed time, text summary |
100
+ | **Convergence** | No | Staged-solve progress (redirects residence profiles to Plots when applicable) |
101
+ | **Network** | Plugin | Live reactor network view |
102
+
103
+ Terminal **OutletSink** nodes: post-solve thermo comes from `_refresh_terminal_sinks` (legacy path). **Inter-stage stream-point** diamonds (`{source}_outlet`) are refreshed during staged solve — preferred for multi-stage models.
104
+
105
+ ## API verification (preferred for agents)
106
+
107
+ Same server session as the browser. See [reference.md](reference.md) for full scripts.
108
+
109
+ Quick checks:
110
+
111
+ ```python
112
+ import json, urllib.request
113
+
114
+ base = "http://127.0.0.1:8050"
115
+ pre = json.loads(urllib.request.urlopen(f"{base}/api/configs/preloaded").read())
116
+ assert pre["preloaded"]
117
+ ```
118
+
119
+ Start simulation → poll results:
120
+
121
+ ```python
122
+ import time
123
+ req = urllib.request.Request(
124
+ f"{base}/api/simulations",
125
+ data=json.dumps({"config": pre["config"]}).encode(),
126
+ headers={"Content-Type": "application/json"},
127
+ method="POST",
128
+ )
129
+ sim_id = json.loads(urllib.request.urlopen(req).read())["simulation_id"]
130
+ for _ in range(120):
131
+ st = json.loads(urllib.request.urlopen(
132
+ f"{base}/api/simulations/{sim_id}/results").read())
133
+ if st.get("is_complete") or st.get("status") == "error":
134
+ break
135
+ time.sleep(0.5)
136
+
137
+ reports = st["reactor_reports"]
138
+ T_K = reports["outlet"]["T"]
139
+ assert T_K > 300, f"outlet stuck at placeholder T: {T_K} K"
140
+ ```
141
+
142
+ Assert invariants on `updated_nodes` / `updated_connections` when testing staged networks (stream points present, no duplicate interface edges).
143
+
144
+ ## Common failures
145
+
146
+ | Symptom | Cause | Fix |
147
+ |---------|-------|-----|
148
+ | `untitled.yaml`, Run disabled | No preload; empty default | Start with YAML arg or Upload Config |
149
+ | Outlet 25 °C / N2 | Terminal `Reservoir` placeholder, or stale preload | Use `OutletSink: {}`; restart server |
150
+ | `Unsupported reactor type` | Kind not in core Boulder; plugin not loaded | Set `BOULDER_PLUGINS`; restart server |
151
+ | JS chunk MIME `text/html` | Stale frontend hash or SPA catch-all | `npm run build`, restart server, hard refresh |
152
+ | Thermo says "Select a node…" | Nothing selected, or canvas click missed | Retry canvas click or use API |
153
+ | Properties Material Stream empty | `terminal_sink` not in `updated_nodes` yet | Thermo tab / API `reactor_reports` still authoritative |
154
+
155
+ ## Dev mode (human)
156
+
157
+ `boulder --dev` — Vite on `:5173` proxies `/api` to backend. Agents testing production-like behavior should use built `frontend/dist` (default server).
158
+
159
+ ## Further reading
160
+
161
+ - [reference.md](reference.md) — SSE stream, upload endpoint, browser checklist
162
+ - Repo [AGENTS.md](../../../AGENTS.md) — conda env, `make qa`, targeted tests
@@ -0,0 +1,89 @@
1
+ # Boulder GUI — API & automation reference
2
+
3
+ Base URL assumed: `http://127.0.0.1:8050`.
4
+
5
+ ## Endpoints
6
+
7
+ | Method | Path | Purpose |
8
+ |--------|------|---------|
9
+ | GET | `/api/health` | Liveness |
10
+ | GET | `/api/configs/preloaded` | Config loaded at server start (`preloaded`, `config`, `yaml`, `filename`) |
11
+ | GET | `/api/configs/default` | Built-in empty template |
12
+ | POST | `/api/configs/upload` | Multipart YAML/YML/PY upload |
13
+ | POST | `/api/configs/validate` | Validate normalized config JSON |
14
+ | POST | `/api/simulations` | Body: `{"config": {...}}` → `{simulation_id}` |
15
+ | GET | `/api/simulations/{id}/stream` | SSE progress (`complete`, `error`, partial updates) |
16
+ | GET | `/api/simulations/{id}/results` | Full results when done; `status: running` while active |
17
+
18
+ ## SSE stream (browser-equivalent completion)
19
+
20
+ ```python
21
+ import urllib.request, json
22
+
23
+ sim_id = "..." # from POST /api/simulations
24
+ with urllib.request.urlopen(
25
+ f"http://127.0.0.1:8050/api/simulations/{sim_id}/stream") as r:
26
+ event = None
27
+ while True:
28
+ line = r.readline().decode().strip()
29
+ if not line:
30
+ continue
31
+ if line.startswith("event:"):
32
+ event = line[6:].strip()
33
+ elif line.startswith("data:") and event in ("complete", "error"):
34
+ payload = json.loads(line[5:])
35
+ print(event, payload.get("error_message"))
36
+ break
37
+ ```
38
+
39
+ On `complete`, payload includes `reactor_reports`, `updated_nodes`, `updated_connections`, `sankey_*`, `summary`, `elapsed_time`.
40
+
41
+ ## Results shape (thermo checks)
42
+
43
+ ```python
44
+ reports = payload["reactor_reports"] # dict[node_id → report]
45
+ report = reports["outlet"]
46
+ report["T"] # K
47
+ report["P"] # Pa
48
+ report["X"] # mole fractions dict
49
+ report["reactor_report"] # human-readable multi-line str
50
+ report["thermo_report"] # Cantera phase report str
51
+ ```
52
+
53
+ Reservoir / OutletSink entries use the reservoir branch of `_generate_reactor_reports` (fixed-state wording in `reactor_report` even when T reflects solved outlet).
54
+
55
+ ## Preloaded vs client config
56
+
57
+ The UI sends the **in-memory normalized config** from Zustand (`useConfigStore`) to `POST /api/simulations`, not a re-read of disk. After server restart, the browser reload pulls fresh preload via `GET /api/configs/preloaded` on mount (`AppShell.tsx`).
58
+
59
+ If the header filename is wrong, the client may still hold an old config — reload the page.
60
+
61
+ ## Browser MCP tool sequence
62
+
63
+ ```
64
+ browser_navigate → http://127.0.0.1:8050/
65
+ browser_snapshot (take_screenshot_afterwards: true)
66
+ browser_click → Run Simulation button ref
67
+ # wait until "Download Python" appears in snapshot
68
+ browser_click → Thermo tab
69
+ browser_snapshot (take_screenshot_afterwards: true)
70
+ browser_mouse_click_xy → screenshot coords on target node (target: canvas)
71
+ browser_snapshot → read Thermo panel text in screenshot / search if rendered
72
+ ```
73
+
74
+ Unlock browser when finished: `browser_lock` action `unlock`.
75
+
76
+ ## Plugins
77
+
78
+ Custom reactor kinds register via `boulder.plugins` entry points or the `BOULDER_PLUGINS` environment variable (dotted module path). The CLI may also pass `--runner PKG.MOD:CLASS` for a custom load/normalize pipeline.
79
+
80
+ Symptom: `Unsupported reactor type: 'MyKind'` → plugin package not loaded or builder not registered.
81
+
82
+ ## Frontend rebuild
83
+
84
+ ```bash
85
+ conda activate boulder
86
+ cd frontend && npm run build
87
+ ```
88
+
89
+ Restart uvicorn after backend or static asset changes.
@@ -0,0 +1,8 @@
1
+ #configs/ # Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
2
+ *.yaml
3
+ !configs/default.yaml # except from this one
4
+ !configs/mix_react_streams.yaml
5
+ !configs/grouped_nodes.yaml
6
+ !examples/mix1.yaml
7
+ *.csv
8
+ *.json
@@ -0,0 +1,21 @@
1
+ These are the Lead developer instructions.
2
+
3
+ Follow the development guidelines :
4
+
5
+ CODING
6
+ - Keep line length under 110 characters
7
+ - Don't wrap code in try except blocks unless you're catching a specific exception.
8
+ - Better raise a clear error message than hide the problems/fix in the back of the user.
9
+
10
+ TESTING
11
+ - Use test-driven development
12
+ - Fix library code, not tests
13
+ - Run tests in 'boulder' conda env. It contains `node`.
14
+ - Frontend build runs from `frontend` with `npm run build`. Always rebuild the frontend after changes.
15
+ - After successful tests, use `make qa` for code quality.
16
+ - Do not implement mock tests, only real tests.
17
+ - Explicit describe in the test docstrings what is asserted in the test.
18
+ - For E2E tests also, explicitely state in the docstrings what is assessed.
19
+
20
+ VERSIONING
21
+ - Never perform git commits
@@ -0,0 +1,193 @@
1
+ name: on-push
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ tags:
8
+ - '*'
9
+ pull_request:
10
+ branches:
11
+ - main
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ defaults:
18
+ run:
19
+ shell: bash -l {0}
20
+
21
+ jobs:
22
+ pre-commit:
23
+ runs-on: ubuntu-latest
24
+ steps:
25
+ - uses: actions/checkout@v4
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: 3.x
29
+ - uses: pre-commit/action@v3.0.1
30
+
31
+ combine-environments:
32
+ runs-on: ubuntu-latest
33
+
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: actions/setup-python@v5
37
+ with:
38
+ python-version: 3.x
39
+ - name: Install conda-merge
40
+ run: |
41
+ python -m pip install conda-merge
42
+ - name: Combine environments
43
+ run: |
44
+ for SUFFIX in ci integration; do
45
+ conda-merge ci/environment-$SUFFIX.yml environment.yml > ci/combined-environment-$SUFFIX.yml || exit
46
+ done
47
+ - uses: actions/upload-artifact@v4
48
+ with:
49
+ name: combined-environments
50
+ path: ci/combined-environment-*.yml
51
+
52
+ unit-tests:
53
+ name: unit-tests
54
+ needs: combine-environments
55
+ runs-on: ubuntu-latest
56
+ strategy:
57
+ matrix:
58
+ python-version: ['3.11']
59
+
60
+ steps:
61
+ - uses: actions/checkout@v4
62
+ - uses: actions/download-artifact@v4
63
+ with:
64
+ name: combined-environments
65
+ path: ci
66
+ - name: Get current date
67
+ id: date
68
+ run: echo "date=$(date +%Y-%m-%d)" >> "${GITHUB_OUTPUT}"
69
+ - uses: mamba-org/setup-micromamba@v2
70
+ with:
71
+ environment-file: ci/combined-environment-ci.yml
72
+ environment-name: DEVELOP
73
+ cache-environment: true
74
+ cache-environment-key: environment-${{ steps.date.outputs.date }}
75
+ cache-downloads-key: downloads-${{ steps.date.outputs.date }}
76
+ create-args: >-
77
+ python=${{ matrix.python-version }}
78
+ - name: Install package
79
+ run: |
80
+ python -m pip install --no-deps -e .
81
+ - name: Run tests
82
+ run: |
83
+ make unit-tests COV_REPORT=xml
84
+
85
+ type-check:
86
+ needs: [combine-environments, unit-tests]
87
+ runs-on: ubuntu-latest
88
+
89
+ steps:
90
+ - uses: actions/checkout@v4
91
+ - uses: actions/download-artifact@v4
92
+ with:
93
+ name: combined-environments
94
+ path: ci
95
+ - name: Get current date
96
+ id: date
97
+ run: echo "date=$(date +%Y-%m-%d)" >> "${GITHUB_OUTPUT}"
98
+ - uses: mamba-org/setup-micromamba@v2
99
+ with:
100
+ environment-file: ci/combined-environment-ci.yml
101
+ environment-name: DEVELOP
102
+ cache-environment: true
103
+ cache-environment-key: environment-${{ steps.date.outputs.date }}
104
+ cache-downloads-key: downloads-${{ steps.date.outputs.date }}
105
+ create-args: >-
106
+ python=3.11
107
+ - name: Install package
108
+ run: |
109
+ python -m pip install --no-deps -e .
110
+ - name: Run code quality checks
111
+ run: |
112
+ make type-check
113
+
114
+ frontend-tests:
115
+ name: frontend-tests
116
+ needs: [unit-tests]
117
+ runs-on: ubuntu-latest
118
+
119
+ steps:
120
+ - uses: actions/checkout@v4
121
+ - uses: actions/setup-node@v4
122
+ with:
123
+ node-version: '20'
124
+ cache: 'npm'
125
+ cache-dependency-path: frontend/package-lock.json
126
+ - name: Install frontend dependencies
127
+ working-directory: frontend
128
+ run: npm ci
129
+ - name: Run frontend unit tests
130
+ working-directory: frontend
131
+ run: npm run test:unit
132
+ - name: Type check frontend
133
+ working-directory: frontend
134
+ run: npm run type-check
135
+ - name: Build frontend
136
+ working-directory: frontend
137
+ run: npm run build
138
+
139
+ docs-build:
140
+ needs: [combine-environments, unit-tests]
141
+ runs-on: ubuntu-latest
142
+
143
+ steps:
144
+ - uses: actions/checkout@v4
145
+ - uses: actions/download-artifact@v4
146
+ with:
147
+ name: combined-environments
148
+ path: ci
149
+ - name: Get current date
150
+ id: date
151
+ run: echo "date=$(date +%Y-%m-%d)" >> "${GITHUB_OUTPUT}"
152
+ - uses: mamba-org/setup-micromamba@v2
153
+ with:
154
+ environment-file: ci/combined-environment-ci.yml
155
+ environment-name: DEVELOP
156
+ cache-environment: true
157
+ cache-environment-key: environment-${{ steps.date.outputs.date }}
158
+ cache-downloads-key: downloads-${{ steps.date.outputs.date }}
159
+ create-args: >-
160
+ python=3.11
161
+ - name: Install package
162
+ run: |
163
+ python -m pip install --no-deps -e .
164
+ - name: Build documentation
165
+ run: |
166
+ make docs-build
167
+ # create docs in html/
168
+ # From https://github.com/marketplace/actions/sphinx-build
169
+ - uses: actions/upload-pages-artifact@v3
170
+ with:
171
+ path: docs/_build/html/
172
+
173
+ # Deployment job.
174
+ deploy:
175
+ needs: docs-build
176
+
177
+ # Deploy only when the main branch is pushed.
178
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
179
+
180
+ # Grant GITHUB_TOKEN the permissions required to make a Pages deployment.
181
+ permissions:
182
+ contents: read
183
+ pages: write # To deploy to Pages.
184
+ id-token: write # To verify the deployment originates from an appropriate source.
185
+
186
+ environment:
187
+ name: github-pages
188
+ url: ${{ steps.deployment.outputs.page_url }}
189
+ runs-on: ubuntu-latest
190
+ steps:
191
+ - name: Deploy to GitHub Pages
192
+ id: deployment
193
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,88 @@
1
+ name: Build and Publish on Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build wheel + sdist (with bundled frontend)
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write # upload artifacts to the GitHub release
13
+
14
+ steps:
15
+ - name: Checkout code
16
+ uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0 # Fetch all history for setuptools_scm to work
19
+
20
+ - name: Set up Node
21
+ uses: actions/setup-node@v4
22
+ with:
23
+ node-version: '20'
24
+ cache: 'npm'
25
+ cache-dependency-path: frontend/package-lock.json
26
+
27
+ - name: Build frontend
28
+ working-directory: frontend
29
+ run: |
30
+ npm ci
31
+ npm run build # emits into boulder/_frontend (see vite.config.ts)
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v5
35
+ with:
36
+ python-version: '3.11'
37
+
38
+ - name: Install build dependencies
39
+ run: |
40
+ python -m pip install --upgrade pip
41
+ pip install build setuptools setuptools_scm wheel
42
+
43
+ - name: Build wheel and source distribution
44
+ run: python -m build
45
+
46
+ - name: Verify frontend is bundled in the wheel
47
+ run: |
48
+ python -m zipfile -l dist/*.whl | grep -q "boulder/_frontend/index.html" \
49
+ && echo "OK: frontend bundled in wheel" \
50
+ || (echo "ERROR: frontend missing from wheel" && exit 1)
51
+
52
+ - name: Display build artifacts
53
+ run: ls -lh dist/
54
+
55
+ - name: Upload artifacts to the GitHub release
56
+ uses: softprops/action-gh-release@v2
57
+ with:
58
+ files: |
59
+ dist/*.whl
60
+ dist/*.tar.gz
61
+ env:
62
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63
+
64
+ - name: Store distribution packages
65
+ uses: actions/upload-artifact@v4
66
+ with:
67
+ name: python-package-distributions
68
+ path: dist/
69
+
70
+ publish-to-pypi:
71
+ name: Publish to PyPI
72
+ needs: build
73
+ runs-on: ubuntu-latest
74
+ environment:
75
+ name: pypi
76
+ url: https://pypi.org/project/boulder/
77
+ permissions:
78
+ id-token: write # mandatory for PyPI Trusted Publishing (OIDC)
79
+
80
+ steps:
81
+ - name: Download distribution packages
82
+ uses: actions/download-artifact@v4
83
+ with:
84
+ name: python-package-distributions
85
+ path: dist/
86
+
87
+ - name: Publish distribution to PyPI
88
+ uses: pypa/gh-action-pypi-publish@release/v1