monkeybrain-runtime 1.0.0__py3-none-any.whl

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 (838) hide show
  1. monkeybrain_runtime-1.0.0.dist-info/METADATA +76 -0
  2. monkeybrain_runtime-1.0.0.dist-info/RECORD +838 -0
  3. monkeybrain_runtime-1.0.0.dist-info/WHEEL +5 -0
  4. monkeybrain_runtime-1.0.0.dist-info/entry_points.txt +3 -0
  5. monkeybrain_runtime-1.0.0.dist-info/top_level.txt +2 -0
  6. services/__init__.py +8 -0
  7. services/agentos/__init__.py +0 -0
  8. services/agentos/main.py +1 -0
  9. services/assets/helpers/__init__.py +12 -0
  10. services/assets/helpers/device.py +59 -0
  11. services/assets/helpers/equipment.py +179 -0
  12. services/assets/helpers/instruments.py +72 -0
  13. services/assets/helpers/machines.py +183 -0
  14. services/assets/helpers/materials.py +76 -0
  15. services/assets/helpers/parts.py +116 -0
  16. services/assets/helpers/plc.py +134 -0
  17. services/assets/helpers/tags.py +108 -0
  18. services/assets/helpers/tools.py +101 -0
  19. services/assets/main.py +75 -0
  20. services/assets/models/__init__.py +12 -0
  21. services/assets/models/device.py +79 -0
  22. services/assets/models/equipment.py +222 -0
  23. services/assets/models/instruments.py +85 -0
  24. services/assets/models/machines.py +230 -0
  25. services/assets/models/material.py +266 -0
  26. services/assets/models/parts.py +96 -0
  27. services/assets/models/plc.py +264 -0
  28. services/assets/models/tags.py +76 -0
  29. services/assets/models/tools.py +179 -0
  30. services/assets/routers/__init__.py +12 -0
  31. services/assets/routers/classes.py +65 -0
  32. services/assets/routers/device.py +86 -0
  33. services/assets/routers/equipment.py +145 -0
  34. services/assets/routers/families.py +61 -0
  35. services/assets/routers/instruments.py +70 -0
  36. services/assets/routers/machines.py +136 -0
  37. services/assets/routers/materials.py +105 -0
  38. services/assets/routers/parts.py +130 -0
  39. services/assets/routers/plc.py +94 -0
  40. services/assets/routers/subclasses.py +68 -0
  41. services/assets/routers/tags.py +138 -0
  42. services/assets/routers/tools.py +113 -0
  43. services/auth/helpers/__init__.py +13 -0
  44. services/auth/helpers/approval_decisions.py +261 -0
  45. services/auth/helpers/audit_elasticsearch_sync.py +350 -0
  46. services/auth/helpers/departments.py +53 -0
  47. services/auth/helpers/graph_store.py +848 -0
  48. services/auth/helpers/influx_store.py +280 -0
  49. services/auth/helpers/me.py +33 -0
  50. services/auth/helpers/nats_consumer.py +618 -0
  51. services/auth/helpers/nats_store.py +242 -0
  52. services/auth/helpers/permissions.py +62 -0
  53. services/auth/helpers/roles.py +87 -0
  54. services/auth/helpers/store.py +54 -0
  55. services/auth/helpers/team_members.py +155 -0
  56. services/auth/helpers/teams.py +87 -0
  57. services/auth/helpers/tokens.py +71 -0
  58. services/auth/helpers/users.py +119 -0
  59. services/auth/helpers/websocket_broadcast.py +41 -0
  60. services/auth/main.py +88 -0
  61. services/auth/models/__init__.py +12 -0
  62. services/auth/models/departments.py +55 -0
  63. services/auth/models/login.py +20 -0
  64. services/auth/models/permissions.py +61 -0
  65. services/auth/models/roles.py +53 -0
  66. services/auth/models/session.py +26 -0
  67. services/auth/models/teamMembers.py +59 -0
  68. services/auth/models/teams.py +56 -0
  69. services/auth/models/users.py +77 -0
  70. services/auth/routers/__init__.py +14 -0
  71. services/auth/routers/auth.py +3839 -0
  72. services/auth/routers/departments.py +84 -0
  73. services/auth/routers/integration_config.py +703 -0
  74. services/auth/routers/me.py +28 -0
  75. services/auth/routers/permissions.py +96 -0
  76. services/auth/routers/roles.py +139 -0
  77. services/auth/routers/session.py +224 -0
  78. services/auth/routers/team_members.py +152 -0
  79. services/auth/routers/teams.py +112 -0
  80. services/auth/routers/users.py +131 -0
  81. services/auth/routers/websocket_events.py +247 -0
  82. services/batch_execution/__init__.py +19 -0
  83. services/batch_execution/pipeline_triggers/__init__.py +53 -0
  84. services/batch_execution/pipeline_triggers/pipeline_parameter_adjuster.py +440 -0
  85. services/batch_execution/pipeline_triggers/pipeline_trigger_engine.py +445 -0
  86. services/batch_execution/pipeline_triggers/re_evaluation_queue.py +341 -0
  87. services/batch_execution/pipeline_triggers/test_phase3_implementation.py +553 -0
  88. services/batch_execution/pipeline_triggers/workflow_reevaluator.py +367 -0
  89. services/batch_execution/smoke_test_e2e_feedback_loop.py +704 -0
  90. services/batch_execution/workflow_executor.py +478 -0
  91. services/changeover/helpers/__init__.py +11 -0
  92. services/changeover/helpers/changeover.py +87 -0
  93. services/changeover/helpers/changeover_common.py +55 -0
  94. services/changeover/helpers/changeover_events.py +66 -0
  95. services/changeover/helpers/changeover_kpis.py +33 -0
  96. services/changeover/helpers/changeover_matrix.py +60 -0
  97. services/changeover/helpers/changeover_procedures.py +164 -0
  98. services/changeover/helpers/changeover_windows.py +96 -0
  99. services/changeover/main.py +52 -0
  100. services/changeover/models/__init__.py +11 -0
  101. services/changeover/models/changeover.py +73 -0
  102. services/changeover/models/changeover_events.py +142 -0
  103. services/changeover/models/changeover_kpis.py +75 -0
  104. services/changeover/models/changeover_matrix.py +63 -0
  105. services/changeover/models/changeover_procedures.py +108 -0
  106. services/changeover/models/changeover_tasks.py +87 -0
  107. services/changeover/models/changeover_windows.py +72 -0
  108. services/changeover/routers/__init__.py +9 -0
  109. services/changeover/routers/changeover_events.py +127 -0
  110. services/changeover/routers/changeover_kpis.py +80 -0
  111. services/changeover/routers/changeover_matrix.py +80 -0
  112. services/changeover/routers/changeover_procedures.py +118 -0
  113. services/changeover/routers/changeover_windows.py +98 -0
  114. services/common/__init__.py +2 -0
  115. services/common/approval_chains.py +648 -0
  116. services/common/auth.py +56 -0
  117. services/common/cdc.py +52 -0
  118. services/common/compat.py +217 -0
  119. services/common/compliance.py +562 -0
  120. services/common/config.py +134 -0
  121. services/common/cors.py +17 -0
  122. services/common/data_transformation.py +195 -0
  123. services/common/db.py +577 -0
  124. services/common/embeddings.py +97 -0
  125. services/common/event_reducers.py +194 -0
  126. services/common/event_types.py +51 -0
  127. services/common/integration_ingestion.py +169 -0
  128. services/common/logging.py +204 -0
  129. services/common/models/__init__.py +2 -0
  130. services/common/models/databricks.py +25 -0
  131. services/common/models/enums.py +64 -0
  132. services/common/module_control.py +422 -0
  133. services/common/mongo_cdc_watcher.py +106 -0
  134. services/common/n8n_auth.py +22 -0
  135. services/common/neo4j_mirror.py +1087 -0
  136. services/common/ontology_registry.py +110 -0
  137. services/common/reasoning_traces.py +52 -0
  138. services/common/supply_chain_cdc.py +555 -0
  139. services/common/tracing.py +159 -0
  140. services/common/utils.py +30 -0
  141. services/customers/helpers/__init__.py +8 -0
  142. services/customers/helpers/customer_details.py +64 -0
  143. services/customers/helpers/customer_metadata.py +64 -0
  144. services/customers/helpers/customer_order_metrics.py +67 -0
  145. services/customers/helpers/customer_payment_data.py +67 -0
  146. services/customers/main.py +50 -0
  147. services/customers/models/__init__.py +8 -0
  148. services/customers/models/customer_details.py +42 -0
  149. services/customers/models/customer_metadata.py +97 -0
  150. services/customers/models/customer_order_metrics.py +86 -0
  151. services/customers/models/customer_payment_data.py +60 -0
  152. services/customers/routers/__init__.py +8 -0
  153. services/customers/routers/customer_details.py +88 -0
  154. services/customers/routers/customer_metadata.py +88 -0
  155. services/customers/routers/customer_order_metrics.py +88 -0
  156. services/customers/routers/customer_payment_data.py +88 -0
  157. services/documents/__init__.py +1 -0
  158. services/documents/helpers/__init__.py +6 -0
  159. services/documents/helpers/document_metadata.py +569 -0
  160. services/documents/helpers/document_workflows.py +215 -0
  161. services/documents/helpers/report_templates.py +113 -0
  162. services/documents/main.py +49 -0
  163. services/documents/models/__init__.py +6 -0
  164. services/documents/models/document_metadata.py +215 -0
  165. services/documents/models/document_workflows.py +136 -0
  166. services/documents/models/report_templates.py +132 -0
  167. services/documents/routers/__init__.py +6 -0
  168. services/documents/routers/document_metadata.py +654 -0
  169. services/documents/routers/document_workflows.py +146 -0
  170. services/documents/routers/report_templates.py +86 -0
  171. services/events/helpers/__init__.py +5 -0
  172. services/events/helpers/events.py +394 -0
  173. services/events/main.py +40 -0
  174. services/events/models/__init__.py +5 -0
  175. services/events/models/events.py +50 -0
  176. services/events/routers/__init__.py +6 -0
  177. services/events/routers/count_events.py +109 -0
  178. services/events/routers/events.py +75 -0
  179. services/events/seed_events.py +196 -0
  180. services/facilities/helpers/__init__.py +8 -0
  181. services/facilities/helpers/lines.py +74 -0
  182. services/facilities/helpers/locations.py +231 -0
  183. services/facilities/helpers/plants.py +59 -0
  184. services/facilities/helpers/stages.py +110 -0
  185. services/facilities/helpers/workstation.py +213 -0
  186. services/facilities/main.py +60 -0
  187. services/facilities/models/__init__.py +10 -0
  188. services/facilities/models/industrialLine.py +72 -0
  189. services/facilities/models/industrialPlant.py +164 -0
  190. services/facilities/models/locations.py +74 -0
  191. services/facilities/models/stages.py +92 -0
  192. services/facilities/models/worker.py +73 -0
  193. services/facilities/models/workstation.py +117 -0
  194. services/facilities/models/workstation_live_state.py +59 -0
  195. services/facilities/routers/__init__.py +8 -0
  196. services/facilities/routers/bays.py +81 -0
  197. services/facilities/routers/buildings.py +92 -0
  198. services/facilities/routers/floors.py +81 -0
  199. services/facilities/routers/lines.py +154 -0
  200. services/facilities/routers/locations.py +208 -0
  201. services/facilities/routers/plant.py +203 -0
  202. services/facilities/routers/rooms.py +81 -0
  203. services/facilities/routers/stages.py +152 -0
  204. services/facilities/routers/workstation.py +173 -0
  205. services/file/backup.py +71 -0
  206. services/file/main.py +45 -0
  207. services/file/recieve.py +54 -0
  208. services/file/send.py +55 -0
  209. services/file/src/core/config.py +90 -0
  210. services/file/src/core/keycloak.py +152 -0
  211. services/file/src/core/logging_config.py +9 -0
  212. services/file/src/core/security.py +33 -0
  213. services/file/src/helpers/cad_conversion.py +331 -0
  214. services/file/src/helpers/helpers.py +825 -0
  215. services/file/src/routes/cad_conversion.py +26 -0
  216. services/file/src/routes/files.py +136 -0
  217. services/file/src/routes/presigned.py +154 -0
  218. services/file/src/services/websocket.py +293 -0
  219. services/floor_layout/helpers/__init__.py +8 -0
  220. services/floor_layout/helpers/bays.py +92 -0
  221. services/floor_layout/helpers/buildings.py +54 -0
  222. services/floor_layout/helpers/floors.py +65 -0
  223. services/floor_layout/helpers/rooms.py +76 -0
  224. services/floor_layout/main.py +52 -0
  225. services/floor_layout/models/__init__.py +8 -0
  226. services/floor_layout/models/bays.py +65 -0
  227. services/floor_layout/models/buildings.py +52 -0
  228. services/floor_layout/models/floors.py +45 -0
  229. services/floor_layout/models/rooms.py +61 -0
  230. services/floor_layout/routers/__init__.py +9 -0
  231. services/floor_layout/routers/bays.py +143 -0
  232. services/floor_layout/routers/buildings.py +116 -0
  233. services/floor_layout/routers/floors.py +89 -0
  234. services/floor_layout/routers/locations.py +80 -0
  235. services/floor_layout/routers/rooms.py +134 -0
  236. services/inventory/helpers/__init__.py +13 -0
  237. services/inventory/helpers/cycle_counts.py +124 -0
  238. services/inventory/helpers/inventory_allocations.py +134 -0
  239. services/inventory/helpers/inventory_item_counts.py +114 -0
  240. services/inventory/helpers/inventory_item_quantities.py +114 -0
  241. services/inventory/helpers/inventory_items.py +103 -0
  242. services/inventory/helpers/inventory_stage_outputs.py +134 -0
  243. services/inventory/helpers/inventory_transactions.py +112 -0
  244. services/inventory/helpers/stock_adjustment_requests.py +101 -0
  245. services/inventory/helpers/warehouse_cycle_counts.py +133 -0
  246. services/inventory/helpers/warehouse_locations.py +213 -0
  247. services/inventory/helpers/warehouse_regulated_records.py +123 -0
  248. services/inventory/main.py +62 -0
  249. services/inventory/models/__init__.py +17 -0
  250. services/inventory/models/cycle_counts.py +99 -0
  251. services/inventory/models/inventory_allocations.py +121 -0
  252. services/inventory/models/inventory_common.py +65 -0
  253. services/inventory/models/inventory_enums.py +21 -0
  254. services/inventory/models/inventory_item_count.py +65 -0
  255. services/inventory/models/inventory_item_quantity.py +82 -0
  256. services/inventory/models/inventory_items.py +168 -0
  257. services/inventory/models/inventory_responses.py +44 -0
  258. services/inventory/models/inventory_stage_outputs.py +96 -0
  259. services/inventory/models/inventory_state.py +15 -0
  260. services/inventory/models/inventory_transactions.py +80 -0
  261. services/inventory/models/stock_adjustment_requests.py +109 -0
  262. services/inventory/models/warehouse_cycle_counts.py +119 -0
  263. services/inventory/models/warehouse_location_models.py +708 -0
  264. services/inventory/models/warehouse_regulated_records.py +358 -0
  265. services/inventory/routers/__init__.py +13 -0
  266. services/inventory/routers/cycle_counts.py +106 -0
  267. services/inventory/routers/inventory_allocations.py +125 -0
  268. services/inventory/routers/inventory_item_counts.py +105 -0
  269. services/inventory/routers/inventory_item_quantities.py +105 -0
  270. services/inventory/routers/inventory_items.py +109 -0
  271. services/inventory/routers/inventory_stage_outputs.py +122 -0
  272. services/inventory/routers/inventory_transactions.py +96 -0
  273. services/inventory/routers/stock_adjustment_requests.py +124 -0
  274. services/inventory/routers/warehouse_cycle_counts.py +124 -0
  275. services/inventory/routers/warehouse_locations.py +426 -0
  276. services/inventory/routers/warehouse_regulated_records.py +273 -0
  277. services/iot/helpers/__init__.py +8 -0
  278. services/iot/helpers/ble_device.py +87 -0
  279. services/iot/helpers/mqtt_bridge.py +115 -0
  280. services/iot/helpers/sensor_readings.py +63 -0
  281. services/iot/helpers/sensors.py +77 -0
  282. services/iot/helpers/servers.py +72 -0
  283. services/iot/helpers/uwb_device.py +95 -0
  284. services/iot/main.py +53 -0
  285. services/iot/models/__init__.py +8 -0
  286. services/iot/models/ble_device.py +118 -0
  287. services/iot/models/sensors.py +256 -0
  288. services/iot/models/servers.py +206 -0
  289. services/iot/models/uwb_device.py +106 -0
  290. services/iot/routers/__init__.py +8 -0
  291. services/iot/routers/ble_device.py +110 -0
  292. services/iot/routers/sensors.py +144 -0
  293. services/iot/routers/servers.py +141 -0
  294. services/iot/routers/uwb_device.py +148 -0
  295. services/module_control/__init__.py +1 -0
  296. services/module_control/helpers/__init__.py +1 -0
  297. services/module_control/helpers/integration_config.py +243 -0
  298. services/module_control/helpers/security.py +104 -0
  299. services/module_control/main.py +44 -0
  300. services/module_control/models/__init__.py +1 -0
  301. services/module_control/models/module_control.py +65 -0
  302. services/module_control/routers/__init__.py +1 -0
  303. services/module_control/routers/module_control.py +219 -0
  304. services/orders/helpers/__init__.py +11 -0
  305. services/orders/helpers/invoices.py +123 -0
  306. services/orders/helpers/order_customer_metrics.py +61 -0
  307. services/orders/helpers/order_details.py +71 -0
  308. services/orders/helpers/order_metadata.py +61 -0
  309. services/orders/helpers/order_payment_metadata.py +74 -0
  310. services/orders/helpers/orders.py +119 -0
  311. services/orders/helpers/sales_orders.py +136 -0
  312. services/orders/main.py +56 -0
  313. services/orders/models/__init__.py +11 -0
  314. services/orders/models/invoices.py +415 -0
  315. services/orders/models/order_customer_metrics.py +78 -0
  316. services/orders/models/order_details.py +46 -0
  317. services/orders/models/order_metadata.py +60 -0
  318. services/orders/models/order_payment_metadata.py +63 -0
  319. services/orders/models/orders.py +64 -0
  320. services/orders/models/sales_orders.py +130 -0
  321. services/orders/routers/__init__.py +11 -0
  322. services/orders/routers/invoices.py +111 -0
  323. services/orders/routers/order_customer_metrics.py +87 -0
  324. services/orders/routers/order_details.py +87 -0
  325. services/orders/routers/order_metadata.py +87 -0
  326. services/orders/routers/order_payment_metadata.py +87 -0
  327. services/orders/routers/orders.py +74 -0
  328. services/orders/routers/sales_orders.py +111 -0
  329. services/pm/helpers/__init__.py +14 -0
  330. services/pm/helpers/calendar_bookings.py +114 -0
  331. services/pm/helpers/calibration_point.py +110 -0
  332. services/pm/helpers/calibrations.py +196 -0
  333. services/pm/helpers/checklists.py +318 -0
  334. services/pm/helpers/cleaning.py +333 -0
  335. services/pm/helpers/downtime.py +376 -0
  336. services/pm/helpers/kanban_boards.py +186 -0
  337. services/pm/helpers/maintainance.py +177 -0
  338. services/pm/helpers/sop.py +1155 -0
  339. services/pm/helpers/sop_cdc.py +324 -0
  340. services/pm/helpers/weekly_schedules.py +79 -0
  341. services/pm/main.py +62 -0
  342. services/pm/models/__init__.py +14 -0
  343. services/pm/models/calendar_booking.py +82 -0
  344. services/pm/models/calibration_point.py +44 -0
  345. services/pm/models/calibrations.py +167 -0
  346. services/pm/models/checklists.py +117 -0
  347. services/pm/models/cleaning.py +203 -0
  348. services/pm/models/downtime.py +109 -0
  349. services/pm/models/kanban_board.py +178 -0
  350. services/pm/models/maintainanceLog.py +148 -0
  351. services/pm/models/sop.py +152 -0
  352. services/pm/models/weekly_schedule.py +91 -0
  353. services/pm/routers/__init__.py +14 -0
  354. services/pm/routers/calendar_bookings.py +143 -0
  355. services/pm/routers/calibration_point.py +94 -0
  356. services/pm/routers/calibrations.py +232 -0
  357. services/pm/routers/checklists.py +188 -0
  358. services/pm/routers/cleaning.py +127 -0
  359. services/pm/routers/downtime.py +143 -0
  360. services/pm/routers/kanban_boards.py +283 -0
  361. services/pm/routers/maintainance.py +241 -0
  362. services/pm/routers/sop.py +437 -0
  363. services/pm/routers/weekly_schedules.py +108 -0
  364. services/process_definitions/helpers/__init__.py +11 -0
  365. services/process_definitions/helpers/cpp_cqa_registry.py +120 -0
  366. services/process_definitions/helpers/mbmr_templates.py +107 -0
  367. services/process_definitions/helpers/packing_instructions.py +113 -0
  368. services/process_definitions/helpers/process_constraints.py +495 -0
  369. services/process_definitions/helpers/process_corrections.py +279 -0
  370. services/process_definitions/helpers/process_definition.py +996 -0
  371. services/process_definitions/helpers/process_node_catalog.py +786 -0
  372. services/process_definitions/helpers/process_post_checks.py +441 -0
  373. services/process_definitions/helpers/process_pre_checks.py +351 -0
  374. services/process_definitions/helpers/process_steps.py +220 -0
  375. services/process_definitions/main.py +71 -0
  376. services/process_definitions/models/__init__.py +13 -0
  377. services/process_definitions/models/cpp_cqa_registry.py +145 -0
  378. services/process_definitions/models/gxp_change_controls.py +38 -0
  379. services/process_definitions/models/gxp_risk_assessments.py +30 -0
  380. services/process_definitions/models/gxp_validation_evidence.py +33 -0
  381. services/process_definitions/models/mbmr_templates.py +173 -0
  382. services/process_definitions/models/packing_instructions.py +176 -0
  383. services/process_definitions/models/process_constraints.py +159 -0
  384. services/process_definitions/models/process_corrections.py +118 -0
  385. services/process_definitions/models/process_definition.py +685 -0
  386. services/process_definitions/models/process_definition_common.py +48 -0
  387. services/process_definitions/models/process_node_catalog.py +25 -0
  388. services/process_definitions/models/process_post_checks.py +171 -0
  389. services/process_definitions/models/process_pre_checks.py +168 -0
  390. services/process_definitions/models/process_steps.py +170 -0
  391. services/process_definitions/node_services/__init__.py +8 -0
  392. services/process_definitions/node_services/common.py +95 -0
  393. services/process_definitions/node_services/executor.py +499 -0
  394. services/process_definitions/node_services/flow_simulator.py +733 -0
  395. services/process_definitions/node_services/functions.py +193 -0
  396. services/process_definitions/node_services/messaging.py +44 -0
  397. services/process_definitions/node_services/models.py +221 -0
  398. services/process_definitions/node_services/network.py +161 -0
  399. services/process_definitions/node_services/parsers.py +87 -0
  400. services/process_definitions/node_services/sequence.py +95 -0
  401. services/process_definitions/node_services/storage.py +50 -0
  402. services/process_definitions/node_services/webhooks.py +52 -0
  403. services/process_definitions/routers/__init__.py +10 -0
  404. services/process_definitions/routers/cpp_cqa_registry.py +86 -0
  405. services/process_definitions/routers/mbmr_templates.py +84 -0
  406. services/process_definitions/routers/packing_instructions.py +84 -0
  407. services/process_definitions/routers/process_constraints.py +564 -0
  408. services/process_definitions/routers/process_corrections.py +343 -0
  409. services/process_definitions/routers/process_definition.py +992 -0
  410. services/process_definitions/routers/process_post_checks.py +529 -0
  411. services/process_definitions/routers/process_pre_checks.py +435 -0
  412. services/process_definitions/routers/process_steps.py +274 -0
  413. services/procurement/helpers/__init__.py +9 -0
  414. services/procurement/helpers/goods_receipts.py +240 -0
  415. services/procurement/helpers/purchase_order_shipping_information.py +85 -0
  416. services/procurement/helpers/purchase_orders.py +68 -0
  417. services/procurement/helpers/quality_control.py +235 -0
  418. services/procurement/helpers/sampling.py +404 -0
  419. services/procurement/main.py +52 -0
  420. services/procurement/models/__init__.py +9 -0
  421. services/procurement/models/goods_receipts.py +165 -0
  422. services/procurement/models/purchase_orders.py +54 -0
  423. services/procurement/models/quality_control.py +464 -0
  424. services/procurement/models/reinspection_records.py +28 -0
  425. services/procurement/models/sampling.py +262 -0
  426. services/procurement/models/shipping_information.py +51 -0
  427. services/procurement/routers/__init__.py +9 -0
  428. services/procurement/routers/goods_receipts.py +201 -0
  429. services/procurement/routers/purchase_orders.py +106 -0
  430. services/procurement/routers/quality_control.py +386 -0
  431. services/procurement/routers/sampling.py +296 -0
  432. services/procurement/routers/shipping_information.py +97 -0
  433. services/production/__init__.py +1 -0
  434. services/production/agents/__init__.py +5 -0
  435. services/production/agents/batch_planning_agent.py +815 -0
  436. services/production/models/__init__.py +25 -0
  437. services/production/models/batch.py +253 -0
  438. services/products/helpers/__init__.py +10 -0
  439. services/products/helpers/boms.py +100 -0
  440. services/products/helpers/drug_research.py +644 -0
  441. services/products/helpers/product_component.py +168 -0
  442. services/products/helpers/product_inventory.py +221 -0
  443. services/products/helpers/product_pricing.py +123 -0
  444. services/products/helpers/product_utils.py +32 -0
  445. services/products/helpers/products.py +81 -0
  446. services/products/main.py +59 -0
  447. services/products/models/__init__.py +9 -0
  448. services/products/models/drug_research.py +138 -0
  449. services/products/models/product_common.py +60 -0
  450. services/products/models/product_component.py +1028 -0
  451. services/products/models/product_inventory.py +118 -0
  452. services/products/models/product_pricing.py +73 -0
  453. services/products/models/products.py +151 -0
  454. services/products/routers/__init__.py +9 -0
  455. services/products/routers/boms.py +116 -0
  456. services/products/routers/drug_research.py +115 -0
  457. services/products/routers/product_components.py +123 -0
  458. services/products/routers/product_inventory.py +185 -0
  459. services/products/routers/product_pricing.py +136 -0
  460. services/products/routers/products.py +165 -0
  461. services/replenishment/__init__.py +1 -0
  462. services/replenishment/main.py +46 -0
  463. services/replenishment/routers/__init__.py +1 -0
  464. services/replenishment/routers/replenishment.py +20 -0
  465. services/shifts/helpers/__init__.py +7 -0
  466. services/shifts/helpers/shift_templates.py +124 -0
  467. services/shifts/helpers/shifts.py +79 -0
  468. services/shifts/helpers/timesheets.py +137 -0
  469. services/shifts/main.py +48 -0
  470. services/shifts/models/__init__.py +8 -0
  471. services/shifts/models/shift.py +62 -0
  472. services/shifts/models/shift_template.py +82 -0
  473. services/shifts/models/time_range.py +31 -0
  474. services/shifts/models/timesheet.py +196 -0
  475. services/shifts/routers/__init__.py +7 -0
  476. services/shifts/routers/shift_templates.py +97 -0
  477. services/shifts/routers/shifts.py +117 -0
  478. services/shifts/routers/timesheets.py +117 -0
  479. services/shipping/helpers/__init__.py +15 -0
  480. services/shipping/helpers/carrier.py +78 -0
  481. services/shipping/helpers/customs_declaration.py +104 -0
  482. services/shipping/helpers/delivery_note.py +99 -0
  483. services/shipping/helpers/package.py +95 -0
  484. services/shipping/helpers/pallet.py +85 -0
  485. services/shipping/helpers/route.py +93 -0
  486. services/shipping/helpers/shipping_information.py +82 -0
  487. services/shipping/helpers/shipping_provider_details.py +59 -0
  488. services/shipping/helpers/shipping_provider_metadata.py +59 -0
  489. services/shipping/helpers/vehicle.py +85 -0
  490. services/shipping/helpers/waybill.py +86 -0
  491. services/shipping/main.py +64 -0
  492. services/shipping/models/__init__.py +15 -0
  493. services/shipping/models/carrier.py +97 -0
  494. services/shipping/models/customs_declaration.py +138 -0
  495. services/shipping/models/delivery_note.py +163 -0
  496. services/shipping/models/package.py +152 -0
  497. services/shipping/models/pallet.py +137 -0
  498. services/shipping/models/route.py +120 -0
  499. services/shipping/models/shipping_information.py +55 -0
  500. services/shipping/models/shipping_provider_details.py +42 -0
  501. services/shipping/models/shipping_provider_metadata.py +54 -0
  502. services/shipping/models/vehicle.py +129 -0
  503. services/shipping/models/waybill.py +189 -0
  504. services/shipping/routers/__init__.py +15 -0
  505. services/shipping/routers/carrier.py +99 -0
  506. services/shipping/routers/customs_declaration.py +132 -0
  507. services/shipping/routers/delivery_note.py +150 -0
  508. services/shipping/routers/package.py +141 -0
  509. services/shipping/routers/pallet.py +108 -0
  510. services/shipping/routers/route.py +128 -0
  511. services/shipping/routers/shipping_information.py +97 -0
  512. services/shipping/routers/shipping_provider_details.py +80 -0
  513. services/shipping/routers/shipping_provider_metadata.py +80 -0
  514. services/shipping/routers/vehicle.py +117 -0
  515. services/shipping/routers/waybill.py +119 -0
  516. services/suppliers/helpers/__init__.py +13 -0
  517. services/suppliers/helpers/supplier_capabilities.py +58 -0
  518. services/suppliers/helpers/supplier_certifications.py +67 -0
  519. services/suppliers/helpers/supplier_details.py +58 -0
  520. services/suppliers/helpers/supplier_financials.py +58 -0
  521. services/suppliers/helpers/supplier_inventory.py +74 -0
  522. services/suppliers/helpers/supplier_locations.py +60 -0
  523. services/suppliers/helpers/supplier_pricing.py +69 -0
  524. services/suppliers/helpers/supplier_quality.py +69 -0
  525. services/suppliers/helpers/supplier_shipping.py +69 -0
  526. services/suppliers/main.py +60 -0
  527. services/suppliers/models/__init__.py +13 -0
  528. services/suppliers/models/supplier_capabilities.py +70 -0
  529. services/suppliers/models/supplier_certifications.py +64 -0
  530. services/suppliers/models/supplier_details.py +75 -0
  531. services/suppliers/models/supplier_financials.py +69 -0
  532. services/suppliers/models/supplier_inventory.py +76 -0
  533. services/suppliers/models/supplier_locations.py +70 -0
  534. services/suppliers/models/supplier_pricing.py +74 -0
  535. services/suppliers/models/supplier_quality.py +74 -0
  536. services/suppliers/models/supplier_shipping.py +76 -0
  537. services/suppliers/routers/__init__.py +13 -0
  538. services/suppliers/routers/supplier_capabilities.py +88 -0
  539. services/suppliers/routers/supplier_certifications.py +87 -0
  540. services/suppliers/routers/supplier_details.py +83 -0
  541. services/suppliers/routers/supplier_financials.py +83 -0
  542. services/suppliers/routers/supplier_inventory.py +105 -0
  543. services/suppliers/routers/supplier_locations.py +89 -0
  544. services/suppliers/routers/supplier_pricing.py +96 -0
  545. services/suppliers/routers/supplier_quality.py +96 -0
  546. services/suppliers/routers/supplier_shipping.py +96 -0
  547. services/supply_allocation/main.py +46 -0
  548. services/supply_allocation/routers/__init__.py +1 -0
  549. services/supply_allocation/routers/allocation.py +20 -0
  550. services/taxonomy/helpers/__init__.py +7 -0
  551. services/taxonomy/helpers/classes.py +48 -0
  552. services/taxonomy/helpers/family.py +53 -0
  553. services/taxonomy/helpers/subclass.py +58 -0
  554. services/taxonomy/main.py +48 -0
  555. services/taxonomy/models/__init__.py +7 -0
  556. services/taxonomy/models/classes.py +52 -0
  557. services/taxonomy/models/family.py +60 -0
  558. services/taxonomy/models/subclass.py +50 -0
  559. services/taxonomy/routers/__init__.py +7 -0
  560. services/taxonomy/routers/classes.py +78 -0
  561. services/taxonomy/routers/family.py +77 -0
  562. services/taxonomy/routers/subclass.py +82 -0
  563. services/warehouse_execution/__init__.py +1 -0
  564. services/warehouse_execution/main.py +46 -0
  565. services/warehouse_execution/routers/__init__.py +1 -0
  566. services/warehouse_execution/routers/execution.py +21 -0
  567. services/work_order_agent/__init__.py +17 -0
  568. services/work_order_agent/agent/__init__.py +17 -0
  569. services/work_order_agent/agent/work_order_agent.py +658 -0
  570. services/work_order_agent/tracking/__init__.py +101 -0
  571. services/work_order_agent/tracking/event_system.py +182 -0
  572. services/work_order_agent/tracking/state_machine.py +163 -0
  573. services/work_order_agent/tracking/state_machine_integrator.py +295 -0
  574. services/work_order_agent/tracking/test_phase2_implementation.py +302 -0
  575. services/work_order_agent/tracking/time_analysis.py +301 -0
  576. services/work_order_agent/tracking/tracked_work_order.py +255 -0
  577. services/work_order_agent/tracking/work_order_adapter.py +367 -0
  578. services/work_order_agent/tracking/work_order_batch_manager.py +406 -0
  579. services/work_order_agent/tracking/work_order_repository.py +431 -0
  580. services/workorders/helpers/__init__.py +5 -0
  581. services/workorders/helpers/area_room_usage_ledger.py +139 -0
  582. services/workorders/helpers/batch_execution_records.py +265 -0
  583. services/workorders/helpers/batch_release_workflows.py +158 -0
  584. services/workorders/helpers/batch_step_executions.py +145 -0
  585. services/workorders/helpers/equipment_usage_ledger.py +209 -0
  586. services/workorders/helpers/executed_bmr_records.py +170 -0
  587. services/workorders/helpers/executed_bpr_records.py +170 -0
  588. services/workorders/helpers/executed_instruction_evidence.py +155 -0
  589. services/workorders/helpers/ipc_result_records.py +134 -0
  590. services/workorders/helpers/production_batches.py +117 -0
  591. services/workorders/helpers/work_orders.py +367 -0
  592. services/workorders/helpers/yield_reconciliation_records.py +158 -0
  593. services/workorders/main.py +110 -0
  594. services/workorders/models/__init__.py +5 -0
  595. services/workorders/models/area_room_usage_ledger.py +154 -0
  596. services/workorders/models/batch_execution_records.py +575 -0
  597. services/workorders/models/batch_release_workflows.py +190 -0
  598. services/workorders/models/batch_step_executions.py +142 -0
  599. services/workorders/models/equipment_usage_ledger.py +144 -0
  600. services/workorders/models/executed_bmr_records.py +220 -0
  601. services/workorders/models/executed_bpr_records.py +220 -0
  602. services/workorders/models/executed_instruction_evidence.py +128 -0
  603. services/workorders/models/ipc_result_records.py +164 -0
  604. services/workorders/models/production_batches.py +181 -0
  605. services/workorders/models/work_orders.py +255 -0
  606. services/workorders/models/yield_reconciliation_records.py +175 -0
  607. services/workorders/routers/__init__.py +5 -0
  608. services/workorders/routers/area_room_usage_ledger.py +117 -0
  609. services/workorders/routers/batch_execution_records.py +103 -0
  610. services/workorders/routers/batch_release_workflows.py +86 -0
  611. services/workorders/routers/batch_step_executions.py +88 -0
  612. services/workorders/routers/equipment_usage_ledger.py +115 -0
  613. services/workorders/routers/executed_bmr_records.py +86 -0
  614. services/workorders/routers/executed_bpr_records.py +86 -0
  615. services/workorders/routers/executed_instruction_evidence.py +86 -0
  616. services/workorders/routers/ipc_result_records.py +86 -0
  617. services/workorders/routers/production_batches.py +86 -0
  618. services/workorders/routers/work_orders.py +257 -0
  619. services/workorders/routers/yield_reconciliation_records.py +86 -0
  620. src/broca/__init__.py +5 -0
  621. src/broca/agent.py +201 -0
  622. src/cerebellum/__init__.py +0 -0
  623. src/cerebellum/adapter.py +84 -0
  624. src/cerebellum/capabilities/__init__.py +0 -0
  625. src/cerebellum/capabilities/agent/__init__.py +0 -0
  626. src/cerebellum/capabilities/agent/agents.py +65 -0
  627. src/cerebellum/capabilities/ai/__init__.py +0 -0
  628. src/cerebellum/capabilities/ai/providers.py +106 -0
  629. src/cerebellum/capabilities/api/__init__.py +0 -0
  630. src/cerebellum/capabilities/api/graphql.py +35 -0
  631. src/cerebellum/capabilities/api/rest_api.py +45 -0
  632. src/cerebellum/capabilities/api/webhook.py +30 -0
  633. src/cerebellum/capabilities/browser/__init__.py +0 -0
  634. src/cerebellum/capabilities/browser/browsers.py +27 -0
  635. src/cerebellum/capabilities/cloud/__init__.py +0 -0
  636. src/cerebellum/capabilities/cloud/cloud.py +46 -0
  637. src/cerebellum/capabilities/communication/__init__.py +0 -0
  638. src/cerebellum/capabilities/communication/communication.py +62 -0
  639. src/cerebellum/capabilities/database/__init__.py +0 -0
  640. src/cerebellum/capabilities/database/elasticsearch.py +40 -0
  641. src/cerebellum/capabilities/database/influxdb.py +37 -0
  642. src/cerebellum/capabilities/database/mongodb.py +50 -0
  643. src/cerebellum/capabilities/database/neo4j.py +32 -0
  644. src/cerebellum/capabilities/database/redis.py +44 -0
  645. src/cerebellum/capabilities/enterprise/__init__.py +0 -0
  646. src/cerebellum/capabilities/enterprise/opensource.py +180 -0
  647. src/cerebellum/capabilities/enterprise/proprietary.py +313 -0
  648. src/cerebellum/capabilities/event_streaming/__init__.py +0 -0
  649. src/cerebellum/capabilities/event_streaming/streaming.py +38 -0
  650. src/cerebellum/capabilities/infrastructure/__init__.py +0 -0
  651. src/cerebellum/capabilities/infrastructure/infrastructure.py +30 -0
  652. src/cerebellum/capabilities/productivity/__init__.py +0 -0
  653. src/cerebellum/capabilities/productivity/productivity.py +158 -0
  654. src/cerebellum/capabilities/robotics/__init__.py +0 -0
  655. src/cerebellum/capabilities/robotics/robotics.py +124 -0
  656. src/cerebellum/capabilities/runtime/__init__.py +0 -0
  657. src/cerebellum/capabilities/runtime/runtimes.py +92 -0
  658. src/cerebellum/capabilities/search/__init__.py +0 -0
  659. src/cerebellum/capabilities/search/search.py +63 -0
  660. src/cerebellum/capabilities/source_control/__init__.py +0 -0
  661. src/cerebellum/capabilities/source_control/source_control.py +113 -0
  662. src/cerebellum/capabilities/storage/__init__.py +0 -0
  663. src/cerebellum/capabilities/storage/storage.py +94 -0
  664. src/cerebellum/capabilities/workflow/__init__.py +0 -0
  665. src/cerebellum/capabilities/workflow/workflows.py +49 -0
  666. src/cerebellum/capability.py +108 -0
  667. src/cerebellum/config.py +157 -0
  668. src/cerebellum/fallback.py +147 -0
  669. src/cerebellum/fallback_engine.py +121 -0
  670. src/cerebellum/key_manager.py +129 -0
  671. src/cerebellum/keystore.py +179 -0
  672. src/cerebellum/lifecycle.py +54 -0
  673. src/cerebellum/metadata.py +61 -0
  674. src/cerebellum/operator/base.py +25 -0
  675. src/cerebellum/peripheral.py +92 -0
  676. src/cerebellum/registry.py +98 -0
  677. src/cerebellum/resolve_entity_capability.py +259 -0
  678. src/cingulate/benchmark/__init__.py +23 -0
  679. src/cingulate/benchmark/reporter.py +102 -0
  680. src/cingulate/benchmark/runner.py +159 -0
  681. src/cingulate/benchmark/scenario_runner.py +150 -0
  682. src/cingulate/benchmark/validator.py +102 -0
  683. src/cingulate/governance/__init__.py +21 -0
  684. src/cingulate/governance/architecture_validator.py +194 -0
  685. src/cingulate/governance/compliance.py +104 -0
  686. src/cingulate/governance/governance.py +77 -0
  687. src/cingulate/governance/policy_registry.py +91 -0
  688. src/cortex/__init__.py +33 -0
  689. src/cortex/cost.py +71 -0
  690. src/cortex/counterfactual.py +162 -0
  691. src/cortex/digital_twin.py +90 -0
  692. src/cortex/experience.py +83 -0
  693. src/cortex/feedback.py +144 -0
  694. src/cortex/loss.py +116 -0
  695. src/cortex/prediction.py +142 -0
  696. src/cortex/replay.py +130 -0
  697. src/cortex/reward.py +113 -0
  698. src/cortex/simulator.py +102 -0
  699. src/cortex/world_model.py +180 -0
  700. src/cortex/world_model_simulation.py +1591 -0
  701. src/cortex/world_state.py +121 -0
  702. src/cortex/xavier.py +250 -0
  703. src/deepdive/__init__.py +29 -0
  704. src/deepdive/aggregation.py +113 -0
  705. src/deepdive/digital_twin_aggregator.py +128 -0
  706. src/deepdive/elasticsearch_adapter.py +110 -0
  707. src/deepdive/fleet_analytics.py +131 -0
  708. src/deepdive/knowledge_aggregator.py +130 -0
  709. src/homeostasis/__init__.py +19 -0
  710. src/homeostasis/control_plane.py +159 -0
  711. src/introspection/__init__.py +38 -0
  712. src/introspection/alerting.py +142 -0
  713. src/introspection/health.py +101 -0
  714. src/introspection/lemon.py +243 -0
  715. src/introspection/logging.py +147 -0
  716. src/introspection/metrics.py +106 -0
  717. src/introspection/tracing.py +162 -0
  718. src/monkey_brain/__init__.py +1 -0
  719. src/monkey_brain/api/main.py +148 -0
  720. src/monkey_brain/api/models.py +81 -0
  721. src/monkey_brain/api/routes/routes/keys.py +106 -0
  722. src/monkey_brain/api/routes/routes/run.py +169 -0
  723. src/monkey_brain/api/routes/routes/simulate.py +485 -0
  724. src/monkey_brain/dlm/__init__.py +44 -0
  725. src/monkey_brain/dlm/dlm.py +139 -0
  726. src/monkey_brain/dlm/gc.py +115 -0
  727. src/monkey_brain/dlm/lifecycle.py +149 -0
  728. src/monkey_brain/dlm/orphans.py +99 -0
  729. src/monkey_brain/dlm/storage.py +149 -0
  730. src/monkey_brain/dlm/ttl.py +140 -0
  731. src/monkey_brain/documents/__init__.py +0 -0
  732. src/monkey_brain/documents/document_ocr.py +6 -0
  733. src/monkey_brain/kernel/__init__.py +53 -0
  734. src/monkey_brain/kernel/capability_interface.py +144 -0
  735. src/monkey_brain/kernel/classifier/__init__.py +1 -0
  736. src/monkey_brain/kernel/classifier/embed_classifier.py +125 -0
  737. src/monkey_brain/kernel/classifier/intent_examples.py +106 -0
  738. src/monkey_brain/kernel/dag.py +23 -0
  739. src/monkey_brain/kernel/execution_state.py +257 -0
  740. src/monkey_brain/kernel/goal_planner.py +85 -0
  741. src/monkey_brain/kernel/goal_router.py +20 -0
  742. src/monkey_brain/kernel/goals/__init__.py +1 -0
  743. src/monkey_brain/kernel/goals/goal.py +130 -0
  744. src/monkey_brain/kernel/goals/goal_bootstrap.py +38 -0
  745. src/monkey_brain/kernel/goals/goal_classifier.py +132 -0
  746. src/monkey_brain/kernel/goals/goal_registry.py +75 -0
  747. src/monkey_brain/kernel/intents/__init__.py +1 -0
  748. src/monkey_brain/kernel/intents/event_adapter.py +246 -0
  749. src/monkey_brain/kernel/intents/helpers.py +13 -0
  750. src/monkey_brain/kernel/intents/intent_registry.py +705 -0
  751. src/monkey_brain/kernel/intents/intent_router.py +102 -0
  752. src/monkey_brain/kernel/intents/predicates/approval_create.py +9 -0
  753. src/monkey_brain/kernel/intents/predicates/approval_decision.py +9 -0
  754. src/monkey_brain/kernel/intents/predicates/approval_hold.py +9 -0
  755. src/monkey_brain/kernel/intents/predicates/approval_query.py +9 -0
  756. src/monkey_brain/kernel/intents/predicates/batch_close.py +9 -0
  757. src/monkey_brain/kernel/intents/predicates/batch_creation.py +9 -0
  758. src/monkey_brain/kernel/intents/predicates/batch_delete.py +9 -0
  759. src/monkey_brain/kernel/intents/predicates/batch_hold.py +9 -0
  760. src/monkey_brain/kernel/intents/predicates/batch_record.py +9 -0
  761. src/monkey_brain/kernel/intents/predicates/batch_update.py +9 -0
  762. src/monkey_brain/kernel/intents/predicates/change_control.py +49 -0
  763. src/monkey_brain/kernel/intents/predicates/compliance_audit.py +14 -0
  764. src/monkey_brain/kernel/intents/predicates/decision_intelligence.py +9 -0
  765. src/monkey_brain/kernel/intents/predicates/drug_research.py +9 -0
  766. src/monkey_brain/kernel/intents/predicates/fuzzy_match.py +19 -0
  767. src/monkey_brain/kernel/intents/predicates/production_kpi.py +9 -0
  768. src/monkey_brain/kernel/intents/predicates/sop_create.py +9 -0
  769. src/monkey_brain/kernel/intents/predicates/sop_query.py +9 -0
  770. src/monkey_brain/kernel/intents/predicates/sop_update.py +9 -0
  771. src/monkey_brain/kernel/intents/predicates/warehouse_shipping.py +9 -0
  772. src/monkey_brain/kernel/intents/predicates/work_order_create.py +9 -0
  773. src/monkey_brain/kernel/intents/predicates/work_order_delete.py +9 -0
  774. src/monkey_brain/kernel/intents/predicates/work_order_hold.py +9 -0
  775. src/monkey_brain/kernel/intents/predicates/work_order_query.py +9 -0
  776. src/monkey_brain/kernel/intents/predicates/work_order_status.py +9 -0
  777. src/monkey_brain/kernel/intents/predicates/work_order_update.py +9 -0
  778. src/monkey_brain/kernel/intents/predicates/worker.py +9 -0
  779. src/monkey_brain/kernel/intents/telemetry_adapter.py +274 -0
  780. src/monkey_brain/kernel/intents/utils.py +68 -0
  781. src/monkey_brain/kernel/learning.py +98 -0
  782. src/monkey_brain/kernel/llm_explorer.py +188 -0
  783. src/monkey_brain/kernel/loss.py +81 -0
  784. src/monkey_brain/kernel/nlp/__init__.py +1 -0
  785. src/monkey_brain/kernel/nlp/compat.py +23 -0
  786. src/monkey_brain/kernel/nlp/models.py +10 -0
  787. src/monkey_brain/kernel/nlp/question_analyzer.py +203 -0
  788. src/monkey_brain/kernel/nlp/spacy_parser.py +53 -0
  789. src/monkey_brain/kernel/observer.py +97 -0
  790. src/monkey_brain/kernel/parser/__init__.py +3 -0
  791. src/monkey_brain/kernel/parser/ast.py +28 -0
  792. src/monkey_brain/kernel/parser/extractors/__init__.py +11 -0
  793. src/monkey_brain/kernel/parser/extractors/entities.py +21 -0
  794. src/monkey_brain/kernel/parser/extractors/filters.py +16 -0
  795. src/monkey_brain/kernel/parser/extractors/projections.py +36 -0
  796. src/monkey_brain/kernel/parser/extractors/verbs.py +31 -0
  797. src/monkey_brain/kernel/parser/parser.py +57 -0
  798. src/monkey_brain/kernel/parser/rules.py +75 -0
  799. src/monkey_brain/kernel/pipeline.py +44 -0
  800. src/monkey_brain/kernel/planner.py +57 -0
  801. src/monkey_brain/kernel/rl/__init__.py +33 -0
  802. src/monkey_brain/kernel/rl/learner.py +98 -0
  803. src/monkey_brain/kernel/rl/policy.py +254 -0
  804. src/monkey_brain/kernel/rl/reward.py +117 -0
  805. src/monkey_brain/kernel/rl/transition.py +112 -0
  806. src/monkey_brain/persistence/__init__.py +47 -0
  807. src/monkey_brain/persistence/adapters.py +49 -0
  808. src/monkey_brain/persistence/events.py +105 -0
  809. src/monkey_brain/persistence/manager.py +124 -0
  810. src/monkey_brain/persistence/mongodb_adapter.py +91 -0
  811. src/monkey_brain/persistence/redis_adapter.py +93 -0
  812. src/monkey_brain/persistence/reducer.py +111 -0
  813. src/monkey_brain/runtime/__init__.py +49 -0
  814. src/monkey_brain/runtime/depedencies.py +8 -0
  815. src/monkey_brain/runtime/engine.py +183 -0
  816. src/monkey_brain/runtime/message_bus.py +82 -0
  817. src/monkey_brain/runtime/process.py +144 -0
  818. src/monkey_brain/runtime/resource_manager.py +100 -0
  819. src/monkey_brain/runtime/routers.py +8 -0
  820. src/monkey_brain/runtime/runtime.py +199 -0
  821. src/monkey_brain/runtime/scheduler.py +165 -0
  822. src/monkey_brain/runtime/supervisor.py +133 -0
  823. src/monkey_brain/runtime/worker_pool.py +111 -0
  824. src/plasticity/seed/__init__.py +30 -0
  825. src/plasticity/seed/benchmark_generator.py +105 -0
  826. src/plasticity/seed/event_generator.py +122 -0
  827. src/plasticity/seed/scenario_builder.py +134 -0
  828. src/plasticity/seed/seed_data.py +206 -0
  829. src/plasticity/seed/seeder.py +122 -0
  830. src/plasticity/testing/__init__.py +28 -0
  831. src/plasticity/testing/performance.py +131 -0
  832. src/plasticity/testing/profiler.py +255 -0
  833. src/plasticity/testing/reporter.py +84 -0
  834. src/plasticity/testing/runner.py +209 -0
  835. src/sync/__init__.py +12 -0
  836. src/sync/cloud_aggregator.py +63 -0
  837. src/sync/edge_node.py +51 -0
  838. src/sync/sync_manager.py +74 -0
