@aryacode/dsh-engineering 0.0.0-stage → 0.4.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 (284) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/LICENSE +21 -0
  3. package/README.md +230 -2
  4. package/cordis.patch.yml +123 -0
  5. package/lib/client.js +3448 -0
  6. package/lib/index.d.ts +2 -0
  7. package/lib/index.js +9 -0
  8. package/locale/en.json +6 -0
  9. package/locale/zh.json +6 -0
  10. package/package.json +94 -3
  11. package/vendor/ftview/README.md +55 -0
  12. package/vendor/ftview/SKILL.md +74 -0
  13. package/vendor/ftview/lib/checks.d.ts +27 -0
  14. package/vendor/ftview/lib/checks.js +39 -0
  15. package/vendor/ftview/lib/core.d.ts +208 -0
  16. package/vendor/ftview/lib/core.js +160 -0
  17. package/vendor/ftview/lib/edit/drafts.d.ts +49 -0
  18. package/vendor/ftview/lib/edit/drafts.js +84 -0
  19. package/vendor/ftview/lib/edit/operations.d.ts +62 -0
  20. package/vendor/ftview/lib/edit/operations.js +126 -0
  21. package/vendor/ftview/lib/edit/prepare.d.ts +38 -0
  22. package/vendor/ftview/lib/edit/prepare.js +133 -0
  23. package/vendor/ftview/lib/edit/tools.d.ts +25 -0
  24. package/vendor/ftview/lib/edit/tools.js +198 -0
  25. package/vendor/ftview/lib/edit.d.ts +30 -0
  26. package/vendor/ftview/lib/edit.js +38 -0
  27. package/vendor/ftview/lib/index.d.ts +31 -0
  28. package/vendor/ftview/lib/index.js +36 -0
  29. package/vendor/ftview/lib/model.d.ts +80 -0
  30. package/vendor/ftview/lib/model.js +267 -0
  31. package/vendor/ftview/lib/session.d.ts +48 -0
  32. package/vendor/ftview/lib/session.js +98 -0
  33. package/vendor/ftview/lib/skill.d.ts +29 -0
  34. package/vendor/ftview/lib/skill.js +43 -0
  35. package/vendor/ftview/lib/studio/discover.d.ts +36 -0
  36. package/vendor/ftview/lib/studio/discover.js +87 -0
  37. package/vendor/ftview/lib/studio/preflight.d.ts +51 -0
  38. package/vendor/ftview/lib/studio/preflight.js +64 -0
  39. package/vendor/ftview/lib/studio/tools.d.ts +15 -0
  40. package/vendor/ftview/lib/studio/tools.js +83 -0
  41. package/vendor/ftview/lib/studio.d.ts +30 -0
  42. package/vendor/ftview/lib/studio.js +37 -0
  43. package/vendor/ftview/lib/tools.d.ts +14 -0
  44. package/vendor/ftview/lib/tools.js +202 -0
  45. package/vendor/l5x/README.md +62 -0
  46. package/vendor/l5x/SKILL.md +81 -0
  47. package/vendor/l5x/lib/approvals.d.ts +50 -0
  48. package/vendor/l5x/lib/approvals.js +93 -0
  49. package/vendor/l5x/lib/client.js +412 -0
  50. package/vendor/l5x/lib/client.js.map +1 -0
  51. package/vendor/l5x/lib/core/errors.d.ts +38 -0
  52. package/vendor/l5x/lib/core/errors.js +48 -0
  53. package/vendor/l5x/lib/core/params.d.ts +49 -0
  54. package/vendor/l5x/lib/core/params.js +39 -0
  55. package/vendor/l5x/lib/core/result.d.ts +46 -0
  56. package/vendor/l5x/lib/core/result.js +21 -0
  57. package/vendor/l5x/lib/core/tool.d.ts +102 -0
  58. package/vendor/l5x/lib/core/tool.js +19 -0
  59. package/vendor/l5x/lib/drafts.d.ts +66 -0
  60. package/vendor/l5x/lib/drafts.js +98 -0
  61. package/vendor/l5x/lib/editing.d.ts +30 -0
  62. package/vendor/l5x/lib/editing.js +42 -0
  63. package/vendor/l5x/lib/engine/acd-edit.d.ts +13 -0
  64. package/vendor/l5x/lib/engine/acd-edit.js +115 -0
  65. package/vendor/l5x/lib/engine/analysis.d.ts +22 -0
  66. package/vendor/l5x/lib/engine/analysis.js +258 -0
  67. package/vendor/l5x/lib/engine/capabilities.d.ts +9 -0
  68. package/vendor/l5x/lib/engine/capabilities.js +40 -0
  69. package/vendor/l5x/lib/engine/change-set.d.ts +122 -0
  70. package/vendor/l5x/lib/engine/change-set.js +446 -0
  71. package/vendor/l5x/lib/engine/configuration.d.ts +21 -0
  72. package/vendor/l5x/lib/engine/configuration.js +42 -0
  73. package/vendor/l5x/lib/engine/details.d.ts +47 -0
  74. package/vendor/l5x/lib/engine/details.js +173 -0
  75. package/vendor/l5x/lib/engine/direct-polling.d.ts +91 -0
  76. package/vendor/l5x/lib/engine/direct-polling.js +193 -0
  77. package/vendor/l5x/lib/engine/download-review.d.ts +15 -0
  78. package/vendor/l5x/lib/engine/download-review.js +63 -0
  79. package/vendor/l5x/lib/engine/logic-syntax.d.ts +60 -0
  80. package/vendor/l5x/lib/engine/logic-syntax.js +237 -0
  81. package/vendor/l5x/lib/engine/observations.d.ts +41 -0
  82. package/vendor/l5x/lib/engine/observations.js +67 -0
  83. package/vendor/l5x/lib/engine/project-analysis.d.ts +131 -0
  84. package/vendor/l5x/lib/engine/project-analysis.js +314 -0
  85. package/vendor/l5x/lib/engine/review-evidence.d.ts +8 -0
  86. package/vendor/l5x/lib/engine/review-evidence.js +49 -0
  87. package/vendor/l5x/lib/engine/review.d.ts +18 -0
  88. package/vendor/l5x/lib/engine/review.js +123 -0
  89. package/vendor/l5x/lib/engine/studio-sdk.d.ts +101 -0
  90. package/vendor/l5x/lib/engine/studio-sdk.js +571 -0
  91. package/vendor/l5x/lib/engine/tag-resolution.d.ts +20 -0
  92. package/vendor/l5x/lib/engine/tag-resolution.js +169 -0
  93. package/vendor/l5x/lib/engine/usage.d.ts +16 -0
  94. package/vendor/l5x/lib/engine/usage.js +92 -0
  95. package/vendor/l5x/lib/engine/xml-regions.d.ts +23 -0
  96. package/vendor/l5x/lib/engine/xml-regions.js +77 -0
  97. package/vendor/l5x/lib/fs.d.ts +17 -0
  98. package/vendor/l5x/lib/fs.js +36 -0
  99. package/vendor/l5x/lib/index.d.ts +36 -0
  100. package/vendor/l5x/lib/index.js +42 -0
  101. package/vendor/l5x/lib/live.d.ts +48 -0
  102. package/vendor/l5x/lib/live.js +48 -0
  103. package/vendor/l5x/lib/model/aois.d.ts +39 -0
  104. package/vendor/l5x/lib/model/aois.js +112 -0
  105. package/vendor/l5x/lib/model/errors.d.ts +15 -0
  106. package/vendor/l5x/lib/model/errors.js +18 -0
  107. package/vendor/l5x/lib/model/index.d.ts +20 -0
  108. package/vendor/l5x/lib/model/index.js +19 -0
  109. package/vendor/l5x/lib/model/module.d.ts +37 -0
  110. package/vendor/l5x/lib/model/module.js +54 -0
  111. package/vendor/l5x/lib/model/modules.d.ts +24 -0
  112. package/vendor/l5x/lib/model/modules.js +55 -0
  113. package/vendor/l5x/lib/model/parse.d.ts +32 -0
  114. package/vendor/l5x/lib/model/parse.js +116 -0
  115. package/vendor/l5x/lib/model/project.d.ts +15 -0
  116. package/vendor/l5x/lib/model/project.js +31 -0
  117. package/vendor/l5x/lib/model/routines.d.ts +71 -0
  118. package/vendor/l5x/lib/model/routines.js +183 -0
  119. package/vendor/l5x/lib/model/tags.d.ts +51 -0
  120. package/vendor/l5x/lib/model/tags.js +119 -0
  121. package/vendor/l5x/lib/model/types.d.ts +304 -0
  122. package/vendor/l5x/lib/model/types.js +12 -0
  123. package/vendor/l5x/lib/model/udts.d.ts +34 -0
  124. package/vendor/l5x/lib/model/udts.js +81 -0
  125. package/vendor/l5x/lib/model/xml.d.ts +139 -0
  126. package/vendor/l5x/lib/model/xml.js +291 -0
  127. package/vendor/l5x/lib/model/xref.d.ts +28 -0
  128. package/vendor/l5x/lib/model/xref.js +154 -0
  129. package/vendor/l5x/lib/session.d.ts +119 -0
  130. package/vendor/l5x/lib/session.js +236 -0
  131. package/vendor/l5x/lib/skill.d.ts +30 -0
  132. package/vendor/l5x/lib/skill.js +44 -0
  133. package/vendor/l5x/lib/studio-state.d.ts +77 -0
  134. package/vendor/l5x/lib/studio-state.js +79 -0
  135. package/vendor/l5x/lib/studio.d.ts +29 -0
  136. package/vendor/l5x/lib/studio.js +40 -0
  137. package/vendor/l5x/lib/tools/checks.d.ts +19 -0
  138. package/vendor/l5x/lib/tools/checks.js +140 -0
  139. package/vendor/l5x/lib/tools/common.d.ts +41 -0
  140. package/vendor/l5x/lib/tools/common.js +84 -0
  141. package/vendor/l5x/lib/tools/controller.d.ts +21 -0
  142. package/vendor/l5x/lib/tools/controller.js +170 -0
  143. package/vendor/l5x/lib/tools/drafts.d.ts +23 -0
  144. package/vendor/l5x/lib/tools/drafts.js +270 -0
  145. package/vendor/l5x/lib/tools/live.d.ts +21 -0
  146. package/vendor/l5x/lib/tools/live.js +175 -0
  147. package/vendor/l5x/lib/tools/read.d.ts +19 -0
  148. package/vendor/l5x/lib/tools/read.js +245 -0
  149. package/vendor/l5x/lib/tools/references.d.ts +20 -0
  150. package/vendor/l5x/lib/tools/references.js +269 -0
  151. package/vendor/l5x/lib/tools/studio.d.ts +21 -0
  152. package/vendor/l5x/lib/tools/studio.js +193 -0
  153. package/vendor/l5x/poll-worker.mjs +77 -0
  154. package/vendor/l5x/studio-bridge/NuGet.Config +1 -0
  155. package/vendor/l5x/studio-bridge/Program.cs +304 -0
  156. package/vendor/l5x/studio-bridge/StudioBridge.csproj +11 -0
  157. package/vendor/mwi/README.md +60 -0
  158. package/vendor/mwi/SKILL.md +87 -0
  159. package/vendor/mwi/bridge/README.md +8 -0
  160. package/vendor/mwi/bridge/mw_bridge.ps1 +2763 -0
  161. package/vendor/mwi/bridge/sdk-interop-importer.cs +30 -0
  162. package/vendor/mwi/bridge/sdk-variable-reader.cs +22 -0
  163. package/vendor/mwi/bridge/start_bridge.cmd +21 -0
  164. package/vendor/mwi/bridge/watch_dialogs.cmd +13 -0
  165. package/vendor/mwi/bridge/watch_dialogs.ps1 +153 -0
  166. package/vendor/mwi/code/engine/motionworks_iec_mcp/__init__.py +14 -0
  167. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/__init__.cpython-312.pyc +0 -0
  168. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/block_interfaces.cpython-312.pyc +0 -0
  169. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/cfb.cpython-312.pyc +0 -0
  170. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/datatypes.cpython-312.pyc +0 -0
  171. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/declarations.cpython-312.pyc +0 -0
  172. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/errors.cpython-312.pyc +0 -0
  173. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/graphical_listing.cpython-312.pyc +0 -0
  174. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/grid.cpython-312.pyc +0 -0
  175. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/knowledge.cpython-312.pyc +0 -0
  176. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/mwt_bind.cpython-312.pyc +0 -0
  177. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/navigation.cpython-312.pyc +0 -0
  178. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/program_checks.cpython-312.pyc +0 -0
  179. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/project.cpython-312.pyc +0 -0
  180. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/staging.cpython-312.pyc +0 -0
  181. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/structure.cpython-312.pyc +0 -0
  182. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/task_bindings.cpython-312.pyc +0 -0
  183. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tasks.cpython-312.pyc +0 -0
  184. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/tree.cpython-312.pyc +0 -0
  185. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/validation.cpython-312.pyc +0 -0
  186. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variable_edit.cpython-312.pyc +0 -0
  187. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/variables.cpython-312.pyc +0 -0
  188. package/vendor/mwi/code/engine/motionworks_iec_mcp/__pycache__/workflow.cpython-312.pyc +0 -0
  189. package/vendor/mwi/code/engine/motionworks_iec_mcp/block_interfaces.py +275 -0
  190. package/vendor/mwi/code/engine/motionworks_iec_mcp/build.py +339 -0
  191. package/vendor/mwi/code/engine/motionworks_iec_mcp/capture.py +448 -0
  192. package/vendor/mwi/code/engine/motionworks_iec_mcp/cfb.py +765 -0
  193. package/vendor/mwi/code/engine/motionworks_iec_mcp/com.py +268 -0
  194. package/vendor/mwi/code/engine/motionworks_iec_mcp/datatypes.py +204 -0
  195. package/vendor/mwi/code/engine/motionworks_iec_mcp/declarations.py +399 -0
  196. package/vendor/mwi/code/engine/motionworks_iec_mcp/descriptions.py +30 -0
  197. package/vendor/mwi/code/engine/motionworks_iec_mcp/eip.py +292 -0
  198. package/vendor/mwi/code/engine/motionworks_iec_mcp/errors.py +36 -0
  199. package/vendor/mwi/code/engine/motionworks_iec_mcp/graphical_listing.py +208 -0
  200. package/vendor/mwi/code/engine/motionworks_iec_mcp/grid.py +227 -0
  201. package/vendor/mwi/code/engine/motionworks_iec_mcp/ide.py +317 -0
  202. package/vendor/mwi/code/engine/motionworks_iec_mcp/iec.py +317 -0
  203. package/vendor/mwi/code/engine/motionworks_iec_mcp/installed_help.py +151 -0
  204. package/vendor/mwi/code/engine/motionworks_iec_mcp/knowledge.py +218 -0
  205. package/vendor/mwi/code/engine/motionworks_iec_mcp/libraries.py +250 -0
  206. package/vendor/mwi/code/engine/motionworks_iec_mcp/manuals.py +520 -0
  207. package/vendor/mwi/code/engine/motionworks_iec_mcp/mwt_bind.py +133 -0
  208. package/vendor/mwi/code/engine/motionworks_iec_mcp/navigation.py +44 -0
  209. package/vendor/mwi/code/engine/motionworks_iec_mcp/pou_writer.py +1547 -0
  210. package/vendor/mwi/code/engine/motionworks_iec_mcp/program_checks.py +360 -0
  211. package/vendor/mwi/code/engine/motionworks_iec_mcp/project.py +704 -0
  212. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/catalog.json +672 -0
  213. package/vendor/mwi/code/engine/motionworks_iec_mcp/reference_data/patterns.json +86 -0
  214. package/vendor/mwi/code/engine/motionworks_iec_mcp/restore.py +208 -0
  215. package/vendor/mwi/code/engine/motionworks_iec_mcp/server.py +1913 -0
  216. package/vendor/mwi/code/engine/motionworks_iec_mcp/snapshot.py +138 -0
  217. package/vendor/mwi/code/engine/motionworks_iec_mcp/st_comments.py +25 -0
  218. package/vendor/mwi/code/engine/motionworks_iec_mcp/staging.py +179 -0
  219. package/vendor/mwi/code/engine/motionworks_iec_mcp/stlint.py +676 -0
  220. package/vendor/mwi/code/engine/motionworks_iec_mcp/structure.py +98 -0
  221. package/vendor/mwi/code/engine/motionworks_iec_mcp/task_bindings.py +68 -0
  222. package/vendor/mwi/code/engine/motionworks_iec_mcp/tasks.py +162 -0
  223. package/vendor/mwi/code/engine/motionworks_iec_mcp/transaction.py +157 -0
  224. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree.py +563 -0
  225. package/vendor/mwi/code/engine/motionworks_iec_mcp/tree_writer.py +790 -0
  226. package/vendor/mwi/code/engine/motionworks_iec_mcp/uia.py +465 -0
  227. package/vendor/mwi/code/engine/motionworks_iec_mcp/validation.py +41 -0
  228. package/vendor/mwi/code/engine/motionworks_iec_mcp/variable_edit.py +183 -0
  229. package/vendor/mwi/code/engine/motionworks_iec_mcp/variables.py +472 -0
  230. package/vendor/mwi/code/engine/motionworks_iec_mcp/workflow.py +130 -0
  231. package/vendor/mwi/code/engine/motionworks_iec_mcp/writer.py +666 -0
  232. package/vendor/mwi/code/mw_code.py +1258 -0
  233. package/vendor/mwi/legacy/edit-session.js +50 -0
  234. package/vendor/mwi/legacy/fb-insertion.js +74 -0
  235. package/vendor/mwi/legacy/native-code.js +83 -0
  236. package/vendor/mwi/legacy/native-groups.js +75 -0
  237. package/vendor/mwi/legacy/native-structure.js +143 -0
  238. package/vendor/mwi/legacy/native-variables.js +77 -0
  239. package/vendor/mwi/legacy/pou-conversion.js +47 -0
  240. package/vendor/mwi/legacy/pou-package.js +66 -0
  241. package/vendor/mwi/lib/core/bridge.d.ts +71 -0
  242. package/vendor/mwi/lib/core/bridge.js +53 -0
  243. package/vendor/mwi/lib/core/code-engine.d.ts +47 -0
  244. package/vendor/mwi/lib/core/code-engine.js +109 -0
  245. package/vendor/mwi/lib/core/errors.d.ts +37 -0
  246. package/vendor/mwi/lib/core/errors.js +47 -0
  247. package/vendor/mwi/lib/core/params.d.ts +52 -0
  248. package/vendor/mwi/lib/core/params.js +46 -0
  249. package/vendor/mwi/lib/core/ps-bridge.d.ts +62 -0
  250. package/vendor/mwi/lib/core/ps-bridge.js +127 -0
  251. package/vendor/mwi/lib/core/result.d.ts +45 -0
  252. package/vendor/mwi/lib/core/result.js +20 -0
  253. package/vendor/mwi/lib/core/tool.d.ts +80 -0
  254. package/vendor/mwi/lib/core/tool.js +55 -0
  255. package/vendor/mwi/lib/edit/compare.d.ts +33 -0
  256. package/vendor/mwi/lib/edit/compare.js +70 -0
  257. package/vendor/mwi/lib/edit.d.ts +20 -0
  258. package/vendor/mwi/lib/edit.js +32 -0
  259. package/vendor/mwi/lib/index.d.ts +21 -0
  260. package/vendor/mwi/lib/index.js +33 -0
  261. package/vendor/mwi/lib/read/service.d.ts +117 -0
  262. package/vendor/mwi/lib/read/service.js +229 -0
  263. package/vendor/mwi/lib/read.d.ts +21 -0
  264. package/vendor/mwi/lib/read.js +33 -0
  265. package/vendor/mwi/lib/session/project.d.ts +34 -0
  266. package/vendor/mwi/lib/session/project.js +54 -0
  267. package/vendor/mwi/lib/session/service.d.ts +167 -0
  268. package/vendor/mwi/lib/session/service.js +566 -0
  269. package/vendor/mwi/lib/session/stage.d.ts +84 -0
  270. package/vendor/mwi/lib/session/stage.js +187 -0
  271. package/vendor/mwi/lib/session/workspace.d.ts +52 -0
  272. package/vendor/mwi/lib/session/workspace.js +90 -0
  273. package/vendor/mwi/lib/skill.d.ts +36 -0
  274. package/vendor/mwi/lib/skill.js +56 -0
  275. package/vendor/mwi/lib/tools/edit.d.ts +20 -0
  276. package/vendor/mwi/lib/tools/edit.js +286 -0
  277. package/vendor/mwi/lib/tools/read.d.ts +19 -0
  278. package/vendor/mwi/lib/tools/read.js +161 -0
  279. package/vendor/mwi/lib/tools/session.d.ts +20 -0
  280. package/vendor/mwi/lib/tools/session.js +166 -0
  281. package/vendor/mwi/lib/tools/verify.d.ts +19 -0
  282. package/vendor/mwi/lib/tools/verify.js +85 -0
  283. package/vendor/mwi/lib/verify.d.ts +17 -0
  284. package/vendor/mwi/lib/verify.js +29 -0
