space-data-module-sdk 0.8.3 → 0.8.5

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 (354) hide show
  1. package/README.md +117 -20
  2. package/bin/space-data-module.js +263 -0
  3. package/docs/.nojekyll +1 -0
  4. package/docs/AGENTS.md +34 -0
  5. package/docs/architecture.svg +141 -0
  6. package/docs/browser-wasmedge-isomorphic.md +179 -0
  7. package/docs/flatsql-streaming-standard.md +341 -0
  8. package/docs/gpu-module-abi.md +193 -0
  9. package/docs/index.html +125 -0
  10. package/docs/isomorphic-pthreads.md +188 -0
  11. package/docs/isomorphic-sdn-runtime-plan.md +167 -0
  12. package/docs/language-runtime-matrix.md +82 -0
  13. package/docs/manifest-structure.svg +114 -0
  14. package/docs/module-bundle-runtime-plan.md +19 -0
  15. package/docs/module-lifecycle.svg +70 -0
  16. package/docs/module-publication-standard.md +540 -0
  17. package/docs/node-red-default-node-parity.md +88 -0
  18. package/docs/protocol-installation.md +196 -0
  19. package/docs/secrets-capability.md +152 -0
  20. package/docs/styles.css +288 -0
  21. package/docs/testing-harness.md +408 -0
  22. package/docs/tri-runtime-parity.md +132 -0
  23. package/docs/wallet-callback.html +2 -0
  24. package/package.json +26 -6
  25. package/schemas/HttpRequestAbi.fbs +41 -0
  26. package/schemas/HttpResponseAbi.fbs +36 -0
  27. package/schemas/PluginManifest.fbs +2 -1
  28. package/src/AGENTS.md +1 -1
  29. package/src/app/codec.js +548 -0
  30. package/src/app/index.js +6 -0
  31. package/src/browser.js +5 -0
  32. package/src/bundle/constants.js +1 -0
  33. package/src/bundle/index.js +15 -0
  34. package/src/bundle/sigdomain.js +179 -0
  35. package/src/bundle/signing.js +767 -0
  36. package/src/bundle/wasm.js +3 -2
  37. package/src/capabilities.js +3 -0
  38. package/src/compat/index.js +7 -0
  39. package/src/compat/sdnLegacy.js +449 -0
  40. package/src/compiler/compileModule.js +556 -55
  41. package/src/compiler/flatcSupport.js +41 -27
  42. package/src/compiler/index.d.ts +2 -0
  43. package/src/compiler/index.js +18 -0
  44. package/src/compiler/invokeGlue.js +1212 -183
  45. package/src/compiler/pthreadArtifactGuard.js +910 -0
  46. package/src/compiler/sdnShimGenerator.js +323 -0
  47. package/src/compiler/wasiThreadsToolchain.js +150 -0
  48. package/src/compliance/index.js +11 -1
  49. package/src/compliance/legacyWildcardPorts.js +228 -0
  50. package/src/compliance/pluginCompliance.js +1000 -96
  51. package/src/field-stream/index.d.ts +31 -0
  52. package/src/field-stream/index.js +119 -0
  53. package/src/flow/dependencyStreamBridge.js +234 -0
  54. package/src/flow/flatsqlLinkShim.js +304 -0
  55. package/src/flow/flowCodec.js +107 -0
  56. package/src/flow/flowCompiler.js +2900 -0
  57. package/src/flow/flowRuntimeHost.js +877 -0
  58. package/src/flow/index.d.ts +117 -0
  59. package/src/flow/index.js +31 -0
  60. package/src/flow/isomorphicFlowHost.js +384 -0
  61. package/src/flow/jsFlowRuntime.js +275 -0
  62. package/src/flow/normalize.js +294 -0
  63. package/src/flow/runtime-src/flow_runtime.cpp +1699 -0
  64. package/src/flow/vendor/sdn-flow/MethodRegistry.js +400 -0
  65. package/src/flow/vendor/sdn-flow/constants.js +39 -0
  66. package/src/flow/vendor/sdn-flow/index.js +29 -0
  67. package/src/flow/vendor/sdn-flow/normalize.js +506 -0
  68. package/src/flow/vendor/sdn-flow/wasmCompatibility.js +189 -0
  69. package/src/generated/http/cpp/HttpRequestAbi_generated.h +285 -0
  70. package/src/generated/http/cpp/HttpResponseAbi_generated.h +192 -0
  71. package/src/generated/http/sdn/http/http-header.js +53 -0
  72. package/src/generated/http/sdn/http/http-header.ts +67 -0
  73. package/src/generated/http/sdn/http/http-request.js +132 -0
  74. package/src/generated/http/sdn/http/http-request.ts +179 -0
  75. package/src/generated/http/sdn/http/http-response.js +144 -0
  76. package/src/generated/http/sdn/http/http-response.ts +168 -0
  77. package/src/generated/http/sdn/http.js +11 -0
  78. package/src/generated/http/sdn/http.ts +4 -0
  79. package/src/generated/orbpro/flow/backpressure-policy.d.ts +12 -0
  80. package/src/generated/orbpro/flow/backpressure-policy.d.ts.map +1 -0
  81. package/src/generated/orbpro/flow/backpressure-policy.js +17 -0
  82. package/src/generated/orbpro/flow/backpressure-policy.js.map +1 -0
  83. package/src/generated/orbpro/flow/backpressure-policy.ts +15 -0
  84. package/src/generated/orbpro/flow/flow-bundle-manifest.d.ts +142 -0
  85. package/src/generated/orbpro/flow/flow-bundle-manifest.d.ts.map +1 -0
  86. package/src/generated/orbpro/flow/flow-bundle-manifest.js +304 -0
  87. package/src/generated/orbpro/flow/flow-bundle-manifest.js.map +1 -0
  88. package/src/generated/orbpro/flow/flow-bundle-manifest.ts +399 -0
  89. package/src/generated/orbpro/flow/flow-edge.d.ts +113 -0
  90. package/src/generated/orbpro/flow/flow-edge.d.ts.map +1 -0
  91. package/src/generated/orbpro/flow/flow-edge.js +221 -0
  92. package/src/generated/orbpro/flow/flow-edge.js.map +1 -0
  93. package/src/generated/orbpro/flow/flow-edge.ts +292 -0
  94. package/src/generated/orbpro/flow/flow-node.d.ts +80 -0
  95. package/src/generated/orbpro/flow/flow-node.d.ts.map +1 -0
  96. package/src/generated/orbpro/flow/flow-node.js +158 -0
  97. package/src/generated/orbpro/flow/flow-node.js.map +1 -0
  98. package/src/generated/orbpro/flow/flow-node.ts +202 -0
  99. package/src/generated/orbpro/flow/flow-program.d.ts +164 -0
  100. package/src/generated/orbpro/flow/flow-program.d.ts.map +1 -0
  101. package/src/generated/orbpro/flow/flow-program.js +350 -0
  102. package/src/generated/orbpro/flow/flow-program.js.map +1 -0
  103. package/src/generated/orbpro/flow/flow-program.ts +477 -0
  104. package/src/generated/orbpro/flow/flow-trigger.d.ts +108 -0
  105. package/src/generated/orbpro/flow/flow-trigger.d.ts.map +1 -0
  106. package/src/generated/orbpro/flow/flow-trigger.js +207 -0
  107. package/src/generated/orbpro/flow/flow-trigger.js.map +1 -0
  108. package/src/generated/orbpro/flow/flow-trigger.ts +276 -0
  109. package/src/generated/orbpro/flow/node-kind.d.ts +13 -0
  110. package/src/generated/orbpro/flow/node-kind.d.ts.map +1 -0
  111. package/src/generated/orbpro/flow/node-kind.js +16 -0
  112. package/src/generated/orbpro/flow/node-kind.js.map +1 -0
  113. package/src/generated/orbpro/{invoke.ts → flow/node-kind.ts} +12 -2
  114. package/src/generated/orbpro/flow/plugin-version-binding.d.ts +52 -0
  115. package/src/generated/orbpro/flow/plugin-version-binding.d.ts.map +1 -0
  116. package/src/generated/orbpro/flow/plugin-version-binding.js +85 -0
  117. package/src/generated/orbpro/flow/plugin-version-binding.js.map +1 -0
  118. package/src/generated/orbpro/flow/plugin-version-binding.ts +121 -0
  119. package/src/generated/orbpro/flow/schema-hash-binding.d.ts +70 -0
  120. package/src/generated/orbpro/flow/schema-hash-binding.d.ts.map +1 -0
  121. package/src/generated/orbpro/flow/schema-hash-binding.js +143 -0
  122. package/src/generated/orbpro/flow/schema-hash-binding.js.map +1 -0
  123. package/src/generated/orbpro/flow/schema-hash-binding.ts +189 -0
  124. package/src/generated/orbpro/flow/trigger-binding.d.ts +71 -0
  125. package/src/generated/orbpro/flow/trigger-binding.d.ts.map +1 -0
  126. package/src/generated/orbpro/flow/trigger-binding.js +142 -0
  127. package/src/generated/orbpro/flow/trigger-binding.js.map +1 -0
  128. package/src/generated/orbpro/flow/trigger-binding.ts +181 -0
  129. package/src/generated/orbpro/flow/trigger-kind.d.ts +14 -0
  130. package/src/generated/orbpro/flow/trigger-kind.d.ts.map +1 -0
  131. package/src/generated/orbpro/flow/trigger-kind.js +17 -0
  132. package/src/generated/orbpro/flow/trigger-kind.js.map +1 -0
  133. package/src/generated/orbpro/flow/trigger-kind.ts +17 -0
  134. package/src/generated/orbpro/flow.d.ts +21 -0
  135. package/src/generated/orbpro/flow.d.ts.map +1 -0
  136. package/src/generated/orbpro/flow.js +23 -0
  137. package/src/generated/orbpro/flow.js.map +1 -0
  138. package/src/generated/orbpro/flow.ts +24 -0
  139. package/src/generated/orbpro/manifest/capability-kind.d.ts +2 -1
  140. package/src/generated/orbpro/manifest/capability-kind.js +1 -0
  141. package/src/generated/orbpro/manifest/capability-kind.ts +1 -0
  142. package/src/generated/orbpro/plugin/stream-invoke-request.d.ts +93 -0
  143. package/src/generated/orbpro/plugin/stream-invoke-request.js +160 -0
  144. package/src/generated/orbpro/plugin/stream-invoke-request.ts +211 -0
  145. package/src/generated/orbpro/plugin/stream-invoke-response.d.ts +99 -0
  146. package/src/generated/orbpro/plugin/stream-invoke-response.js +175 -0
  147. package/src/generated/orbpro/plugin/stream-invoke-response.ts +226 -0
  148. package/src/generated/orbpro/plugin.js +8 -0
  149. package/src/generated/orbpro/stream.d.ts +12 -0
  150. package/src/generated/orbpro/stream.js +14 -0
  151. package/src/generated/orbpro/stream.ts +15 -0
  152. package/src/generated/spacedatastandards/plg/EntryFunction.d.ts +52 -0
  153. package/src/generated/spacedatastandards/plg/EntryFunction.d.ts.map +1 -0
  154. package/src/generated/spacedatastandards/plg/EntryFunction.js +106 -0
  155. package/src/generated/spacedatastandards/plg/EntryFunction.ts +162 -0
  156. package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.d.ts +90 -0
  157. package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.d.ts.map +1 -0
  158. package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.js +189 -0
  159. package/src/generated/spacedatastandards/plg/FlatBufferTypeRef.ts +270 -0
  160. package/src/generated/spacedatastandards/plg/PLG.d.ts +492 -0
  161. package/src/generated/spacedatastandards/plg/PLG.d.ts.map +1 -0
  162. package/src/generated/spacedatastandards/plg/PLG.js +1139 -0
  163. package/src/generated/spacedatastandards/plg/PLG.ts +1600 -0
  164. package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.d.ts +63 -0
  165. package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.d.ts.map +1 -0
  166. package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.js +138 -0
  167. package/src/generated/spacedatastandards/plg/PLGAcceptedTypeSet.ts +189 -0
  168. package/src/generated/spacedatastandards/plg/PLGBuildArtifact.d.ts +41 -0
  169. package/src/generated/spacedatastandards/plg/PLGBuildArtifact.d.ts.map +1 -0
  170. package/src/generated/spacedatastandards/plg/PLGBuildArtifact.js +104 -0
  171. package/src/generated/spacedatastandards/plg/PLGBuildArtifact.ts +151 -0
  172. package/src/generated/spacedatastandards/plg/PLGFlowEdge.d.ts +68 -0
  173. package/src/generated/spacedatastandards/plg/PLGFlowEdge.d.ts.map +1 -0
  174. package/src/generated/spacedatastandards/plg/PLGFlowEdge.js +124 -0
  175. package/src/generated/spacedatastandards/plg/PLGFlowEdge.ts +184 -0
  176. package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.d.ts +58 -0
  177. package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.d.ts.map +1 -0
  178. package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.js +126 -0
  179. package/src/generated/spacedatastandards/plg/PLGFlowEdgeContract.ts +161 -0
  180. package/src/generated/spacedatastandards/plg/PLGFlowNode.d.ts +82 -0
  181. package/src/generated/spacedatastandards/plg/PLGFlowNode.d.ts.map +1 -0
  182. package/src/generated/spacedatastandards/plg/PLGFlowNode.js +164 -0
  183. package/src/generated/spacedatastandards/plg/PLGFlowNode.ts +244 -0
  184. package/src/generated/spacedatastandards/plg/PLGFlowTrigger.d.ts +55 -0
  185. package/src/generated/spacedatastandards/plg/PLGFlowTrigger.d.ts.map +1 -0
  186. package/src/generated/spacedatastandards/plg/PLGFlowTrigger.js +105 -0
  187. package/src/generated/spacedatastandards/plg/PLGFlowTrigger.ts +162 -0
  188. package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.d.ts +42 -0
  189. package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.d.ts.map +1 -0
  190. package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.js +83 -0
  191. package/src/generated/spacedatastandards/plg/PLGFlowTriggerBinding.ts +127 -0
  192. package/src/generated/spacedatastandards/plg/PLGHostCapability.d.ts +36 -0
  193. package/src/generated/spacedatastandards/plg/PLGHostCapability.d.ts.map +1 -0
  194. package/src/generated/spacedatastandards/plg/PLGHostCapability.js +90 -0
  195. package/src/generated/spacedatastandards/plg/PLGHostCapability.ts +127 -0
  196. package/src/generated/spacedatastandards/plg/PLGMethodManifest.d.ts +53 -0
  197. package/src/generated/spacedatastandards/plg/PLGMethodManifest.d.ts.map +1 -0
  198. package/src/generated/spacedatastandards/plg/PLGMethodManifest.js +153 -0
  199. package/src/generated/spacedatastandards/plg/PLGMethodManifest.ts +210 -0
  200. package/src/generated/spacedatastandards/plg/PLGPortManifest.d.ts +70 -0
  201. package/src/generated/spacedatastandards/plg/PLGPortManifest.d.ts.map +1 -0
  202. package/src/generated/spacedatastandards/plg/PLGPortManifest.js +149 -0
  203. package/src/generated/spacedatastandards/plg/PLGPortManifest.ts +212 -0
  204. package/src/generated/spacedatastandards/plg/PLGProtocolSpec.d.ts +73 -0
  205. package/src/generated/spacedatastandards/plg/PLGProtocolSpec.d.ts.map +1 -0
  206. package/src/generated/spacedatastandards/plg/PLGProtocolSpec.js +199 -0
  207. package/src/generated/spacedatastandards/plg/PLGProtocolSpec.ts +292 -0
  208. package/src/generated/spacedatastandards/plg/PLGTimerSpec.d.ts +40 -0
  209. package/src/generated/spacedatastandards/plg/PLGTimerSpec.d.ts.map +1 -0
  210. package/src/generated/spacedatastandards/plg/PLGTimerSpec.js +103 -0
  211. package/src/generated/spacedatastandards/plg/PLGTimerSpec.ts +148 -0
  212. package/src/generated/spacedatastandards/plg/PluginCapability.d.ts +41 -0
  213. package/src/generated/spacedatastandards/plg/PluginCapability.d.ts.map +1 -0
  214. package/src/generated/spacedatastandards/plg/PluginCapability.js +82 -0
  215. package/src/generated/spacedatastandards/plg/{plugin-capability.ts → PluginCapability.ts} +55 -18
  216. package/src/generated/spacedatastandards/plg/PluginDependency.d.ts +42 -0
  217. package/src/generated/spacedatastandards/plg/PluginDependency.d.ts.map +1 -0
  218. package/src/generated/spacedatastandards/plg/PluginDependency.js +80 -0
  219. package/src/generated/spacedatastandards/plg/PluginDependency.ts +124 -0
  220. package/src/generated/spacedatastandards/plg/TAB.d.ts +85 -0
  221. package/src/generated/spacedatastandards/plg/TAB.d.ts.map +1 -0
  222. package/src/generated/spacedatastandards/plg/TAB.js +173 -0
  223. package/src/generated/spacedatastandards/plg/TAB.ts +228 -0
  224. package/src/generated/spacedatastandards/plg/bufferMutability.d.ts +18 -0
  225. package/src/generated/spacedatastandards/plg/bufferMutability.d.ts.map +1 -0
  226. package/src/generated/spacedatastandards/plg/bufferMutability.js +20 -0
  227. package/src/generated/spacedatastandards/plg/bufferMutability.ts +23 -0
  228. package/src/generated/spacedatastandards/plg/bufferOwnership.d.ts +18 -0
  229. package/src/generated/spacedatastandards/plg/bufferOwnership.d.ts.map +1 -0
  230. package/src/generated/spacedatastandards/plg/bufferOwnership.js +20 -0
  231. package/src/generated/spacedatastandards/plg/bufferOwnership.ts +23 -0
  232. package/src/generated/spacedatastandards/plg/drainBehavior.d.ts +20 -0
  233. package/src/generated/spacedatastandards/plg/drainBehavior.d.ts.map +1 -0
  234. package/src/generated/spacedatastandards/plg/drainBehavior.js +22 -0
  235. package/src/generated/spacedatastandards/plg/drainBehavior.ts +25 -0
  236. package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.d.ts +10 -0
  237. package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.d.ts.map +1 -0
  238. package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.js +12 -0
  239. package/src/generated/spacedatastandards/plg/flowEdgeRoutePolicy.ts +13 -0
  240. package/src/generated/spacedatastandards/plg/hostCapabilityKind.d.ts +51 -0
  241. package/src/generated/spacedatastandards/plg/hostCapabilityKind.d.ts.map +1 -0
  242. package/src/generated/spacedatastandards/plg/hostCapabilityKind.js +53 -0
  243. package/src/generated/spacedatastandards/plg/hostCapabilityKind.ts +55 -0
  244. package/src/generated/spacedatastandards/plg/invokeSurfaceKind.d.ts +15 -0
  245. package/src/generated/spacedatastandards/plg/invokeSurfaceKind.d.ts.map +1 -0
  246. package/src/generated/spacedatastandards/plg/invokeSurfaceKind.js +17 -0
  247. package/src/generated/spacedatastandards/plg/invokeSurfaceKind.ts +19 -0
  248. package/src/generated/spacedatastandards/plg/main.d.ts +29 -0
  249. package/src/generated/spacedatastandards/plg/main.d.ts.map +1 -0
  250. package/src/generated/spacedatastandards/plg/main.js +30 -0
  251. package/src/generated/spacedatastandards/plg/main.ts +32 -0
  252. package/src/generated/spacedatastandards/plg/payloadWireFormat.d.ts +17 -0
  253. package/src/generated/spacedatastandards/plg/payloadWireFormat.d.ts.map +1 -0
  254. package/src/generated/spacedatastandards/plg/payloadWireFormat.js +19 -0
  255. package/src/generated/spacedatastandards/plg/payloadWireFormat.ts +21 -0
  256. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts +82 -0
  257. package/src/generated/spacedatastandards/plg/pluginCategory.d.ts.map +1 -0
  258. package/src/generated/spacedatastandards/plg/pluginCategory.js +84 -0
  259. package/src/generated/spacedatastandards/plg/pluginCategory.ts +103 -0
  260. package/src/generated/spacedatastandards/plg/publicationState.d.ts +18 -0
  261. package/src/generated/spacedatastandards/plg/publicationState.d.ts.map +1 -0
  262. package/src/generated/spacedatastandards/plg/publicationState.js +20 -0
  263. package/src/generated/spacedatastandards/plg/purchaseTier.d.ts +18 -0
  264. package/src/generated/spacedatastandards/plg/purchaseTier.d.ts.map +1 -0
  265. package/src/generated/spacedatastandards/plg/purchaseTier.js +20 -0
  266. package/src/host/AGENTS.md +2 -2
  267. package/src/host/abi.js +53 -66
  268. package/src/host/browserHost.js +355 -8
  269. package/src/host/cpp/keyslotClient.hpp +231 -0
  270. package/src/host/cpp/secretsClient.hpp +583 -0
  271. package/src/host/hostcallWire.js +183 -0
  272. package/src/host/index.js +2 -0
  273. package/src/host/isomorphicLoader.js +44 -7
  274. package/src/host/nodeHost.js +227 -8
  275. package/src/host/sabHostcallChannel.js +206 -0
  276. package/src/host/timerDriver.js +203 -0
  277. package/src/host/wasiShim.js +41 -1
  278. package/src/host/wasiThreadBrowserWorker.mjs +48 -0
  279. package/src/host/wasiThreadHost.js +393 -0
  280. package/src/host/wasiThreadWorker.mjs +32 -0
  281. package/src/host/wasiThreadWorkerRuntime.js +93 -0
  282. package/src/http/index.js +339 -0
  283. package/src/index.d.ts +716 -10
  284. package/src/index.js +4 -0
  285. package/src/invoke/codec.js +1202 -128
  286. package/src/invoke/index.js +5 -1
  287. package/src/licensing/index.d.ts +2 -0
  288. package/src/licensing/index.js +2 -0
  289. package/src/licensing/records.js +287 -8
  290. package/src/manifest/codec.js +62 -14
  291. package/src/manifest/index.js +2 -0
  292. package/src/manifest/legacyToPlg.js +164 -25
  293. package/src/manifest/normalize.js +1 -0
  294. package/src/manifest/plgCodec.js +1303 -62
  295. package/src/manifest/typeRefs.js +162 -24
  296. package/src/runtime/compiledRuntimeAbi.js +47 -0
  297. package/src/runtime/compiledRuntimeAbi.json +33 -0
  298. package/src/runtime/index.d.ts +32 -0
  299. package/src/runtime/index.js +1 -0
  300. package/src/runtime-host/index.js +3 -0
  301. package/src/runtime-host/methodRegistry.js +55 -0
  302. package/src/standards/index.js +200 -56
  303. package/src/standards/sharedCatalog.js +16 -0
  304. package/src/testing/browserModuleHarness.js +604 -31
  305. package/src/testing/docker/wasmedge-parity.Dockerfile +39 -0
  306. package/src/testing/index.d.ts +151 -0
  307. package/src/testing/index.js +21 -0
  308. package/src/testing/native/wasmedge_emscripten_pthread_runner.c +25 -1
  309. package/src/testing/parityBrowserRunner.js +138 -0
  310. package/src/testing/parityHarness.js +616 -0
  311. package/src/testing/parityLanes.js +537 -0
  312. package/src/testing/processInvoke.js +29 -21
  313. package/src/testing/streamInvokeCodec.js +10 -175
  314. package/src/testing/wasmedgePin.json +6 -0
  315. package/src/testing/workerModuleHarness.js +242 -0
  316. package/src/testing/workerModuleHarnessWorker.js +176 -0
  317. package/src/transport/index.js +3 -0
  318. package/src/transport/pki.js +299 -11
  319. package/src/transport/records.js +16 -7
  320. package/src/utils/ecdsaDer.js +106 -0
  321. package/src/utils/wasmCrypto.js +0 -43
  322. package/templates/gpu-module/README.md +48 -0
  323. package/templates/gpu-module/include/space_data_gpu_abi.h +122 -0
  324. package/templates/gpu-module/manifest.json +84 -0
  325. package/schemas/PluginInvokeRequest.fbs +0 -18
  326. package/schemas/PluginInvokeResponse.fbs +0 -30
  327. package/schemas/spacedatastandards/PLG.fbs +0 -191
  328. package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts +0 -51
  329. package/src/generated/orbpro/invoke/plugin-invoke-request.d.ts.map +0 -1
  330. package/src/generated/orbpro/invoke/plugin-invoke-request.js +0 -131
  331. package/src/generated/orbpro/invoke/plugin-invoke-request.js.map +0 -1
  332. package/src/generated/orbpro/invoke/plugin-invoke-request.ts +0 -173
  333. package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts +0 -76
  334. package/src/generated/orbpro/invoke/plugin-invoke-response.d.ts.map +0 -1
  335. package/src/generated/orbpro/invoke/plugin-invoke-response.js +0 -184
  336. package/src/generated/orbpro/invoke/plugin-invoke-response.js.map +0 -1
  337. package/src/generated/orbpro/invoke/plugin-invoke-response.ts +0 -243
  338. package/src/generated/orbpro/invoke.d.ts +0 -3
  339. package/src/generated/orbpro/invoke.js +0 -5
  340. package/src/generated/spacedatastandards/plg/entry-function.js +0 -86
  341. package/src/generated/spacedatastandards/plg/entry-function.ts +0 -119
  342. package/src/generated/spacedatastandards/plg/index.js +0 -16
  343. package/src/generated/spacedatastandards/plg/index.ts +0 -11
  344. package/src/generated/spacedatastandards/plg/plg.js +0 -657
  345. package/src/generated/spacedatastandards/plg/plg.ts +0 -924
  346. package/src/generated/spacedatastandards/plg/plugin-capability.js +0 -66
  347. package/src/generated/spacedatastandards/plg/plugin-category.js +0 -15
  348. package/src/generated/spacedatastandards/plg/plugin-category.ts +0 -53
  349. package/src/generated/spacedatastandards/plg/plugin-dependency.js +0 -63
  350. package/src/generated/spacedatastandards/plg/plugin-dependency.ts +0 -86
  351. package/src/generated/spacedatastandards/plg/publication-state.js +0 -9
  352. package/src/generated/spacedatastandards/plg/purchase-tier.js +0 -9
  353. /package/src/generated/spacedatastandards/plg/{publication-state.ts → publicationState.ts} +0 -0
  354. /package/src/generated/spacedatastandards/plg/{purchase-tier.ts → purchaseTier.ts} +0 -0