@@ -0,0 +1,992 @@
1
+ from typing import Any
2
+ import json
3
+ import logging
4
+ from uuid import uuid4
5
+
6
+ from fastapi import APIRouter, HTTPException, Depends, Query, Request, status
7
+ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
8
+ from motor.motor_asyncio import AsyncIOMotorDatabase
9
+ from jose import JWTError
10
+ import httpx
11
+ from neo4j import AsyncGraphDatabase
12
+
13
+ from services.common.db import get_database
14
+ from services.common.config import settings
15
+ from services.common.auth import permission_ids
16
+ from services.common.n8n_auth import n8n_webhook_auth_headers
17
+ from services.auth.helpers import nats_store
18
+ from services.auth.helpers.tokens import decode_access_token
19
+ from services.auth.helpers.graph_store import delete_canvas_node as delete_neo4j_canvas_node
20
+ from services.process_definitions.models.process_definition import (
21
+ PaginatedProcessDefinitionCanvasLayoutResponse,
22
+ ProcessDefinitionCreate,
23
+ ProcessDefinitionCanvasAttachment,
24
+ ProcessDefinitionCanvasHyperlink,
25
+ ProcessDefinitionCanvasLayoutCreate,
26
+ ProcessDefinitionCanvasLayoutResponse,
27
+ ProcessDefinitionCanvasLayoutUpdate,
28
+ ProcessDefinitionCanvasBackendCall,
29
+ ProcessDefinitionCanvasFlowSimulationRequest,
30
+ ProcessDefinitionCanvasFlowSimulationResponse,
31
+ ProcessDefinitionCanvasNodeExecutionRequest,
32
+ ProcessDefinitionCanvasNodeExecutionResponse,
33
+ ProcessDefinitionCanvasNodePosition,
34
+ ProcessDefinitionCanvasNodeProperties,
35
+ ProcessDefinitionCanvasNodePropertiesUpdate,
36
+ ProcessDefinitionCanvasPageId,
37
+ ProcessDefinitionCanvasWebhookDispatchRequest,
38
+ ProcessDefinitionCanvasWebhookDispatchResponse,
39
+ ProcessDefinitionCanvasWebhookEvent,
40
+ ProcessDefinitionUpdate,
41
+ ProcessDefinitionResponse,
42
+ PaginatedProcessDefinitionResponse,
43
+ ProcessDefinitionEndpointInfo,
44
+ normalize_canvas_page_id,
45
+ )
46
+ from services.process_definitions.models.process_node_catalog import (
47
+ ProcessNodeCatalogItem,
48
+ ProcessNodeCatalogResponse,
49
+ )
50
+ from services.process_definitions.helpers import process_definition as crud
51
+ from services.process_definitions.helpers.process_node_catalog import get_catalog_item, get_node_catalog
52
+ from services.process_definitions.node_services.executor import execute_canvas_node_service
53
+ from services.process_definitions.node_services.flow_simulator import simulate_canvas_flow
54
+
55
+ router = APIRouter()
56
+ log = logging.getLogger("uvicorn.error")
57
+
58
+ bearer_scheme = HTTPBearer()
59
+
60
+ PERMISSION_ALIASES = {
61
+ "perm-view-process-definitions": {"perm-view-workflows"},
62
+ "perm-create-process-definitions": {"perm-create-workflows"},
63
+ "perm-update-process-definitions": {"perm-update-workflows"},
64
+ "perm-delete-process-definitions": {"perm-delete-workflows"},
65
+ }
66
+
67
+
68
+ async def _publish_canvas_webhook_nats_event(
69
+ *,
70
+ layout_id: str,
71
+ canvas_node_id: str,
72
+ event: ProcessDefinitionCanvasWebhookEvent,
73
+ node: dict | None = None,
74
+ ) -> None:
75
+ event_type = f"canvas-webhook-{event.direction or 'event'}"
76
+ payload = {
77
+ "event_id": event.event_id or str(uuid4()),
78
+ "event_type": event_type,
79
+ "source": "process_definition-service",
80
+ "layout_id": layout_id,
81
+ "canvas_node_id": canvas_node_id,
82
+ "node_id": (node or {}).get("node_id") or (node or {}).get("id"),
83
+ "node_name": (node or {}).get("name") or (node or {}).get("title"),
84
+ "provider": event.provider,
85
+ "direction": event.direction,
86
+ "status": event.status,
87
+ "status_code": event.status_code,
88
+ "received_at": event.received_at.isoformat() if event.received_at else None,
89
+ "payload": event.model_dump(mode="json"),
90
+ }
91
+ try:
92
+ await nats_store.publish_typed_event(payload, event_type=event_type)
93
+ except Exception:
94
+ log.exception(
95
+ "Failed to publish canvas webhook event to NATS event_type=%s layout_id=%s canvas_node_id=%s",
96
+ event_type,
97
+ layout_id,
98
+ canvas_node_id,
99
+ )
100
+
101
+
102
+ def _webhook_settings_from_node(node: dict) -> dict:
103
+ metadata = node.get("metadata") if isinstance(node.get("metadata"), dict) else {}
104
+ provider = str(node.get("automationProvider") or metadata.get("automationProvider") or "webhook").lower()
105
+ direction = str(node.get("automationDirection") or metadata.get("automationDirection") or "outgoing").lower()
106
+ url = str(
107
+ node.get("webhookUrl")
108
+ or node.get("webhook_url")
109
+ or node.get("n8nWebhookUrl")
110
+ or metadata.get("webhookUrl")
111
+ or metadata.get("webhook_url")
112
+ or metadata.get("n8nWebhookUrl")
113
+ or ""
114
+ ).strip()
115
+ method = str(node.get("webhookMethod") or node.get("n8nMethod") or metadata.get("webhookMethod") or metadata.get("n8nMethod") or "POST").upper()
116
+ secret = str(node.get("webhookSecret") or metadata.get("webhookSecret") or "").strip()
117
+ return {"provider": provider, "direction": direction, "url": url, "method": method, "secret": secret}
118
+
119
+
120
+ async def _request_json_or_body(request: Request) -> dict:
121
+ try:
122
+ value = await request.json()
123
+ return value if isinstance(value, dict) else {"value": value}
124
+ except Exception:
125
+ body = await request.body()
126
+ return {"body": body.decode("utf-8", errors="replace")}
127
+
128
+
129
+ def _node_type_from_record(node: dict) -> str | None:
130
+ metadata = node.get("metadata") if isinstance(node.get("metadata"), dict) else {}
131
+ return (
132
+ node.get("node_type")
133
+ or metadata.get("nodeType")
134
+ or metadata.get("type")
135
+ )
136
+
137
+
138
+ def _backend_call_from_node(node: dict, override: ProcessDefinitionCanvasBackendCall | None = None) -> ProcessDefinitionCanvasBackendCall | None:
139
+ if override is not None:
140
+ return override
141
+ metadata = node.get("metadata") if isinstance(node.get("metadata"), dict) else {}
142
+ config = node.get("config") if isinstance(node.get("config"), dict) else {}
143
+ backend = node.get("backend")
144
+ if not isinstance(backend, dict):
145
+ backend = metadata.get("backend")
146
+ if not isinstance(backend, dict):
147
+ backend = {}
148
+ backend = dict(backend)
149
+ if config.get("url") and not backend.get("url"):
150
+ backend["url"] = config["url"]
151
+ if config.get("method"):
152
+ backend["method"] = config["method"]
153
+ headers = dict(backend.get("headers") or {})
154
+ if isinstance(config.get("headers"), dict):
155
+ headers.update({str(key): str(value) for key, value in config["headers"].items() if value is not None})
156
+ if config.get("client_key") and "x-client-key" not in {key.lower(): value for key, value in headers.items()}:
157
+ headers["x-client-key"] = str(config["client_key"])
158
+ if config.get("client_secret") and "x-client-secret" not in {key.lower(): value for key, value in headers.items()}:
159
+ headers["x-client-secret"] = str(config["client_secret"])
160
+ backend["headers"] = headers
161
+ if not backend.get("service"):
162
+ backend["service"] = "process_definition"
163
+ if not backend.get("action"):
164
+ backend["action"] = _node_type_from_record(node)
165
+ return ProcessDefinitionCanvasBackendCall(**backend)
166
+
167
+
168
+ def _process_definition_endpoint_catalog() -> list[ProcessDefinitionEndpointInfo]:
169
+ base = "/api/v1/process-definitions"
170
+ return [
171
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/endpoints", description="List process_definition backend endpoints."),
172
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/", description="List process definitions."),
173
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/by-owner/{{owner}}", description="List process definitions by owner."),
174
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/by-status/{{status_value}}", description="List process definitions by status."),
175
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/by-tag/{{tag}}", description="List process definitions by tag."),
176
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/{{process_definition_id}}", description="Get one process_definition."),
177
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/", description="Create a process_definition."),
178
+ ProcessDefinitionEndpointInfo(method="PATCH", path=f"{base}/{{process_definition_id}}", description="Update a process_definition."),
179
+ ProcessDefinitionEndpointInfo(method="PATCH", path=f"{base}/{{process_definition_id}}/status/{{status_value}}", description="Update process_definition status."),
180
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/{{process_definition_id}}", description="Delete a process_definition."),
181
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas/node-catalog", description="List supported canvas node types."),
182
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas/node-catalog/{{identifier}}", description="Get one canvas node type by label, type, or key."),
183
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas", description="List process_definition canvas layouts."),
184
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas/by-process_definition/{{process_definition_id}}", description="List canvas layouts by process_definition."),
185
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas/by-page/{{page_id}}", description="List canvas layouts by page."),
186
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas", description="Create a process_definition canvas layout."),
187
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas/{{layout_id}}", description="Get a process_definition canvas layout."),
188
+ ProcessDefinitionEndpointInfo(method="PATCH", path=f"{base}/canvas/{{layout_id}}", description="Update a process_definition canvas layout."),
189
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/simulate", description="Simulate a full canvas flow and return a node-level trace."),
190
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/nodes/drop", description="Add a typed backend-capable node to a canvas."),
191
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/execute", description="Execute a canvas node backend action."),
192
+ ProcessDefinitionEndpointInfo(method="GET", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/properties", description="Get canvas node properties."),
193
+ ProcessDefinitionEndpointInfo(method="PATCH", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/properties", description="Update canvas node properties."),
194
+ ProcessDefinitionEndpointInfo(method="PUT", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/metadata", description="Replace canvas node metadata."),
195
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/hyperlinks", description="Add a canvas node hyperlink."),
196
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/hyperlinks/{{index}}", description="Remove a canvas node hyperlink."),
197
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/attachments", description="Add a canvas node attachment."),
198
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/attachments/{{index}}", description="Remove a canvas node attachment."),
199
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/webhook/incoming", description="Receive an incoming webhook for a canvas node."),
200
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/nodes/{{canvas_node_id}}/webhook/dispatch", description="Dispatch a canvas node webhook/http call."),
201
+ ProcessDefinitionEndpointInfo(method="POST", path=f"{base}/canvas/{{layout_id}}/clear", description="Clear canvas nodes and edges."),
202
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/canvas/{{layout_id}}/board", description="Clear canvas nodes and edges."),
203
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/canvas/{{layout_id}}/nodes", description="Clear canvas nodes and edges."),
204
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/canvas/nodes/{{canvas_node_id}}", description="Delete a canvas node from layouts and graph mirror."),
205
+ ProcessDefinitionEndpointInfo(method="DELETE", path=f"{base}/canvas/{{layout_id}}", description="Delete a canvas layout."),
206
+ ]
207
+
208
+
209
+ # ── Auth helpers ──────────────────────────────────────────────────────────────
210
+
211
+ def get_current_user(
212
+ credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
213
+ ) -> dict:
214
+ try:
215
+ return decode_access_token(credentials.credentials)
216
+ except JWTError:
217
+ raise HTTPException(
218
+ status_code=status.HTTP_401_UNAUTHORIZED,
219
+ detail="Invalid or expired token",
220
+ headers={"WWW-Authenticate": "Bearer"},
221
+ )
222
+
223
+
224
+ def require_permission(required: str):
225
+ def _check(current_user: dict = Depends(get_current_user)):
226
+ granted = permission_ids(current_user)
227
+ accepted = {required, *PERMISSION_ALIASES.get(required, set())}
228
+ if not (granted & accepted):
229
+ raise HTTPException(
230
+ status_code=status.HTTP_403_FORBIDDEN,
231
+ detail=f"Permission '{required}' required",
232
+ )
233
+ return current_user
234
+ return _check
235
+
236
+
237
+ # ── List (paginated) ──────────────────────────────────────────────────────────
238
+
239
+ @router.get("/endpoints", response_model=list[ProcessDefinitionEndpointInfo])
240
+ async def list_process_definition_endpoints(
241
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
242
+ ):
243
+ return _process_definition_endpoint_catalog()
244
+
245
+
246
+ @router.get("/")
247
+ async def list_process_definitions(
248
+ page: int = Query(1, ge=1),
249
+ page_size: int = Query(20, ge=1),
250
+ db: AsyncIOMotorDatabase = Depends(get_database),
251
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
252
+ ):
253
+ """Return a paginated list of all process definitions."""
254
+ process_definitions, total = await crud.get_all(db, page=page, page_size=page_size)
255
+ return {"total": total, "page": page, "page_size": page_size, "results": process_definitions}
256
+
257
+
258
+ # ── Filtered list endpoints ───────────────────────────────────────────────────
259
+
260
+ @router.get("/by-owner/{owner}", response_model=list[ProcessDefinitionResponse])
261
+ async def list_process_definitions_by_owner(
262
+ owner: str,
263
+ db: AsyncIOMotorDatabase = Depends(get_database),
264
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
265
+ ):
266
+ """Return all process definitions belonging to the given owner."""
267
+ return await crud.get_by_owner(db, owner)
268
+
269
+
270
+ @router.get("/by-status/{status_value}", response_model=PaginatedProcessDefinitionResponse)
271
+ async def list_process_definitions_by_status(
272
+ status_value: str,
273
+ page: int = Query(1, ge=1),
274
+ page_size: int = Query(20, ge=1),
275
+ db: AsyncIOMotorDatabase = Depends(get_database),
276
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
277
+ ):
278
+ """Return a paginated list of process definitions filtered by lifecycle status."""
279
+ process_definitions, total = await crud.get_by_status(db, status_value, page=page, page_size=page_size)
280
+ return PaginatedProcessDefinitionResponse(
281
+ total=total,
282
+ page=page,
283
+ page_size=page_size,
284
+ results=process_definitions,
285
+ )
286
+
287
+
288
+ @router.get("/by-tag/{tag}", response_model=list[ProcessDefinitionResponse])
289
+ async def list_process_definitions_by_tag(
290
+ tag: str,
291
+ db: AsyncIOMotorDatabase = Depends(get_database),
292
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
293
+ ):
294
+ """Return all process definitions that carry the given tag."""
295
+ return await crud.get_by_tag(db, tag)
296
+
297
+
298
+ # ── Canvas layouts ────────────────────────────────────────────────────────────
299
+
300
+ @router.get("/canvas/node-catalog", response_model=ProcessNodeCatalogResponse)
301
+ async def get_canvas_node_catalog(
302
+ _: dict = Depends(get_current_user),
303
+ ):
304
+ return get_node_catalog()
305
+
306
+
307
+ @router.get("/canvas/node-catalog/{identifier}", response_model=ProcessNodeCatalogItem)
308
+ async def get_canvas_node_catalog_item(
309
+ identifier: str,
310
+ _: dict = Depends(get_current_user),
311
+ ):
312
+ item = get_catalog_item(identifier)
313
+ if item is None:
314
+ raise HTTPException(
315
+ status_code=status.HTTP_404_NOT_FOUND,
316
+ detail=f"ProcessDefinition node catalog item '{identifier}' not found",
317
+ )
318
+ return item
319
+
320
+
321
+ @router.get("/canvas/{layout_id}")
322
+ async def get_canvas_layout(
323
+ layout_id: str,
324
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
325
+ ):
326
+ driver = AsyncGraphDatabase.driver(
327
+ settings.NEO4J_URI,
328
+ auth=(settings.NEO4J_USER, settings.NEO4J_PASSWORD),
329
+ )
330
+ try:
331
+ async with driver.session() as session:
332
+ nodes_result = await session.run(
333
+ """
334
+ MATCH (n)
335
+ WHERE n:Plant OR n:Line OR n:Stage OR n:Workstation OR n:Machine
336
+ OR n:Equipment OR n:Device OR n:Worker OR n:Role OR n:SOP
337
+ RETURN labels(n) AS labels, properties(n) AS props
338
+ """
339
+ )
340
+ nodes = []
341
+ async for record in nodes_result:
342
+ labels = record["labels"]
343
+ props = dict(record["props"])
344
+ label = labels[0] if labels else "Unknown"
345
+ entity_id = props.get("entity_id") or props.get("id")
346
+ name = props.get("name") or props.get("title") or entity_id
347
+ nodes.append({
348
+ "node_id": f"{label.lower()}s:{entity_id}",
349
+ "entity_id": entity_id,
350
+ "collection": f"{label.lower()}s",
351
+ "name": name,
352
+ "properties": props,
353
+ })
354
+
355
+ edges_result = await session.run(
356
+ """
357
+ MATCH (a)-[r]->(b)
358
+ WHERE (a:Plant OR a:Line OR a:Stage OR a:Workstation OR a:Machine
359
+ OR a:Equipment OR a:Device OR a:Worker OR a:Role OR a:SOP)
360
+ AND (b:Plant OR b:Line OR b:Stage OR b:Workstation OR b:Machine
361
+ OR b:Equipment OR b:Device OR b:Worker OR b:Role OR b:SOP)
362
+ RETURN labels(a) AS sl, properties(a) AS sp,
363
+ type(r) AS rel,
364
+ labels(b) AS tl, properties(b) AS tp
365
+ """
366
+ )
367
+ edges = []
368
+ async for record in edges_result:
369
+ sl = record["sl"][0] if record["sl"] else "Unknown"
370
+ tl = record["tl"][0] if record["tl"] else "Unknown"
371
+ sp = dict(record["sp"])
372
+ tp = dict(record["tp"])
373
+ s_id = sp.get("entity_id") or sp.get("id")
374
+ t_id = tp.get("entity_id") or tp.get("id")
375
+ s_node = f"{sl.lower()}s:{s_id}"
376
+ t_node = f"{tl.lower()}s:{t_id}"
377
+ rel = record["rel"]
378
+ edges.append({
379
+ "edge_id": f"{s_node}->{t_node}:{rel}",
380
+ "source_node_id": s_node,
381
+ "target_node_id": t_node,
382
+ "relationship_type": rel,
383
+ "label": rel.replace("_", " ").title(),
384
+ })
385
+
386
+ return {
387
+ "layout_id": layout_id,
388
+ "page_id": layout_id,
389
+ "nodes": nodes,
390
+ "edges": edges,
391
+ "links": edges,
392
+ "connections": [],
393
+ "visual_nodes": [],
394
+ }
395
+ finally:
396
+ await driver.close()
397
+
398
+
399
+ @router.post(
400
+ "/canvas/{layout_id}/simulate",
401
+ response_model=ProcessDefinitionCanvasFlowSimulationResponse,
402
+ )
403
+ async def simulate_canvas_layout_flow(
404
+ layout_id: str,
405
+ data: ProcessDefinitionCanvasFlowSimulationRequest,
406
+ db: AsyncIOMotorDatabase = Depends(get_database),
407
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
408
+ ):
409
+ try:
410
+ result = await simulate_canvas_flow(
411
+ db=db,
412
+ layout_id=layout_id,
413
+ payload=data.payload,
414
+ start_node_id=data.start_node_id,
415
+ max_steps=data.max_steps,
416
+ persist_trace=data.persist_trace,
417
+ )
418
+ except ValueError as exc:
419
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) from exc
420
+ return ProcessDefinitionCanvasFlowSimulationResponse(**result)
421
+
422
+
423
+ @router.post(
424
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/execute",
425
+ response_model=ProcessDefinitionCanvasNodeExecutionResponse,
426
+ )
427
+ async def execute_canvas_node(
428
+ layout_id: str,
429
+ canvas_node_id: str,
430
+ data: ProcessDefinitionCanvasNodeExecutionRequest,
431
+ db: AsyncIOMotorDatabase = Depends(get_database),
432
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
433
+ ):
434
+ node = await crud.get_canvas_node_record(db, layout_id, canvas_node_id)
435
+ if not node:
436
+ raise HTTPException(
437
+ status_code=status.HTTP_404_NOT_FOUND,
438
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
439
+ )
440
+
441
+ node_type = _node_type_from_record(node)
442
+ backend = _backend_call_from_node(node, data.backend)
443
+ event_payload = {
444
+ "source": "sentinel-x-process_definition-service",
445
+ "layoutId": layout_id,
446
+ "nodeId": canvas_node_id,
447
+ "nodeType": node_type,
448
+ "node": node,
449
+ "backend": backend.model_dump() if backend else None,
450
+ "payload": data.payload,
451
+ }
452
+ event = ProcessDefinitionCanvasWebhookEvent(
453
+ event_id=str(uuid4()),
454
+ provider="process_definition-node",
455
+ direction="execution",
456
+ payload=event_payload,
457
+ status="queued" if backend and not backend.url and not data.dry_run else "executed",
458
+ )
459
+
460
+ status_code = None
461
+ response_body = None
462
+ error = None
463
+ ok = True
464
+ config = node.get("config") if isinstance(node.get("config"), dict) else {}
465
+ if not data.dry_run:
466
+ result = await execute_canvas_node_service(
467
+ db=db,
468
+ layout_id=layout_id,
469
+ node_type=node_type,
470
+ node=node,
471
+ backend=backend,
472
+ config=config,
473
+ payload=data.payload,
474
+ )
475
+ ok = result.ok
476
+ status_code = result.status_code
477
+ response_body = result.response_body
478
+ error = result.error
479
+ if node_type in {"mqtt out", "websocket out", "tcp out", "udp out"}:
480
+ event.status = "sent" if ok else "failed"
481
+ elif node_type in {"mqtt in", "websocket in", "tcp in", "udp in"}:
482
+ event.status = "registered" if ok else "failed"
483
+ else:
484
+ event.status = response_body.get("status", "executed") if isinstance(response_body, dict) else "sent" if ok and backend and backend.url else "executed" if ok else "failed"
485
+ event.status_code = status_code
486
+ event.response_body = response_body
487
+ event.error = error
488
+
489
+ await _publish_canvas_webhook_nats_event(
490
+ layout_id=layout_id,
491
+ canvas_node_id=canvas_node_id,
492
+ event=event,
493
+ node=node,
494
+ )
495
+ await crud.record_canvas_node_webhook_event(db, layout_id, canvas_node_id, event)
496
+ return ProcessDefinitionCanvasNodeExecutionResponse(
497
+ ok=ok,
498
+ layout_id=layout_id,
499
+ node_id=canvas_node_id,
500
+ node_type=node_type,
501
+ backend=backend,
502
+ status_code=status_code,
503
+ response_body=response_body,
504
+ error=error,
505
+ event=event,
506
+ )
507
+
508
+
509
+ @router.post(
510
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/webhook/incoming",
511
+ response_model=ProcessDefinitionCanvasNodeProperties,
512
+ status_code=status.HTTP_202_ACCEPTED,
513
+ )
514
+ async def receive_canvas_node_webhook_event(
515
+ layout_id: str,
516
+ canvas_node_id: str,
517
+ request: Request,
518
+ db: AsyncIOMotorDatabase = Depends(get_database),
519
+ ):
520
+ node = await crud.get_canvas_node_record(db, layout_id, canvas_node_id)
521
+ if not node:
522
+ raise HTTPException(
523
+ status_code=status.HTTP_404_NOT_FOUND,
524
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
525
+ )
526
+ webhook_settings = _webhook_settings_from_node(node)
527
+ configured_secret = webhook_settings["secret"]
528
+ supplied_secret = request.headers.get("x-canvas-webhook-secret") or request.query_params.get("secret") or ""
529
+ if configured_secret and supplied_secret != configured_secret:
530
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid canvas webhook secret")
531
+ payload = await _request_json_or_body(request)
532
+ event = ProcessDefinitionCanvasWebhookEvent(
533
+ event_id=str(uuid4()),
534
+ provider=webhook_settings["provider"],
535
+ direction="incoming",
536
+ payload=payload,
537
+ headers={key: value for key, value in request.headers.items()},
538
+ status="received",
539
+ )
540
+ await _publish_canvas_webhook_nats_event(
541
+ layout_id=layout_id,
542
+ canvas_node_id=canvas_node_id,
543
+ event=event,
544
+ node=node,
545
+ )
546
+ properties = await crud.record_canvas_node_webhook_event(db, layout_id, canvas_node_id, event)
547
+ if properties is None:
548
+ raise HTTPException(
549
+ status_code=status.HTTP_404_NOT_FOUND,
550
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
551
+ )
552
+ return properties
553
+
554
+
555
+ @router.post(
556
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/webhook/dispatch",
557
+ response_model=ProcessDefinitionCanvasWebhookDispatchResponse,
558
+ )
559
+ async def dispatch_canvas_node_webhook_event(
560
+ layout_id: str,
561
+ canvas_node_id: str,
562
+ data: ProcessDefinitionCanvasWebhookDispatchRequest,
563
+ db: AsyncIOMotorDatabase = Depends(get_database),
564
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
565
+ ):
566
+ node = await crud.get_canvas_node_record(db, layout_id, canvas_node_id)
567
+ if not node:
568
+ raise HTTPException(
569
+ status_code=status.HTTP_404_NOT_FOUND,
570
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
571
+ )
572
+ webhook_settings = _webhook_settings_from_node(node)
573
+ url = (data.url or webhook_settings["url"]).strip()
574
+ if not url:
575
+ raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="No webhook URL configured for this canvas node")
576
+ method = (data.method or webhook_settings["method"] or "POST").upper()
577
+ connected = await crud.get_connected_canvas_nodes(db, layout_id, canvas_node_id)
578
+ payload = data.payload or {}
579
+ if not payload:
580
+ payload = {
581
+ "source": "sentinel-x-process_definition-service",
582
+ "layoutId": layout_id,
583
+ "nodeId": canvas_node_id,
584
+ "provider": webhook_settings["provider"],
585
+ "direction": "outgoing",
586
+ "node": node,
587
+ "incoming": connected["incoming"],
588
+ "outgoing": connected["outgoing"],
589
+ }
590
+ event = ProcessDefinitionCanvasWebhookEvent(
591
+ event_id=str(uuid4()),
592
+ provider=webhook_settings["provider"],
593
+ direction="outgoing",
594
+ payload=payload,
595
+ status="pending",
596
+ )
597
+ headers = dict(data.headers or {})
598
+ if webhook_settings["provider"] == "n8n":
599
+ headers = n8n_webhook_auth_headers(headers)
600
+ if webhook_settings["secret"]:
601
+ headers.setdefault("x-canvas-webhook-secret", webhook_settings["secret"])
602
+ try:
603
+ async with httpx.AsyncClient(timeout=20.0) as client:
604
+ response = await client.request(method, url, json=payload, headers=headers)
605
+ try:
606
+ response_body = response.json()
607
+ except Exception:
608
+ response_body = response.text
609
+ event.status = "sent" if response.is_success else "failed"
610
+ event.status_code = response.status_code
611
+ event.response_body = response_body
612
+ await _publish_canvas_webhook_nats_event(
613
+ layout_id=layout_id,
614
+ canvas_node_id=canvas_node_id,
615
+ event=event,
616
+ node=node,
617
+ )
618
+ return ProcessDefinitionCanvasWebhookDispatchResponse(
619
+ ok=response.is_success,
620
+ status_code=response.status_code,
621
+ response_body=response_body,
622
+ event=event,
623
+ )
624
+ except httpx.HTTPError as exc:
625
+ event.status = "failed"
626
+ event.error = str(exc)
627
+ await _publish_canvas_webhook_nats_event(
628
+ layout_id=layout_id,
629
+ canvas_node_id=canvas_node_id,
630
+ event=event,
631
+ node=node,
632
+ )
633
+ return ProcessDefinitionCanvasWebhookDispatchResponse(ok=False, error=str(exc), event=event)
634
+
635
+
636
+ @router.get(
637
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/properties",
638
+ response_model=ProcessDefinitionCanvasNodeProperties,
639
+ )
640
+ async def get_canvas_node_properties(
641
+ layout_id: str,
642
+ canvas_node_id: str,
643
+ db: AsyncIOMotorDatabase = Depends(get_database),
644
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
645
+ ):
646
+ properties = await crud.get_canvas_node_properties(db, layout_id, canvas_node_id)
647
+ if properties is None:
648
+ raise HTTPException(
649
+ status_code=status.HTTP_404_NOT_FOUND,
650
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
651
+ )
652
+ return properties
653
+
654
+
655
+ @router.patch(
656
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/properties",
657
+ response_model=ProcessDefinitionCanvasNodeProperties,
658
+ )
659
+ async def update_canvas_node_properties(
660
+ layout_id: str,
661
+ canvas_node_id: str,
662
+ data: ProcessDefinitionCanvasNodePropertiesUpdate,
663
+ db: AsyncIOMotorDatabase = Depends(get_database),
664
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
665
+ ):
666
+ properties = await crud.update_canvas_node_properties(db, layout_id, canvas_node_id, data)
667
+ if properties is None:
668
+ raise HTTPException(
669
+ status_code=status.HTTP_404_NOT_FOUND,
670
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
671
+ )
672
+ return properties
673
+
674
+
675
+ @router.put(
676
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/metadata",
677
+ response_model=ProcessDefinitionCanvasNodeProperties,
678
+ )
679
+ async def replace_canvas_node_metadata(
680
+ layout_id: str,
681
+ canvas_node_id: str,
682
+ metadata: dict[str, Any],
683
+ db: AsyncIOMotorDatabase = Depends(get_database),
684
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
685
+ ):
686
+ properties = await crud.replace_canvas_node_metadata(db, layout_id, canvas_node_id, metadata)
687
+ if properties is None:
688
+ raise HTTPException(
689
+ status_code=status.HTTP_404_NOT_FOUND,
690
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
691
+ )
692
+ return properties
693
+
694
+
695
+ @router.post(
696
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/hyperlinks",
697
+ response_model=ProcessDefinitionCanvasNodeProperties,
698
+ status_code=status.HTTP_201_CREATED,
699
+ )
700
+ async def add_canvas_node_hyperlink(
701
+ layout_id: str,
702
+ canvas_node_id: str,
703
+ hyperlink: ProcessDefinitionCanvasHyperlink,
704
+ db: AsyncIOMotorDatabase = Depends(get_database),
705
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
706
+ ):
707
+ properties = await crud.add_canvas_node_hyperlink(db, layout_id, canvas_node_id, hyperlink)
708
+ if properties is None:
709
+ raise HTTPException(
710
+ status_code=status.HTTP_404_NOT_FOUND,
711
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
712
+ )
713
+ return properties
714
+
715
+
716
+ @router.delete(
717
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/hyperlinks/{index}",
718
+ response_model=ProcessDefinitionCanvasNodeProperties,
719
+ )
720
+ async def remove_canvas_node_hyperlink(
721
+ layout_id: str,
722
+ canvas_node_id: str,
723
+ index: int,
724
+ db: AsyncIOMotorDatabase = Depends(get_database),
725
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
726
+ ):
727
+ properties = await crud.remove_canvas_node_list_item(db, layout_id, canvas_node_id, "hyperlinks", index)
728
+ if properties is None:
729
+ raise HTTPException(
730
+ status_code=status.HTTP_404_NOT_FOUND,
731
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
732
+ )
733
+ return properties
734
+
735
+
736
+ @router.post(
737
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/attachments",
738
+ response_model=ProcessDefinitionCanvasNodeProperties,
739
+ status_code=status.HTTP_201_CREATED,
740
+ )
741
+ async def add_canvas_node_attachment(
742
+ layout_id: str,
743
+ canvas_node_id: str,
744
+ attachment: ProcessDefinitionCanvasAttachment,
745
+ db: AsyncIOMotorDatabase = Depends(get_database),
746
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
747
+ ):
748
+ properties = await crud.add_canvas_node_attachment(db, layout_id, canvas_node_id, attachment)
749
+ if properties is None:
750
+ raise HTTPException(
751
+ status_code=status.HTTP_404_NOT_FOUND,
752
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
753
+ )
754
+ return properties
755
+
756
+
757
+ @router.delete(
758
+ "/canvas/{layout_id}/nodes/{canvas_node_id}/attachments/{index}",
759
+ response_model=ProcessDefinitionCanvasNodeProperties,
760
+ )
761
+ async def remove_canvas_node_attachment(
762
+ layout_id: str,
763
+ canvas_node_id: str,
764
+ index: int,
765
+ db: AsyncIOMotorDatabase = Depends(get_database),
766
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
767
+ ):
768
+ properties = await crud.remove_canvas_node_list_item(db, layout_id, canvas_node_id, "attachments", index)
769
+ if properties is None:
770
+ raise HTTPException(
771
+ status_code=status.HTTP_404_NOT_FOUND,
772
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
773
+ )
774
+ return properties
775
+
776
+
777
+ @router.post(
778
+ "/canvas/{layout_id}/nodes/drop",
779
+ response_model=ProcessDefinitionCanvasLayoutResponse,
780
+ status_code=status.HTTP_201_CREATED,
781
+ )
782
+ async def add_dropped_canvas_node(
783
+ layout_id: str,
784
+ data: ProcessDefinitionCanvasNodePosition,
785
+ db: AsyncIOMotorDatabase = Depends(get_database),
786
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
787
+ ):
788
+ updated = await crud.add_canvas_node(db, layout_id, data)
789
+ if not updated:
790
+ raise HTTPException(
791
+ status_code=status.HTTP_404_NOT_FOUND,
792
+ detail=f"ProcessDefinition canvas layout '{layout_id}' not found",
793
+ )
794
+ return updated
795
+
796
+
797
+ @router.delete("/canvas/nodes/{canvas_node_id}", status_code=status.HTTP_204_NO_CONTENT)
798
+ async def delete_canvas_node(
799
+ canvas_node_id: str,
800
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
801
+ ):
802
+ driver = AsyncGraphDatabase.driver(
803
+ settings.NEO4J_URI,
804
+ auth=(settings.NEO4J_USER, settings.NEO4J_PASSWORD),
805
+ )
806
+ try:
807
+ async with driver.session() as session:
808
+ parts = canvas_node_id.split(":", 1)
809
+ if len(parts) == 2:
810
+ collection, entity_id = parts
811
+ label = collection.rstrip("s").capitalize()
812
+ result = await session.run(
813
+ f"MATCH (n:{label} {{entity_id: $eid}}) DETACH DELETE n RETURN count(n) AS c",
814
+ eid=entity_id,
815
+ )
816
+ record = await result.single()
817
+ if record and record["c"] > 0:
818
+ return
819
+ raise HTTPException(
820
+ status_code=status.HTTP_404_NOT_FOUND,
821
+ detail=f"ProcessDefinition canvas node '{canvas_node_id}' not found",
822
+ )
823
+ finally:
824
+ await driver.close()
825
+
826
+
827
+ # ── Single record ─────────────────────────────────────────────────────────────
828
+
829
+ @router.get("/{process_definition_id}", response_model=ProcessDefinitionResponse)
830
+ async def get_process_definition(
831
+ process_definition_id: str,
832
+ db: AsyncIOMotorDatabase = Depends(get_database),
833
+ _: dict = Depends(require_permission("perm-view-process-definitions")),
834
+ ):
835
+ """Fetch a single process_definition by its unique process_definition_id."""
836
+ record = await crud.get_by_id(db, process_definition_id)
837
+ if not record:
838
+ raise HTTPException(
839
+ status_code=status.HTTP_404_NOT_FOUND,
840
+ detail=f"ProcessDefinition '{process_definition_id}' not found",
841
+ )
842
+ return record
843
+
844
+
845
+ # ── Create ────────────────────────────────────────────────────────────────────
846
+
847
+ @router.post("/", response_model=ProcessDefinitionResponse, status_code=status.HTTP_201_CREATED)
848
+ async def create_process_definition(
849
+ data: ProcessDefinitionCreate,
850
+ db: AsyncIOMotorDatabase = Depends(get_database),
851
+ _: dict = Depends(require_permission("perm-create-process-definitions")),
852
+ ):
853
+ """Register a new process_definition. Returns 409 if process_definition_id already exists."""
854
+ if await crud.get_by_id(db, data.process_definition_id):
855
+ raise HTTPException(
856
+ status_code=status.HTTP_409_CONFLICT,
857
+ detail=f"ProcessDefinition '{data.process_definition_id}' already exists",
858
+ )
859
+ return await crud.create(db, data)
860
+
861
+
862
+ # ── Update ────────────────────────────────────────────────────────────────────
863
+
864
+ @router.patch("/{process_definition_id}", response_model=ProcessDefinitionResponse)
865
+ async def update_process_definition(
866
+ process_definition_id: str,
867
+ data: ProcessDefinitionUpdate,
868
+ db: AsyncIOMotorDatabase = Depends(get_database),
869
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
870
+ ):
871
+ """Partially update a process_definition's fields. Returns 404 if the process_definition does not exist."""
872
+ try:
873
+ updated = await crud.update(db, process_definition_id, data)
874
+ except ValueError as exc:
875
+ raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc)) from exc
876
+ if not updated:
877
+ raise HTTPException(
878
+ status_code=status.HTTP_404_NOT_FOUND,
879
+ detail=f"ProcessDefinition '{process_definition_id}' not found",
880
+ )
881
+ return updated
882
+
883
+
884
+ @router.post("/{process_definition_id}/submit", response_model=ProcessDefinitionResponse)
885
+ async def submit_process_definition_for_review(
886
+ process_definition_id: str,
887
+ db: AsyncIOMotorDatabase = Depends(get_database),
888
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
889
+ ):
890
+ updated = await crud.submit_for_review(db, process_definition_id)
891
+ if not updated:
892
+ raise HTTPException(
893
+ status_code=status.HTTP_404_NOT_FOUND,
894
+ detail=f"Draft process definition '{process_definition_id}' not found",
895
+ )
896
+ return updated
897
+
898
+
899
+ @router.post("/{process_definition_id}/approve", response_model=ProcessDefinitionResponse)
900
+ async def approve_process_definition(
901
+ process_definition_id: str,
902
+ approved_by: str | None = Query(None),
903
+ make_effective: bool = Query(True),
904
+ db: AsyncIOMotorDatabase = Depends(get_database),
905
+ current_user: dict = Depends(require_permission("perm-update-process-definitions")),
906
+ ):
907
+ approver = approved_by or current_user.get("sub") or current_user.get("user_id") or current_user.get("username")
908
+ updated = await crud.approve(db, process_definition_id, approved_by=approver, make_effective=make_effective)
909
+ if not updated:
910
+ raise HTTPException(
911
+ status_code=status.HTTP_404_NOT_FOUND,
912
+ detail=f"Reviewable process definition '{process_definition_id}' not found",
913
+ )
914
+ return updated
915
+
916
+
917
+ @router.post("/{process_definition_id}/retire", response_model=ProcessDefinitionResponse)
918
+ async def retire_process_definition(
919
+ process_definition_id: str,
920
+ db: AsyncIOMotorDatabase = Depends(get_database),
921
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
922
+ ):
923
+ updated = await crud.retire(db, process_definition_id)
924
+ if not updated:
925
+ raise HTTPException(
926
+ status_code=status.HTTP_404_NOT_FOUND,
927
+ detail=f"ProcessDefinition '{process_definition_id}' not found",
928
+ )
929
+ return updated
930
+
931
+
932
+ @router.post("/{process_definition_id}/new-revision", response_model=ProcessDefinitionResponse, status_code=status.HTTP_201_CREATED)
933
+ async def create_process_definition_revision(
934
+ process_definition_id: str,
935
+ new_process_definition_id: str = Query(...),
936
+ revision: str = Query(...),
937
+ db: AsyncIOMotorDatabase = Depends(get_database),
938
+ _: dict = Depends(require_permission("perm-create-process-definitions")),
939
+ ):
940
+ if await crud.get_by_id(db, new_process_definition_id):
941
+ raise HTTPException(
942
+ status_code=status.HTTP_409_CONFLICT,
943
+ detail=f"ProcessDefinition '{new_process_definition_id}' already exists",
944
+ )
945
+ created = await crud.create_new_revision(db, process_definition_id, new_process_definition_id, revision)
946
+ if not created:
947
+ raise HTTPException(
948
+ status_code=status.HTTP_404_NOT_FOUND,
949
+ detail=f"ProcessDefinition '{process_definition_id}' not found",
950
+ )
951
+ return created
952
+
953
+
954
+ @router.post("/backfill/recipe-routes")
955
+ async def backfill_recipe_routes_as_process_definitions(
956
+ db: AsyncIOMotorDatabase = Depends(get_database),
957
+ _: dict = Depends(require_permission("perm-create-process-definitions")),
958
+ ):
959
+ return await crud.backfill_recipe_routes(db)
960
+
961
+
962
+ @router.patch("/{process_definition_id}/status", response_model=ProcessDefinitionResponse)
963
+ async def update_process_definition_status(
964
+ process_definition_id: str,
965
+ status_value: str = Query(..., alias="value"),
966
+ db: AsyncIOMotorDatabase = Depends(get_database),
967
+ _: dict = Depends(require_permission("perm-update-process-definitions")),
968
+ ):
969
+ """Update only the lifecycle status of a process_definition."""
970
+ updated = await crud.update_status(db, process_definition_id, status_value)
971
+ if not updated:
972
+ raise HTTPException(
973
+ status_code=status.HTTP_404_NOT_FOUND,
974
+ detail=f"ProcessDefinition '{process_definition_id}' not found",
975
+ )
976
+ return updated
977
+
978
+
979
+ # ── Delete ────────────────────────────────────────────────────────────────────
980
+
981
+ @router.delete("/{process_definition_id}", status_code=status.HTTP_204_NO_CONTENT)
982
+ async def delete_process_definition(
983
+ process_definition_id: str,
984
+ db: AsyncIOMotorDatabase = Depends(get_database),
985
+ _: dict = Depends(require_permission("perm-delete-process-definitions")),
986
+ ):
987
+ """Permanently remove a process_definition record. Returns 404 if the process_definition does not exist."""
988
+ if not await crud.delete(db, process_definition_id):
989
+ raise HTTPException(
990
+ status_code=status.HTTP_404_NOT_FOUND,
991
+ detail=f"ProcessDefinition '{process_definition_id}' not found",
992
+ )