@@ -0,0 +1,50 @@
1
+ // Declaration plans are immutable, workspace-bound read-back evidence, not UI input.
2
+ import { randomUUID } from 'node:crypto';
3
+ const fields = ['name','type','section','group','address','initial_value','description'];
4
+ const plans = new Map();
5
+ const ttl = 30 * 60 * 1000;
6
+ export function variableExpectation(baseline, declaration, pou, existingGroups = baseline.map(v=>v.group)) {
7
+ if (!Array.isArray(baseline)) throw new Error('Missing saved declaration baseline');
8
+ if (!declaration || fields.some(f => !Object.hasOwn(declaration, f)
9
+ || (declaration[f] !== null && typeof declaration[f] !== 'string')))
10
+ throw new Error('Supply all seven declaration fields, using null for absent metadata');
11
+ if (!/^[A-Za-z_][A-Za-z0-9_]{0,29}$/.test(declaration.name ?? ''))
12
+ throw new Error('Use an IEC identifier of at most 30 characters; never silently truncate or rename');
13
+ if (!declaration.type || /[;\r\n]/.test(declaration.type)) throw new Error('Invalid data type');
14
+ if (!(pou ? ['VAR','VAR_INPUT','VAR_OUTPUT','VAR_IN_OUT','VAR_EXTERNAL'] : ['VAR_GLOBAL']).includes(declaration.section))
15
+ throw new Error('Usage does not match worksheet scope');
16
+ if (!declaration.group) throw new Error('An explicit existing variable group is required');
17
+ if (declaration.section === 'VAR_EXTERNAL' && (declaration.address || declaration.initial_value))
18
+ throw new Error('Externals must not duplicate a global physical address or initializer');
19
+ if (baseline.some(v => v.name.toUpperCase() === declaration.name.toUpperCase()))
20
+ throw new Error('Variable already exists; addition refused');
21
+ if (!existingGroups.includes(declaration.group))
22
+ throw new Error('Group is not in the saved baseline; create/verify it separately');
23
+ if (declaration.address && baseline.some(v => v.address?.toUpperCase() === declaration.address.toUpperCase()))
24
+ throw new Error('Exact IEC address already used; addition refused');
25
+ // Distinct strings can overlap physically. This is not an IO allocation proof.
26
+ return [...baseline.map(v => Object.fromEntries(fields.map(f => [f,v[f] ?? null]))), {...declaration}];
27
+ }
28
+ export function requireVariableView(view, pou) {
29
+ const expected = pou ? `/Pous/${pou}/${pou}V` : null;
30
+ if (!view || (expected ? view.toLowerCase() !== expected.toLowerCase()
31
+ : !/^\/Hardware\/[^/]+\/[^/]+\/Global_Variables$/i.test(view)))
32
+ throw new Error(`REFUSED: active worksheet '${view ?? ''}' is not the requested variable worksheet`);
33
+ }
34
+ export function retainPlan({workspace, project, pou, view, baseline, declaration}, now = Date.now()) {
35
+ requireVariableView(view,pou);
36
+ const expected = variableExpectation(baseline,declaration,pou);
37
+ const token = randomUUID();
38
+ for (const [id,p] of plans) if (now - p.created > ttl) plans.delete(id);
39
+ plans.set(token, structuredClone({workspace,project,pou:pou ?? null,view,baseline,expected,created:now}));
40
+ return {token, expected_count:expected.length, declaration:{...declaration},
41
+ dialog_values:{name:declaration.name,type:declaration.type,usage:declaration.section,
42
+ address:declaration.address ?? '',initial_value:declaration.initial_value ?? '',description:declaration.description ?? ''},
43
+ action_performed:false, expires_in_seconds:ttl/1000};
44
+ }
45
+ export function lookupPlan(token, workspace, now = Date.now()) {
46
+ const plan = plans.get(token);
47
+ if (!plan || plan.workspace !== workspace || now - plan.created > ttl)
48
+ throw new Error('REFUSED: declaration plan is missing, expired, or belongs to another workspace; do not reconstruct it from damaged read-back');
49
+ return structuredClone(plan);
50
+ }
@@ -0,0 +1,74 @@
1
+ import {createHash} from 'node:crypto';
2
+ const identifier=name=>typeof name==='string'&&/^[A-Za-z_][A-Za-z0-9_]{0,29}$/.test(name);
3
+ const sameType=(a,b)=>a.replace(/\s+/g,'').toUpperCase()===b.replace(/\s+/g,'').toUpperCase();
4
+ function expression(value){
5
+ if(typeof value!=='string'||!value.trim()||/[^\x20-\x7e]/.test(value))throw new Error('Binding must be one printable ASCII expression');
6
+ let quote=null;const stack=[];
7
+ for(let n=0;n<value.length;n++){
8
+ const c=value[n],pair=value.slice(n,n+2);
9
+ if(quote){if(c==='$'){n++;continue;}if(c===quote){if(value[n+1]===quote)n++;else quote=null;}continue;}
10
+ if(c==="'"||c==='"'){quote=c;continue;}
11
+ if(['(*','*)','//'].includes(pair)||/[;{}]/.test(c)||c===','&&!stack.length)throw new Error('Binding contains a statement/comment or extra argument');
12
+ if(c==='('||c==='[')stack.push(c);
13
+ if(c===')'||c===']'){if(stack.pop()!==(c===')'?'(':'['))throw new Error('Unbalanced binding expression');}
14
+ }
15
+ if(quote||stack.length)throw new Error('Unbalanced binding expression');
16
+ return value.trim();
17
+ }
18
+ function insertionPoint(body,offset){
19
+ if(!Number.isInteger(offset)||offset<0||offset>body.length||offset!==0&&offset!==body.length&&body[offset-1]!=='\n')throw new Error('offset must be a line boundary in expected_body');
20
+ let depth=0,quote=null,lineComment=false;
21
+ for(let n=0;n<offset;n++){
22
+ const c=body[n],pair=body.slice(n,n+2);
23
+ if(lineComment){if(c==='\n')lineComment=false;continue;}
24
+ if(depth){if(pair==='(*'){depth++;n++;}else if(pair==='*)'){depth--;n++;}continue;}
25
+ if(quote){if(c==='$'){n++;continue;}if(c===quote){if(body[n+1]===quote)n++;else quote=null;}continue;}
26
+ if(pair==='(*'){depth++;n++;}else if(pair==='//'){lineComment=true;n++;}else if(c==="'"||c==='"')quote=c;
27
+ }
28
+ if(depth||quote||lineComment)throw new Error('Insertion point is inside a comment/string');
29
+ }
30
+ export function fbInsertionPlan(args,block,read){
31
+ if(args.baseline_saved!==true||!identifier(args.instance))throw new Error('Saved baseline and valid new instance name required');
32
+ const compiled=block.evidence_kind==='fresh-bound-compiled-block-interface';
33
+ if(block.kind!=='FUNCTION_BLOCK'||block.hidden||!identifier(block.name)||!['installed-declared-block-interface','fresh-bound-compiled-block-interface'].includes(block.evidence_kind))throw new Error('Requires a visible, declared function-block interface or verified current compiled contract');
34
+ if(compiled&&(block.insertion_eligible!==true||block.project_compiler_freshness_verified!==true||block.compiler_library_binding_verified!==true||block.compiler_pin_types_verified!==true||block.compile_acceptance_only!==true||!Number.isFinite(block.verified_at_ms)||block.verified_at_ms>Date.now()||Date.now()-block.verified_at_ms>300000||! /^[a-f0-9]{64}$/.test(block.library_manifest_digest??'')||! /^[a-f0-9]{64}$/.test(block.source_baseline_digest??'')))throw new Error('Current compiled contract is incomplete or expired');
35
+ if(block.insertion_eligible===false)throw new Error('Diagnostic compiled interface is not insertion eligible; freshness/source binding remains unverified');
36
+ const hashMode=args.expected_body_sha256!==undefined;
37
+ if(hashMode&&(args.expected_body!==undefined||! /^[a-f0-9]{64}$/.test(args.expected_body_sha256)))throw new Error('Supply either exact expected_body or readable expected_body_sha256');
38
+ if(read.language!=='ST'||read.body_error||typeof read.body!=='string'||(hashMode?
39
+ createHash('sha256').update(read.body).digest('hex')!==args.expected_body_sha256:read.body!==args.expected_body))throw new Error('Exact saved ST expected_body/readable hash required');
40
+ if(/[^\x09\x0a\x0d\x20-\x7e]/.test(read.body))throw new Error('Native ST insertion currently requires printable ASCII source');
41
+ if(read.variables.some(v=>v.name.toUpperCase()===args.instance.toUpperCase()))throw new Error('Instance already declared');
42
+ const bindings=args.bindings??{},pins=new Map(block.pins.map(p=>[p.name.toUpperCase(),p]));
43
+ const seen=new Set(),fields=[],outputs=[],warnings=[];
44
+ for(const [name,input] of Object.entries(bindings)){
45
+ const pin=pins.get(name.toUpperCase());
46
+ if(!pin||seen.has(pin.name.toUpperCase()))throw new Error('Unknown or repeated pin: '+name);
47
+ seen.add(pin.name.toUpperCase());const value=expression(input);
48
+ const variable=read.variables.find(v=>v.name.toUpperCase()===value.toUpperCase());
49
+ if(pin.direction==='output'||pin.direction==='in_out'){
50
+ if(!identifier(value)||!variable)throw new Error('Output/in-out binding requires an existing direct variable: '+pin.name);
51
+ if(pin.type.toUpperCase()!=='ANY'&&!sameType(variable.type,pin.type))throw new Error('Binding type mismatch: '+pin.name);
52
+ }else if(pin.direction==='input'){
53
+ if(variable&&pin.type.toUpperCase()!=='ANY'&&!sameType(variable.type,pin.type))throw new Error('Binding type mismatch: '+pin.name);
54
+ if(!variable)warnings.push('Compiler must validate input expression type: '+pin.name);
55
+ }else throw new Error('Unresolved pin direction: '+pin.name);
56
+ // eCLR requires both sides of VAR_IN_OUT connected to the same variable.
57
+ // Installed help: TheVARINOUTParameterIsNotConnectedToAVariable.htm and
58
+ // TheVARINOUTParameterIsConnectedToDifferentVariables.htm.
59
+ if(pin.direction==='output'||pin.direction==='in_out')outputs.push(value+' := '+args.instance+'.'+pin.name+';\r\n');
60
+ if(pin.direction!=='output')fields.push(pin.name+' := '+value);
61
+ }
62
+ for(const pin of block.pins)if(pin.direction==='in_out'&&!seen.has(pin.name.toUpperCase()))throw new Error('Required in-out binding missing: '+pin.name);
63
+ let offset=args.offset??read.body.length;
64
+ if(args.before!==undefined){
65
+ if(args.offset!==undefined||typeof args.before!=='string'||!args.before)throw new Error('Use either a nonempty before anchor or offset');
66
+ offset=read.body.indexOf(args.before);
67
+ if(offset<0||read.body.indexOf(args.before,offset+1)>=0)throw new Error('before anchor must match exactly once');
68
+ }
69
+ insertionPoint(read.body,offset);
70
+ const call=args.instance+'('+fields.join(', ')+');\r\n'+outputs.join('');
71
+ const prefix=read.body.slice(0,offset),suffix=read.body.slice(offset);
72
+ return {declaration:{name:args.instance,type:block.name,section:'VAR',group:args.group??'Default',address:null,initial_value:null,description:null},
73
+ code:prefix+(prefix&&!prefix.endsWith('\n')?'\r\n':'')+call+suffix,call,offset,warnings,interface:block};
74
+ }
@@ -0,0 +1,83 @@
1
+ import {createHash} from 'node:crypto';
2
+ const hash=text=>createHash('sha256').update(text).digest('hex');
3
+ const stable=v=>JSON.stringify(v,(_k,x)=>x&&typeof x==='object'&&!Array.isArray(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
4
+ const same=(a,b)=>stable(a)===stable(b);
5
+ const canonical=text=>text.replace(/\r\n?/g,'\n');
6
+ // Assemble small exact edits locally; never ask the model to reproduce the body.
7
+ export function patchedCode(body,args){
8
+ if(!Array.isArray(args.changes)||args.changes.length===0||args.changes.length>50)throw new Error('Supply 1-50 exact code changes');
9
+ let code=canonical(body);
10
+ for(const change of args.changes){
11
+ if(!change||typeof change.find!=='string'||!change.find||typeof change.replace!=='string')throw new Error('Each change needs nonempty find and string replace');
12
+ const find=canonical(change.find),replacement=canonical(change.replace),count=change.count??1;
13
+ if(!Number.isInteger(count)||count<1||count>100)throw new Error('Change count must be 1-100');
14
+ const parts=code.split(find),actual=parts.length-1;
15
+ if(actual!==count)throw new Error(`Exact change expected ${count} occurrence(s), found ${actual}; nothing imported`);
16
+ code=parts.join(replacement);
17
+ }
18
+ return code;
19
+ }
20
+ const sorted=rows=>rows.sort((a,b)=>a.name.localeCompare(b.name));
21
+ const inventory=s=>({pous:sorted(s.pous.map(({name,type})=>({name,type}))),tasks:sorted(s.tasks.map(({name,kind,instances})=>({name,kind,instances})))});
22
+ export async function nativeCodeChange(args,deps){
23
+ if(args.baseline_saved!==true||(await deps.status()).is_modified!==false)throw new Error('Reconcile/save native edits before code import');
24
+ if(!/^[A-Za-z_][A-Za-z0-9_]{0,29}$/.test(args.pou??''))throw new Error('Invalid POU name');
25
+ const patchMode=args.changes!==undefined||args.expected_body_sha256!==undefined;
26
+ if(patchMode?(args.code!==undefined||args.expected_body!==undefined||! /^[a-f0-9]{64}$/.test(args.expected_body_sha256??'')):
27
+ (typeof args.code!=='string'||typeof args.expected_body!=='string'))throw new Error('Supply either expected_body + code, or expected_body_sha256 + changes');
28
+ const before=await deps.saved(),live=await deps.snapshot(),body=await deps.read();
29
+ const target=before.pous.find(p=>p.name.toLowerCase()===args.pou.toLowerCase());
30
+ const native=live.pous.find(p=>p.name.toLowerCase()===args.pou.toLowerCase());
31
+ const languages={ST:2,IL:1};
32
+ if(!target||!Object.hasOwn(languages,target.language)||!native||native.language!==languages[target.language]||native.read_only)throw new Error('Target must be an existing writable ST/IL POU with matching saved/native language');
33
+ if(!same(inventory(before),inventory(live)))throw new Error('Native/saved inventories disagree');
34
+ if(body.body_error||typeof body.body!=='string'||(patchMode?hash(body.body)!==args.expected_body_sha256:canonical(body.body)!==canonical(args.expected_body)))throw new Error('Saved code differs from expected_body/hash or cannot be resolved');
35
+ const desired=patchMode?patchedCode(body.body,args):args.code;
36
+ if(/[^\x09\x0a\x0d\x20-\x7e]/.test(desired))throw new Error('Supply printable ASCII ST/IL code');
37
+ const document=await deps.document();
38
+ const code=canonical(desired).replace(/\n/g,'\r\n');
39
+ const input_text=code&&!code.endsWith('\r\n')?code+'\r\n':code;
40
+ const declarations=await deps.declarations();
41
+ const input=await deps.prepare(input_text,target.language);
42
+ const request={pou:target.name,language:target.language,worksheet:document.urn.split('.').at(-1),before_native:live,before_files:before.file_hashes,before_declarations:declarations,input_text,...input};
43
+ await deps.retain({args,baseline:before,native_baseline:live,body_before:body.body,expected_body:input_text,request});
44
+ const result=await deps.mutate(request),after=await deps.saved(),read=await deps.read(),errors=[];
45
+ if(result.saved!==true||result.is_modified!==false)errors.push('Native save not proven');
46
+ if(!same(result.snapshot,live))errors.push('Native structure changed');
47
+ if(!same(await deps.declarations(),declarations))errors.push('Native declaration flags changed');
48
+ if(!same(before.globals,after.globals)||!same(before.tasks,after.tasks))errors.push('Globals/tasks changed');
49
+ if(!same(inventory(before),inventory(after)))errors.push('Saved structure changed');
50
+ for(const old of before.pous){
51
+ const current=after.pous.find(p=>p.name===old.name);
52
+ if(old.name!==target.name&&!same(old,current))errors.push('Unrelated POU changed: '+old.name);
53
+ if(old.name===target.name){
54
+ const omit=({body_sha256,body_blank,identifiers,...rest})=>rest;
55
+ if(!current||!same(omit(old),omit(current)))errors.push('Target declarations/metadata changed');
56
+ }
57
+ }
58
+ const targetFile='POE/'+target.name+'/src.st1';
59
+ const bodyExtension=target.language==='IL'?'.AB':'.STB';
60
+ if(!same(Object.keys(before.program_sources).sort(),Object.keys(after.program_sources).sort()))errors.push('Program source inventory changed');
61
+ const targetBefore=before.program_sources[targetFile]??{},targetAfter=after.program_sources[targetFile]??{};
62
+ const vb=Object.keys(targetBefore).find(name=>name.toUpperCase().endsWith('.VB'));
63
+ // Reading a new blank POU's native variable collection initializes its empty
64
+ // Default-group storage at the next Save. Permit only this observed header,
65
+ // with complete declarations, groups and raw native flags still unchanged.
66
+ const initialized_empty_storage=target.variables.length===0&&vb&&targetBefore[vb]===hash('')&&targetAfter[vb]===hash('\r\n(*Group:Default*)\r\n\r\n');
67
+ for(const [file,streams] of Object.entries(before.program_sources)){
68
+ const omitBody=rows=>Object.fromEntries(Object.entries(rows).filter(([name])=>!name.toUpperCase().endsWith(bodyExtension)&&!(initialized_empty_storage&&name.toUpperCase().endsWith('.VB'))&&!(initialized_empty_storage&&name.toUpperCase().endsWith('.VGR'))));
69
+ if(!same(file===targetFile?omitBody(streams):streams,file===targetFile?omitBody(after.program_sources[file]??{}):after.program_sources[file]))errors.push('Unexpected program stream change: '+file);
70
+ }
71
+ const translation='POE/'+target.name+'/'+request.worksheet+'Translation.xml';
72
+ const otherTranslations=rows=>Object.fromEntries(Object.entries(rows??{}).filter(([file])=>file.toLowerCase()!==translation.toLowerCase()));
73
+ const beforeComments=otherTranslations(before.translation_files),afterComments=otherTranslations(after.translation_files);
74
+ const emptyNewVariableTranslation='POE/'+target.name+'/'+document.variable_worksheet+'Translation.xml';
75
+ if(initialized_empty_storage){
76
+ for(const file of Object.keys(afterComments)){
77
+ if(!Object.hasOwn(beforeComments,file)&&file.toLowerCase()===emptyNewVariableTranslation.toLowerCase()&&after.empty_translation_files?.includes(file))delete afterComments[file];
78
+ }
79
+ }
80
+ if(!same(beforeComments,afterComments))errors.push('Unrelated comment translations changed');
81
+ if(read.body_error||typeof read.body!=='string'||canonical(read.body)!==canonical(input_text))errors.push('Saved code/comments differ from requested code');
82
+ return {pou:target.name,language:target.language,action_performed:true,method:'native_dde_ChangeCodeWS',native_result:result,verification:{accepted:errors.length===0,errors,initialized_empty_storage:!!initialized_empty_storage},body:read.body,baseline:before,saved_result:after,next_step:'Fresh Build/Make; compilation and controller acceptance are separate.'};
83
+ }
@@ -0,0 +1,75 @@
1
+ import {isDeepStrictEqual as same} from 'node:util';
2
+ const fields=['name','type','section','group','address','initial_value','description'];
3
+ const flags=['retain','pdd','opc','disabled','not_on_plc','redundant'];
4
+ const projection=rows=>rows.map(v=>Object.fromEntries(fields.map(k=>[k,k==='description'?(v[k]?.trim()||null):k==='group'?v[k]?.trim():v[k]??null]))).sort((a,b)=>a.name.localeCompare(b.name));
5
+ const fold=name=>name.toLowerCase();
6
+ export function groupPlan(args,before,live){
7
+ if(args.pou!==undefined&&(typeof args.pou!=='string'||!/^[A-Za-z_][A-Za-z0-9_]{0,29}$/.test(args.pou)))throw Error('Exact POU identifier required; omit pou only for globals');
8
+ if(!['create','rename','delete'].includes(args.operation))throw Error('Unsupported group operation');
9
+ if(typeof args.name!=='string'||!args.name.trim()||args.name.length>255||/[\x00-\x1f]/.test(args.name))throw Error('Exact nonempty group name required');
10
+ if(args.operation==='delete'&&args.user_approved!==true)throw Error('Explicit empty-group deletion approval required');
11
+ if(args.operation!=='rename'&&args.new_name!==undefined)throw Error('new_name applies only to rename');
12
+ const destination=args.operation==='rename'?args.new_name:args.name;
13
+ if(args.operation!=='delete'&&(typeof destination!=='string'||destination!==destination.trim()||!destination||destination.length>255||/[\x00-\x1f]/.test(destination)))throw Error('New group name must be nonempty, trimmed and contain no control characters');
14
+ if(!Array.isArray(live.variables)||!Array.isArray(live.groups)||live.groups.some(g=>typeof g.name!=='string'||!g.name.trim()||typeof g.read_only!=='boolean')||live.variables.some(v=>flags.some(k=>typeof v[k]!=='boolean')))throw Error('Complete native groups and declaration flags required');
15
+ if(new Set(live.groups.map(g=>fold(g.name.trim()))).size!==live.groups.length)throw Error('Ambiguous native group inventory');
16
+ const pou=args.pou?before.pous.find(p=>p.name===args.pou):null;
17
+ if(args.pou&&(!pou||live.structure.pous.find(p=>p.name===args.pou)?.read_only!==false))throw Error('Exact writable POU required');
18
+ const savedRows=args.pou?pou.variables:before.globals;
19
+ if(!same(projection(savedRows),projection(live.variables)))throw Error('Saved/native declarations disagree; no group action');
20
+ const inventories=rows=>rows.map(({name,type,language})=>({name,type,language})).sort((a,b)=>a.name.localeCompare(b.name));
21
+ if(!same(inventories(before.pous.map(p=>({...p,language:{IL:1,ST:2,FBD:3,LD:4}[p.language]}))),inventories(live.structure.pous)))throw Error('Saved/native POU inventories disagree');
22
+ const tasks=rows=>rows.map(({name,kind,instances})=>({name,kind,instances})).sort((a,b)=>a.name.localeCompare(b.name));
23
+ if(!same(tasks(before.tasks),tasks(live.structure.tasks)))throw Error('Saved/native task inventories disagree');
24
+ const sheets={globals:before.globals,...Object.fromEntries(before.pous.map(p=>['pou:'+p.name,p.variables]))};
25
+ if(!live.group_membership||!same(Object.keys(sheets).sort(),Object.keys(live.group_membership).sort()))throw Error('Complete native group membership inventory required');
26
+ for(const [key,rows] of Object.entries(sheets)){
27
+ const groups=live.group_membership[key];
28
+ if(!Array.isArray(groups)||groups.some(g=>typeof g.name!=='string'||!g.name.trim()||typeof g.read_only!=='boolean'||!Array.isArray(g.members)||g.members.some(name=>typeof name!=='string'||!name)))throw Error('Complete group metadata required');
29
+ if(new Set(groups.map(g=>fold(g.name.trim()))).size!==groups.length)throw Error('Ambiguous native group inventory: '+key);
30
+ const memberRows=groups.flatMap(g=>g.members.map(name=>({name,group:g.name.trim()}))).sort((a,b)=>a.name.localeCompare(b.name));
31
+ if(!same(memberRows,rows.map(v=>({name:v.name,group:v.group?.trim()})).sort((a,b)=>a.name.localeCompare(b.name))))throw Error('Saved/native group membership disagrees: '+key);
32
+ }
33
+ const key=args.pou?'pou:'+args.pou:'globals';
34
+ if(!same(live.group_membership[key].map(({name,read_only})=>({name,read_only})),live.groups))throw Error('Target group metadata disagrees');
35
+ const target=live.groups.find(g=>g.name===args.name);
36
+ if(args.operation==='create'&&live.groups.some(g=>fold(g.name)===fold(args.name)))throw Error('Group already exists');
37
+ if(args.operation!=='create'&&(!target||target.read_only!==false))throw Error('Exact writable group required');
38
+ if(args.operation==='rename'&&live.groups.some(g=>g!==target&&fold(g.name)===fold(destination)))throw Error('Destination group already exists');
39
+ if(args.operation==='rename'&&target.name===destination)throw Error('Group already has requested name');
40
+ const members=live.variables.filter(v=>v.group===args.name);
41
+ if(live.variables.some(v=>!live.groups.some(g=>g.name===v.group)))throw Error('Native variable has unknown group');
42
+ if(args.operation==='delete'&&(members.length||live.groups.length<=1))throw Error('Delete requires an empty group and another remaining group');
43
+ const expected=structuredClone(live),savedExpected=structuredClone(before);
44
+ if(args.operation==='create'){expected.groups.push({name:destination,read_only:false});expected.group_membership[key].push({name:destination,read_only:false,members:[]});}
45
+ if(args.operation==='delete'){expected.groups=expected.groups.filter(g=>g.name!==args.name);expected.group_membership[key]=expected.group_membership[key].filter(g=>g.name!==args.name);}
46
+ if(args.operation==='rename'){
47
+ expected.groups.find(g=>g.name===args.name).name=destination;
48
+ expected.group_membership[key].find(g=>g.name===args.name).name=destination;
49
+ for(const v of expected.variables)if(v.group===args.name)v.group=destination;
50
+ const names=new Set(members.map(v=>v.name));
51
+ for(const v of args.pou?savedExpected.pous.find(p=>p.name===args.pou).variables:savedExpected.globals)if(names.has(v.name)&&v.group?.trim()===args.name.trim())v.group=destination;
52
+ }
53
+ const prefix=args.pou?`poe/${args.pou.toLowerCase()}/`:'c/configuration/r/resource/';
54
+ const sources=Object.entries(before.program_sources).filter(([path])=>path.replaceAll('\\','/').toLowerCase().startsWith(prefix));
55
+ if(sources.length!==1)throw Error('Exact declaration source container required');
56
+ const [source,streams]=sources[0],vb=Object.keys(streams).filter(s=>s.toUpperCase().endsWith('.VB'));
57
+ if(vb.length!==1)throw Error('Exact variable worksheet stream required');
58
+ return {expected,savedExpected,members,source,allowed:new Set([vb[0],vb[0].slice(0,-3)+'.VGR'])};
59
+ }
60
+ export async function nativeGroupChange(args,deps){
61
+ if(args.baseline_saved!==true||(await deps.status()).is_modified!==false)throw Error('Reconcile/save native edits before group changes');
62
+ const before=await deps.saved(),live=await deps.snapshot(),plan=groupPlan(args,before,live);
63
+ await deps.retain({before,live,expected:plan.expected,members:plan.members,source:plan.source});
64
+ const result=await deps.mutate({...args,before_state:live,expected_state:plan.expected,before_files:before.file_hashes});
65
+ const after=await deps.saved(),nativeAfter=await deps.snapshot(),state=await deps.status(),errors=[];
66
+ if(result.saved!==true||result.is_modified!==false||state.is_modified!==false)errors.push('Native save completion unverified');
67
+ if(!same(plan.expected,nativeAfter))errors.push('Native groups, declaration flags, structure or libraries differ from plan');
68
+ for(const key of ['pous','tasks','globals','translation_files'])if(!same(plan.savedExpected[key],after[key]))errors.push('Saved '+key+' differs from plan');
69
+ for(const path of new Set([...Object.keys(before.program_sources),...Object.keys(after.program_sources)])){
70
+ const old=before.program_sources[path],now=after.program_sources[path];
71
+ if(path!==plan.source){if(!same(old,now))errors.push('Unrelated program streams changed: '+path);continue;}
72
+ for(const stream of new Set([...Object.keys(old??{}),...Object.keys(now??{})]))if(!plan.allowed.has(stream)&&old?.[stream]!==now?.[stream])errors.push('Unrelated worksheet stream changed: '+stream);
73
+ }
74
+ return {accepted:!errors.length,operation:args.operation,action_performed:true,member_count:plan.members.length,errors,before,after,native_before:live,native_after:nativeAfter};
75
+ }
@@ -0,0 +1,143 @@
1
+ const eq=(a,b)=>a.toUpperCase()===b.toUpperCase();
2
+ const stable=v=>JSON.stringify(v,(_k,x)=>x&&typeof x==='object'&&!Array.isArray(x)?Object.fromEntries(Object.keys(x).sort().map(k=>[k,x[k]])):x);
3
+ const same=(a,b)=>stable(a)===stable(b);
4
+ const sorted=rows=>[...rows].sort((a,b)=>a.name.localeCompare(b.name));
5
+ const inventory=s=>({pous:sorted(s.pous.map(({name,type})=>({name,type}))),tasks:sorted(s.tasks.map(({name,kind,instances})=>({name,kind,instances})))});
6
+ const identifier=(name,max=30)=>typeof name==='string'&&new RegExp(`^[A-Za-z_][A-Za-z0-9_]{0,${max-1}}$`).test(name);
7
+ const byName=(rows,name)=>rows.find(r=>eq(r.name,name??''));
8
+ function references(saved,name){
9
+ const found=[];
10
+ for(const p of saved.pous){
11
+ if(eq(p.name,name))continue;
12
+ // references_reviewed also covers graphical/indirect calls, which this
13
+ // saved ST scan cannot prove absent. Preserve that limitation in evidence.
14
+ if(p.identifiers.includes(name.toUpperCase())||p.variables.some(v=>new RegExp(`\\b${name}\\b`,'i').test(v.type)))found.push(p.name);
15
+ }
16
+ if(saved.globals.some(v=>new RegExp(`\\b${name}\\b`,'i').test(v.type)))found.push('resource globals');
17
+ if(saved.tasks.some(t=>t.instances.some(i=>eq(i.type,name))))found.push('task assignment');
18
+ return found;
19
+ }
20
+ export function structurePlan(scope,args,saved,live){
21
+ const native=structuredClone(live),op=args.operation;
22
+ if(!identifier(args.name,scope==='task'?7:30))throw new Error('Invalid native object name');
23
+ const target=byName(scope==='pou'?native.pous:native.tasks,args.name);
24
+ const plan={native,target,affected:[]};
25
+ if(scope==='pou'){
26
+ if(!['create','copy','rename','delete'].includes(op))throw new Error('Unknown POU operation');
27
+ if(op!=='create'&&args.language!==undefined)throw new Error('Language applies only to POU creation');
28
+ if(op==='create'){
29
+ if(target)throw new Error('POU already exists');
30
+ const type=args.pou_type??'PROGRAM';
31
+ if(!['PROGRAM','FUNCTION_BLOCK','FUNCTION'].includes(type))throw new Error('Unknown POU type');
32
+ if(type==='FUNCTION'&&!identifier(args.return_type))throw new Error('Function requires a simple explicit return type');
33
+ if(type!=='FUNCTION'&&args.return_type)throw new Error('Return type applies only to functions');
34
+ const language=args.language??'ST',languages={IL:1,ST:2,FBD:3,LD:4};
35
+ if(!Object.hasOwn(languages,language))throw new Error('Unsupported POU language');
36
+ native.pous.push({name:args.name,type,language:languages[language],plc_type:'',processor_type:'',read_only:false});
37
+ plan.affected=[args.name];
38
+ }else{
39
+ if(!target||target.read_only)throw new Error('POU missing or read-only');
40
+ if(op==='copy'||op==='rename'){
41
+ if(!identifier(args.new_name)||byName(native.pous,args.new_name))throw new Error('New POU name invalid or already exists');
42
+ plan.affected=[args.new_name,...(op==='rename'?[target.name]:[])];
43
+ }
44
+ if(op==='rename'||op==='delete'){
45
+ if(args.references_reviewed!==true||op==='delete'&&args.user_approved!==true)throw new Error('Explicit reference review/deletion approval required');
46
+ const refs=references(saved,target.name);
47
+ if(refs.length)throw new Error('POU has remaining references: '+refs.join(', '));
48
+ }
49
+ if(op==='copy')native.pous.push({...target,name:args.new_name});
50
+ if(op==='rename')target.name=args.new_name;
51
+ if(op==='delete'){native.pous=native.pous.filter(p=>p!==target);plan.affected=[target.name];}
52
+ }
53
+ }else{
54
+ if(!['create','edit','delete','assign','unassign'].includes(op))throw new Error('Unknown task operation');
55
+ if(op==='create'){
56
+ if(target)throw new Error('Task already exists');
57
+ const kind=args.kind??'CYCLIC';
58
+ if(!['CYCLIC','DEFAULT','SYSTEM'].includes(kind))throw new Error('Unknown task kind');
59
+ native.tasks.push({name:args.name,kind,instances:[]});
60
+ }else{
61
+ if(!target)throw new Error('Task missing');
62
+ if(op==='delete'){
63
+ if(args.user_approved!==true)throw new Error('Task deletion approval required');
64
+ if(target.instances.length)throw new Error('Unassign programs before deleting task');
65
+ native.tasks=native.tasks.filter(t=>t!==target);
66
+ }
67
+ if(op==='assign'){
68
+ const pou=byName(native.pous,args.pou),instance=args.instance??args.pou;
69
+ if(!pou||pou.type!=='PROGRAM'||!identifier(instance)||byName(target.instances,instance))throw new Error('Invalid program or duplicate instance');
70
+ target.instances.push({name:instance,type:pou.name});
71
+ }
72
+ if(op==='unassign'){
73
+ if(args.user_approved!==true)throw new Error('Unassignment approval required');
74
+ const instance=byName(target.instances,args.instance);
75
+ if(!instance)throw new Error('Exact instance missing');
76
+ target.instances=target.instances.filter(i=>i!==instance);
77
+ }
78
+ if(op==='edit'){
79
+ const old=byName(saved.tasks,args.name).settings,changes=args.settings_changes;
80
+ if(!changes||!Object.keys(changes).length)throw new Error('Supply task settings changes');
81
+ for(const [key,value] of Object.entries(changes)){
82
+ if(!Object.hasOwn(old,key)||typeof value!=='string'||/[^\x20-\x7e]|[,;():=]/.test(value))throw new Error('Unsupported task setting field/value');
83
+ if(key==='INTERVAL'&&(!/^T#[0-9]+(?:\.[0-9]+)?(?:ns|us|ms|s|m|h)$/i.test(value)||Number(value.slice(2).replace(/[a-z]+$/i,''))<=0))throw new Error('Invalid cyclic interval');
84
+ if(key==='PRIORITY'&&!/^\d+$/.test(value))throw new Error('Invalid task priority');
85
+ if(key==='WATCHDOG'&&!/^\d+$/.test(value))throw new Error('Invalid watchdog');
86
+ if(key==='WATCHDOG_ENABLED'&&!['YES','NO'].includes(value))throw new Error('Invalid watchdog state');
87
+ }
88
+ plan.settings={...old,...changes};
89
+ if(!['CYCLIC','DEFAULT','SYSTEM'].includes(plan.settings.TYPE))throw new Error('Invalid task type');
90
+ target.kind=plan.settings.TYPE;
91
+ plan.settings_text=`TASK ${target.name}\r\n(`+Object.entries(plan.settings).map(([k,v])=>`${k} := ${v}`).join(',\r\n')+'\r\n);\r\n';
92
+ }
93
+ }
94
+ }
95
+ return plan;
96
+ }
97
+ export async function nativeStructureChange(scope,args,{status,saved,snapshot,mutate,prepareSettings,retainPlan}){
98
+ if(args.baseline_saved!==true||(await status()).is_modified!==false)throw new Error('Reconcile/save native edits before structural changes');
99
+ const before=await saved(),live=await snapshot();
100
+ if(!same(inventory(before),inventory(live)))throw new Error('Saved and native structural inventories disagree');
101
+ const plan=structurePlan(scope,args,before,live);
102
+ const settings=plan.settings_text?await prepareSettings(plan.settings_text):{};
103
+ if(retainPlan)await retainPlan({scope,args,baseline:before,native_baseline:live,expected_native:plan.native,expected_settings:plan.settings});
104
+ const result=await mutate({...args,scope,before_native:live,before_files:before.file_hashes,expected_native:plan.native,...settings});
105
+ const after=await saved(),errors=[];
106
+ if(!same(result.snapshot,plan.native))errors.push('Complete native result differs from plan');
107
+ if(!same(inventory(after),inventory(plan.native)))errors.push('Saved structural inventory differs from plan');
108
+ if(result.saved!==true||result.is_modified!==false)errors.push('Native save not proven');
109
+ if(!same(before.globals,after.globals))errors.push('Global declarations changed unexpectedly');
110
+ for(const old of before.pous){
111
+ if(plan.affected.some(n=>eq(n,old.name)))continue;
112
+ if(!same(old,byName(after.pous,old.name)))errors.push('Unrelated POU changed: '+old.name);
113
+ }
114
+ const affectedFile=file=>plan.affected.some(n=>file.replace(/\\/g,'/').toLowerCase().startsWith('poe/'+n.toLowerCase()+'/'));
115
+ for(const file of new Set([...Object.keys(before.program_sources),...Object.keys(after.program_sources)])){
116
+ if(affectedFile(file))continue;
117
+ if(!same(before.program_sources[file],after.program_sources[file]))errors.push('Unrelated program streams changed: '+file);
118
+ }
119
+ for(const file of new Set([...Object.keys(before.translation_files??{}),...Object.keys(after.translation_files??{})])){
120
+ if(affectedFile(file))continue;
121
+ if(before.translation_files?.[file]!==after.translation_files?.[file])errors.push('Unrelated translation file changed: '+file);
122
+ }
123
+ if(scope==='pou'){
124
+ const created=byName(after.pous,args.operation==='create'?args.name:args.new_name);
125
+ if(args.operation==='create'&&(!created?.body_blank||created.variables.length||created.return_type!==(args.return_type??'')||created.language!==(args.language??'ST')))errors.push('New POU language/defaults/return type not proven');
126
+ if(['rename','copy'].includes(args.operation)){
127
+ const old=byName(before.pous,args.name);
128
+ if(!created||!same({...old,name:created.name},created))errors.push('Renamed/copied POU source differs from retained source');
129
+ }
130
+ }
131
+ for(const old of before.tasks){
132
+ if(scope==='task'&&eq(old.name,args.name))continue;
133
+ if(!same(old,byName(after.tasks,old.name)))errors.push('Unrelated task changed: '+old.name);
134
+ }
135
+ if(scope==='task'&&args.operation!=='delete'){
136
+ const actual=byName(after.tasks,args.name),old=byName(before.tasks,args.name);
137
+ const expected=plan.settings??old?.settings;
138
+ if(expected&&!same(expected,actual?.settings))errors.push('Task settings differ from plan');
139
+ }
140
+ return {scope,operation:args.operation,action_performed:true,native_result:result,verification:{accepted:!errors.length,errors},
141
+ baseline:before,saved_result:after,expected_native:plan.native,next_step:errors.length?'STOP: inspect retained evidence; no automatic retry/repair.':
142
+ scope==='pou'&&args.operation==='create'&&args.pou_type==='FUNCTION'?'Define the function VAR_INPUT interface and return logic before fresh Build/Make; a blank function is not a compiled implementation.':'Run fresh Build/Make after intended edits.'};
143
+ }
@@ -0,0 +1,77 @@
1
+ import {variableExpectation} from './edit-session.js';
2
+ const fields=['name','type','section','group','address','initial_value','description'];
3
+ const flagFields=['retain','pdd','opc','disabled','not_on_plc','redundant'];
4
+ const equalName=(a,b)=>a.toUpperCase()===b.toUpperCase();
5
+ // The saved reader trims comment padding; raw native rows remain the mutation guard.
6
+ const savedProjection=rows=>rows.map(v=>Object.fromEntries(fields.map(f=>[f,f==='description'?(v[f]?.trim()||null):v[f]??null])));
7
+ export function variableChangePlan(baseline,args,existingGroups=baseline.map(v=>v.group)) {
8
+ if(!['add','edit','delete'].includes(args.operation))throw new Error('Unknown variable operation');
9
+ let old;
10
+ if(args.operation!=='add') {
11
+ old=baseline.find(v=>equalName(v.name,args.name??''));
12
+ if(!old)throw new Error('Target variable does not exist');
13
+ }
14
+ if(args.operation==='delete') {
15
+ if(args.user_approved!==true||args.references_reviewed!==true)throw new Error('Explicit deletion approval and reference review required');
16
+ return {expected:baseline.filter(v=>v!==old),old};
17
+ }
18
+ const declaration=args.declaration;
19
+ if(old && !equalName(old.name,declaration?.name??'') && args.rename_reviewed!==true)
20
+ throw new Error('Review all code/external references before renaming a declaration');
21
+ const rest=old?baseline.filter(v=>v!==old):baseline;
22
+ // Retain original group evidence even when editing its sole declaration.
23
+ const expected=variableExpectation(rest,declaration,args.pou,existingGroups);
24
+ return {expected,old};
25
+ }
26
+ export async function nativeVariableChange(args,{status,saved,snapshot,snapshotSaved,mutate,compare,globals}) {
27
+ if(args.flags!==undefined) {
28
+ if(!args.flags||typeof args.flags!=='object'||Array.isArray(args.flags)||Object.entries(args.flags).some(([k,v])=>!flagFields.includes(k)||typeof v!=='boolean'))throw new Error('flags must contain only explicit boolean native variable flags');
29
+ if(args.operation==='delete')throw new Error('Flag changes require add/edit, not delete');
30
+ }
31
+ if(args.baseline_saved!==true)throw new Error('Save/reconcile IDE edits before a native variable operation');
32
+ // The combined native read proves saved state before/after enumeration. It is
33
+ // fresh for this operation; the mutation still checks the complete baseline.
34
+ const combined=snapshotSaved?await snapshotSaved():null;
35
+ const state=snapshotSaved?combined:await status();
36
+ if(state?.is_modified!==false)throw new Error('Native project has unsaved or unknown changes; save/reconcile first');
37
+ const before=await saved();
38
+ if(before.warnings?.length)throw new Error('Saved declaration parse warnings; no native action performed');
39
+ const live=snapshotSaved?combined:await snapshot();
40
+ const nativeRows=savedProjection(live.variables);
41
+ const aligned=compare(before.variables,nativeRows);
42
+ if(!aligned.accepted)throw new Error('Live declarations disagree with saved baseline; no native action performed');
43
+ const plan=variableChangePlan(before.variables,args,live.groups.map(g=>g.name));
44
+ if(args.declaration?.section==='VAR_EXTERNAL') {
45
+ const global=await globals();
46
+ const match=global.variables.find(v=>equalName(v.name,args.declaration.name));
47
+ if(!match || match.type.toUpperCase()!==args.declaration.type.toUpperCase())throw new Error('External requires an existing global of the exact name/type');
48
+ }
49
+ const target=live.variables.find(v=>equalName(v.name,args.name??''));
50
+ const group=args.declaration?.group??target?.group;
51
+ if(!live.groups.some(g=>g.name===group&&!g.read_only))throw new Error('Target group is absent or read-only');
52
+ if(args.operation==='edit' && group!==target.group)throw new Error('Native Variable.Group is read-only; use the observed variable-grid move workflow in docs/VARIABLE_WORKSHEET_WORKFLOW.md and verify complete saved/native declarations');
53
+ const result=await mutate({...args,before_native:live.variables});
54
+ const after=await saved();
55
+ const verification=compare(plan.expected,after.variables);
56
+ if(!Array.isArray(result.variables)) {
57
+ verification.accepted=false;verification.errors.push('Complete native read-back is missing');
58
+ } else {
59
+ const nativeExpected=args.operation==='delete'?live.variables.filter(v=>v!==target):
60
+ [...live.variables.filter(v=>args.operation==='add'||v!==target),{...args.declaration}];
61
+ const nativeAfter=compare(nativeExpected,result.variables);
62
+ if(!nativeAfter.accepted){verification.accepted=false;verification.errors.push('Native read-back disagrees with expected declarations');}
63
+ }
64
+ if(result.saved!==true||result.is_modified!==false){verification.accepted=false;verification.errors.push('Native save completion not proven');}
65
+ for(const row of result.variables??[]) {
66
+ const original=live.variables.find(v=>equalName(v.name,row.name))??(plan.old&&equalName(row.name,args.declaration?.name??'')?target:null);
67
+ const changedTarget=args.operation!=='delete'&&equalName(row.name,args.declaration.name);
68
+ for(const field of flagFields) {
69
+ const expected=changedTarget&&Object.hasOwn(args.flags??{},field)?args.flags[field]:(original?.[field]??false);
70
+ if(row[field] !== expected){verification.accepted=false;verification.errors.push(`Native ${field} disagrees with expected flag on ${row.name}`);}
71
+ }
72
+ }
73
+ if(after.warnings?.length){verification.accepted=false;verification.errors.push(...after.warnings);}
74
+ return {operation:args.operation,action_performed:true,native_result:result,verification,
75
+ next_step:verification.accepted?'Run fresh Build/Make after the intended changes are complete.':'STOP: inspect retained before/after evidence; no automatic repair or retry.',
76
+ baseline:before.variables,expected_variables:plan.expected};
77
+ }
@@ -0,0 +1,47 @@
1
+ import {isDeepStrictEqual as same} from 'node:util';
2
+ import {alignSavedNative} from './pou-package.js';
3
+ const sourceKeys=['pous','tasks','globals','program_sources','translation_files'];
4
+ const exact=(saved,name)=>saved.pous.find(p=>p.name===name);
5
+ const identical=(a,b)=>sourceKeys.every(k=>same(a[k],b[k]));
6
+ const inside=(file,name)=>file.replaceAll('\\','/').toLowerCase().startsWith('poe/'+name.toLowerCase()+'/');
7
+
8
+ // Native conversion replaces a POU's source language. It is never a text write
9
+ // into a graphical container, and compile success does not prove canvas wiring.
10
+ export async function convertPou(args,deps){
11
+ if(args.baseline_saved!==true||args.conversion_reviewed!==true)throw Error('Saved baseline and explicit conversion review required');
12
+ if(!/^[A-Za-z_][A-Za-z0-9_]{0,29}$/.test(args.pou??'')||!['FBD','LD'].includes(args.language))throw Error('Exact POU and FBD/LD destination required');
13
+ if(!/^[a-f0-9]{64}$/.test(args.expected_body_sha256??''))throw Error('Exact saved body SHA-256 required');
14
+ const state=await deps.status();if(state.is_modified!==false)throw Error('Unsaved or unknown native baseline');
15
+ const before=await deps.saved(),native=await deps.snapshot(),pou=exact(before,args.pou),meta=native.structure.pous.find(p=>p.name===args.pou);
16
+ alignSavedNative(before,native);
17
+ const flags=['retain','pdd','opc','disabled','not_on_plc','redundant'];
18
+ if(Object.values(native.declarations).some(sheet=>!Array.isArray(sheet.variables)||!Array.isArray(sheet.groups)||sheet.variables.some(v=>flags.some(k=>typeof v[k]!=='boolean'))||sheet.groups.some(g=>typeof g.read_only!=='boolean')))throw Error('Complete native declaration flags/groups are unknown');
19
+ if(!pou||!meta||meta.read_only!==false||!['ST','FBD','LD'].includes(pou.language)||pou.language===args.language)throw Error('Exact writable POU in a different supported language required');
20
+ if(pou.body_sha256!==args.expected_body_sha256)throw Error('Stale POU body; inspect current source before conversion');
21
+ const build_started_ms=Date.now();
22
+ const build=await deps.build(),compiled=await deps.saved(),compiledNative=await deps.snapshot(),compiledState=await deps.status();
23
+ if(!identical(before,compiled)||!same(native,compiledNative))throw Error('Fresh Build changed saved/native source baseline');
24
+ if(build.is_compiled!==true||build.evidence_kind!=='observed_compile_transition'||compiledState.is_compiled!==true||compiledState.is_modified!==false)throw Error('Fresh compiled saved state unverified');
25
+ const source=await deps.source();
26
+ if(source.pou!==args.pou||source.language!==pou.language||!Array.isArray(source.artifacts)||source.artifacts.length!==4||source.artifacts.some(a=>!Number.isFinite(a.modified_ms)||a.modified_ms<build_started_ms||a.modified_ms>Date.now()))throw Error('Exact source POU compiler artifacts were not regenerated during this Build');
27
+ const result=await deps.mutate({pou:args.pou,language:args.language,before_state:compiledNative,before_files:compiled.file_hashes});
28
+ const after=await deps.saved(),afterNative=await deps.snapshot(),afterState=await deps.status(),errors=[];
29
+ const converted=exact(after,args.pou);
30
+ if(result.name!==args.pou||result.saved!==true||result.language!==({FBD:3,LD:4}[args.language])||afterState.is_modified!==false)errors.push('Native conversion/save not proven');
31
+ if(!converted||converted.language!==args.language||converted.type!==pou.type||converted.return_type!==pou.return_type||!same(converted.variables,pou.variables))errors.push('Converted language/type/declarations differ from plan');
32
+ if(pou.body_blank===false&&converted?.body_blank!==false)errors.push('Populated source became blank');
33
+ if(!same(before.pous.filter(p=>p.name!==args.pou),after.pous.filter(p=>p.name!==args.pou)))errors.push('Unrelated POU changed');
34
+ for(const key of ['tasks','globals'])if(!same(before[key],after[key]))errors.push('Unrelated '+key+' changed');
35
+ for(const key of ['program_sources','translation_files'])for(const file of new Set([...Object.keys(before[key]),...Object.keys(after[key])])){
36
+ if(!inside(file,args.pou)&&!same(before[key][file],after[key][file]))errors.push('Unrelated source/translation changed: '+file);
37
+ }
38
+ const expectedNative=structuredClone(native);expectedNative.structure.pous.find(p=>p.name===args.pou).language=({FBD:3,LD:4}[args.language]);
39
+ if(!same(expectedNative,afterNative))errors.push('Native inventory, libraries, groups or declaration flags differ from plan');
40
+ const verification={accepted:!errors.length,errors};
41
+ if(errors.length)return {verification,result,build,before,after,native_before:native,native_after:afterNative};
42
+ // A separate fresh Build verifies the generated graphical source, with all
43
+ // artifact identities and source preservation checked by graphicalListing.
44
+ const listing=await deps.listing(),final=await deps.saved(),finalNative=await deps.snapshot();
45
+ if(listing.accepted!==true||listing.compiler_cache_freshness_verified!==true||!identical(after,final)||!same(afterNative,finalNative))throw Error('Converted source compilation/listing or final native preservation unverified');
46
+ return {verification,result,build,source,build_started_ms,listing,before,after,native_before:native,native_after:afterNative,layout_verified:false,wiring_verified:false};
47
+ }