@@ -0,0 +1,616 @@
1
+ /**
2
+ * Tri-runtime parity harness — ONE dist/isomorphic/module.wasm, THREE lanes:
3
+ *
4
+ * 1. "browser" — real headless Chrome (COOP/COEP-served page, the SDK
5
+ * browser WASI shim; never jsdom).
6
+ * 2. "wasmedge" — native WasmEdge binary, version-locked to the pin.
7
+ * 3. "docker-wasmedge" — pinned Docker WasmEdge container.
8
+ *
9
+ * Identical inputs (the SAME encoded stdin bytes, the SAME scripted guest env
10
+ * and args, delivered explicitly — never ambient host state) must produce
11
+ * byte-identical outputs and identical error/trap classes across every lane
12
+ * and every thread count. Any divergence is a P1 SDK defect and the harness
13
+ * FAILS LOUDLY: non-zero exit, per-case mismatch report with SHA-256 digests
14
+ * and the first divergent byte offset.
15
+ *
16
+ * The WasmEdge version pin (native binary AND docker image) lives in ONE
17
+ * place: src/testing/wasmedgePin.json. Both wasmedge lanes verify the live
18
+ * runtime against that pin before executing anything; drift is itself a
19
+ * failure ("pin-drift"), never a warning.
20
+ *
21
+ * Node-only module (lane runners spawn processes / servers). The browser-side
22
+ * code that runs inside Chrome is bundled from parityBrowserRunner.js.
23
+ */
24
+
25
+ import { createHash } from "node:crypto";
26
+ import { readFile } from "node:fs/promises";
27
+ import { readFileSync } from "node:fs";
28
+ import path from "node:path";
29
+ import { fileURLToPath } from "node:url";
30
+
31
+ import { encodePluginInvokeRequest } from "../invoke/codec.js";
32
+ import { toUint8Array } from "../runtime/bufferLike.js";
33
+ import { toLoadableWasmBytes } from "./browserModuleHarness.js";
34
+
35
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
36
+
37
+ export const PARITY_LANES = Object.freeze([
38
+ "browser",
39
+ "wasmedge",
40
+ "docker-wasmedge",
41
+ ]);
42
+
43
+ export const DEFAULT_THREAD_ENV_VAR = "SDM_PARITY_THREADS";
44
+ export const DEFAULT_THREAD_COUNTS = Object.freeze([1]);
45
+
46
+ /** Exit classes every lane runner must normalize to. */
47
+ export const ExitClass = Object.freeze({
48
+ Ok: "ok",
49
+ GuestError: "guest-error",
50
+ Trap: "trap",
51
+ HarnessFailure: "harness-failure",
52
+ });
53
+
54
+ // --- Pin (single source of truth) ------------------------------------------
55
+
56
+ /**
57
+ * Load the WasmEdge runtime pin. This is THE one place the native-binary and
58
+ * docker-image versions come from; the docker image tag is derived here and
59
+ * nowhere else, so the pair can only bump together.
60
+ */
61
+ export function loadWasmEdgePin() {
62
+ const pinPath = path.join(__dirname, "wasmedgePin.json");
63
+ const pin = JSON.parse(readFileSync(pinPath, "utf8"));
64
+ const version = String(pin.wasmedgeVersion ?? "").trim();
65
+ if (!/^\d+\.\d+\.\d+$/.test(version)) {
66
+ throw new Error(
67
+ `wasmedgePin.json wasmedgeVersion must be a semver string, got "${version}".`,
68
+ );
69
+ }
70
+ const repository = String(pin.dockerImageRepository ?? "").trim();
71
+ if (!repository) {
72
+ throw new Error("wasmedgePin.json dockerImageRepository is required.");
73
+ }
74
+ return Object.freeze({
75
+ wasmedgeVersion: version,
76
+ dockerImageRepository: repository,
77
+ dockerImage: `${repository}:${version}`,
78
+ dockerfilePath: path.join(__dirname, String(pin.dockerfile)),
79
+ dockerfileContextDir: path.dirname(
80
+ path.join(__dirname, String(pin.dockerfile)),
81
+ ),
82
+ pinPath,
83
+ });
84
+ }
85
+
86
+ /**
87
+ * Assert a `wasmedge --version` style output matches the pin exactly.
88
+ * Loud pin-drift error otherwise — drift between the host-embedded runtime
89
+ * and the container runtime is itself a defect, never a warning.
90
+ */
91
+ export function assertWasmEdgeVersionMatchesPin(versionOutput, pin, context) {
92
+ const match = /wasmedge version\s+([0-9]+\.[0-9]+\.[0-9]+)/i.exec(
93
+ String(versionOutput ?? ""),
94
+ );
95
+ if (!match) {
96
+ throw new Error(
97
+ `PARITY PIN-DRIFT (${context}): could not parse a WasmEdge version from: ${String(versionOutput).trim().slice(0, 200)}`,
98
+ );
99
+ }
100
+ if (match[1] !== pin.wasmedgeVersion) {
101
+ throw new Error(
102
+ `PARITY PIN-DRIFT (${context}): runtime reports WasmEdge ${match[1]} but src/testing/wasmedgePin.json pins ${pin.wasmedgeVersion}. ` +
103
+ "Host and container WasmEdge versions pin and bump TOGETHER; align the runtime with the pin (or bump the pin pair deliberately).",
104
+ );
105
+ }
106
+ return match[1];
107
+ }
108
+
109
+ // --- Fixture normalization ---------------------------------------------------
110
+
111
+ function decodeBase64(value) {
112
+ return new Uint8Array(Buffer.from(String(value), "base64"));
113
+ }
114
+
115
+ function decodeHex(value) {
116
+ const clean = String(value).replace(/\s+/g, "");
117
+ if (clean.length % 2 !== 0 || /[^0-9a-fA-F]/.test(clean)) {
118
+ throw new Error("Invalid hex payload in parity fixture.");
119
+ }
120
+ return new Uint8Array(Buffer.from(clean, "hex"));
121
+ }
122
+
123
+ async function resolveCasePayload(spec, fixtureDir, label) {
124
+ if (spec.payloadFile !== undefined) {
125
+ const filePath = path.resolve(fixtureDir, String(spec.payloadFile));
126
+ return new Uint8Array(await readFile(filePath));
127
+ }
128
+ if (spec.payloadBase64 !== undefined) return decodeBase64(spec.payloadBase64);
129
+ if (spec.payloadHex !== undefined) return decodeHex(spec.payloadHex);
130
+ if (spec.payloadUtf8 !== undefined) {
131
+ return new TextEncoder().encode(String(spec.payloadUtf8));
132
+ }
133
+ if (spec.payload !== undefined) {
134
+ const bytes = toUint8Array(spec.payload);
135
+ if (bytes) return bytes;
136
+ }
137
+ throw new Error(
138
+ `Parity fixture ${label}: input needs payloadFile, payloadBase64, payloadHex, payloadUtf8, or payload bytes.`,
139
+ );
140
+ }
141
+
142
+ function normalizeEnv(env, label) {
143
+ if (env === undefined || env === null) return {};
144
+ if (typeof env !== "object" || Array.isArray(env)) {
145
+ throw new Error(`Parity fixture ${label}: env must be a plain object.`);
146
+ }
147
+ const normalized = {};
148
+ for (const [key, value] of Object.entries(env)) {
149
+ normalized[String(key)] = String(value);
150
+ }
151
+ return normalized;
152
+ }
153
+
154
+ function normalizeExpect(expect, label) {
155
+ if (expect === undefined || expect === null) return null;
156
+ const value = String(expect);
157
+ if (
158
+ value !== ExitClass.Ok &&
159
+ value !== ExitClass.GuestError &&
160
+ value !== ExitClass.Trap
161
+ ) {
162
+ throw new Error(
163
+ `Parity fixture ${label}: expect must be "ok", "guest-error", or "trap" (omit for class-parity-only).`,
164
+ );
165
+ }
166
+ return value;
167
+ }
168
+
169
+ function normalizeThreadCounts(threadCounts, label) {
170
+ if (threadCounts === undefined || threadCounts === null) return null;
171
+ if (!Array.isArray(threadCounts) || threadCounts.length === 0) {
172
+ throw new Error(
173
+ `Parity fixture ${label}: threadCounts must be a non-empty array.`,
174
+ );
175
+ }
176
+ const counts = threadCounts.map((value) => {
177
+ const count = Number(value);
178
+ if (!Number.isInteger(count) || count < 1) {
179
+ throw new Error(
180
+ `Parity fixture ${label}: thread counts must be positive integers.`,
181
+ );
182
+ }
183
+ return count;
184
+ });
185
+ return Object.freeze([...new Set(counts)]);
186
+ }
187
+
188
+ /**
189
+ * Normalize a parity fixture document into an executable plan. Every case is
190
+ * reduced to EXACT stdin bytes here, once, on the orchestrating side — all
191
+ * lanes then receive the identical bytes (base64 across the browser wire).
192
+ *
193
+ * Fixture shape:
194
+ * {
195
+ * name, threadEnvVar?, threadCounts?, env?, args?,
196
+ * cases: [{
197
+ * id,
198
+ * request?: {methodId, inputs:[{portId, payloadFile|payloadBase64|payloadHex|payloadUtf8, typeRef?}], ...},
199
+ * stdinFile? | stdinBase64? | stdinHex? | stdinUtf8?,
200
+ * env?, args?, expect?: "ok"|"guest-error"|"trap", threadCounts?
201
+ * }]
202
+ * }
203
+ */
204
+ export async function normalizeParityFixture(fixture, options = {}) {
205
+ if (!fixture || typeof fixture !== "object") {
206
+ throw new Error("Parity fixture must be an object.");
207
+ }
208
+ const fixtureDir = options.fixtureDir ?? process.cwd();
209
+ const name = String(fixture.name ?? "parity-fixture");
210
+ const threadEnvVar = String(fixture.threadEnvVar ?? DEFAULT_THREAD_ENV_VAR);
211
+ const fixtureThreadCounts =
212
+ normalizeThreadCounts(fixture.threadCounts, name) ?? DEFAULT_THREAD_COUNTS;
213
+ const fixtureEnv = normalizeEnv(fixture.env, name);
214
+ const fixtureArgs = Array.isArray(fixture.args)
215
+ ? fixture.args.map(String)
216
+ : [];
217
+
218
+ if (!Array.isArray(fixture.cases) || fixture.cases.length === 0) {
219
+ throw new Error(`Parity fixture ${name}: cases[] must be non-empty.`);
220
+ }
221
+
222
+ const cases = [];
223
+ const seenIds = new Set();
224
+ for (const caseSpec of fixture.cases) {
225
+ const id = String(caseSpec?.id ?? "").trim();
226
+ if (!id) throw new Error(`Parity fixture ${name}: every case needs an id.`);
227
+ if (seenIds.has(id)) {
228
+ throw new Error(`Parity fixture ${name}: duplicate case id "${id}".`);
229
+ }
230
+ seenIds.add(id);
231
+ const label = `${name}/${id}`;
232
+
233
+ let stdinBytes;
234
+ if (caseSpec.request !== undefined) {
235
+ const request = caseSpec.request;
236
+ const inputs = [];
237
+ for (const input of request.inputs ?? []) {
238
+ inputs.push({
239
+ ...input,
240
+ payload: await resolveCasePayload(input, fixtureDir, label),
241
+ payloadFile: undefined,
242
+ payloadBase64: undefined,
243
+ payloadHex: undefined,
244
+ payloadUtf8: undefined,
245
+ });
246
+ }
247
+ stdinBytes = encodePluginInvokeRequest({ ...request, inputs });
248
+ } else if (caseSpec.stdinFile !== undefined) {
249
+ stdinBytes = new Uint8Array(
250
+ await readFile(path.resolve(fixtureDir, String(caseSpec.stdinFile))),
251
+ );
252
+ } else if (caseSpec.stdinBase64 !== undefined) {
253
+ stdinBytes = decodeBase64(caseSpec.stdinBase64);
254
+ } else if (caseSpec.stdinHex !== undefined) {
255
+ stdinBytes = decodeHex(caseSpec.stdinHex);
256
+ } else if (caseSpec.stdinUtf8 !== undefined) {
257
+ stdinBytes = new TextEncoder().encode(String(caseSpec.stdinUtf8));
258
+ } else {
259
+ throw new Error(
260
+ `Parity fixture ${label}: case needs request or stdinFile/stdinBase64/stdinHex/stdinUtf8.`,
261
+ );
262
+ }
263
+
264
+ cases.push(
265
+ Object.freeze({
266
+ id,
267
+ stdinBytes,
268
+ env: Object.freeze({ ...fixtureEnv, ...normalizeEnv(caseSpec.env, label) }),
269
+ args: Object.freeze(
270
+ Array.isArray(caseSpec.args) ? caseSpec.args.map(String) : fixtureArgs,
271
+ ),
272
+ expect: normalizeExpect(caseSpec.expect, label),
273
+ threadCounts:
274
+ normalizeThreadCounts(caseSpec.threadCounts, label) ??
275
+ fixtureThreadCounts,
276
+ }),
277
+ );
278
+ }
279
+
280
+ return Object.freeze({ name, threadEnvVar, cases: Object.freeze(cases) });
281
+ }
282
+
283
+ export async function loadParityFixture(fixturePath) {
284
+ const resolved = path.resolve(fixturePath);
285
+ const fixture = JSON.parse(await readFile(resolved, "utf8"));
286
+ return normalizeParityFixture(fixture, {
287
+ fixtureDir: path.dirname(resolved),
288
+ });
289
+ }
290
+
291
+ // --- Diff engine --------------------------------------------------------------
292
+
293
+ export function sha256Hex(bytes) {
294
+ return createHash("sha256").update(bytes).digest("hex");
295
+ }
296
+
297
+ function firstDivergentOffset(a, b) {
298
+ const limit = Math.min(a.length, b.length);
299
+ for (let index = 0; index < limit; index += 1) {
300
+ if (a[index] !== b[index]) return index;
301
+ }
302
+ return a.length === b.length ? -1 : limit;
303
+ }
304
+
305
+ function hexWindow(bytes, offset, radius = 8) {
306
+ const start = Math.max(0, offset - radius);
307
+ const end = Math.min(bytes.length, offset + radius);
308
+ return Buffer.from(bytes.subarray(start, end)).toString("hex");
309
+ }
310
+
311
+ function runKey(run) {
312
+ return `${run.lane}@t${run.threadCount}`;
313
+ }
314
+
315
+ /**
316
+ * Compare lane results for one plan. `runs` is a flat array of
317
+ * {lane, caseId, threadCount, exitClass, exitDetail?, stdout, stderr?, stateFiles?}.
318
+ *
319
+ * Rules (per case, across EVERY lane × thread-count run):
320
+ * - exit classes must all match (and match `expect` when the case sets one);
321
+ * - when the class is "ok", stdout must be byte-identical everywhere —
322
+ * within a lane across thread counts AND across lanes;
323
+ * - persisted state manifests (stateFiles: {relPath: sha256}) must be
324
+ * identical across every run that reports one (lanes without filesystem
325
+ * authority report null and are exempt, never silently equal).
326
+ *
327
+ * Returns {ok, failures:[{caseId, kind, message, runs:[...]}], comparisons}.
328
+ */
329
+ export function diffParityRuns(plan, runs) {
330
+ const failures = [];
331
+ let comparisons = 0;
332
+
333
+ for (const planCase of plan.cases) {
334
+ const caseRuns = runs.filter((run) => run.caseId === planCase.id);
335
+ const expectedRunCount = planCase.threadCounts.length;
336
+ const lanes = [...new Set(caseRuns.map((run) => run.lane))];
337
+ for (const lane of lanes) {
338
+ const laneRuns = caseRuns.filter((run) => run.lane === lane);
339
+ if (laneRuns.length !== expectedRunCount) {
340
+ failures.push({
341
+ caseId: planCase.id,
342
+ kind: "missing-runs",
343
+ message: `lane ${lane} produced ${laneRuns.length}/${expectedRunCount} runs`,
344
+ });
345
+ }
346
+ }
347
+ if (caseRuns.length === 0) {
348
+ failures.push({
349
+ caseId: planCase.id,
350
+ kind: "missing-runs",
351
+ message: "no lane produced a result for this case",
352
+ });
353
+ continue;
354
+ }
355
+
356
+ const harnessFailures = caseRuns.filter(
357
+ (run) => run.exitClass === ExitClass.HarnessFailure,
358
+ );
359
+ for (const run of harnessFailures) {
360
+ failures.push({
361
+ caseId: planCase.id,
362
+ kind: "harness-failure",
363
+ message: `${runKey(run)}: ${run.exitDetail ?? "lane failed to execute"}`,
364
+ });
365
+ }
366
+ const usable = caseRuns.filter(
367
+ (run) => run.exitClass !== ExitClass.HarnessFailure,
368
+ );
369
+ if (usable.length === 0) continue;
370
+
371
+ // Exit-class parity (+ expectation).
372
+ const reference = usable[0];
373
+ for (const run of usable) {
374
+ comparisons += 1;
375
+ if (planCase.expect && run.exitClass !== planCase.expect) {
376
+ failures.push({
377
+ caseId: planCase.id,
378
+ kind: "expectation",
379
+ message: `${runKey(run)}: expected class "${planCase.expect}", got "${run.exitClass}"${run.exitDetail ? ` (${run.exitDetail})` : ""}`,
380
+ });
381
+ }
382
+ if (run.exitClass !== reference.exitClass) {
383
+ failures.push({
384
+ caseId: planCase.id,
385
+ kind: "class-divergence",
386
+ message: `${runKey(reference)} class "${reference.exitClass}" vs ${runKey(run)} class "${run.exitClass}"${run.exitDetail ? ` (${run.exitDetail})` : ""}`,
387
+ });
388
+ }
389
+ }
390
+
391
+ // Byte parity of outputs — only meaningful when everything agrees on "ok".
392
+ const okRuns = usable.filter((run) => run.exitClass === ExitClass.Ok);
393
+ if (okRuns.length > 1) {
394
+ const base = okRuns[0];
395
+ const baseStdout = toUint8Array(base.stdout) ?? new Uint8Array(0);
396
+ for (const run of okRuns.slice(1)) {
397
+ comparisons += 1;
398
+ const stdout = toUint8Array(run.stdout) ?? new Uint8Array(0);
399
+ const offset = firstDivergentOffset(baseStdout, stdout);
400
+ if (offset !== -1) {
401
+ failures.push({
402
+ caseId: planCase.id,
403
+ kind: "output-divergence",
404
+ message:
405
+ `${runKey(base)} stdout sha256=${sha256Hex(baseStdout).slice(0, 16)} len=${baseStdout.length} vs ` +
406
+ `${runKey(run)} sha256=${sha256Hex(stdout).slice(0, 16)} len=${stdout.length}; ` +
407
+ `first divergent byte at offset ${offset} ` +
408
+ `(${runKey(base)}: ${hexWindow(baseStdout, offset)} | ${runKey(run)}: ${hexWindow(stdout, offset)})`,
409
+ });
410
+ }
411
+ }
412
+ }
413
+
414
+ // Persisted-state parity across every run that reports a manifest.
415
+ const stateRuns = usable.filter(
416
+ (run) => run.stateFiles && typeof run.stateFiles === "object",
417
+ );
418
+ if (stateRuns.length > 1) {
419
+ const base = stateRuns[0];
420
+ const baseEntries = JSON.stringify(
421
+ Object.entries(base.stateFiles).sort(([a], [b]) => a.localeCompare(b)),
422
+ );
423
+ for (const run of stateRuns.slice(1)) {
424
+ comparisons += 1;
425
+ const entries = JSON.stringify(
426
+ Object.entries(run.stateFiles).sort(([a], [b]) => a.localeCompare(b)),
427
+ );
428
+ if (entries !== baseEntries) {
429
+ failures.push({
430
+ caseId: planCase.id,
431
+ kind: "state-divergence",
432
+ message: `${runKey(base)} persisted-state manifest != ${runKey(run)}: ${baseEntries} vs ${entries}`,
433
+ });
434
+ }
435
+ }
436
+ }
437
+ }
438
+
439
+ return { ok: failures.length === 0, failures, comparisons };
440
+ }
441
+
442
+ export function formatParityReport(report) {
443
+ const lines = [];
444
+ const laneSummary = report.lanes
445
+ .map((lane) => `${lane.lane}(${lane.runs} runs, ${lane.durationMs}ms)`)
446
+ .join(", ");
447
+ lines.push(
448
+ `parity ${report.ok ? "PASS" : "FAIL"} fixture=${report.fixture} module=${report.moduleSha256.slice(0, 16)} lanes=[${laneSummary}] comparisons=${report.comparisons}`,
449
+ );
450
+ for (const failure of report.failures) {
451
+ lines.push(
452
+ ` PARITY FAIL case=${failure.caseId ?? "-"} kind=${failure.kind}: ${failure.message}`,
453
+ );
454
+ }
455
+ if (report.ok) {
456
+ lines.push(
457
+ ` ${report.cases.length} case(s) byte-identical across ${report.lanes.length} lane(s).`,
458
+ );
459
+ }
460
+ return lines.join("\n");
461
+ }
462
+
463
+ // --- Orchestrator --------------------------------------------------------------
464
+
465
+ function applyInjectedDivergence(runs, injectDivergence) {
466
+ if (!injectDivergence) return runs;
467
+ const lane = String(injectDivergence);
468
+ let injected = false;
469
+ const mutated = runs.map((run) => {
470
+ if (run.lane !== lane || injected) return run;
471
+ const stdout = toUint8Array(run.stdout) ?? new Uint8Array(0);
472
+ const flipped = stdout.slice();
473
+ if (flipped.length === 0) {
474
+ return { ...run, exitClass: ExitClass.Trap, exitDetail: "self-test-divergence" };
475
+ }
476
+ flipped[flipped.length - 1] ^= 0xff;
477
+ injected = true;
478
+ return { ...run, stdout: flipped };
479
+ });
480
+ if (!injected && !mutated.some((run) => run.exitDetail === "self-test-divergence")) {
481
+ throw new Error(
482
+ `--self-test-divergence: lane "${lane}" produced no runs to corrupt.`,
483
+ );
484
+ }
485
+ return mutated;
486
+ }
487
+
488
+ /**
489
+ * Run the parity harness.
490
+ *
491
+ * @param {Object} options
492
+ * @param {string} options.wasmPath - the ONE module.wasm (dist/isomorphic).
493
+ * @param {Object} [options.plan] - normalized fixture plan.
494
+ * @param {string} [options.fixturePath] - fixture JSON path (alternative to plan).
495
+ * @param {string[]} [options.lanes] - subset of PARITY_LANES. Defaults to ALL
496
+ * THREE. Narrowing is always an explicit caller decision — lanes are never
497
+ * skipped silently, and a single-lane run can never claim parity.
498
+ * @param {Object} [options.laneRunners] - {laneName: async (context) => runs[]}
499
+ * override for tests. Default runners come from parityLanes.js.
500
+ * @param {string} [options.injectDivergence] - fire-drill: XOR one output byte
501
+ * of this lane AFTER execution to prove the diff fails loudly end-to-end.
502
+ * @returns {Promise<Object>} report {ok, failures, lanes, cases, ...}
503
+ */
504
+ export async function runParityHarness(options = {}) {
505
+ const wasmPath = options.wasmPath ? path.resolve(options.wasmPath) : null;
506
+ if (!wasmPath) throw new Error("parity harness requires wasmPath.");
507
+ const wasmBytes = new Uint8Array(await readFile(wasmPath));
508
+ // Signed/published artifacts carry appended publication records that wasm
509
+ // engines reject. Strip ONCE here so every lane executes the identical
510
+ // canonical module payload.
511
+ const loadableBytes = toLoadableWasmBytes(wasmBytes);
512
+
513
+ const plan =
514
+ options.plan ??
515
+ (options.fixturePath
516
+ ? await loadParityFixture(options.fixturePath)
517
+ : null);
518
+ if (!plan) throw new Error("parity harness requires plan or fixturePath.");
519
+
520
+ const laneNames =
521
+ options.lanes && options.lanes.length > 0
522
+ ? options.lanes.map(String)
523
+ : [...PARITY_LANES];
524
+ for (const lane of laneNames) {
525
+ if (!PARITY_LANES.includes(lane)) {
526
+ throw new Error(
527
+ `Unknown parity lane "${lane}". Lanes: ${PARITY_LANES.join(", ")}.`,
528
+ );
529
+ }
530
+ }
531
+ if (new Set(laneNames).size < 2 && !options.allowSingleLane) {
532
+ throw new Error(
533
+ "Parity requires at least two lanes; a single-lane run proves nothing. " +
534
+ "(Pass allowSingleLane for lane bring-up debugging only.)",
535
+ );
536
+ }
537
+
538
+ let laneRunners = options.laneRunners;
539
+ if (!laneRunners) {
540
+ const lanesModule = await import("./parityLanes.js");
541
+ laneRunners = lanesModule.defaultParityLaneRunners;
542
+ }
543
+
544
+ const context = {
545
+ wasmPath,
546
+ wasmBytes,
547
+ loadableBytes,
548
+ plan,
549
+ pin: loadWasmEdgePin(),
550
+ log: options.log ?? (() => {}),
551
+ chromeBinary: options.chromeBinary,
552
+ wasmedgeBinary: options.wasmedgeBinary,
553
+ dockerBinary: options.dockerBinary,
554
+ dockerPlatform: options.dockerPlatform,
555
+ autoBuildDockerImage: options.autoBuildDockerImage !== false,
556
+ timeoutMs: options.timeoutMs ?? 120_000,
557
+ };
558
+
559
+ const allRuns = [];
560
+ const laneStats = [];
561
+ for (const lane of laneNames) {
562
+ const runner = laneRunners[lane];
563
+ if (typeof runner !== "function") {
564
+ throw new Error(`No parity lane runner registered for "${lane}".`);
565
+ }
566
+ const startedAt = Date.now();
567
+ let laneRuns;
568
+ try {
569
+ laneRuns = await runner(context);
570
+ } catch (error) {
571
+ // A lane that cannot execute at all is a loud harness failure for every
572
+ // case — parity is unprovable, so the whole run fails.
573
+ laneRuns = plan.cases.flatMap((planCase) =>
574
+ planCase.threadCounts.map((threadCount) => ({
575
+ lane,
576
+ caseId: planCase.id,
577
+ threadCount,
578
+ exitClass: ExitClass.HarnessFailure,
579
+ exitDetail: error?.message ?? String(error),
580
+ stdout: new Uint8Array(0),
581
+ })),
582
+ );
583
+ }
584
+ laneStats.push({
585
+ lane,
586
+ runs: laneRuns.length,
587
+ durationMs: Date.now() - startedAt,
588
+ });
589
+ allRuns.push(...laneRuns.map((run) => ({ ...run, lane })));
590
+ }
591
+
592
+ const runs = applyInjectedDivergence(allRuns, options.injectDivergence);
593
+ const diff = diffParityRuns(plan, runs);
594
+
595
+ return {
596
+ ok: diff.ok,
597
+ fixture: plan.name,
598
+ wasmPath,
599
+ artifactSha256: sha256Hex(wasmBytes),
600
+ moduleSha256: sha256Hex(loadableBytes),
601
+ pin: context.pin.wasmedgeVersion,
602
+ lanes: laneStats,
603
+ cases: plan.cases.map((planCase) => planCase.id),
604
+ comparisons: diff.comparisons,
605
+ failures: diff.failures,
606
+ runs: runs.map((run) => ({
607
+ lane: run.lane,
608
+ caseId: run.caseId,
609
+ threadCount: run.threadCount,
610
+ exitClass: run.exitClass,
611
+ exitDetail: run.exitDetail ?? null,
612
+ stdoutSha256: sha256Hex(toUint8Array(run.stdout) ?? new Uint8Array(0)),
613
+ stdoutLength: (toUint8Array(run.stdout) ?? new Uint8Array(0)).length,
614
+ })),
615
+ };
616
+ }