agentworkshop 0.7.71 → 0.8.0

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 (358) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +332 -0
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +332 -0
  3. package/.output/nitro.json +1 -1
  4. package/.output/public/_nuxt/BH8RvpVy.js +1 -0
  5. package/.output/public/_nuxt/Byf4IxU2.js +14 -0
  6. package/.output/public/_nuxt/CxVhG0Xx.js +1 -0
  7. package/.output/public/_nuxt/D0mzKkZr.js +1 -0
  8. package/.output/public/_nuxt/DIFkTkdS.js +1 -0
  9. package/.output/public/_nuxt/DgrK8UCQ.js +0 -0
  10. package/.output/public/_nuxt/builds/latest.json +1 -1
  11. package/.output/public/_nuxt/builds/meta/7a79a7e4-d691-4cf5-ae3a-3b01814479fb.json +1 -0
  12. package/.output/public/_nuxt/{default.BzOcvmSE.css → default.BEXGWZFQ.css} +1 -1
  13. package/.output/public/_nuxt/{entry.CL2i0F8o.css → entry.C7koBPom.css} +1 -1
  14. package/.output/public/_nuxt/ontology.QMGeh66l.css +1 -0
  15. package/.output/server/chunks/_/access.mjs +1 -1
  16. package/.output/server/chunks/_/binding-authz.mjs +1 -1
  17. package/.output/server/chunks/_/catalog-view.mjs +1 -1
  18. package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
  19. package/.output/server/chunks/_/chart-repo.mjs +1 -1
  20. package/.output/server/chunks/_/dashboard-repo.mjs +1 -1
  21. package/.output/server/chunks/_/data-export.mjs +6 -6
  22. package/.output/server/chunks/_/data-query.mjs +3 -3
  23. package/.output/server/chunks/_/eda-run.repo.mjs +2 -2
  24. package/.output/server/chunks/_/entity.mjs +1 -1
  25. package/.output/server/chunks/_/features.mjs +5 -5
  26. package/.output/server/chunks/_/golden.mjs +3 -3
  27. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  28. package/.output/server/chunks/_/index.mjs +1 -1
  29. package/.output/server/chunks/_/mes-console.mjs +1 -1
  30. package/.output/server/chunks/_/nitro.mjs +5362 -2240
  31. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  32. package/.output/server/chunks/_/ocap.mjs +1 -1
  33. package/.output/server/chunks/_/rest-auth.mjs +1 -1
  34. package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
  35. package/.output/server/chunks/_/spc.mjs +2 -2
  36. package/.output/server/chunks/build/TownView-BWMDbosY.mjs +1 -1
  37. package/.output/server/chunks/build/api-BEW_ebY_.mjs +64 -0
  38. package/.output/server/chunks/build/api-BEW_ebY_.mjs.map +1 -0
  39. package/.output/server/chunks/build/daq-CdxcfY_G.mjs +1 -1
  40. package/.output/server/chunks/build/dcw-BlDQtS4q.mjs +1 -1
  41. package/.output/server/chunks/build/{default-BeB88U7y.mjs → default-CJkOcmrm.mjs} +14 -8
  42. package/.output/server/chunks/build/{default-BeB88U7y.mjs.map → default-CJkOcmrm.mjs.map} +1 -1
  43. package/.output/server/chunks/build/ontology-C3HS5pMo.mjs +977 -0
  44. package/.output/server/chunks/build/ontology-C3HS5pMo.mjs.map +1 -0
  45. package/.output/server/chunks/build/types-D6J7slEa.mjs +6 -2
  46. package/.output/server/chunks/build/useDaqAddNode-D_Rkju0f.mjs +1 -1
  47. package/.output/server/chunks/build/useDaqDetailNode-DeOsDxK5.mjs +1 -1
  48. package/.output/server/chunks/build/useDaqDetailParams-Cm9a9r5G.mjs +1 -1
  49. package/.output/server/chunks/build/useDaqFilters-CctmO03p.mjs +1 -1
  50. package/.output/server/chunks/build/useDaqStream-B9ZBVQrI.mjs +1 -1
  51. package/.output/server/chunks/build/useDaqTemplates-CK37sMfi.mjs +1 -1
  52. package/.output/server/chunks/build/useDcwAddNode-DACVcEHs.mjs +1 -1
  53. package/.output/server/chunks/build/useDcwStream-Dir5ufAU.mjs +1 -1
  54. package/.output/server/chunks/routes/api/mcp/workshop.mjs +1 -1
  55. package/.output/server/chunks/routes/api/system/backup/run.post.mjs +2 -2
  56. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +17 -4
  57. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs.map +1 -1
  58. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +1 -1
  59. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +1 -1
  60. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/grant.post.mjs +1 -1
  61. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/revoke.post.mjs +1 -1
  62. package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +1 -1
  63. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +1 -1
  64. package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +1 -1
  65. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +1 -1
  66. package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +1 -1
  67. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +1 -1
  68. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +1 -1
  69. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +1 -1
  70. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +1 -1
  71. package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +1 -1
  72. package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +1 -1
  73. package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +1 -1
  74. package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +1 -1
  75. package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +1 -1
  76. package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +1 -1
  77. package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +1 -1
  78. package/.output/server/chunks/routes/api/workshop/aml/index3.post.mjs +1 -1
  79. package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +1 -1
  80. package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +1 -1
  81. package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +1 -1
  82. package/.output/server/chunks/routes/api/workshop/aml/index7.get.mjs +1 -1
  83. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +1 -1
  84. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +1 -1
  85. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +1 -1
  86. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +1 -1
  87. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +1 -1
  88. package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +1 -1
  89. package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +1 -1
  90. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +1 -1
  91. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +1 -1
  92. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id/train.post.mjs +1 -1
  93. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.delete.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.patch.mjs +1 -1
  95. package/.output/server/chunks/routes/api/workshop/aml/twin/providers.get.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/analysis/_id_.delete.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/analysis/_id_.get.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/analysis/_id_.patch.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +2 -2
  103. package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.put.mjs +1 -1
  108. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +1 -1
  109. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +1 -1
  110. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +1 -1
  111. package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/channels/_id/tools/reset.post.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.get.mjs +1 -1
  116. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.patch.mjs +1 -1
  117. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +1 -1
  118. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +7 -7
  119. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
  120. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
  121. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
  122. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
  123. package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs +2 -2
  124. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
  125. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
  126. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
  127. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
  128. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
  129. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +1 -1
  130. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
  131. package/.output/server/chunks/routes/api/workshop/daq/export/_id_.get.mjs +1 -1
  132. package/.output/server/chunks/routes/api/workshop/daq/index2.post.mjs +1 -1
  133. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
  134. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  135. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  136. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  137. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  139. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
  140. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
  141. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
  142. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  143. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  144. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  145. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
  146. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  147. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
  148. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  149. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  150. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  151. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
  152. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
  153. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
  154. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  155. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  157. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/dcw/index7.get.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +1 -1
  165. package/.output/server/chunks/routes/api/workshop/dcw/mes-artifacts/file.get.mjs +1 -1
  166. package/.output/server/chunks/routes/api/workshop/dcw/mes-hook-test.post.mjs +1 -1
  167. package/.output/server/chunks/routes/api/workshop/dcw/mes-test-read.post.mjs +1 -1
  168. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  169. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  170. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +1 -1
  173. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +1 -1
  174. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  175. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  176. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  177. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  178. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  179. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
  180. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +1 -1
  181. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  182. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +1 -1
  183. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
  184. package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +1 -1
  185. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
  186. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  187. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  188. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  189. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  190. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
  191. package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +1 -1
  192. package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +1 -1
  193. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
  194. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
  195. package/.output/server/chunks/routes/api/workshop/eda/commonality.get.mjs +1 -1
  196. package/.output/server/chunks/routes/api/workshop/eda/features/run.post.mjs +1 -1
  197. package/.output/server/chunks/routes/api/workshop/eda/index3.get.mjs +1 -1
  198. package/.output/server/chunks/routes/api/workshop/eda/index6.get.mjs +1 -1
  199. package/.output/server/chunks/routes/api/workshop/eda/index6.post.mjs +1 -1
  200. package/.output/server/chunks/routes/api/workshop/eda/runs/_id/labels.patch.mjs +1 -1
  201. package/.output/server/chunks/routes/api/workshop/eda/runs.get.mjs +1 -1
  202. package/.output/server/chunks/routes/api/workshop/eda/vm/predict.post.mjs +1 -1
  203. package/.output/server/chunks/routes/api/workshop/exp/confirmations/_id/decide.post.mjs +1 -1
  204. package/.output/server/chunks/routes/api/workshop/exp/index.get.mjs +1 -1
  205. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +1 -1
  206. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  207. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  208. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  209. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  210. package/.output/server/chunks/routes/api/workshop/index.mjs +62 -0
  211. package/.output/server/chunks/routes/api/workshop/index.mjs.map +1 -0
  212. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  213. package/.output/server/chunks/routes/api/workshop/index10.get.mjs +1 -1
  214. package/.output/server/chunks/routes/api/workshop/index11.get.mjs +1 -1
  215. package/.output/server/chunks/routes/api/workshop/index12.get.mjs +1 -1
  216. package/.output/server/chunks/routes/api/workshop/index2.get.mjs +1 -1
  217. package/.output/server/chunks/routes/api/workshop/index3.get.mjs +1 -1
  218. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
  219. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
  220. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
  221. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +1 -1
  222. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
  223. package/.output/server/chunks/routes/api/workshop/index7.get.mjs +1 -1
  224. package/.output/server/chunks/routes/api/workshop/index8.get.mjs +1 -1
  225. package/.output/server/chunks/routes/api/workshop/mes/datasets.get.mjs +1 -1
  226. package/.output/server/chunks/routes/api/workshop/mes/preview.post.mjs +1 -1
  227. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs +54 -0
  228. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs.map +1 -0
  229. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs +51 -0
  230. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs.map +1 -0
  231. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs +67 -0
  232. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs.map +1 -0
  233. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs +49 -0
  234. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs.map +1 -0
  235. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs +35 -0
  236. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs.map +1 -0
  237. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs +49 -0
  238. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs.map +1 -0
  239. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs +35 -0
  240. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs.map +1 -0
  241. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs +70 -0
  242. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs.map +1 -0
  243. package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs +64 -0
  244. package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs.map +1 -0
  245. package/.output/server/chunks/routes/api/workshop/ontology/index.mjs +44 -0
  246. package/.output/server/chunks/routes/api/workshop/ontology/index.mjs.map +1 -0
  247. package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs +36 -0
  248. package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs.map +1 -0
  249. package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs +38 -0
  250. package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs.map +1 -0
  251. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs +40 -0
  252. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs.map +1 -0
  253. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs +63 -0
  254. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs.map +1 -0
  255. package/.output/server/chunks/routes/api/workshop/optimization/capabilities.get.mjs +1 -1
  256. package/.output/server/chunks/routes/api/workshop/optimization/index.get.mjs +1 -1
  257. package/.output/server/chunks/routes/api/workshop/optimization/index.mjs +1 -1
  258. package/.output/server/chunks/routes/api/workshop/optimization/index2.mjs +1 -1
  259. package/.output/server/chunks/routes/api/workshop/optimization/profiles/_id/publish.post.mjs +1 -1
  260. package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/crosscheck.post.mjs +1 -1
  261. package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/dispatch.post.mjs +1 -1
  262. package/.output/server/chunks/routes/api/workshop/optimization/solve.post.mjs +1 -1
  263. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  264. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  265. package/.output/server/chunks/routes/api/workshop/process-model/datasets.post.mjs +1 -1
  266. package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
  267. package/.output/server/chunks/routes/api/workshop/process-model/experiments.mjs +1 -1
  268. package/.output/server/chunks/routes/api/workshop/process-model/index.mjs +1 -1
  269. package/.output/server/chunks/routes/api/workshop/process-model/specs/_id/publish.post.mjs +1 -1
  270. package/.output/server/chunks/routes/api/workshop/process-model/train.post.mjs +1 -1
  271. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  272. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  273. package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +1 -1
  274. package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +1 -1
  275. package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +1 -1
  276. package/.output/server/chunks/routes/api/workshop/tools/_name_.get.mjs +1 -1
  277. package/.output/server/chunks/routes/api/workshop/tools/sync.post.mjs +1 -1
  278. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +2 -2
  279. package/.output/server/chunks/routes/renderer.mjs +2 -2
  280. package/.output/server/chunks/virtual/entry.mjs +234 -3
  281. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  282. package/.output/server/package.json +1 -1
  283. package/README-zh.md +27 -6
  284. package/README.md +25 -4
  285. package/app/components/AppSidebar.vue +1 -0
  286. package/app/pages/ontology/api.ts +18 -0
  287. package/app/pages/ontology/index.vue +779 -0
  288. package/app/pages/ontology/types.ts +122 -0
  289. package/config.yml +1 -1
  290. package/docs/cli.en.md +2 -2
  291. package/docs/cli.md +2 -2
  292. package/docs/plugins.en.md +1 -1
  293. package/docs/plugins.md +1 -1
  294. package/docs/sdk.en.md +1 -1
  295. package/docs/sdk.md +1 -1
  296. package/i18n/locales/en.ts +58 -0
  297. package/i18n/locales/zh-CN.ts +58 -0
  298. package/mcp/aw-mcp-server.mjs +35 -1
  299. package/package.json +1 -1
  300. package/scripts/docs-shots.mjs +36 -0
  301. package/scripts/e2e-model-loop-live.mjs +731 -0
  302. package/scripts/e2e-ontology-live.mjs +774 -0
  303. package/scripts/e2e-ontology.mjs +328 -0
  304. package/scripts/ontology-idd-rag-e2e.mjs +449 -0
  305. package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +16 -0
  306. package/server/api/workshop/ontology/drafts/[draftId]/compile.ts +39 -0
  307. package/server/api/workshop/ontology/drafts/[draftId]/evidence.ts +33 -0
  308. package/server/api/workshop/ontology/drafts/[draftId]/publish.ts +44 -0
  309. package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/approval-decision.ts +28 -0
  310. package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/approval.ts +17 -0
  311. package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/transition.ts +32 -0
  312. package/server/api/workshop/ontology/drafts/[draftId]/validate.ts +16 -0
  313. package/server/api/workshop/ontology/drafts/[draftId].ts +52 -0
  314. package/server/api/workshop/ontology/drafts/index.ts +20 -0
  315. package/server/api/workshop/ontology/export/[ontologyId].ts +50 -0
  316. package/server/api/workshop/ontology/index.ts +38 -0
  317. package/server/api/workshop/ontology/scene.ts +21 -0
  318. package/server/api/workshop/ontology/traces/[ontologyId].ts +19 -0
  319. package/server/api/workshop/ontology/versions/[ontologyId]/compare.ts +21 -0
  320. package/server/api/workshop/ontology/versions/[ontologyId].ts +42 -0
  321. package/server/services/workshop/agents/hitl-decision/dispatcher.ts +15 -0
  322. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +13 -1
  323. package/server/services/workshop/agents/industrial/grant-guard.ts +59 -0
  324. package/server/services/workshop/agents/industrial/index.ts +38 -32
  325. package/server/services/workshop/agents/industrial/ontology-tools.ts +320 -0
  326. package/server/services/workshop/agents/industrial/optimizer-tools.ts +79 -0
  327. package/server/services/workshop/agents/industrial/tool-verify.ts +32 -2
  328. package/server/services/workshop/agents/tool-reference-spec.ts +1 -1
  329. package/server/services/workshop/aml/twin/physics-spec.ts +6 -22
  330. package/server/services/workshop/daq/storage/index.ts +6 -5
  331. package/server/services/workshop/daq/storage/sqlite.adapter.ts +25 -13
  332. package/server/services/workshop/db/database/open.ts +2 -0
  333. package/server/services/workshop/db/database/schema.ts +155 -1
  334. package/server/services/workshop/ontology/compiler.ts +474 -0
  335. package/server/services/workshop/ontology/contracts.ts +441 -0
  336. package/server/services/workshop/ontology/guards.ts +139 -0
  337. package/server/services/workshop/ontology/index.ts +10 -0
  338. package/server/services/workshop/ontology/ontology.service.ts +892 -0
  339. package/server/services/workshop/ontology/repo.ts +449 -0
  340. package/server/services/workshop/ontology/semantic-binding.service.ts +226 -0
  341. package/server/services/workshop/ontology/validator.ts +218 -0
  342. package/server/services/workshop/optimization/binding.repo.ts +7 -0
  343. package/server/services/workshop/optimization/contracts.ts +14 -0
  344. package/server/services/workshop/optimization/dispatch.service.ts +16 -0
  345. package/server/services/workshop/optimization/profile-adopt.service.ts +256 -0
  346. package/server/services/workshop/optimization/profile.repo.ts +19 -3
  347. package/server/services/workshop/optimization/python/__pycache__/problems.cpython-312.pyc +0 -0
  348. package/shared/dimensions.ts +124 -0
  349. package/.output/public/_nuxt/B7s2qeyT.js +0 -14
  350. package/.output/public/_nuxt/LHIav-Kv.js +0 -1
  351. package/.output/public/_nuxt/RuMLNeQE.js +0 -1
  352. package/.output/public/_nuxt/builds/meta/0ae8cb0f-6ce5-4096-9692-9b1fc197b30d.json +0 -1
  353. /package/.output/public/_nuxt/{62J4vljm2.js → 62J4vljm.js} +0 -0
  354. /package/.output/public/_nuxt/{BO0BJx3w.js → BO0BJx3w2.js} +0 -0
  355. /package/.output/public/_nuxt/{C-MY-JQE2.js → C-MY-JQE.js} +0 -0
  356. /package/.output/public/_nuxt/{D5kCimyw.js → D5kCimyw2.js} +0 -0
  357. /package/.output/public/_nuxt/{DGrrucbG2.js → DGrrucbG.js} +0 -0
  358. /package/.output/public/_nuxt/{Y4sMSPrS.js → Y4sMSPrS2.js} +0 -0
@@ -0,0 +1,892 @@
1
+ /**
2
+ * Ontology Service —— 草稿生命周期 / 校验 / 编译预览 / 规则审批 / 版本发布。
3
+ *
4
+ * 治理不变量(验收逐条断言):
5
+ * 1) 自审禁止:审查人 ≠ 草稿发起人(单管理员实例豁免并打 solo_operator 标记);
6
+ * 2) 硬约束/阻断级守卫只接受 approved_for_control(第二人批准);
7
+ * 3) 发布 = 不可变快照(同版本改内容 → 409);运行中本体更新不改在跑版本;
8
+ * 4) 草稿编辑乐观锁(rev),并发写 → 409 而非静默覆盖;
9
+ * 5) 每次状态迁移/发布/审批都落 ontology_publish_log(actuator 三态 + 归因)。
10
+ */
11
+ import { DatabaseSync } from 'node:sqlite'
12
+ import { join } from 'node:path'
13
+ import { AppError } from '../../../utils/errors'
14
+ import { ensureDataDir } from '@/shared/config/home.mjs'
15
+ import type {
16
+ BindingDraft,
17
+ CompileOptions,
18
+ ControlGuard,
19
+ EvidenceRecord,
20
+ OntologyCompileResult,
21
+ OntologyDraftDoc,
22
+ OntologyRole,
23
+ OntologyState,
24
+ OntologyTarget,
25
+ } from './contracts'
26
+ import {
27
+ ONTOLOGY_SCHEMA_VERSION,
28
+ RULE_ERROR_CODES,
29
+ RULE_TRANSITIONS,
30
+ draftContentHash,
31
+ emptyDraftDoc,
32
+ parseDraftDoc,
33
+ } from './contracts'
34
+ import { dimensionOf } from '@/shared/dimensions'
35
+ import { compileOntology, ontologyPackHash } from './compiler'
36
+ import { evaluateGuards, extractGuardSet, type GuardEvaluationInput, type GuardEvaluationResult } from './guards'
37
+ import {
38
+ getOntologyRepo,
39
+ parseDraftRow,
40
+ type DraftRow,
41
+ type EvidenceRow,
42
+ type VersionRow,
43
+ } from './repo'
44
+ import { detectBindingDrift, validateDraftBindings } from './semantic-binding.service'
45
+ import { getDcwController } from '../dcw/dcw-controller'
46
+ import { validateDraftDoc, type DraftValidationReport } from './validator'
47
+
48
+ export interface Actor {
49
+ userId: string
50
+ role: 'admin' | 'editor' | 'user'
51
+ /** agent 发起时携带其 channel 属主(归因与撤权收敛) */
52
+ actorKind?: 'user' | 'agent' | 'system'
53
+ initiatorUserId?: string
54
+ }
55
+
56
+ /** REST 调用者 → 治理 Actor(角色收敛到三档,未知角色按 user) */
57
+ export function actorFromUser(user: { id: string, role: string }): Actor {
58
+ const role: Actor['role'] = user.role === 'admin' || user.role === 'editor' ? user.role : 'user'
59
+ return { userId: user.id, role, actorKind: 'user' }
60
+ }
61
+
62
+ const EDITOR_ROLES = new Set(['admin', 'editor'])
63
+
64
+ function newId(prefix: string): string {
65
+ return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
66
+ }
67
+
68
+ function requireEditor(actor: Actor, what: string): void {
69
+ if (!EDITOR_ROLES.has(actor.role)) {
70
+ throw new AppError(403, 'FORBIDDEN_ROLE', `${what} 需要 admin 或 editor 角色`)
71
+ }
72
+ }
73
+
74
+ function requireAdmin(actor: Actor, what: string): void {
75
+ if (actor.role !== 'admin') throw new AppError(403, 'ADMIN_REQUIRED', `${what} 仅管理员可执行`)
76
+ }
77
+
78
+ /**
79
+ * 单管理员部署豁免自审(打 solo_operator 标记,审计可追溯)。
80
+ *
81
+ * 用户与角色在独立的 users.sqlite(user.repository),不在 workshop 主库。
82
+ * env `AW_ONTOLOGY_SOLO_OPERATOR=1|0` 可显式覆盖(部署/测试用);否则按管理员人数判断。
83
+ */
84
+ function isSoloOperator(): boolean {
85
+ const override = process.env.AW_ONTOLOGY_SOLO_OPERATOR
86
+ if (override === '1') return true
87
+ if (override === '0') return false
88
+ try {
89
+ // 用户/角色在独立的 users.sqlite(不在 workshop 主库);只读短连接统计管理员人数
90
+ const db = new DatabaseSync(join(ensureDataDir(), 'users.sqlite'), { readOnly: true })
91
+ try {
92
+ const row = db.prepare('SELECT COUNT(*) AS n FROM users WHERE role = \'admin\' AND status = \'active\'').get() as { n: number } | undefined
93
+ return (row?.n ?? 0) <= 1
94
+ }
95
+ finally { db.close() }
96
+ }
97
+ catch { return true }
98
+ }
99
+
100
+ function log(input: {
101
+ ontologyId: string
102
+ version?: number
103
+ action: string
104
+ actor: Actor
105
+ soloOperator?: boolean
106
+ approvalId?: string | null
107
+ detail?: Record<string, unknown>
108
+ }): void {
109
+ getOntologyRepo().appendPublishLog({
110
+ id: newId('opl'),
111
+ ontologyId: input.ontologyId,
112
+ version: input.version ?? 0,
113
+ action: input.action,
114
+ actor: input.actor.userId,
115
+ actorKind: input.actor.actorKind ?? 'user',
116
+ soloOperator: input.soloOperator ? 1 : 0,
117
+ approvalId: input.approvalId ?? null,
118
+ detailJson: JSON.stringify({ ...input.detail, initiatorUserId: input.actor.initiatorUserId ?? '' }),
119
+ createdAt: new Date().toISOString(),
120
+ })
121
+ }
122
+
123
+ // ===== 草稿 =====
124
+
125
+ export interface CreateDraftInput {
126
+ ontologyId: string
127
+ /** 草稿属主(缺省=调用者;admin 可代建) */
128
+ ownerId?: string
129
+ baseVersion?: number
130
+ /** 从已发布版本克隆(基于上一版继续演进) */
131
+ cloneFromVersion?: boolean
132
+ lineId?: string
133
+ name?: string
134
+ }
135
+
136
+ export function createDraft(actor: Actor, input: CreateDraftInput): DraftRow {
137
+ requireEditor(actor, '创建本体草稿')
138
+ const ontologyId = input.ontologyId.trim()
139
+ if (!/^[a-z][a-z0-9_-]{1,79}$/.test(ontologyId)) {
140
+ throw new AppError(422, 'ONTOLOGY_INVALID', 'ontologyId 必须形如 ont-pva-line01(小写字母开头,允许数字/下划线/连字符)')
141
+ }
142
+ const ownerId = input.ownerId || actor.userId
143
+ const repo = getOntologyRepo()
144
+ const active = repo.getActiveDraft(ontologyId, ownerId)
145
+ if (active) {
146
+ throw new AppError(409, 'DRAFT_ALREADY_ACTIVE', `本体 ${ontologyId} 已有活跃草稿(${active.id}, rev=${active.rev});请继续编辑`)
147
+ }
148
+ let doc: OntologyDraftDoc = emptyDraftDoc(ontologyId, input.lineId ?? '')
149
+ if (input.name) doc.name = input.name
150
+ if (input.cloneFromVersion) {
151
+ const latest = repo.latestVersion(ontologyId, 'published')
152
+ if (!latest) throw new AppError(404, 'VERSION_NOT_FOUND', `本体 ${ontologyId} 还没有已发布版本可克隆`)
153
+ doc = cloneVersionDoc(latest)
154
+ input = { ...input, baseVersion: latest.version }
155
+ }
156
+ const row = repo.insertDraft({
157
+ id: newId('ontd'),
158
+ ontologyId,
159
+ baseVersion: input.baseVersion ?? null,
160
+ draftJson: JSON.stringify(doc),
161
+ ownerUserId: ownerId,
162
+ })
163
+ log({ ontologyId, version: input.baseVersion ?? 0, action: 'draft.create', actor, detail: { draftId: row.id } })
164
+ return row
165
+ }
166
+
167
+ function cloneVersionDoc(version: VersionRow): OntologyDraftDoc {
168
+ const repo = getOntologyRepo()
169
+ const entities = repo.listEntities(version.id, version.version)
170
+ const relations = repo.listRelations(version.id, version.version)
171
+ const rules = repo.listRules(version.id, version.version)
172
+ const bindings = repo.listBindings(version.id, version.version).map(row => ({
173
+ id: row.id,
174
+ entityKey: row.entityKey,
175
+ platformKind: row.platformKind as BindingDraft['platformKind'],
176
+ platformId: row.platformId,
177
+ targetSub: row.targetSub,
178
+ scope: row.scope,
179
+ direction: row.direction as BindingDraft['direction'],
180
+ unitDeclared: row.unitDeclared,
181
+ unitTarget: row.unitTarget,
182
+ scale: row.scale,
183
+ offset: row.offset,
184
+ }))
185
+ return {
186
+ schemaVersion: ONTOLOGY_SCHEMA_VERSION,
187
+ name: version.name || version.id,
188
+ lineId: version.lineId,
189
+ entities,
190
+ relations,
191
+ rules,
192
+ bindings,
193
+ note: '',
194
+ }
195
+ }
196
+
197
+ export function getDraft(draftId: string): DraftRow {
198
+ const row = getOntologyRepo().getDraft(draftId)
199
+ if (!row) throw new AppError(404, 'DRAFT_NOT_FOUND', `草稿 ${draftId} 不存在`)
200
+ return row
201
+ }
202
+
203
+ export function requireDraftAccess(draft: DraftRow, actor: Actor, opts: { write?: boolean } = {}): void {
204
+ if (draft.ownerUserId && draft.ownerUserId !== actor.userId && actor.role !== 'admin') {
205
+ throw new AppError(403, 'DRAFT_SCOPE_VIOLATION', `草稿 ${draft.id} 属于其他用户;只有属主或管理员可${opts.write ? '修改' : '查看'}`)
206
+ }
207
+ }
208
+
209
+ export function listDrafts(actor: Actor, filter: { ontologyId?: string } = {}): DraftRow[] {
210
+ const repo = getOntologyRepo()
211
+ if (actor.role === 'admin') return repo.listDrafts(filter)
212
+ return repo.listDrafts({ ...filter, ownerUserId: actor.userId })
213
+ }
214
+
215
+ export interface DraftDetail {
216
+ draft: DraftRow
217
+ doc: OntologyDraftDoc
218
+ validation: DraftValidationReport
219
+ bindings: ReturnType<typeof validateDraftBindings>
220
+ }
221
+
222
+ export function getDraftDetail(draftId: string, actor: Actor): DraftDetail {
223
+ const draft = getDraft(draftId)
224
+ requireDraftAccess(draft, actor)
225
+ const doc = parseDraftRow(draft)
226
+ const validation = validateDraftDoc(doc)
227
+ // 绑定活体校验只对「目标仍存在」的 kind 有意义;device/spec_variable 等无目录面会报不存在,
228
+ // 因此这里只报告,不阻断非绑定编辑。
229
+ const bindings = validateDraftBindings(doc, { lineId: doc.lineId })
230
+ return { draft, doc, validation, bindings }
231
+ }
232
+
233
+ export interface UpdateDraftInput {
234
+ doc: unknown
235
+ expectedRev: number
236
+ /** 保存前是否强制活体绑定校验通过(默认 true:不允许落库已知失效的绑定) */
237
+ strictBindings?: boolean
238
+ }
239
+
240
+ export function updateDraft(draftId: string, actor: Actor, input: UpdateDraftInput): DraftDetail {
241
+ requireEditor(actor, '修改本体草稿')
242
+ const draft = getDraft(draftId)
243
+ requireDraftAccess(draft, actor, { write: true })
244
+ let doc: OntologyDraftDoc
245
+ try {
246
+ doc = parseDraftDoc(input.doc)
247
+ }
248
+ catch (err) {
249
+ throw new AppError(422, RULE_ERROR_CODES.INVALID, `本体文档形状非法:${(err as Error).message}`)
250
+ }
251
+ const validation = validateDraftDoc(doc)
252
+ if (!validation.ok) {
253
+ const first = validation.issues.filter(i => i.severity === 'error').slice(0, 5)
254
+ .map(i => `[${i.code}] ${i.path}: ${i.message}`).join('; ')
255
+ throw new AppError(422, RULE_ERROR_CODES.INVALID, `本体文档校验未通过:${first}`)
256
+ }
257
+ const bindings = validateDraftBindings(doc, { lineId: doc.lineId })
258
+ if ((input.strictBindings ?? true) && !bindings.ok) {
259
+ const first = bindings.failures.slice(0, 5).map(f => `[${f.code}] ${f.message}`).join('; ')
260
+ throw new AppError(422, 'ONTOLOGY_BINDING_INVALID', `语义绑定校验未通过:${first}`)
261
+ }
262
+ const updated = getOntologyRepo().updateDraft(draftId, JSON.stringify(doc), input.expectedRev)
263
+ log({ ontologyId: updated.ontologyId, version: updated.baseVersion ?? 0, action: 'draft.update', actor, detail: { draftId, rev: updated.rev } })
264
+ return { draft: updated, doc, validation, bindings }
265
+ }
266
+
267
+ export function deleteDraft(draftId: string, actor: Actor): void {
268
+ requireEditor(actor, '删除本体草稿')
269
+ const draft = getDraft(draftId)
270
+ requireDraftAccess(draft, actor, { write: true })
271
+ getOntologyRepo().deleteDraft(draftId)
272
+ log({ ontologyId: draft.ontologyId, action: 'draft.delete', actor, detail: { draftId } })
273
+ }
274
+
275
+ // ===== 校验 / 编译预览 =====
276
+
277
+ export function validateDraft(draftId: string, actor: Actor): DraftValidationReport & { bindings: ReturnType<typeof validateDraftBindings> } {
278
+ const detail = getDraftDetail(draftId, actor)
279
+ return { ...detail.validation, bindings: detail.bindings }
280
+ }
281
+
282
+ export interface CompilePreview {
283
+ result: OntologyCompileResult
284
+ guards: ControlGuard[]
285
+ contentHash: string
286
+ packHash: string
287
+ }
288
+
289
+ export function compileDraft(draftId: string, actor: Actor, options: Partial<CompileOptions> = {}): CompilePreview {
290
+ const draft = getDraft(draftId)
291
+ requireDraftAccess(draft, actor)
292
+ const doc = parseDraftRow(draft)
293
+ const version = draft.baseVersion ? (draft.baseVersion + 1) : 1
294
+ const result = compileOntology(doc, {
295
+ ontologyId: draft.ontologyId,
296
+ version,
297
+ scope: { lineId: doc.lineId },
298
+ approvedOnly: options.approvedOnly ?? false,
299
+ currentBounds: options.currentBounds,
300
+ createdAt: options.createdAt,
301
+ })
302
+ const contentHash = draftContentHash(doc)
303
+ return { result, guards: result.artifacts.controlGuard.guards, contentHash, packHash: ontologyPackHash({ rulePackHash: result.rulePackHash, contentHash }) }
304
+ }
305
+
306
+ // ===== 证据 =====
307
+
308
+ export interface AppendEvidenceInput {
309
+ targetKind: 'entity' | 'relation' | 'rule' | 'binding'
310
+ targetKey: string
311
+ level: EvidenceRecord['level']
312
+ source: EvidenceRecord['source']
313
+ sourceRef?: string
314
+ note?: string
315
+ }
316
+
317
+ export function appendEvidence(draftId: string, actor: Actor, input: AppendEvidenceInput): DraftDetail {
318
+ requireEditor(actor, '登记本体证据')
319
+ const draft = getDraft(draftId)
320
+ requireDraftAccess(draft, actor, { write: true })
321
+ const doc = parseDraftRow(draft)
322
+ const record: EvidenceRecord = {
323
+ id: newId('ev'),
324
+ level: input.level,
325
+ source: input.source,
326
+ sourceRefHash: input.sourceRef ? ontologyPackHash({ rulePackHash: input.sourceRef, contentHash: input.level }) : '',
327
+ createdAt: new Date().toISOString(),
328
+ note: input.note,
329
+ }
330
+ if (input.targetKind === 'rule') {
331
+ const rule = doc.rules.find(r => r.id === input.targetKey || r.key === input.targetKey)
332
+ if (!rule) throw new AppError(404, 'RULE_NOT_FOUND', `草稿 ${draftId} 中没有规则「${input.targetKey}」`)
333
+ rule.evidence = [...rule.evidence, record]
334
+ }
335
+ else if (input.targetKind === 'relation') {
336
+ const relation = doc.relations.find(r => r.id === input.targetKey)
337
+ if (!relation) throw new AppError(404, 'RELATION_NOT_FOUND', `草稿 ${draftId} 中没有关系「${input.targetKey}」`)
338
+ relation.evidence = [...relation.evidence, record]
339
+ }
340
+ else if (input.targetKind === 'entity') {
341
+ const entity = doc.entities.find(e => e.key === input.targetKey)
342
+ if (!entity) throw new AppError(404, 'ENTITY_NOT_FOUND', `草稿 ${draftId} 中没有实体「${input.targetKey}」`)
343
+ }
344
+ const ledgerRow: EvidenceRow = {
345
+ id: record.id,
346
+ ontologyId: draft.ontologyId,
347
+ targetKind: input.targetKind,
348
+ targetKey: input.targetKey,
349
+ level: record.level,
350
+ sourceType: record.source.type,
351
+ sourceRef: input.sourceRef ?? '',
352
+ sourceRefHash: record.sourceRefHash,
353
+ detailJson: JSON.stringify({ source: record.source, note: input.note ?? '' }),
354
+ createdBy: actor.userId,
355
+ createdAt: record.createdAt,
356
+ }
357
+ // 追加行与草稿更新保持同一 rev 语义:先落账本,再以乐观锁写回草稿
358
+ getOntologyRepo().appendEvidence(ledgerRow)
359
+ const updated = getOntologyRepo().updateDraft(draftId, JSON.stringify(doc), draft.rev)
360
+ log({ ontologyId: draft.ontologyId, action: 'evidence.append', actor, detail: { draftId, targetKind: input.targetKind, targetKey: input.targetKey, level: input.level } })
361
+ return { draft: updated, doc, validation: validateDraftDoc(doc), bindings: validateDraftBindings(doc, { lineId: doc.lineId }) }
362
+ }
363
+
364
+ export function listEvidence(ontologyId: string, filter: { targetKind?: string, targetKey?: string } = {}): EvidenceRow[] {
365
+ return getOntologyRepo().listEvidence(ontologyId, filter)
366
+ }
367
+
368
+ // ===== 规则生命周期 =====
369
+
370
+ export interface TransitionInput {
371
+ to: OntologyState
372
+ /** approved_for_control 必须携带已批准的审批单 id */
373
+ approvalId?: string
374
+ comment?: string
375
+ }
376
+
377
+ export function transitionRule(draftId: string, ruleId: string, actor: Actor, input: TransitionInput): DraftDetail {
378
+ requireEditor(actor, '规则状态迁移')
379
+ // 审查/批准是**治理动作**,允许第二人对他人草稿执行(自审由 ownerUserId 比对单独拦截);
380
+ // 编辑类动作(updateDraft/appendEvidence)才是属主私有。
381
+ const draft = getDraft(draftId)
382
+ const doc = parseDraftRow(draft)
383
+ const rule = doc.rules.find(r => r.id === ruleId || r.key === ruleId)
384
+ if (!rule) throw new AppError(404, 'RULE_NOT_FOUND', `草稿 ${draftId} 中没有规则「${ruleId}」`)
385
+
386
+ const allowed = RULE_TRANSITIONS[rule.status] ?? []
387
+ if (!allowed.includes(input.to)) {
388
+ throw new AppError(409, 'RULE_LIFECYCLE_CONFLICT',
389
+ `规则 ${rule.id} 当前状态 ${rule.status},不能迁移到 ${input.to};合法目标:${allowed.join(', ') || '(终态)'}`)
390
+ }
391
+ const evidenceMax = rule.evidence.reduce((rank, e) => {
392
+ const order = ['candidate_influence', 'observed_correlation', 'validated_control']
393
+ return Math.max(rank, order.indexOf(e.level))
394
+ }, -1)
395
+
396
+ if (input.to === 'reviewed') {
397
+ // 自审禁止(单管理员实例豁免并打标)
398
+ const solo = isSoloOperator()
399
+ if (draft.ownerUserId === actor.userId && !solo) {
400
+ throw new AppError(403, 'SELF_REVIEW_FORBIDDEN',
401
+ `规则 ${rule.id} 的起草人不能自审(本体草稿属主=${draft.ownerUserId});请由另一名编辑/管理员审查`)
402
+ }
403
+ if (!solo) requireEditor(actor, '规则审查')
404
+ doc.rules = doc.rules.map(r => r.id === rule.id ? { ...r, status: 'reviewed' as OntologyState } : r)
405
+ log({ ontologyId: draft.ontologyId, action: 'rule.transition', actor, soloOperator: solo, detail: { draftId, ruleId, from: rule.status, to: 'reviewed', comment: input.comment ?? '' } })
406
+ }
407
+ else if (input.to === 'validated') {
408
+ if (evidenceMax < 1) {
409
+ throw new AppError(422, RULE_ERROR_CODES.EVIDENCE_INSUFFICIENT,
410
+ `规则 ${rule.id} 缺少可解析的证据(至少需要 observed_correlation:数据统计/工件系数);当前最高级别 ${rule.evidence[evidenceMax]?.level ?? '无'}`)
411
+ }
412
+ doc.rules = doc.rules.map(r => r.id === rule.id ? { ...r, status: 'validated' as OntologyState } : r)
413
+ log({ ontologyId: draft.ontologyId, action: 'rule.transition', actor, detail: { draftId, ruleId, from: rule.status, to: 'validated', comment: input.comment ?? '' } })
414
+ }
415
+ else if (input.to === 'approved_for_control') {
416
+ if (evidenceMax < 2) {
417
+ throw new AppError(422, RULE_ERROR_CODES.EVIDENCE_INSUFFICIENT,
418
+ `规则 ${rule.id} 缺少 validated_control 级证据(run+crosscheck+outcome 全链);当前最高级别 ${rule.evidence[evidenceMax]?.level ?? '无'}`)
419
+ }
420
+ if (!input.approvalId) {
421
+ throw new AppError(403, 'APPROVAL_REQUIRED',
422
+ `规则 ${rule.id} 进入 approved_for_control 必须有第二人批准单;请先 requestRuleApproval 并取得已批准的 approvalId`)
423
+ }
424
+ const decision = latestApprovalDecision(draft.ontologyId, input.approvalId)
425
+ if (!decision) throw new AppError(404, 'APPROVAL_NOT_FOUND', `审批单 ${input.approvalId} 不存在`)
426
+ if (decision.decision !== 'approved') {
427
+ throw new AppError(409, 'APPROVAL_NOT_GRANTED', `审批单 ${input.approvalId} 当前结论为 ${decision.decision};不能据以批准规则`)
428
+ }
429
+ if (decision.approver === draft.ownerUserId && !isSoloOperator()) {
430
+ throw new AppError(403, 'SELF_REVIEW_FORBIDDEN', `审批人 ${decision.approver} 是草稿属主,不能自我批准控制级规则`)
431
+ }
432
+ if (decision.ruleId !== rule.id) {
433
+ throw new AppError(409, 'APPROVAL_SCOPE_MISMATCH', `审批单 ${input.approvalId} 针对规则 ${decision.ruleId},与本次规则 ${rule.id} 不一致`)
434
+ }
435
+ doc.rules = doc.rules.map(r => r.id === rule.id ? { ...r, status: 'approved_for_control' as OntologyState } : r)
436
+ log({
437
+ ontologyId: draft.ontologyId, action: 'rule.approve', actor, approvalId: input.approvalId,
438
+ detail: { draftId, ruleId, from: rule.status, to: 'approved_for_control', comment: input.comment ?? '' },
439
+ })
440
+ }
441
+ else {
442
+ doc.rules = doc.rules.map(r => r.id === rule.id ? { ...r, status: input.to } : r)
443
+ log({ ontologyId: draft.ontologyId, action: 'rule.transition', actor, detail: { draftId, ruleId, from: rule.status, to: input.to, comment: input.comment ?? '' } })
444
+ }
445
+
446
+ const updated = getOntologyRepo().updateDraft(draftId, JSON.stringify(doc), draft.rev)
447
+ return { draft: updated, doc, validation: validateDraftDoc(doc), bindings: validateDraftBindings(doc, { lineId: doc.lineId }) }
448
+ }
449
+
450
+ // ===== 审批单(第二人批准) =====
451
+
452
+ export interface ApprovalRequestRow { approvalId: string, ruleId: string, requester: string, createdAt: string, detail?: Record<string, unknown> }
453
+
454
+ export function requestRuleApproval(draftId: string, ruleId: string, actor: Actor): { approvalId: string, ruleId: string } {
455
+ requireEditor(actor, '申请规则批准')
456
+ const draft = getDraft(draftId)
457
+ requireDraftAccess(draft, actor, { write: true })
458
+ const doc = parseDraftRow(draft)
459
+ const rule = doc.rules.find(r => r.id === ruleId || r.key === ruleId)
460
+ if (!rule) throw new AppError(404, 'RULE_NOT_FOUND', `草稿 ${draftId} 中没有规则「${ruleId}」`)
461
+ if (rule.status !== 'validated') {
462
+ throw new AppError(409, 'RULE_LIFECYCLE_CONFLICT', `只有 validated 状态的规则可申请控制级批准;当前 ${rule.status}`)
463
+ }
464
+ const approvalId = newId('oap')
465
+ log({
466
+ ontologyId: draft.ontologyId, action: 'rule.approve_request', actor, approvalId,
467
+ detail: { draftId, ruleId: rule.id, ruleKey: rule.key, requester: actor.userId },
468
+ })
469
+ return { approvalId, ruleId: rule.id }
470
+ }
471
+
472
+ export function latestApprovalDecision(ontologyId: string, approvalId: string): { decision: string, approver: string, ruleId: string } | null {
473
+ const rows = getOntologyRepo().listPublishLog(ontologyId, 500).filter(r => r.approvalId === approvalId)
474
+ const request = rows.find(r => r.action === 'rule.approve_request')
475
+ if (!request) return null
476
+ const ruleId = (JSON.parse(request.detailJson || '{}') as { ruleId?: string }).ruleId ?? ''
477
+ const decision = rows.filter(r => r.action === 'rule.approve' || r.action === 'rule.reject')
478
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0]
479
+ if (!decision) return { decision: 'pending', approver: '', ruleId }
480
+ return { decision: decision.action === 'rule.approve' ? 'approved' : 'rejected', approver: decision.actor, ruleId }
481
+ }
482
+
483
+ export function decideRuleApproval(draftId: string, approvalId: string, approver: Actor, decision: { approved: boolean, comment?: string }): { approvalId: string, decision: string } {
484
+ requireEditor(approver, '裁决规则批准')
485
+ const draft = getDraft(draftId)
486
+ const current = latestApprovalDecision(draft.ontologyId, approvalId)
487
+ if (!current) throw new AppError(404, 'APPROVAL_NOT_FOUND', `审批单 ${approvalId} 不存在`)
488
+ if (current.decision !== 'pending') {
489
+ throw new AppError(409, 'APPROVAL_ALREADY_DECIDED', `审批单 ${approvalId} 已裁决(${current.decision})`)
490
+ }
491
+ const solo = isSoloOperator()
492
+ if (draft.ownerUserId === approver.userId && !solo) {
493
+ throw new AppError(403, 'SELF_REVIEW_FORBIDDEN', '起草人不能裁决自己的批准申请(需要第二人)')
494
+ }
495
+ const action = decision.approved ? 'rule.approve' : 'rule.reject'
496
+ log({
497
+ ontologyId: draft.ontologyId, action, actor: approver, approvalId, soloOperator: solo,
498
+ detail: { draftId, ruleId: current.ruleId, comment: decision.comment ?? '' },
499
+ })
500
+ return { approvalId, decision: decision.approved ? 'approved' : 'rejected' }
501
+ }
502
+
503
+ // ===== 发布 =====
504
+
505
+ export interface PublishResult {
506
+ version: VersionRow
507
+ compile: OntologyCompileResult
508
+ packHash: string
509
+ draft: DraftRow
510
+ }
511
+
512
+ export function publishDraft(
513
+ draftId: string,
514
+ actor: Actor,
515
+ options: { currentBounds?: CompileOptions['currentBounds'] } = {},
516
+ ): PublishResult {
517
+ requireAdmin(actor, '发布本体版本')
518
+ const draft = getDraft(draftId)
519
+ requireDraftAccess(draft, actor, { write: true })
520
+ const doc = parseDraftRow(draft)
521
+
522
+ const validation = validateDraftDoc(doc)
523
+ if (!validation.ok) {
524
+ throw new AppError(422, 'VALIDATION_BLOCKED',
525
+ `本体文档校验未通过,不能发布:${validation.issues.filter(i => i.severity === 'error').slice(0, 5).map(i => `[${i.code}] ${i.message}`).join('; ')}`)
526
+ }
527
+ const bindings = validateDraftBindings(doc, { lineId: doc.lineId })
528
+ if (!bindings.ok) {
529
+ throw new AppError(422, 'ONTOLOGY_BINDING_INVALID',
530
+ `语义绑定校验未通过,不能发布:${bindings.failures.slice(0, 5).map(f => `[${f.code}] ${f.message}`).join('; ')}`)
531
+ }
532
+ if (!doc.lineId && actor.role !== 'admin') {
533
+ throw new AppError(403, 'LINE_REQUIRED', '全局本体(lineId 为空)仅管理员可发布')
534
+ }
535
+
536
+ const repo = getOntologyRepo()
537
+ const versionNumber = repo.nextVersionNumber(draft.ontologyId)
538
+ const compile = compileOntology(doc, {
539
+ ontologyId: draft.ontologyId,
540
+ version: versionNumber,
541
+ scope: { lineId: doc.lineId },
542
+ currentBounds: options.currentBounds,
543
+ approvedOnly: true,
544
+ })
545
+ // 未获批规则绝不可进入发布版控制约束(即使整体 status 只是 warning/rejected)
546
+ const notApproved = compile.issues.filter(i => i.code === RULE_ERROR_CODES.NOT_APPROVED)
547
+ if (notApproved.length > 0) {
548
+ throw new AppError(422, RULE_ERROR_CODES.NOT_APPROVED,
549
+ `存在未获批规则,不能进入发布版控制约束:${notApproved.slice(0, 5).map(i => i.message).join('; ')}`)
550
+ }
551
+ if (compile.status === 'rejected') {
552
+ throw new AppError(422, 'VALIDATION_BLOCKED',
553
+ `编译结果 rejected,不能发布:${compile.issues.slice(0, 5).map(i => `[${i.code}] ${i.message}`).join('; ')}`)
554
+ }
555
+
556
+ const contentHash = draftContentHash(doc)
557
+ const packHash = ontologyPackHash({ rulePackHash: compile.rulePackHash, contentHash })
558
+ const parent = repo.latestVersion(draft.ontologyId)
559
+ const compiledJson = JSON.stringify({
560
+ schemaVersion: ONTOLOGY_SCHEMA_VERSION,
561
+ ontologyId: draft.ontologyId,
562
+ ontologyVersion: versionNumber,
563
+ contentHash,
564
+ rulePackHash: compile.rulePackHash,
565
+ packHash,
566
+ status: compile.status,
567
+ compiledAt: new Date().toISOString(),
568
+ artifacts: compile.artifacts,
569
+ issues: compile.issues,
570
+ counts: {
571
+ compiled: compile.compiledCount, guard: compile.guardCount,
572
+ explained: compile.explainedCount, rejected: compile.rejectedCount,
573
+ },
574
+ })
575
+ const changelog = JSON.stringify(buildChangelog(parent, doc, compile))
576
+
577
+ let version: VersionRow
578
+ try {
579
+ version = repo.publishSnapshot({
580
+ version: {
581
+ id: draft.ontologyId, version: versionNumber, name: doc.name, lineId: doc.lineId,
582
+ parentVersion: parent?.version ?? null, contentHash, compiledJson, changelogJson: changelog,
583
+ status: 'published', ownerUserId: draft.ownerUserId, publishedBy: actor.userId,
584
+ createdAt: '', updatedAt: '',
585
+ },
586
+ entities: doc.entities.map(e => ({ ...e, dimension: e.dimension || dimensionOf(e.unit) })),
587
+ relations: doc.relations,
588
+ rules: doc.rules,
589
+ bindings: doc.bindings,
590
+ traces: compile.traces,
591
+ guards: compile.artifacts.controlGuard.guards,
592
+ createdBy: actor.userId,
593
+ })
594
+ }
595
+ catch (err) {
596
+ throw new AppError(409, 'ONTOLOGY_VERSION_CONFLICT',
597
+ `本体 ${draft.ontologyId} 版本 ${versionNumber} 并发发布冲突,请重新读取后重试(${(err as Error).message})`)
598
+ }
599
+
600
+ // 草稿保留为新版本的工作副本(baseVersion 前移,rev++),便于继续演进
601
+ repo.updateDraftMeta(draftId, { baseVersion: versionNumber })
602
+ const refreshed = repo.updateDraft(draftId, JSON.stringify(doc), draft.rev)
603
+
604
+ log({
605
+ ontologyId: draft.ontologyId, version: versionNumber, action: 'publish', actor,
606
+ detail: {
607
+ draftId, contentHash, packHash, compiled: compile.compiledCount, guards: compile.guardCount,
608
+ rejected: compile.rejectedCount, explained: compile.explainedCount,
609
+ },
610
+ })
611
+ return { version, compile, packHash, draft: refreshed }
612
+ }
613
+
614
+ function buildChangelog(parent: VersionRow | undefined, doc: OntologyDraftDoc, compile: OntologyCompileResult) {
615
+ return {
616
+ generatedAt: new Date().toISOString(),
617
+ parentVersion: parent?.version ?? null,
618
+ counts: {
619
+ entities: doc.entities.length, relations: doc.relations.length,
620
+ rules: doc.rules.length, bindings: doc.bindings.length,
621
+ },
622
+ ruleDecisions: compile.traces.map(t => ({ ruleId: t.ruleId, decision: t.decision, code: t.decisionCode })),
623
+ artifacts: {
624
+ mvBounds: Object.keys(compile.artifacts.problemIr.mvBounds),
625
+ cvBounds: Object.keys(compile.artifacts.problemIr.cvBounds),
626
+ guards: compile.artifacts.controlGuard.guards.map(g => g.id),
627
+ featureCandidates: compile.artifacts.processModel.featureCandidates.length,
628
+ delayPriors: compile.artifacts.processModel.delayPriors.length,
629
+ },
630
+ }
631
+ }
632
+
633
+ // ===== 版本读取 / 比较 / 退役 =====
634
+
635
+ export function listVersions(ontologyId: string): VersionRow[] {
636
+ return getOntologyRepo().listVersions(ontologyId)
637
+ }
638
+
639
+ export function getVersion(ontologyId: string, version: number): VersionRow {
640
+ const row = getOntologyRepo().getVersion(ontologyId, version)
641
+ if (!row) throw new AppError(404, 'VERSION_NOT_FOUND', `本体 ${ontologyId}@${version} 不存在`)
642
+ return row
643
+ }
644
+
645
+ export interface VersionDiff {
646
+ from: number
647
+ to: number
648
+ entities: { added: string[], removed: string[], changed: string[] }
649
+ relations: { added: string[], removed: string[], changed: string[] }
650
+ rules: { added: string[], removed: string[], changed: string[] }
651
+ bindings: { added: string[], removed: string[], changed: string[] }
652
+ }
653
+
654
+ function diffSections<T>(a: T[], b: T[], keyOf: (item: T) => string): { added: string[], removed: string[], changed: string[] } {
655
+ const mapA = new Map(a.map(item => [keyOf(item), item]))
656
+ const mapB = new Map(b.map(item => [keyOf(item), item]))
657
+ const added: string[] = []
658
+ const removed: string[] = []
659
+ const changed: string[] = []
660
+ for (const [key, item] of mapB) {
661
+ if (!mapA.has(key)) added.push(key)
662
+ else if (JSON.stringify(mapA.get(key)) !== JSON.stringify(item)) changed.push(key)
663
+ }
664
+ for (const key of mapA.keys()) if (!mapB.has(key)) removed.push(key)
665
+ return { added: added.sort(), removed: removed.sort(), changed: changed.sort() }
666
+ }
667
+
668
+ export function compareVersions(ontologyId: string, from: number, to: number): VersionDiff {
669
+ const repo = getOntologyRepo()
670
+ const a = getVersion(ontologyId, from)
671
+ const b = getVersion(ontologyId, to)
672
+ return {
673
+ from, to,
674
+ entities: diffSections(repo.listEntities(a.id, a.version), repo.listEntities(b.id, b.version), e => e.key),
675
+ relations: diffSections(repo.listRelations(a.id, a.version), repo.listRelations(b.id, b.version), r => r.id),
676
+ rules: diffSections(repo.listRules(a.id, a.version), repo.listRules(b.id, b.version), r => r.id),
677
+ bindings: diffSections(repo.listBindings(a.id, a.version), repo.listBindings(b.id, b.version), r => r.id),
678
+ }
679
+ }
680
+
681
+ export function retireVersion(ontologyId: string, version: number, actor: Actor): void {
682
+ requireAdmin(actor, '退役本体版本')
683
+ const row = getVersion(ontologyId, version)
684
+ if (row.status !== 'published') {
685
+ throw new AppError(409, 'VERSION_NOT_PUBLISHED', `本体 ${ontologyId}@${version} 状态为 ${row.status},只有 published 可退役`)
686
+ }
687
+ const ok = getOntologyRepo().transitionVersion(ontologyId, version, 'retired', 'published')
688
+ if (!ok) throw new AppError(409, 'VERSION_TRANSITION_CONFLICT', `本体 ${ontologyId}@${version} 并发状态变更,请重试`)
689
+ log({ ontologyId, version, action: 'retire', actor })
690
+ }
691
+
692
+ export function listOntologies(lineId?: string) {
693
+ return getOntologyRepo().listAllOntologyIds(lineId)
694
+ }
695
+
696
+ // ===== 运行时消费面 =====
697
+
698
+ export interface ActiveOntology {
699
+ version: VersionRow
700
+ guards: ControlGuard[]
701
+ artifacts: OntologyCompileResult['artifacts']
702
+ packHash: string
703
+ }
704
+
705
+ /** 取某产线当前生效的已发布本体(该线最新的 published 版本;无则 null) */
706
+ export function resolveActiveOntology(lineId: string): ActiveOntology | null {
707
+ const repo = getOntologyRepo()
708
+ const version = repo.latestPublishedForLine(lineId)
709
+ if (!version) return null
710
+ const compiled = JSON.parse(version.compiledJson || '{}') as {
711
+ artifacts?: OntologyCompileResult['artifacts']
712
+ packHash?: string
713
+ controlGuard?: { guards?: ControlGuard[] }
714
+ }
715
+ const guards = extractGuardSet(compiled)
716
+ const artifacts = compiled.artifacts ?? { processModel: { featureCandidates: [], delayPriors: [], monotonicity: [] }, problemIr: { templateRef: 'linear_mpc_quadratic@1', mvBounds: {}, cvBounds: {}, softRegularization: [] }, sceneContract: { variables: [], constraints: [], inferredRelations: [] }, controlGuard: { guards } }
717
+ return { version, guards, artifacts, packHash: compiled.packHash ?? version.contentHash }
718
+ }
719
+
720
+ /** 运行时守卫求值(供求解/下发前置检查调用) */
721
+ export function evaluateOntologyGuards(lineId: string, input: Omit<GuardEvaluationInput, 'scope'> & { recipeId?: string, conditionId?: string }): GuardEvaluationResult & { packHash: string } {
722
+ const active = resolveActiveOntology(lineId)
723
+ if (!active) return { ok: true, blocked: false, violations: [], evaluated: 0, skipped: 0, packHash: '' }
724
+ const result = evaluateGuards(active.guards, { ...input, scope: { lineId, recipeId: input.recipeId, conditionId: input.conditionId } })
725
+ return { ...result, packHash: active.packHash }
726
+ }
727
+
728
+ /**
729
+ * Profile 漂移校验:Profile 冻结时记录的 packHash 必须与当前已发布版本一致。
730
+ * 不一致 → 409 ONTOLOGY_PACK_DRIFTED(只拒不放行;与 D12 工件 hash 独立比对同构)。
731
+ */
732
+ export function assertOntologyUnchanged(refs: Array<{ ontologyId: string, ontologyVersion: number, rulePackHash?: string, contentHash?: string }> | undefined): void {
733
+ if (!refs || refs.length === 0) return
734
+ const repo = getOntologyRepo()
735
+ for (const ref of refs) {
736
+ const row = repo.getVersion(ref.ontologyId, ref.ontologyVersion)
737
+ if (!row) {
738
+ throw new AppError(409, 'ONTOLOGY_PACK_DRIFTED', `Profile 引用的本体 ${ref.ontologyId}@${ref.ontologyVersion} 已不存在`)
739
+ }
740
+ if (row.status !== 'published') {
741
+ throw new AppError(409, 'ONTOLOGY_PACK_DRIFTED', `Profile 引用的本体 ${ref.ontologyId}@${ref.ontologyVersion} 状态为 ${row.status}(需 published)`)
742
+ }
743
+ if (ref.contentHash && row.contentHash !== ref.contentHash) {
744
+ throw new AppError(409, 'ONTOLOGY_PACK_DRIFTED',
745
+ `Profile 引用的本体 ${ref.ontologyId}@${ref.ontologyVersion} 内容已变(冻结 ${ref.contentHash.slice(0, 12)} ≠ 当前 ${row.contentHash.slice(0, 12)})`)
746
+ }
747
+ }
748
+ }
749
+
750
+ /** 绑定漂移巡检(发布前与周期巡检共用) */
751
+ export function verifyVersionBindings(ontologyId: string, version: number): { ok: boolean, drifted: Array<{ bindingId: string, reason: string }> } {
752
+ const repo = getOntologyRepo()
753
+ const row = getVersion(ontologyId, version)
754
+ const report = detectBindingDrift(repo.listBindings(row.id, row.version))
755
+ for (const drift of report.drifted) repo.markBindingStatus(drift.bindingId, 'drifted')
756
+ return report
757
+ }
758
+
759
+ /**
760
+ * 下发意图的守卫检查(运行时真正生效的落点之一)。
761
+ *
762
+ * 输入是**平台节点级**的动作(from→to 工程值),经语义绑定映射到本体实体 key,
763
+ * 再用本体实体 key 观测当前状态并求值。返回 blocked 时调用方必须拒绝下发。
764
+ *
765
+ * 映射规则:
766
+ * - 守卫的 when.variableKey 绑定到 dcw_node → 取该节点 readValue/value 作观测;
767
+ * 绑定到 daq_node → 无同步读值(交 fail-closed:未知状态 + 提速 = 阻断);
768
+ * - 守卫的 forbid.variableKey 绑定到 dcw_node → 由 from/to 计算增量。
769
+ */
770
+ export function evaluateOntologyGuardsForNodes(
771
+ lineId: string,
772
+ moves: Array<{ nodeId: string, from: number | null, to: number }>,
773
+ opts: { recipeId?: string, conditionId?: string } = {},
774
+ ): GuardEvaluationResult & { packHash: string, unmapped: string[] } {
775
+ const active = resolveActiveOntology(lineId)
776
+ if (!active || active.guards.length === 0) {
777
+ return { ok: true, blocked: false, violations: [], evaluated: 0, skipped: 0, packHash: '', unmapped: [] }
778
+ }
779
+ const repo = getOntologyRepo()
780
+ const bindings = repo.listBindings(active.version.id, active.version.version)
781
+ const byEntity = new Map<string, typeof bindings>()
782
+ for (const b of bindings) {
783
+ const list = byEntity.get(b.entityKey) ?? []
784
+ list.push(b)
785
+ byEntity.set(b.entityKey, list)
786
+ }
787
+ const nodeOf = (entityKey: string): string | null => {
788
+ const list = byEntity.get(entityKey) ?? []
789
+ // 优先写通道(dcw),其次任意通道
790
+ const dcw = list.find(b => b.platformKind === 'dcw_node')
791
+ const any = dcw ?? list[0]
792
+ return any ? any.platformId : null
793
+ }
794
+ const moveOf = (nodeId: string | null) => (nodeId ? moves.find(m => m.nodeId === nodeId) : undefined)
795
+
796
+ const values: GuardEvaluationInput['values'] = {}
797
+ const deltas: Record<string, number> = {}
798
+ const unmapped: string[] = []
799
+ for (const guard of active.guards) {
800
+ const whenNode = nodeOf(guard.when.variableKey)
801
+ if (!whenNode) {
802
+ unmapped.push(guard.when.variableKey)
803
+ continue
804
+ }
805
+ const node = getDcwNodeReadValue(whenNode)
806
+ if (node !== undefined) values[guard.when.variableKey] = node
807
+ const forbidNode = nodeOf(guard.forbid.variableKey)
808
+ const move = moveOf(forbidNode)
809
+ if (move) {
810
+ const from = typeof move.from === 'number' && Number.isFinite(move.from) ? move.from : move.to
811
+ deltas[guard.forbid.variableKey] = move.to - from
812
+ }
813
+ }
814
+ const result = evaluateGuards(active.guards, { values, deltas, scope: { lineId, recipeId: opts.recipeId, conditionId: opts.conditionId } })
815
+ return { ...result, packHash: active.packHash, unmapped }
816
+ }
817
+
818
+ /** 读 dcw 节点当前值(工程值);节点不存在返回 undefined */
819
+ function getDcwNodeReadValue(nodeId: string): number | undefined {
820
+ try {
821
+ const node = getDcwController().byId(nodeId)
822
+ if (!node) return undefined
823
+ const value = typeof node.readValue === 'number' ? node.readValue : (typeof node.value === 'number' ? node.value : undefined)
824
+ return value
825
+ }
826
+ catch { return undefined }
827
+ }
828
+
829
+ /** 供 Agent 上下文/工具用的场景子图(只含绑定面实体 + 命中规则摘要) */
830
+ export interface SceneSubgraph {
831
+ ontologyId: string
832
+ version: number
833
+ lineId: string
834
+ entities: Array<{ key: string, kind: string, name: string, unit: string, role: string | undefined, bindings: Array<{ platformKind: string, platformId: string, targetSub: string, direction: string }> }>
835
+ relations: Array<{ id: string, kind: string, srcKey: string, dstKey: string, evidenceTier: string }>
836
+ rules: Array<{ id: string, key: string, kind: string, status: string, decision: string, message: string }>
837
+ guards: Array<{ id: string, ruleId: string, message: string, onViolate: string }>
838
+ }
839
+
840
+ export function sceneSubgraph(lineId: string, opts: { limit?: number } = {}): SceneSubgraph | null {
841
+ const active = resolveActiveOntology(lineId)
842
+ if (!active) return null
843
+ const repo = getOntologyRepo()
844
+ const entities = repo.listEntities(active.version.id, active.version.version)
845
+ const relations = repo.listRelations(active.version.id, active.version.version)
846
+ const rules = repo.listRules(active.version.id, active.version.version)
847
+ const bindings = repo.listBindings(active.version.id, active.version.version)
848
+ const traces = repo.listTraces(active.version.id, active.version.version)
849
+ const traceOf = new Map(traces.map(t => [t.ruleId, t]))
850
+ const bindingsOf = new Map<string, SceneSubgraph['entities'][number]['bindings']>()
851
+ for (const b of bindings) {
852
+ const list = bindingsOf.get(b.entityKey) ?? []
853
+ list.push({ platformKind: b.platformKind, platformId: b.platformId, targetSub: b.targetSub, direction: b.direction })
854
+ bindingsOf.set(b.entityKey, list)
855
+ }
856
+ const limit = opts.limit ?? 200
857
+ return {
858
+ ontologyId: active.version.id,
859
+ version: active.version.version,
860
+ lineId,
861
+ entities: entities.slice(0, limit).map(e => ({
862
+ key: e.key, kind: e.kind, name: e.name, unit: e.unit, role: e.role,
863
+ bindings: bindingsOf.get(e.key) ?? [],
864
+ })),
865
+ relations: relations.map(r => ({ id: r.id, kind: r.kind, srcKey: r.srcKey, dstKey: r.dstKey, evidenceTier: r.properties.evidenceTier })),
866
+ rules: rules.map((r) => {
867
+ const trace = traceOf.get(r.id)
868
+ return {
869
+ id: r.id, key: r.key, kind: r.kind, status: r.status,
870
+ decision: trace?.decision ?? 'unknown', message: trace?.message ?? '',
871
+ }
872
+ }),
873
+ guards: active.guards.map(g => ({ id: g.id, ruleId: g.ruleId, message: g.message, onViolate: g.onViolate })),
874
+ }
875
+ }
876
+
877
+ /** AML 派生:特征候选 + 时滞先验(供训练编排消费) */
878
+ export function deriveTrainingPriors(lineId: string): {
879
+ featureCandidates: Array<{ entityKey: string, role: string, relation: string, evidenceLevel: string, priorDelayMs?: number, variabilityChecked: boolean }>
880
+ delayPriors: Array<{ entityKey: string, delayMs: number, basis: string, variabilityChecked: boolean }>
881
+ monotonicity: Array<{ output: string, input: string, direction: string }>
882
+ } | null {
883
+ const active = resolveActiveOntology(lineId)
884
+ if (!active) return null
885
+ return {
886
+ featureCandidates: active.artifacts.processModel.featureCandidates,
887
+ delayPriors: active.artifacts.processModel.delayPriors,
888
+ monotonicity: active.artifacts.processModel.monotonicity,
889
+ }
890
+ }
891
+
892
+ export type { OntologyRole, OntologyTarget }