space-data-module-sdk 0.8.4 → 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 +25 -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,1699 @@
1
+ /*
2
+ * Generic compiled-flow runtime — the SDK flow compiler's runtime template.
3
+ *
4
+ * `space-data-module flow compile` links this translation unit with the
5
+ * flow's generated descriptor tables (flow_generated.inc), the flow's
6
+ * embedded manifests, and each linked-direct dependency's PREFIXED
7
+ * guest-link wasm object (the "Prefixed guest-link wasm object for
8
+ * monolithic flow linking" emitted by compileModuleFromSource) into ONE
9
+ * artifact with ONE linear memory.
10
+ *
11
+ * Implements the `space_data_module_runtime_*` ABI bound by BOTH hosts —
12
+ * sdn-server internal/flowrt (abi.go/runtime.go, WasmEdge) and the SDK's
13
+ * src/flow/flowRuntimeHost.js (browser/node):
14
+ * - descriptor tables (FlowNodeDispatchDescriptor 60B,
15
+ * SignedArtifactDependencyDescriptor 72B, FlowFrameDescriptor 48B,
16
+ * FlowInvocationDescriptor 24B, node state 32B, ingress state 24B),
17
+ * - a frame-queue scheduler (trigger bindings -> node queues -> edges)
18
+ * whose readiness rule honours the dependency manifests' required
19
+ * input ports (compiled in from the manifests by the flow compiler),
20
+ * - linked-direct dispatch: node entries are the dependencies' prefixed
21
+ * guest-link method symbols; their SDK invoke ABI
22
+ * (plugin_get_input_frame / plugin_push_output* / ...) is provided by
23
+ * the in-artifact shim below, so frames flow node-to-node inside this
24
+ * module's linear memory with zero host copies. Capability hostcalls
25
+ * (e.g. space_data_module_host.call) remain wasm imports.
26
+ *
27
+ * The flow graph tables (node/edge/trigger-binding/dependency/required-port
28
+ * tables and the entry dispatch switch) are generated from the *.flow.json
29
+ * document by src/flow/flowCompiler.js into flow_generated.inc.
30
+ */
31
+
32
+ #include <stdint.h>
33
+ #include <stdlib.h>
34
+ #include <string.h>
35
+
36
+ #include <string>
37
+ #include <memory>
38
+ #include <vector>
39
+
40
+ #include "space_data_module_invoke.h"
41
+
42
+ #define FLOW_EXPORT extern "C" __attribute__((visibility("default")))
43
+
44
+ #ifdef SDN_FLATSQL_LINKED
45
+ static void sdn_flatsql_link_reset_refs(void);
46
+ #endif
47
+
48
+ // Guest-link objects compiled from Emscripten C++ may import the memory
49
+ // growth notification; provide the same weak no-op the module compiler uses.
50
+ extern "C" __attribute__((weak)) void emscripten_notify_memory_growth(int) {}
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // ABI structs — layouts must match sdn-server internal/flowrt/abi.go exactly.
54
+ // ---------------------------------------------------------------------------
55
+
56
+ struct FlowFrameDescriptorC {
57
+ uint32_t ingress_index;
58
+ uint32_t type_descriptor_idx;
59
+ uint32_t port_id_ptr;
60
+ uint32_t alignment;
61
+ uint32_t offset;
62
+ uint32_t size;
63
+ uint32_t stream_id;
64
+ uint32_t sequence;
65
+ uint64_t trace_token;
66
+ uint8_t end_of_stream;
67
+ uint8_t occupied;
68
+ uint8_t wire_format;
69
+ uint8_t ownership;
70
+ uint8_t mutability;
71
+ uint8_t lifetime;
72
+ uint8_t _pad[2];
73
+ };
74
+ static_assert(sizeof(FlowFrameDescriptorC) == 48, "FlowFrameDescriptor must be 48 bytes");
75
+
76
+ struct FlowRoutingRuntimeStateC {
77
+ uint64_t aligned_shared_routes;
78
+ uint64_t aligned_copied_routes;
79
+ uint64_t canonical_routes;
80
+ uint64_t rejected_frames;
81
+ };
82
+ static_assert(sizeof(FlowRoutingRuntimeStateC) == 32,
83
+ "FlowRoutingRuntimeState must be 32 bytes");
84
+
85
+ struct FlowInvocationDescriptorC {
86
+ uint32_t node_index;
87
+ uint32_t dispatch_descriptor_idx;
88
+ uint32_t plugin_id_ptr;
89
+ uint32_t method_id_ptr;
90
+ uint32_t frames_ptr;
91
+ uint32_t frame_count;
92
+ };
93
+ static_assert(sizeof(FlowInvocationDescriptorC) == 24, "FlowInvocationDescriptor must be 24 bytes");
94
+
95
+ struct FlowNodeDispatchDescriptorC {
96
+ uint32_t node_id_ptr;
97
+ uint32_t node_index;
98
+ uint32_t dependency_id_ptr;
99
+ uint32_t dependency_index;
100
+ uint32_t plugin_id_ptr;
101
+ uint32_t method_id_ptr;
102
+ uint32_t dispatch_model_ptr;
103
+ uint32_t entrypoint_ptr;
104
+ uint32_t manifest_bytes_symbol_ptr;
105
+ uint32_t manifest_size_symbol_ptr;
106
+ uint32_t init_symbol_ptr;
107
+ uint32_t destroy_symbol_ptr;
108
+ uint32_t malloc_symbol_ptr;
109
+ uint32_t free_symbol_ptr;
110
+ uint32_t stream_invoke_symbol_ptr;
111
+ };
112
+ static_assert(sizeof(FlowNodeDispatchDescriptorC) == 60, "FlowNodeDispatchDescriptor must be 60 bytes");
113
+
114
+ struct SignedArtifactDependencyDescriptorC {
115
+ uint32_t dependency_id_ptr;
116
+ uint32_t plugin_id_ptr;
117
+ uint32_t version_ptr;
118
+ uint32_t sha256_ptr;
119
+ uint32_t signature_ptr;
120
+ uint32_t signer_public_key_ptr;
121
+ uint32_t entrypoint_ptr;
122
+ uint32_t manifest_bytes_symbol_ptr;
123
+ uint32_t manifest_size_symbol_ptr;
124
+ uint32_t init_symbol_ptr;
125
+ uint32_t destroy_symbol_ptr;
126
+ uint32_t malloc_symbol_ptr;
127
+ uint32_t free_symbol_ptr;
128
+ uint32_t stream_invoke_symbol_ptr;
129
+ uint32_t wasm_bytes_ptr;
130
+ uint32_t wasm_size;
131
+ uint32_t manifest_bytes_ptr;
132
+ uint32_t manifest_size;
133
+ };
134
+ static_assert(sizeof(SignedArtifactDependencyDescriptorC) == 72, "SignedArtifactDependencyDescriptor must be 72 bytes");
135
+
136
+ struct FlowIngressRuntimeStateC {
137
+ uint64_t total_received;
138
+ uint64_t total_dropped;
139
+ uint32_t queued_frames;
140
+ uint8_t _pad[4];
141
+ };
142
+ static_assert(sizeof(FlowIngressRuntimeStateC) == 24, "FlowIngressRuntimeState must be 24 bytes");
143
+
144
+ struct FlowNodeRuntimeStateC {
145
+ uint64_t invocation_count;
146
+ uint64_t consumed_frames;
147
+ uint32_t queued_frames;
148
+ uint32_t backlog_remaining;
149
+ uint32_t last_status;
150
+ uint8_t ready;
151
+ uint8_t yielded;
152
+ uint8_t _pad[2];
153
+ };
154
+ static_assert(sizeof(FlowNodeRuntimeStateC) == 32, "FlowNodeRuntimeState must be 32 bytes");
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // Generated flow graph tables (from the flow document via flowCompiler.js):
158
+ // FLOW_NODE_COUNT / FLOW_ROUTE_EDGE_COUNT / FLOW_EDGE_COUNT
159
+ // FLOW_TRIGGER_COUNT / FLOW_DEP_COUNT
160
+ // FLOW_TRIGGER_BINDING_COUNT / FLOW_REQUIRED_PORT_COUNT
161
+ // g_dispatch_descriptors[] / g_dependency_descriptors[]
162
+ // FlowEdge g_edges[] (endpoints + exact compiled type contract)
163
+ // FlowTriggerBinding g_trigger_bindings[] (target + exact input-port type)
164
+ // FlowRequiredPort g_required_ports[] (node_index, port_id)
165
+ // flow_call_entry(node_index) — linked-direct entry dispatch switch
166
+ // flow_node_is_linked(node_index)
167
+ // ---------------------------------------------------------------------------
168
+
169
+ struct FlowEdge {
170
+ uint32_t from_node;
171
+ const char *from_port;
172
+ uint32_t to_node;
173
+ const char *to_port;
174
+ const char *schema_name;
175
+ const char *file_identifier;
176
+ const char *schema_version;
177
+ const uint8_t *schema_hash;
178
+ uint32_t schema_hash_size;
179
+ const char *root_type_name;
180
+ uint32_t canonical_fallback_available;
181
+ uint32_t aligned_eligible;
182
+ uint32_t aligned_layout_fields;
183
+ uint32_t aligned_byte_length;
184
+ uint32_t aligned_fixed_string_length;
185
+ uint32_t aligned_required_alignment;
186
+ // OPAQUE (SDS $PLG 1.0.13 / v1.164.0): the edge carries BYTES that have no
187
+ // SDS identity by signed assertion. Such an edge has neither a canonical
188
+ // FlatBuffer type nor an aligned layout, so both typed routes reject it —
189
+ // which, before this field existed, made every opaque edge unroutable and
190
+ // the flow inert. An opaque edge takes the BYTE route instead: no identity
191
+ // match, no layout proof, alignment 1.
192
+ uint32_t opaque;
193
+ };
194
+ static_assert(sizeof(FlowEdge) == 68, "FlowEdge must be 68 bytes on wasm32");
195
+
196
+ struct FlowTriggerBinding {
197
+ uint32_t trigger_index;
198
+ uint32_t target_node;
199
+ const char *port;
200
+ uint32_t type_descriptor_idx;
201
+ };
202
+ static_assert(sizeof(FlowTriggerBinding) == 16,
203
+ "FlowTriggerBinding must be 16 bytes on wasm32");
204
+
205
+ struct FlowRequiredPort {
206
+ uint32_t node_index;
207
+ const char *port_id;
208
+ };
209
+
210
+ #include "flow_generated.inc"
211
+
212
+ enum FlowWireFormat : uint8_t {
213
+ kFlowFlatbuffer = 0,
214
+ kFlowAlignedBinary = 1,
215
+ };
216
+
217
+ struct FlowTypeDescriptorView {
218
+ const char *schema_name;
219
+ const char *file_identifier;
220
+ const char *schema_version;
221
+ const uint8_t *schema_hash;
222
+ uint32_t schema_hash_size;
223
+ const char *root_type_name;
224
+ uint32_t canonical_fallback_available;
225
+ uint32_t aligned_eligible;
226
+ uint32_t aligned_layout_fields;
227
+ uint32_t aligned_byte_length;
228
+ uint32_t aligned_fixed_string_length;
229
+ uint32_t aligned_required_alignment;
230
+ uint32_t opaque;
231
+ };
232
+
233
+ static bool flow_resolve_type_descriptor(uint32_t index,
234
+ FlowTypeDescriptorView *out) {
235
+ if (out == nullptr) return false;
236
+ if (index < FLOW_EDGE_COUNT) {
237
+ const FlowEdge &edge = g_edges[index];
238
+ out->schema_name = edge.schema_name;
239
+ out->file_identifier = edge.file_identifier;
240
+ out->schema_version = edge.schema_version;
241
+ out->schema_hash = edge.schema_hash;
242
+ out->schema_hash_size = edge.schema_hash_size;
243
+ out->root_type_name = edge.root_type_name;
244
+ out->canonical_fallback_available = edge.canonical_fallback_available;
245
+ out->aligned_eligible = edge.aligned_eligible;
246
+ out->aligned_layout_fields = edge.aligned_layout_fields;
247
+ out->aligned_byte_length = edge.aligned_byte_length;
248
+ out->aligned_fixed_string_length = edge.aligned_fixed_string_length;
249
+ out->aligned_required_alignment = edge.aligned_required_alignment;
250
+ out->opaque = edge.opaque;
251
+ return true;
252
+ }
253
+ return false;
254
+ }
255
+
256
+ static bool flow_nullable_string_equal(const char *left, const char *right) {
257
+ if (left == nullptr || right == nullptr) return left == right;
258
+ return strcmp(left, right) == 0;
259
+ }
260
+
261
+ static bool flow_type_identity_equal(const FlowTypeDescriptorView &left,
262
+ const FlowTypeDescriptorView &right) {
263
+ if (!flow_nullable_string_equal(left.schema_name, right.schema_name) ||
264
+ !flow_nullable_string_equal(left.file_identifier, right.file_identifier) ||
265
+ !flow_nullable_string_equal(left.schema_version, right.schema_version) ||
266
+ !flow_nullable_string_equal(left.root_type_name, right.root_type_name) ||
267
+ left.schema_hash_size != right.schema_hash_size) {
268
+ return false;
269
+ }
270
+ if (left.schema_hash_size == 0) return true;
271
+ if (left.schema_hash == nullptr || right.schema_hash == nullptr) return false;
272
+ return memcmp(left.schema_hash, right.schema_hash, left.schema_hash_size) == 0;
273
+ }
274
+
275
+ static bool flow_binding_accepts_descriptor(uint32_t binding_index,
276
+ uint32_t descriptor_index,
277
+ uint8_t wire_format) {
278
+ FlowTypeDescriptorView binding_type;
279
+ FlowTypeDescriptorView provided_type;
280
+ if (binding_index >= FLOW_TRIGGER_BINDING_COUNT ||
281
+ !flow_resolve_type_descriptor(g_trigger_bindings[binding_index].type_descriptor_idx,
282
+ &binding_type) ||
283
+ !flow_resolve_type_descriptor(descriptor_index, &provided_type) ||
284
+ !flow_type_identity_equal(binding_type, provided_type)) {
285
+ return false;
286
+ }
287
+ // OPAQUE byte route: an opaque descriptor asserts "these bytes have no SDS
288
+ // identity". Both sides must agree on the opacity — a typed binding never
289
+ // accepts an opaque frame and vice versa — and once they do, neither wire
290
+ // format carries a claim to check, so both are admitted as bytes.
291
+ if (provided_type.opaque != 0 || binding_type.opaque != 0) {
292
+ return provided_type.opaque != 0 && binding_type.opaque != 0;
293
+ }
294
+ if (wire_format == kFlowFlatbuffer) {
295
+ return provided_type.canonical_fallback_available != 0;
296
+ }
297
+ return provided_type.aligned_eligible != 0 &&
298
+ binding_type.aligned_eligible != 0 &&
299
+ provided_type.aligned_layout_fields == binding_type.aligned_layout_fields &&
300
+ provided_type.aligned_byte_length == binding_type.aligned_byte_length &&
301
+ provided_type.aligned_fixed_string_length ==
302
+ binding_type.aligned_fixed_string_length &&
303
+ provided_type.aligned_required_alignment ==
304
+ binding_type.aligned_required_alignment;
305
+ }
306
+
307
+ // ---------------------------------------------------------------------------
308
+ // Scheduler state
309
+ // ---------------------------------------------------------------------------
310
+
311
+ enum FlowOwnership : uint8_t {
312
+ kFlowHostOwned = 0,
313
+ kFlowPluginOwned = 1,
314
+ kFlowTransferred = 2,
315
+ };
316
+
317
+ enum FlowMutability : uint8_t {
318
+ kFlowImmutable = 0,
319
+ kFlowSingleWriterMutable = 1,
320
+ kFlowAppendOnly = 2,
321
+ };
322
+
323
+ struct PayloadStorage {
324
+ std::vector<uint8_t> bytes;
325
+ uint32_t payload_offset = 0;
326
+ };
327
+
328
+ struct QueuedFrame {
329
+ std::string port;
330
+ std::shared_ptr<PayloadStorage> storage;
331
+ uint32_t payload_offset = 0;
332
+ uint32_t payload_size = 0;
333
+ uint32_t type_descriptor_idx = 0xFFFFFFFFu;
334
+ uint32_t alignment = 1;
335
+ uint32_t byte_length = 0;
336
+ uint32_t fixed_string_length = 0;
337
+ uint32_t required_alignment = 1;
338
+ uint32_t stream_id = 0;
339
+ uint32_t sequence = 0;
340
+ uint64_t frame_id = 0;
341
+ uint8_t end_of_stream = 0;
342
+ uint8_t wire_format = kFlowFlatbuffer;
343
+ uint8_t ownership = kFlowHostOwned;
344
+ uint8_t mutability = kFlowImmutable;
345
+ uint8_t tracks_external_lifetime = 0;
346
+
347
+ const uint8_t *data() const {
348
+ if (!storage || payload_size == 0) return nullptr;
349
+ return storage->bytes.data() + payload_offset;
350
+ }
351
+ };
352
+
353
+ static std::vector<QueuedFrame> g_queues[FLOW_NODE_COUNT];
354
+ static FlowNodeRuntimeStateC g_node_states[FLOW_NODE_COUNT];
355
+ static FlowIngressRuntimeStateC g_ingress_states[FLOW_TRIGGER_COUNT];
356
+
357
+ // Current invocation (one at a time — both host drain loops are serialized).
358
+ static constexpr uint32_t kMaxInvocationFrames = 64;
359
+ static constexpr uint32_t kInvalidIndex = 0xFFFFFFFFu;
360
+ static FlowInvocationDescriptorC g_current_desc;
361
+ static FlowFrameDescriptorC g_current_frames[kMaxInvocationFrames];
362
+ static QueuedFrame g_current_owned[kMaxInvocationFrames];
363
+ static uint32_t g_current_node = kInvalidIndex;
364
+ static uint32_t g_invocation_generation = 0;
365
+ static uint64_t g_next_frame_id = 1;
366
+ struct ExternalFrameLifetime {
367
+ uint64_t frame_id;
368
+ uint32_t references;
369
+ };
370
+ static std::vector<ExternalFrameLifetime> g_active_external_frames;
371
+ static FlowRoutingRuntimeStateC g_routing_state;
372
+ // Fair ready-node cursor. A yielding source can remain ready for thousands of
373
+ // bounded pages; always scanning from node zero would let it monopolize the
374
+ // scheduler and accumulate every downstream frame in linear memory.
375
+ static uint32_t g_ready_cursor = 0;
376
+
377
+ // Readiness: a node is ready when it has queued frames AND every required
378
+ // input port of its bound method (compiled in from the dependency manifest)
379
+ // has at least one queued frame. Host-model nodes have no required-port rows
380
+ // and fire on any queued frame.
381
+ static bool flow_node_is_ready(uint32_t node) {
382
+ // A linked guest that yielded with internal backlog owns its continuation
383
+ // state. Resume it without fabricating/replaying an input frame. This is the
384
+ // compiled-runtime meaning of plugin_set_yielded + backlog_remaining.
385
+ if (g_node_states[node].yielded != 0 &&
386
+ g_node_states[node].backlog_remaining > 0) {
387
+ return true;
388
+ }
389
+ if (g_queues[node].empty()) return false;
390
+ for (uint32_t r = 0; r < FLOW_REQUIRED_PORT_COUNT; r++) {
391
+ if (g_required_ports[r].node_index != node) continue;
392
+ bool present = false;
393
+ for (const QueuedFrame &frame : g_queues[node]) {
394
+ if (strcmp(frame.port.c_str(), g_required_ports[r].port_id) == 0) {
395
+ present = true;
396
+ break;
397
+ }
398
+ }
399
+ if (!present) return false;
400
+ }
401
+ return true;
402
+ }
403
+
404
+ static uint32_t flow_find_ready_node(void) {
405
+ for (uint32_t offset = 0; offset < FLOW_NODE_COUNT; offset++) {
406
+ const uint32_t node = (g_ready_cursor + offset) % FLOW_NODE_COUNT;
407
+ if (flow_node_is_ready(node)) return node;
408
+ }
409
+ return kInvalidIndex;
410
+ }
411
+
412
+ static uint64_t flow_memory_byte_size(void) {
413
+ return static_cast<uint64_t>(__builtin_wasm_memory_size(0)) * 65536ull;
414
+ }
415
+
416
+ static bool flow_valid_memory_range(uint32_t offset, uint32_t size) {
417
+ const uint64_t memory_size = flow_memory_byte_size();
418
+ return static_cast<uint64_t>(offset) <= memory_size &&
419
+ static_cast<uint64_t>(size) <= memory_size - static_cast<uint64_t>(offset);
420
+ }
421
+
422
+ static bool flow_is_power_of_two(uint32_t value) {
423
+ return value != 0 && (value & (value - 1u)) == 0;
424
+ }
425
+
426
+ // A frame descriptor's `alignment` of ZERO is an ABSENT field, not a claim of
427
+ // "aligned to nothing" — and the ABI must give it exactly one meaning, or the
428
+ // hosts diverge on the same artifact. They did: the browser/JS host defaults an
429
+ // unset alignment to 1 while the Go/WasmEdge host leaves the field zeroed, so a
430
+ // timer tick that started the flow in one runtime was rejected (-30) before its
431
+ // first node in the other. Unspecified means byte alignment. An aligned-binary
432
+ // frame still has to satisfy its layout's required alignment below, so nothing
433
+ // a typed route depends on is relaxed.
434
+ static uint32_t flow_effective_alignment(uint32_t alignment) {
435
+ return alignment == 0 ? 1u : alignment;
436
+ }
437
+
438
+ static bool flow_valid_c_string(uint32_t ptr, uint32_t max_length = 1024) {
439
+ if (ptr == 0) return true;
440
+ const uint64_t memory_size = flow_memory_byte_size();
441
+ if (static_cast<uint64_t>(ptr) >= memory_size) return false;
442
+ const char *text = reinterpret_cast<const char *>(ptr);
443
+ const uint64_t remaining = memory_size - static_cast<uint64_t>(ptr);
444
+ const uint64_t limit = remaining < max_length ? remaining : max_length;
445
+ for (uint64_t i = 0; i < limit; i++) {
446
+ if (text[i] == '\0') return true;
447
+ }
448
+ return false;
449
+ }
450
+
451
+ static std::shared_ptr<PayloadStorage> flow_copy_payload(
452
+ const uint8_t *payload, uint32_t length, uint32_t alignment,
453
+ uint32_t *payload_offset) {
454
+ const uint32_t normalized_alignment = flow_is_power_of_two(alignment) ? alignment : 1;
455
+ auto storage = std::make_shared<PayloadStorage>();
456
+ if (length == 0) {
457
+ *payload_offset = 0;
458
+ return storage;
459
+ }
460
+ storage->bytes.resize(static_cast<size_t>(length) + normalized_alignment - 1u);
461
+ const uintptr_t base = reinterpret_cast<uintptr_t>(storage->bytes.data());
462
+ const uint32_t padding = static_cast<uint32_t>(
463
+ (normalized_alignment - (base % normalized_alignment)) % normalized_alignment);
464
+ storage->payload_offset = padding;
465
+ *payload_offset = padding;
466
+ memcpy(storage->bytes.data() + padding, payload, length);
467
+ return storage;
468
+ }
469
+
470
+ static bool flow_find_current_alias(const uint8_t *payload, uint32_t length,
471
+ std::shared_ptr<PayloadStorage> *storage,
472
+ uint32_t *payload_offset) {
473
+ if (payload == nullptr && length > 0) return false;
474
+ const uintptr_t requested_begin = reinterpret_cast<uintptr_t>(payload);
475
+ const uintptr_t requested_end = requested_begin + length;
476
+ if (requested_end < requested_begin) return false;
477
+ for (uint32_t i = 0; i < g_current_desc.frame_count; i++) {
478
+ const QueuedFrame &current = g_current_owned[i];
479
+ const uint8_t *current_data = current.data();
480
+ if (current_data == nullptr && current.payload_size > 0) continue;
481
+ const uintptr_t current_begin = reinterpret_cast<uintptr_t>(current_data);
482
+ const uintptr_t current_end = current_begin + current.payload_size;
483
+ if (requested_begin < current_begin || requested_end > current_end) continue;
484
+ *storage = current.storage;
485
+ *payload_offset = current.payload_offset +
486
+ static_cast<uint32_t>(requested_begin - current_begin);
487
+ return true;
488
+ }
489
+ return false;
490
+ }
491
+
492
+ struct FlowOutputFrameView {
493
+ const char *port = nullptr;
494
+ const char *schema_name = nullptr;
495
+ const char *file_identifier = nullptr;
496
+ const char *root_type_name = nullptr;
497
+ const uint8_t *payload = nullptr;
498
+ // Storage the CALLER already owns for `payload` (the invoke shim copies or
499
+ // retains at push time). When set the router shares it instead of copying
500
+ // again; when null the payload is live host/guest memory and is copied here.
501
+ std::shared_ptr<PayloadStorage> owned_storage;
502
+ uint32_t owned_offset = 0;
503
+ uint32_t length = 0;
504
+ uint32_t wire_format = kFlowFlatbuffer;
505
+ uint32_t byte_length = 0;
506
+ uint32_t fixed_string_length = 0;
507
+ uint32_t required_alignment = 1;
508
+ uint32_t alignment = 1;
509
+ uint32_t stream_id = 0;
510
+ uint32_t sequence = 0;
511
+ uint64_t frame_id = 0;
512
+ uint8_t end_of_stream = 0;
513
+ uint8_t ownership = kFlowHostOwned;
514
+ uint8_t mutability = kFlowImmutable;
515
+ };
516
+
517
+ // An OPAQUE edge carries bytes with no SDS identity. A producer that names a
518
+ // schema, file identifier or root type is claiming one, which the edge's
519
+ // signed contract says does not exist — that contradiction is refused rather
520
+ // than silently stripped.
521
+ static bool flow_output_declares_identity(const FlowOutputFrameView &out) {
522
+ return (out.schema_name != nullptr && out.schema_name[0] != '\0') ||
523
+ (out.file_identifier != nullptr && out.file_identifier[0] != '\0') ||
524
+ (out.root_type_name != nullptr && out.root_type_name[0] != '\0');
525
+ }
526
+
527
+ static bool flow_output_matches_edge_identity(const FlowOutputFrameView &out,
528
+ const FlowEdge &edge) {
529
+ if (out.schema_name != nullptr && out.schema_name[0] != '\0' &&
530
+ strcmp(out.schema_name, edge.schema_name) != 0) {
531
+ return false;
532
+ }
533
+ if (out.file_identifier != nullptr && out.file_identifier[0] != '\0' &&
534
+ strcmp(out.file_identifier, edge.file_identifier) != 0) {
535
+ return false;
536
+ }
537
+ if (out.root_type_name != nullptr && out.root_type_name[0] != '\0' &&
538
+ strcmp(out.root_type_name, edge.root_type_name) != 0) {
539
+ return false;
540
+ }
541
+ return true;
542
+ }
543
+
544
+ static int32_t route_output(uint32_t from_node, const FlowOutputFrameView &out) {
545
+ if (out.port == nullptr) return -20;
546
+ if (out.wire_format > kFlowAlignedBinary || out.ownership > kFlowTransferred ||
547
+ out.mutability > kFlowAppendOnly) {
548
+ g_routing_state.rejected_frames++;
549
+ return -21;
550
+ }
551
+ if (out.length > 0) {
552
+ if (out.payload == nullptr) {
553
+ g_routing_state.rejected_frames++;
554
+ return -22;
555
+ }
556
+ const uint32_t payload_ptr = reinterpret_cast<uint32_t>(out.payload);
557
+ if (!flow_valid_memory_range(payload_ptr, out.length)) {
558
+ g_routing_state.rejected_frames++;
559
+ return -22;
560
+ }
561
+ }
562
+ std::vector<uint32_t> matching_edges;
563
+ for (uint32_t e = 0; e < FLOW_ROUTE_EDGE_COUNT; e++) {
564
+ if (g_edges[e].from_node != from_node) continue;
565
+ if (strcmp(g_edges[e].from_port, out.port) != 0) continue;
566
+ matching_edges.push_back(e);
567
+ }
568
+ if (matching_edges.empty()) return 0;
569
+ if (out.mutability != kFlowImmutable &&
570
+ (out.ownership != kFlowTransferred || matching_edges.size() != 1)) {
571
+ g_routing_state.rejected_frames++;
572
+ return -23;
573
+ }
574
+
575
+ for (uint32_t edge_index : matching_edges) {
576
+ const FlowEdge &edge = g_edges[edge_index];
577
+ // OPAQUE byte route. The edge asserts the payload has NO SDS identity, so
578
+ // there is nothing to match and no layout to prove — but the assertion
579
+ // must stay honest in the other direction too: a frame that CLAIMS an SDS
580
+ // identity may not travel an edge that was signed as carrying none.
581
+ if (edge.opaque != 0) {
582
+ if (flow_output_declares_identity(out)) {
583
+ g_routing_state.rejected_frames++;
584
+ return -27;
585
+ }
586
+ continue;
587
+ }
588
+ if (!flow_output_matches_edge_identity(out, edge)) {
589
+ g_routing_state.rejected_frames++;
590
+ return -24;
591
+ }
592
+ if (out.wire_format == kFlowFlatbuffer) {
593
+ if (edge.canonical_fallback_available == 0) {
594
+ g_routing_state.rejected_frames++;
595
+ return -25;
596
+ }
597
+ continue;
598
+ }
599
+ // NO ALIGNED ROUTE ON A TYPED EDGE IS NOT A REJECTION.
600
+ //
601
+ // The comment further down already states the rule for the opaque route:
602
+ // "Producers that stamp ALIGNED_BINARY to mean 'raw bytes' are normalized
603
+ // here rather than silently rejected at the consumer's port." Nearly every
604
+ // shipped foundation node does exactly that (plugin_push_output_ex with
605
+ // PLUGIN_PAYLOAD_WIRE_FORMAT_ALIGNED_BINARY is the ONLY way to emit
606
+ // variable-length bytes). A TYPED edge that offers no aligned route still
607
+ // offers the canonical one, so the same normalization applies — otherwise
608
+ // an $HTR response frame stamped aligned-binary is dropped here, the flow
609
+ // emits nothing, and the node answers 502 "flow produced no HTTP response"
610
+ // while the JS host, which normalizes, serves the same artifact correctly.
611
+ // That divergence is the defect; matching the two runtimes is the fix.
612
+ if (edge.aligned_eligible == 0) {
613
+ if (edge.canonical_fallback_available == 0) {
614
+ g_routing_state.rejected_frames++;
615
+ return -26;
616
+ }
617
+ continue;
618
+ }
619
+ // aligned_byte_length == 0 declares a VARIABLE-LENGTH aligned stream: the
620
+ // payload must sit on the declared alignment, but there is no fixed stride
621
+ // to check its size against. A declared stride is still enforced exactly.
622
+ const bool edge_fixed_stride = edge.aligned_byte_length != 0;
623
+ if (edge.aligned_required_alignment == 0 ||
624
+ !flow_is_power_of_two(edge.aligned_required_alignment) ||
625
+ (edge_fixed_stride && out.length != edge.aligned_byte_length) ||
626
+ (edge_fixed_stride && out.byte_length != 0 &&
627
+ out.byte_length != edge.aligned_byte_length) ||
628
+ (out.fixed_string_length != 0 &&
629
+ out.fixed_string_length != edge.aligned_fixed_string_length) ||
630
+ (out.required_alignment != 0 &&
631
+ out.required_alignment != edge.aligned_required_alignment) ||
632
+ reinterpret_cast<uintptr_t>(out.payload) % edge.aligned_required_alignment != 0) {
633
+ g_routing_state.rejected_frames++;
634
+ return -26;
635
+ }
636
+ }
637
+
638
+ std::shared_ptr<PayloadStorage> aliased_storage;
639
+ uint32_t aliased_offset = 0;
640
+ const bool aliases_current = flow_find_current_alias(
641
+ out.payload, out.length, &aliased_storage, &aliased_offset);
642
+
643
+ for (uint32_t edge_index : matching_edges) {
644
+ const FlowEdge &edge = g_edges[edge_index];
645
+ QueuedFrame frame;
646
+ frame.port = edge.to_port;
647
+ frame.payload_size = out.length;
648
+ frame.type_descriptor_idx = edge_index;
649
+ frame.stream_id = out.stream_id;
650
+ frame.sequence = out.sequence;
651
+ frame.end_of_stream = out.end_of_stream;
652
+ frame.frame_id = out.frame_id != 0 ? out.frame_id : g_next_frame_id++;
653
+ // An opaque edge has no aligned layout to inherit (the compiler forces
654
+ // alignedEligible=false, so its layout scalars are all zero). Byte frames
655
+ // carry alignment 1 and make no layout claim.
656
+ // A byte route is either an OPAQUE edge or a typed edge that offers no
657
+ // aligned route: in both cases the edge carries no aligned layout for the
658
+ // frame to inherit, so the payload travels as untyped bytes at alignment 1.
659
+ const bool byte_route =
660
+ edge.opaque != 0 ||
661
+ (out.wire_format == kFlowAlignedBinary && edge.aligned_eligible == 0);
662
+ // `aligned-binary` is a claim about a FIXED SDS layout — byte length,
663
+ // fixed-string length, required alignment. An opaque payload has none of
664
+ // those, so the claim cannot be carried across the edge: the frame is
665
+ // delivered in the UNTYPED form, which is what a wildcard input port
666
+ // accepts and what the pre-type-contract generation of these artifacts
667
+ // delivered. Producers that stamp ALIGNED_BINARY to mean "raw bytes" are
668
+ // normalized here rather than silently rejected at the consumer's port.
669
+ frame.wire_format =
670
+ byte_route ? kFlowFlatbuffer : static_cast<uint8_t>(out.wire_format);
671
+ frame.ownership = kFlowHostOwned;
672
+ frame.mutability = kFlowImmutable;
673
+ if (!byte_route && out.wire_format == kFlowAlignedBinary) {
674
+ frame.alignment = edge.aligned_required_alignment;
675
+ // A variable-length aligned stream (edge stride 0) carries the frame's
676
+ // own size; a fixed-stride edge carries the declared stride.
677
+ frame.byte_length = edge.aligned_byte_length != 0
678
+ ? edge.aligned_byte_length
679
+ : out.length;
680
+ frame.fixed_string_length = edge.aligned_fixed_string_length;
681
+ frame.required_alignment = edge.aligned_required_alignment;
682
+ } else {
683
+ frame.alignment = 1;
684
+ frame.byte_length = 0;
685
+ frame.fixed_string_length = 0;
686
+ frame.required_alignment = 1;
687
+ }
688
+ const uint32_t copy_alignment =
689
+ !byte_route && out.wire_format == kFlowAlignedBinary
690
+ ? edge.aligned_required_alignment
691
+ : 1;
692
+ const bool preowned_usable =
693
+ out.owned_storage != nullptr && out.length > 0 &&
694
+ (copy_alignment <= 1 ||
695
+ (reinterpret_cast<uintptr_t>(out.owned_storage->bytes.data()) +
696
+ out.owned_offset) %
697
+ copy_alignment ==
698
+ 0);
699
+ if (preowned_usable) {
700
+ frame.storage = out.owned_storage;
701
+ frame.payload_offset = out.owned_offset;
702
+ if (!byte_route && out.wire_format == kFlowAlignedBinary) {
703
+ g_routing_state.aligned_shared_routes++;
704
+ } else {
705
+ g_routing_state.canonical_routes++;
706
+ }
707
+ } else if (aliases_current) {
708
+ frame.storage = aliased_storage;
709
+ frame.payload_offset = aliased_offset;
710
+ if (!byte_route && out.wire_format == kFlowAlignedBinary) {
711
+ g_routing_state.aligned_shared_routes++;
712
+ } else {
713
+ g_routing_state.canonical_routes++;
714
+ }
715
+ } else {
716
+ frame.storage = flow_copy_payload(
717
+ out.payload, out.length, copy_alignment, &frame.payload_offset);
718
+ if (!byte_route && out.wire_format == kFlowAlignedBinary) {
719
+ g_routing_state.aligned_copied_routes++;
720
+ } else {
721
+ g_routing_state.canonical_routes++;
722
+ }
723
+ }
724
+ uint32_t to = edge.to_node;
725
+ g_queues[to].push_back(static_cast<QueuedFrame &&>(frame));
726
+ g_node_states[to].queued_frames = static_cast<uint32_t>(g_queues[to].size());
727
+ g_node_states[to].ready = flow_node_is_ready(to) ? 1 : 0;
728
+ }
729
+ return static_cast<int32_t>(matching_edges.size());
730
+ }
731
+
732
+ // ---------------------------------------------------------------------------
733
+ // SDK invoke-ABI shim: the linked guest-link method entries read their inputs
734
+ // and push their outputs through these functions (declared by the SDK's
735
+ // generated space_data_module_invoke.h). Inputs alias the current
736
+ // invocation's queued payload buffers — zero copies inside the flow.
737
+ // ---------------------------------------------------------------------------
738
+
739
+ static std::vector<plugin_input_frame_t> g_shim_inputs;
740
+
741
+ struct ShimOutput {
742
+ std::string port;
743
+ std::string schema_name;
744
+ std::string file_identifier;
745
+ std::string root_type_name;
746
+ // OWNED AT PUSH TIME. A guest entry pushes from ITS OWN buffers — very often
747
+ // a temporary that dies at the end of the pushing statement — and routing
748
+ // happens only after the entry returns. Borrowing the pointer here was a
749
+ // use-after-free that corrupted payloads silently instead of failing: the
750
+ // first shipped symptom was a downstream node receiving another output's
751
+ // bytes. The push either takes a reference to the invocation storage the
752
+ // payload already aliases (still zero-copy) or copies immediately.
753
+ std::shared_ptr<PayloadStorage> storage;
754
+ uint32_t payload_offset = 0;
755
+ uint32_t payload_length = 0;
756
+ uint32_t wire_format = kFlowFlatbuffer;
757
+ uint32_t fixed_string_length = 0;
758
+ uint32_t byte_length = 0;
759
+ uint32_t required_alignment = 1;
760
+ uint64_t sequence = 0;
761
+ int32_t end_of_stream = 0;
762
+ };
763
+ static std::vector<ShimOutput> g_shim_outputs;
764
+ static std::string g_shim_error_code;
765
+ static std::string g_shim_error_message;
766
+ static int32_t g_shim_yielded = 0;
767
+ static uint32_t g_shim_backlog_remaining = 0;
768
+
769
+ extern "C" uint32_t plugin_get_input_count(void) {
770
+ return static_cast<uint32_t>(g_shim_inputs.size());
771
+ }
772
+
773
+ extern "C" const plugin_input_frame_t *plugin_get_input_frame(uint32_t index) {
774
+ if (index >= g_shim_inputs.size()) return nullptr;
775
+ return &g_shim_inputs[index];
776
+ }
777
+
778
+ extern "C" int32_t plugin_find_input_index(const char *port_id, uint32_t ordinal) {
779
+ if (port_id == nullptr) return -1;
780
+ uint32_t seen = 0;
781
+ for (uint32_t i = 0; i < g_shim_inputs.size(); i++) {
782
+ if (g_shim_inputs[i].port_id != nullptr && strcmp(g_shim_inputs[i].port_id, port_id) == 0) {
783
+ if (seen == ordinal) return static_cast<int32_t>(i);
784
+ seen++;
785
+ }
786
+ }
787
+ return -1;
788
+ }
789
+
790
+ extern "C" void plugin_reset_output_state(void) {
791
+ g_shim_outputs.clear();
792
+ g_shim_error_code.clear();
793
+ g_shim_error_message.clear();
794
+ g_shim_yielded = 0;
795
+ g_shim_backlog_remaining = 0;
796
+ }
797
+
798
+ static int32_t shim_push_output(
799
+ const char *port_id, const char *schema_name, const char *file_identifier,
800
+ uint32_t wire_format, const char *root_type_name,
801
+ uint32_t fixed_string_length, uint32_t byte_length,
802
+ uint32_t required_alignment, const uint8_t *payload_ptr,
803
+ uint32_t payload_length) {
804
+ ShimOutput out;
805
+ out.port = port_id != nullptr ? port_id : "";
806
+ out.schema_name = schema_name != nullptr ? schema_name : "";
807
+ out.file_identifier = file_identifier != nullptr ? file_identifier : "";
808
+ out.root_type_name = root_type_name != nullptr ? root_type_name : "";
809
+ out.payload_length = payload_length;
810
+ if (payload_ptr != nullptr && payload_length > 0) {
811
+ // Zero-copy stays available for the case it exists to serve: a payload
812
+ // that IS a slice of one of this invocation's own input buffers (a raw
813
+ // passthrough, a stream page). That storage outlives the entry because
814
+ // the invocation owns it. Anything else is copied here, while it is
815
+ // still alive.
816
+ std::shared_ptr<PayloadStorage> aliased_storage;
817
+ uint32_t aliased_offset = 0;
818
+ if (flow_find_current_alias(payload_ptr, payload_length, &aliased_storage,
819
+ &aliased_offset)) {
820
+ out.storage = aliased_storage;
821
+ out.payload_offset = aliased_offset;
822
+ } else {
823
+ out.storage = flow_copy_payload(
824
+ payload_ptr, payload_length,
825
+ required_alignment > 0 ? required_alignment : 1, &out.payload_offset);
826
+ }
827
+ }
828
+ out.wire_format = wire_format;
829
+ out.fixed_string_length = fixed_string_length;
830
+ out.byte_length = byte_length;
831
+ out.required_alignment = required_alignment > 0 ? required_alignment : 1;
832
+ g_shim_outputs.push_back(static_cast<ShimOutput &&>(out));
833
+ return 0;
834
+ }
835
+
836
+ extern "C" int32_t plugin_push_output(const char *port_id, const char *schema_name,
837
+ const char *file_identifier,
838
+ const uint8_t *payload_ptr, uint32_t payload_length) {
839
+ return shim_push_output(
840
+ port_id, schema_name, file_identifier, kFlowFlatbuffer, nullptr,
841
+ 0, 0, 1, payload_ptr, payload_length);
842
+ }
843
+
844
+ extern "C" int32_t plugin_push_output_typed(
845
+ const char *port_id, const char *schema_name, const char *file_identifier,
846
+ uint32_t wire_format, const char *root_type_name,
847
+ uint16_t fixed_string_length, uint32_t byte_length,
848
+ uint16_t required_alignment, const uint8_t *payload_ptr,
849
+ uint32_t payload_length) {
850
+ return shim_push_output(
851
+ port_id, schema_name, file_identifier, wire_format, root_type_name,
852
+ fixed_string_length, byte_length, required_alignment,
853
+ payload_ptr, payload_length);
854
+ }
855
+
856
+ extern "C" int32_t plugin_push_output_ex(
857
+ const char *port_id, const char *schema_name, const char *file_identifier,
858
+ uint32_t wire_format, const char *root_type_name,
859
+ uint16_t fixed_string_length, uint16_t required_alignment,
860
+ const uint8_t *payload_ptr, uint32_t payload_length) {
861
+ return shim_push_output(
862
+ port_id, schema_name, file_identifier, wire_format, root_type_name,
863
+ fixed_string_length, payload_length, required_alignment,
864
+ payload_ptr, payload_length);
865
+ }
866
+
867
+ extern "C" int32_t plugin_set_output_frame_id(uint32_t output_index, uint64_t frame_id) {
868
+ if (output_index >= g_shim_outputs.size()) return -1;
869
+ g_shim_outputs[output_index].sequence = frame_id >> 1;
870
+ g_shim_outputs[output_index].end_of_stream = static_cast<int32_t>(frame_id & 1u);
871
+ return 0;
872
+ }
873
+
874
+ extern "C" int32_t plugin_set_output_stream_frame(uint32_t output_index, uint64_t sequence,
875
+ int32_t end_of_stream) {
876
+ if (output_index >= g_shim_outputs.size()) return -1;
877
+ g_shim_outputs[output_index].sequence = sequence;
878
+ g_shim_outputs[output_index].end_of_stream = end_of_stream;
879
+ return 0;
880
+ }
881
+
882
+ extern "C" void plugin_set_yielded(int32_t yielded) { g_shim_yielded = yielded; }
883
+
884
+ extern "C" void plugin_set_backlog_remaining(uint32_t backlog_remaining) {
885
+ g_shim_backlog_remaining = backlog_remaining;
886
+ }
887
+
888
+ extern "C" void plugin_set_error(const char *error_code, const char *error_message) {
889
+ g_shim_error_code = error_code != nullptr ? error_code : "";
890
+ g_shim_error_message = error_message != nullptr ? error_message : "";
891
+ }
892
+
893
+ static int32_t flow_validate_frame_descriptor(
894
+ const FlowFrameDescriptorC &fd, bool require_active_generation) {
895
+ if (fd.occupied == 0) return 0;
896
+ const uint32_t alignment = flow_effective_alignment(fd.alignment);
897
+ if (!flow_is_power_of_two(alignment)) return -30;
898
+ if (fd.wire_format > kFlowAlignedBinary || fd.ownership > kFlowTransferred ||
899
+ fd.mutability > kFlowAppendOnly) {
900
+ return -31;
901
+ }
902
+ if (fd.mutability != kFlowImmutable && fd.ownership != kFlowTransferred) {
903
+ return -32;
904
+ }
905
+ if (fd.size > 0 &&
906
+ (fd.offset == 0 || !flow_valid_memory_range(fd.offset, fd.size))) {
907
+ return -33;
908
+ }
909
+ if (fd.size > 0 && fd.offset % alignment != 0) return -34;
910
+ if (!flow_valid_c_string(fd.port_id_ptr)) return -35;
911
+ if (require_active_generation &&
912
+ (g_current_node == kInvalidIndex || fd.ingress_index != g_invocation_generation)) {
913
+ return -36;
914
+ }
915
+ if (fd.wire_format == kFlowAlignedBinary) {
916
+ FlowTypeDescriptorView type_descriptor;
917
+ if (require_active_generation) {
918
+ if (fd.type_descriptor_idx >= FLOW_ROUTE_EDGE_COUNT) return -37;
919
+ } else if (!flow_resolve_type_descriptor(fd.type_descriptor_idx,
920
+ &type_descriptor)) {
921
+ return -37;
922
+ }
923
+ if (require_active_generation &&
924
+ !flow_resolve_type_descriptor(fd.type_descriptor_idx,
925
+ &type_descriptor)) {
926
+ return -37;
927
+ }
928
+ // An OPAQUE descriptor has no layout to prove the frame against: the
929
+ // aligned-binary wire format is the SDK's byte-frame convention here, not
930
+ // a claim about a fixed SDS layout. Bytes still have to live inside linear
931
+ // memory (checked above); they simply have no byte_length/alignment
932
+ // contract to satisfy.
933
+ if (type_descriptor.opaque != 0) return 0;
934
+ if (type_descriptor.aligned_eligible == 0 ||
935
+ type_descriptor.aligned_byte_length == 0 ||
936
+ type_descriptor.aligned_required_alignment == 0 ||
937
+ !flow_is_power_of_two(type_descriptor.aligned_required_alignment) ||
938
+ fd.size != type_descriptor.aligned_byte_length ||
939
+ alignment < type_descriptor.aligned_required_alignment ||
940
+ fd.offset % type_descriptor.aligned_required_alignment != 0) {
941
+ return -38;
942
+ }
943
+ }
944
+ return 0;
945
+ }
946
+
947
+ static bool flow_external_frame_id_active(uint64_t frame_id) {
948
+ if (frame_id == 0) return false;
949
+ for (const ExternalFrameLifetime &active : g_active_external_frames) {
950
+ if (active.frame_id == frame_id) return true;
951
+ }
952
+ return false;
953
+ }
954
+
955
+ static void flow_retain_external_frame_id(uint64_t frame_id) {
956
+ if (frame_id == 0) return;
957
+ for (ExternalFrameLifetime &active : g_active_external_frames) {
958
+ if (active.frame_id != frame_id) continue;
959
+ active.references++;
960
+ return;
961
+ }
962
+ g_active_external_frames.push_back({frame_id, 1});
963
+ }
964
+
965
+ static void flow_release_external_frame_id(const QueuedFrame &frame) {
966
+ if (frame.tracks_external_lifetime == 0 || frame.frame_id == 0) return;
967
+ for (size_t index = 0; index < g_active_external_frames.size(); index++) {
968
+ ExternalFrameLifetime &active = g_active_external_frames[index];
969
+ if (active.frame_id != frame.frame_id) continue;
970
+ if (active.references > 1) {
971
+ active.references--;
972
+ } else {
973
+ g_active_external_frames.erase(g_active_external_frames.begin() + index);
974
+ }
975
+ return;
976
+ }
977
+ }
978
+
979
+ // ---------------------------------------------------------------------------
980
+ // space_data_module_runtime_* exports
981
+ // ---------------------------------------------------------------------------
982
+
983
+ // The descriptor-table ABI GENERATION. A host reads g_edges as a packed array
984
+ // at a fixed stride, and nothing in the artifact used to say which stride —
985
+ // so a host that had moved on read every edge past the first at the wrong
986
+ // offset and silently believed the garbage. That is the one failure mode a
987
+ // binary interface must never have. Generation 2 is the 68-byte FlowEdge
988
+ // carrying OPAQUE; artifacts predating this export do not export it at all,
989
+ // which is itself the answer (generation 1) and is why the host must treat a
990
+ // MISSING export as a mismatch rather than as permission.
991
+ #define FLOW_DESCRIPTOR_ABI_GENERATION 2u
992
+
993
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_descriptor_abi_generation(void) {
994
+ return FLOW_DESCRIPTOR_ABI_GENERATION;
995
+ }
996
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_node_descriptor_count(void) {
997
+ return FLOW_NODE_COUNT;
998
+ }
999
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_edge_descriptor_count(void) {
1000
+ return FLOW_EDGE_COUNT;
1001
+ }
1002
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_edge_descriptors(void) {
1003
+ return reinterpret_cast<uint32_t>(g_edges);
1004
+ }
1005
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_route_edge_descriptor_count(void) {
1006
+ return FLOW_ROUTE_EDGE_COUNT;
1007
+ }
1008
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_routing_state(void) {
1009
+ return reinterpret_cast<uint32_t>(&g_routing_state);
1010
+ }
1011
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_current_invocation_generation(void) {
1012
+ return g_current_node == kInvalidIndex ? 0 : g_invocation_generation;
1013
+ }
1014
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_trigger_descriptor_count(void) {
1015
+ return FLOW_TRIGGER_COUNT;
1016
+ }
1017
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_dependency_descriptor_count(void) {
1018
+ return FLOW_DEP_COUNT;
1019
+ }
1020
+
1021
+ FLOW_EXPORT void space_data_module_runtime_reset_state(void) {
1022
+ for (uint32_t n = 0; n < FLOW_NODE_COUNT; n++) {
1023
+ g_queues[n].clear();
1024
+ memset(&g_node_states[n], 0, sizeof(FlowNodeRuntimeStateC));
1025
+ }
1026
+ for (uint32_t t = 0; t < FLOW_TRIGGER_COUNT; t++) {
1027
+ memset(&g_ingress_states[t], 0, sizeof(FlowIngressRuntimeStateC));
1028
+ }
1029
+ for (uint32_t index = 0; index < kMaxInvocationFrames; index++) {
1030
+ g_current_owned[index] = QueuedFrame();
1031
+ memset(&g_current_frames[index], 0, sizeof(FlowFrameDescriptorC));
1032
+ }
1033
+ g_current_desc = FlowInvocationDescriptorC();
1034
+ memset(&g_routing_state, 0, sizeof(g_routing_state));
1035
+ g_active_external_frames.clear();
1036
+ g_current_node = kInvalidIndex;
1037
+ g_ready_cursor = 0;
1038
+ g_invocation_generation = 0;
1039
+ g_next_frame_id = 1;
1040
+ #ifdef SDN_FLATSQL_LINKED
1041
+ sdn_flatsql_link_reset_refs();
1042
+ #endif
1043
+ }
1044
+
1045
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_ready_node_index(void) {
1046
+ if (g_current_node != kInvalidIndex) return kInvalidIndex; // invocation open
1047
+ return flow_find_ready_node();
1048
+ }
1049
+
1050
+ FLOW_EXPORT int32_t space_data_module_runtime_begin_node_invocation(int32_t node_index,
1051
+ int32_t frame_budget) {
1052
+ if (node_index < 0 || static_cast<uint32_t>(node_index) >= FLOW_NODE_COUNT) return -1;
1053
+ uint32_t node = static_cast<uint32_t>(node_index);
1054
+ uint32_t budget = frame_budget > 0 ? static_cast<uint32_t>(frame_budget) : kMaxInvocationFrames;
1055
+ if (budget > kMaxInvocationFrames) budget = kMaxInvocationFrames;
1056
+
1057
+ uint32_t count = 0;
1058
+ auto &queue = g_queues[node];
1059
+ while (count < budget && !queue.empty()) {
1060
+ g_current_owned[count] = static_cast<QueuedFrame &&>(queue.front());
1061
+ queue.erase(queue.begin());
1062
+ count++;
1063
+ }
1064
+ g_invocation_generation++;
1065
+ if (g_invocation_generation == 0) g_invocation_generation = 1;
1066
+ for (uint32_t i = 0; i < count; i++) {
1067
+ FlowFrameDescriptorC &fd = g_current_frames[i];
1068
+ memset(&fd, 0, sizeof(fd));
1069
+ fd.ingress_index = g_invocation_generation;
1070
+ fd.type_descriptor_idx = g_current_owned[i].type_descriptor_idx;
1071
+ fd.port_id_ptr = reinterpret_cast<uint32_t>(g_current_owned[i].port.c_str());
1072
+ fd.alignment = g_current_owned[i].alignment;
1073
+ fd.offset = reinterpret_cast<uint32_t>(g_current_owned[i].data());
1074
+ fd.size = g_current_owned[i].payload_size;
1075
+ fd.stream_id = g_current_owned[i].stream_id;
1076
+ fd.sequence = g_current_owned[i].sequence;
1077
+ fd.trace_token = g_current_owned[i].frame_id;
1078
+ fd.end_of_stream = g_current_owned[i].end_of_stream;
1079
+ fd.occupied = 1;
1080
+ fd.wire_format = g_current_owned[i].wire_format;
1081
+ fd.ownership = g_current_owned[i].ownership;
1082
+ fd.mutability = g_current_owned[i].mutability;
1083
+ fd.lifetime = 1;
1084
+ }
1085
+
1086
+ const FlowNodeDispatchDescriptorC &dd = g_dispatch_descriptors[node];
1087
+ g_current_desc.node_index = node;
1088
+ g_current_desc.dispatch_descriptor_idx = node;
1089
+ g_current_desc.plugin_id_ptr = dd.plugin_id_ptr;
1090
+ g_current_desc.method_id_ptr = dd.method_id_ptr;
1091
+ g_current_desc.frames_ptr = count > 0 ? reinterpret_cast<uint32_t>(&g_current_frames[0]) : 0;
1092
+ g_current_desc.frame_count = count;
1093
+ g_current_node = node;
1094
+
1095
+ g_node_states[node].queued_frames = static_cast<uint32_t>(queue.size());
1096
+ g_node_states[node].ready = flow_node_is_ready(node) ? 1 : 0;
1097
+ return static_cast<int32_t>(count);
1098
+ }
1099
+
1100
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_current_invocation_descriptor(void) {
1101
+ if (g_current_node == kInvalidIndex) return 0;
1102
+ return reinterpret_cast<uint32_t>(&g_current_desc);
1103
+ }
1104
+
1105
+ FLOW_EXPORT int32_t space_data_module_runtime_apply_node_invocation_result(
1106
+ int32_t node_index, int32_t status_code, int32_t backlog_remaining, int32_t yielded,
1107
+ int32_t frames_ptr, int32_t frame_count) {
1108
+ if (node_index < 0 || static_cast<uint32_t>(node_index) >= FLOW_NODE_COUNT) return -40;
1109
+ uint32_t node = static_cast<uint32_t>(node_index);
1110
+ if (g_current_node == kInvalidIndex || g_current_node != node) return -41;
1111
+ if (frame_count < 0 || frame_count > static_cast<int32_t>(kMaxInvocationFrames)) return -42;
1112
+ if (frame_count > 0 &&
1113
+ (frames_ptr <= 0 ||
1114
+ !flow_valid_memory_range(static_cast<uint32_t>(frames_ptr),
1115
+ static_cast<uint32_t>(frame_count) *
1116
+ sizeof(FlowFrameDescriptorC)))) {
1117
+ return -43;
1118
+ }
1119
+ uint32_t routed = 0;
1120
+ const FlowFrameDescriptorC *frames = reinterpret_cast<const FlowFrameDescriptorC *>(frames_ptr);
1121
+ for (int32_t i = 0; i < frame_count; i++) {
1122
+ const FlowFrameDescriptorC &fd = frames[i];
1123
+ if (!fd.occupied) continue;
1124
+ const int32_t validation = flow_validate_frame_descriptor(fd, true);
1125
+ if (validation < 0) {
1126
+ g_routing_state.rejected_frames++;
1127
+ return validation;
1128
+ }
1129
+ const char *port = fd.port_id_ptr != 0 ? reinterpret_cast<const char *>(fd.port_id_ptr) : "";
1130
+ FlowOutputFrameView output;
1131
+ output.port = port;
1132
+ output.payload = reinterpret_cast<const uint8_t *>(fd.offset);
1133
+ output.length = fd.size;
1134
+ output.wire_format = fd.wire_format;
1135
+ output.alignment = flow_effective_alignment(fd.alignment);
1136
+ output.stream_id = fd.stream_id;
1137
+ output.sequence = fd.sequence;
1138
+ output.frame_id = fd.trace_token;
1139
+ output.end_of_stream = fd.end_of_stream;
1140
+ output.ownership = fd.ownership;
1141
+ output.mutability = fd.mutability;
1142
+ if (fd.type_descriptor_idx < FLOW_ROUTE_EDGE_COUNT) {
1143
+ const FlowEdge &edge = g_edges[fd.type_descriptor_idx];
1144
+ output.schema_name = edge.schema_name;
1145
+ output.file_identifier = edge.file_identifier;
1146
+ output.root_type_name = edge.root_type_name;
1147
+ output.byte_length = edge.aligned_byte_length;
1148
+ output.fixed_string_length = edge.aligned_fixed_string_length;
1149
+ output.required_alignment = edge.aligned_required_alignment;
1150
+ }
1151
+ const int32_t route_count = route_output(node, output);
1152
+ if (route_count < 0) return route_count;
1153
+ routed += static_cast<uint32_t>(route_count);
1154
+ }
1155
+ g_node_states[node].invocation_count++;
1156
+ g_node_states[node].consumed_frames += g_current_desc.frame_count;
1157
+ g_node_states[node].backlog_remaining = static_cast<uint32_t>(backlog_remaining);
1158
+ g_node_states[node].last_status = static_cast<uint32_t>(status_code);
1159
+ g_node_states[node].yielded = yielded != 0 ? 1 : 0;
1160
+ g_node_states[node].ready = flow_node_is_ready(node) ? 1 : 0;
1161
+ return static_cast<int32_t>(routed);
1162
+ }
1163
+
1164
+ FLOW_EXPORT void space_data_module_runtime_complete_node_invocation(int32_t node_index) {
1165
+ (void)node_index;
1166
+ if (g_current_node == kInvalidIndex) return;
1167
+ const uint32_t completed_node = g_current_node;
1168
+ for (uint32_t i = 0; i < g_current_desc.frame_count; i++) {
1169
+ flow_release_external_frame_id(g_current_owned[i]);
1170
+ g_current_owned[i] = QueuedFrame();
1171
+ }
1172
+ g_current_desc = FlowInvocationDescriptorC();
1173
+ g_current_node = kInvalidIndex;
1174
+ g_ready_cursor = (completed_node + 1u) % FLOW_NODE_COUNT;
1175
+ }
1176
+
1177
+ static void flow_enqueue_binding(const FlowTriggerBinding &binding,
1178
+ const FlowFrameDescriptorC *descriptor) {
1179
+ QueuedFrame frame;
1180
+ frame.port = binding.port;
1181
+ if (descriptor != nullptr) {
1182
+ const uint8_t *payload = descriptor->size > 0
1183
+ ? reinterpret_cast<const uint8_t *>(descriptor->offset)
1184
+ : nullptr;
1185
+ frame.payload_size = descriptor->size;
1186
+ frame.type_descriptor_idx = binding.type_descriptor_idx;
1187
+ frame.alignment = flow_effective_alignment(descriptor->alignment);
1188
+ frame.stream_id = descriptor->stream_id;
1189
+ frame.sequence = descriptor->sequence;
1190
+ frame.end_of_stream = descriptor->end_of_stream;
1191
+ frame.frame_id = descriptor->trace_token != 0
1192
+ ? descriptor->trace_token
1193
+ : g_next_frame_id++;
1194
+ frame.tracks_external_lifetime = descriptor->trace_token != 0 ? 1 : 0;
1195
+ frame.wire_format = descriptor->wire_format;
1196
+ frame.ownership = kFlowHostOwned;
1197
+ frame.mutability = kFlowImmutable;
1198
+ if (descriptor->wire_format == kFlowAlignedBinary) {
1199
+ FlowTypeDescriptorView type_descriptor;
1200
+ if (flow_resolve_type_descriptor(frame.type_descriptor_idx,
1201
+ &type_descriptor) &&
1202
+ type_descriptor.opaque == 0) {
1203
+ frame.byte_length = type_descriptor.aligned_byte_length;
1204
+ frame.fixed_string_length = type_descriptor.aligned_fixed_string_length;
1205
+ frame.required_alignment = type_descriptor.aligned_required_alignment;
1206
+ }
1207
+ }
1208
+ frame.storage = flow_copy_payload(
1209
+ payload, descriptor->size, flow_effective_alignment(descriptor->alignment),
1210
+ &frame.payload_offset);
1211
+ } else {
1212
+ frame.type_descriptor_idx = binding.type_descriptor_idx;
1213
+ frame.frame_id = g_next_frame_id++;
1214
+ frame.storage = flow_copy_payload(nullptr, 0, 1, &frame.payload_offset);
1215
+ }
1216
+ g_queues[binding.target_node].push_back(static_cast<QueuedFrame &&>(frame));
1217
+ if (descriptor != nullptr && descriptor->trace_token != 0) {
1218
+ flow_retain_external_frame_id(descriptor->trace_token);
1219
+ }
1220
+ g_node_states[binding.target_node].queued_frames =
1221
+ static_cast<uint32_t>(g_queues[binding.target_node].size());
1222
+ g_node_states[binding.target_node].ready =
1223
+ flow_node_is_ready(binding.target_node) ? 1 : 0;
1224
+ }
1225
+
1226
+ FLOW_EXPORT void space_data_module_runtime_enqueue_trigger_frames(int32_t trigger_index) {
1227
+ if (trigger_index < 0 || static_cast<uint32_t>(trigger_index) >= FLOW_TRIGGER_COUNT) return;
1228
+ for (uint32_t b = 0; b < FLOW_TRIGGER_BINDING_COUNT; b++) {
1229
+ if (g_trigger_bindings[b].trigger_index != static_cast<uint32_t>(trigger_index)) continue;
1230
+ flow_enqueue_binding(g_trigger_bindings[b], nullptr);
1231
+ }
1232
+ g_ingress_states[trigger_index].total_received++;
1233
+ g_ingress_states[trigger_index].queued_frames++;
1234
+ }
1235
+
1236
+ FLOW_EXPORT int32_t space_data_module_runtime_enqueue_trigger_frame(int32_t trigger_index,
1237
+ int32_t frame_ptr) {
1238
+ if (trigger_index < 0 || static_cast<uint32_t>(trigger_index) >= FLOW_TRIGGER_COUNT) return -50;
1239
+ if (frame_ptr == 0) {
1240
+ space_data_module_runtime_enqueue_trigger_frames(trigger_index);
1241
+ return 0;
1242
+ }
1243
+ if (frame_ptr < 0 ||
1244
+ !flow_valid_memory_range(static_cast<uint32_t>(frame_ptr),
1245
+ sizeof(FlowFrameDescriptorC))) {
1246
+ g_routing_state.rejected_frames++;
1247
+ return -51;
1248
+ }
1249
+ const FlowFrameDescriptorC *fd = reinterpret_cast<const FlowFrameDescriptorC *>(frame_ptr);
1250
+ const int32_t validation = flow_validate_frame_descriptor(*fd, false);
1251
+ if (validation < 0) {
1252
+ g_routing_state.rejected_frames++;
1253
+ return validation;
1254
+ }
1255
+ if (fd->trace_token != 0 && flow_external_frame_id_active(fd->trace_token)) {
1256
+ g_routing_state.rejected_frames++;
1257
+ return -52;
1258
+ }
1259
+ if (fd->size > 0 && fd->type_descriptor_idx == kInvalidIndex) {
1260
+ FlowTypeDescriptorView common_type;
1261
+ bool common_type_set = false;
1262
+ for (uint32_t b = 0; b < FLOW_TRIGGER_BINDING_COUNT; b++) {
1263
+ if (g_trigger_bindings[b].trigger_index !=
1264
+ static_cast<uint32_t>(trigger_index)) {
1265
+ continue;
1266
+ }
1267
+ FlowTypeDescriptorView binding_type;
1268
+ if (!flow_resolve_type_descriptor(
1269
+ g_trigger_bindings[b].type_descriptor_idx, &binding_type) ||
1270
+ (common_type_set &&
1271
+ !flow_type_identity_equal(common_type, binding_type))) {
1272
+ g_routing_state.rejected_frames++;
1273
+ return -54;
1274
+ }
1275
+ if (!common_type_set) {
1276
+ common_type = binding_type;
1277
+ common_type_set = true;
1278
+ }
1279
+ }
1280
+ }
1281
+ for (uint32_t b = 0; b < FLOW_TRIGGER_BINDING_COUNT; b++) {
1282
+ if (g_trigger_bindings[b].trigger_index !=
1283
+ static_cast<uint32_t>(trigger_index)) {
1284
+ continue;
1285
+ }
1286
+ if (fd->type_descriptor_idx != kInvalidIndex &&
1287
+ !flow_binding_accepts_descriptor(b, fd->type_descriptor_idx,
1288
+ fd->wire_format)) {
1289
+ g_routing_state.rejected_frames++;
1290
+ return -53;
1291
+ }
1292
+ }
1293
+ uint32_t enqueued = 0;
1294
+ for (uint32_t b = 0; b < FLOW_TRIGGER_BINDING_COUNT; b++) {
1295
+ if (g_trigger_bindings[b].trigger_index != static_cast<uint32_t>(trigger_index)) continue;
1296
+ flow_enqueue_binding(g_trigger_bindings[b], fd);
1297
+ enqueued++;
1298
+ }
1299
+ g_ingress_states[trigger_index].total_received++;
1300
+ g_ingress_states[trigger_index].queued_frames++;
1301
+ return static_cast<int32_t>(enqueued);
1302
+ }
1303
+
1304
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_node_dispatch_descriptors(void) {
1305
+ return reinterpret_cast<uint32_t>(&g_dispatch_descriptors[0]);
1306
+ }
1307
+
1308
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_dependency_descriptors(void) {
1309
+ #if FLOW_DEP_COUNT > 0
1310
+ return reinterpret_cast<uint32_t>(&g_dependency_descriptors[0]);
1311
+ #else
1312
+ return 0;
1313
+ #endif
1314
+ }
1315
+
1316
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_node_states(void) {
1317
+ return reinterpret_cast<uint32_t>(&g_node_states[0]);
1318
+ }
1319
+
1320
+ FLOW_EXPORT uint32_t space_data_module_runtime_get_ingress_states(void) {
1321
+ #if FLOW_TRIGGER_COUNT > 0
1322
+ return reinterpret_cast<uint32_t>(&g_ingress_states[0]);
1323
+ #else
1324
+ return 0;
1325
+ #endif
1326
+ }
1327
+
1328
+ FLOW_EXPORT int32_t space_data_module_runtime_dispatch_current_invocation_direct(
1329
+ int32_t frame_budget);
1330
+
1331
+ // In-wasm scheduler loop (loop C.5c): run every ready LINKED-DIRECT node to
1332
+ // completion inside one host call — ready-node selection, invocation
1333
+ // begin/dispatch/complete, and frame routing all stay in this module's
1334
+ // linear memory instead of costing 3-4 host<->wasm round-trips per node.
1335
+ // Returns the number of linked dispatches performed and stops (without
1336
+ // consuming it) at the first ready node that is NOT linked (host-model,
1337
+ // e.g. the egress sink) so the host loop handles it — node order is
1338
+ // identical to the host-driven loop (first ready by index). Hosts probe for
1339
+ // this export and fall back to per-node driving when absent (older
1340
+ // artifacts).
1341
+ FLOW_EXPORT int32_t space_data_module_runtime_drain_linked(int32_t max_iterations) {
1342
+ if (g_current_node != kInvalidIndex) return -1; // invocation open
1343
+ if (max_iterations == 0) return 0; // presence probe
1344
+ int32_t budget = max_iterations > 0 ? max_iterations : 1024;
1345
+ int32_t dispatched = 0;
1346
+ for (int32_t i = 0; i < budget; i++) {
1347
+ const uint32_t node = flow_find_ready_node();
1348
+ if (node == kInvalidIndex || !flow_node_is_linked(node)) break;
1349
+ if (space_data_module_runtime_begin_node_invocation(static_cast<int32_t>(node), 64) < 0) {
1350
+ space_data_module_runtime_complete_node_invocation(static_cast<int32_t>(node));
1351
+ break;
1352
+ }
1353
+ space_data_module_runtime_dispatch_current_invocation_direct(64);
1354
+ space_data_module_runtime_complete_node_invocation(static_cast<int32_t>(node));
1355
+ dispatched++;
1356
+ }
1357
+ return dispatched;
1358
+ }
1359
+
1360
+ // Linked-direct dispatch: run the current node's linked guest-link entry over
1361
+ // the current invocation frames — all inside this module's linear memory.
1362
+ FLOW_EXPORT int32_t space_data_module_runtime_dispatch_current_invocation_direct(
1363
+ int32_t frame_budget) {
1364
+ (void)frame_budget;
1365
+ if (g_current_node == kInvalidIndex) return -1;
1366
+ uint32_t node = g_current_node;
1367
+ if (!flow_node_is_linked(node)) return -2;
1368
+
1369
+ g_shim_inputs.clear();
1370
+ for (uint32_t i = 0; i < g_current_desc.frame_count; i++) {
1371
+ const QueuedFrame &owned = g_current_owned[i];
1372
+ plugin_input_frame_t input;
1373
+ memset(&input, 0, sizeof(input));
1374
+ input.port_id = owned.port.c_str();
1375
+ FlowTypeDescriptorView type_descriptor;
1376
+ if (flow_resolve_type_descriptor(owned.type_descriptor_idx,
1377
+ &type_descriptor)) {
1378
+ input.schema_name = type_descriptor.schema_name;
1379
+ input.file_identifier = type_descriptor.file_identifier;
1380
+ input.root_type_name = type_descriptor.root_type_name;
1381
+ input.schema_version = type_descriptor.schema_version;
1382
+ input.schema_hash = type_descriptor.schema_hash;
1383
+ input.schema_hash_length = type_descriptor.schema_hash_size;
1384
+ }
1385
+ input.payload = owned.data();
1386
+ input.payload_length = owned.payload_size;
1387
+ input.fixed_string_length = static_cast<uint16_t>(owned.fixed_string_length);
1388
+ input.byte_length = owned.byte_length;
1389
+ input.size = owned.payload_size;
1390
+ input.generation = g_invocation_generation;
1391
+ input.alignment = static_cast<uint16_t>(owned.alignment);
1392
+ input.required_alignment = static_cast<uint16_t>(owned.required_alignment);
1393
+ input.wire_format = owned.wire_format;
1394
+ input.trace_id = owned.frame_id;
1395
+ input.stream_id = owned.stream_id;
1396
+ input.sequence = owned.sequence;
1397
+ input.end_of_stream = owned.end_of_stream != 0 ? 1 : 0;
1398
+ input.ownership = owned.ownership;
1399
+ input.mutability = owned.mutability;
1400
+ input.frame_id = owned.frame_id;
1401
+ g_shim_inputs.push_back(input);
1402
+ }
1403
+ plugin_reset_output_state();
1404
+
1405
+ int32_t status = flow_call_entry(node);
1406
+
1407
+ uint32_t routed = 0;
1408
+ for (const ShimOutput &out : g_shim_outputs) {
1409
+ FlowOutputFrameView output;
1410
+ output.port = out.port.c_str();
1411
+ output.schema_name = out.schema_name.c_str();
1412
+ output.file_identifier = out.file_identifier.c_str();
1413
+ output.root_type_name = out.root_type_name.c_str();
1414
+ output.owned_storage = out.storage;
1415
+ output.owned_offset = out.payload_offset;
1416
+ output.payload =
1417
+ out.storage != nullptr
1418
+ ? out.storage->bytes.data() + out.payload_offset
1419
+ : nullptr;
1420
+ output.length = out.payload_length;
1421
+ output.wire_format = out.wire_format;
1422
+ output.fixed_string_length = out.fixed_string_length;
1423
+ output.byte_length = out.byte_length;
1424
+ output.required_alignment = out.required_alignment;
1425
+ output.alignment = out.required_alignment;
1426
+ output.sequence = static_cast<uint32_t>(out.sequence);
1427
+ output.frame_id = (out.sequence << 1) |
1428
+ (out.end_of_stream != 0 ? 1ull : 0ull);
1429
+ output.end_of_stream = out.end_of_stream != 0 ? 1 : 0;
1430
+ output.ownership = kFlowPluginOwned;
1431
+ output.mutability = kFlowImmutable;
1432
+ const int32_t route_count = route_output(node, output);
1433
+ if (route_count < 0) {
1434
+ status = route_count;
1435
+ break;
1436
+ }
1437
+ routed += static_cast<uint32_t>(route_count);
1438
+ }
1439
+ g_node_states[node].invocation_count++;
1440
+ g_node_states[node].consumed_frames += g_current_desc.frame_count;
1441
+ g_node_states[node].last_status = static_cast<uint32_t>(status);
1442
+ g_node_states[node].yielded = g_shim_yielded != 0 ? 1 : 0;
1443
+ g_node_states[node].backlog_remaining = g_shim_backlog_remaining;
1444
+ g_node_states[node].ready = flow_node_is_ready(node) ? 1 : 0;
1445
+ g_shim_inputs.clear();
1446
+ return static_cast<int32_t>(routed);
1447
+ }
1448
+
1449
+ #ifdef SDN_FLATSQL_LINKED
1450
+ // ============================================================================
1451
+ // Direct FlatSQL engine linkage (loop C.7 — the B-iv end state).
1452
+ //
1453
+ // Compiled ONLY into linked-mode flows (`flow.engineLinkage == "flatsql"`).
1454
+ // The artifact imports the live store engine's FUNCTION exports from module
1455
+ // "flatsql" (scalars cross instances fine) and crosses the memory boundary
1456
+ // through the tiny flatsql_link shim (src/flow/flatsqlLinkShim.js), whose
1457
+ // memory IS the engine memory. Query submission therefore never touches the
1458
+ // hostcall bridge: SQL text + TLV params are poked into engine-malloc'd
1459
+ // space, flatsql_query_raw_flatbuffer_stream runs as a direct in-wasm call,
1460
+ // and the materialized aligned stream stays in ENGINE memory.
1461
+ //
1462
+ // Result delivery keeps the loop C.5c body-reference contract: the guest
1463
+ // registers the engine artifact in the exported engine-ref table below and
1464
+ // forwards a {"$sdnbodyref":1,...} descriptor whose token carries the "SDNE"
1465
+ // magic. The host resolves the token AFTER the linked drain, while it still
1466
+ // holds the store engine lock (mirror hit = zero copies warm; miss = one
1467
+ // engine->host copy verified against the descriptor's fnv1a64).
1468
+ //
1469
+ // Concurrency contract (docs/flatsql-component-linkage.md section 4.1 item
1470
+ // 4): the engine is SQLITE_THREADSAFE=0 — the HOST must hold the store
1471
+ // engine lock for the whole linked drain that reaches these calls.
1472
+ // ============================================================================
1473
+
1474
+ #define FSL_IMPORT(mod, name) \
1475
+ extern "C" __attribute__((import_module(mod), import_name(name)))
1476
+
1477
+ FSL_IMPORT("flatsql", "malloc") uint32_t fsl_engine_malloc(uint32_t size);
1478
+ FSL_IMPORT("flatsql", "free") void fsl_engine_free(uint32_t ptr);
1479
+ FSL_IMPORT("flatsql", "flatsql_query_raw_flatbuffer_stream")
1480
+ int32_t fsl_engine_query_raw(int32_t handle, uint32_t sql_ptr, uint32_t param_ptr,
1481
+ uint32_t param_len, int32_t param_count);
1482
+ FSL_IMPORT("flatsql", "flatsql_response_artifact_data") uint32_t fsl_engine_artifact_data(void);
1483
+ FSL_IMPORT("flatsql", "flatsql_response_artifact_size") int32_t fsl_engine_artifact_size(void);
1484
+ FSL_IMPORT("flatsql", "flatsql_response_artifact_row_count") double fsl_engine_artifact_rows(void);
1485
+ FSL_IMPORT("flatsql", "flatsql_response_artifact_column_count") double fsl_engine_artifact_cols(void);
1486
+ FSL_IMPORT("flatsql", "flatsql_response_artifact_cache_hit") int32_t fsl_engine_artifact_cache_hit(void);
1487
+ FSL_IMPORT("flatsql", "flatsql_query_cache_generation") double fsl_engine_generation(int32_t handle);
1488
+ FSL_IMPORT("flatsql", "flatsql_get_error") uint32_t fsl_engine_get_error(void);
1489
+ FSL_IMPORT("flatsql_link", "peek8") uint32_t fsl_link_peek8(uint32_t addr);
1490
+ FSL_IMPORT("flatsql_link", "peek64") uint64_t fsl_link_peek64(uint32_t addr);
1491
+ FSL_IMPORT("flatsql_link", "poke8") void fsl_link_poke8(uint32_t addr, uint32_t value);
1492
+ FSL_IMPORT("flatsql_link", "fnv1a64") uint64_t fsl_link_fnv1a64(uint32_t addr, uint32_t len);
1493
+ FSL_IMPORT("flatsql_link", "count_frames") int32_t fsl_link_count_frames(uint32_t addr, uint32_t len);
1494
+
1495
+ // ABI struct shared with guest-link objects compiled -DSDN_FLATSQL_LINKED
1496
+ // (e.g. data-source/retrieval). Field order/sizes are load-bearing.
1497
+ struct SdnFlatsqlLinkedResult {
1498
+ uint64_t generation;
1499
+ uint64_t fnv1a64;
1500
+ uint64_t token; // engine body-ref token (0 unless want_ref)
1501
+ uint32_t engine_ptr;
1502
+ uint32_t size;
1503
+ int32_t rows;
1504
+ int32_t cols;
1505
+ int32_t cache_hit;
1506
+ int32_t frames;
1507
+ };
1508
+ static_assert(sizeof(SdnFlatsqlLinkedResult) == 48, "SdnFlatsqlLinkedResult must be 48 bytes");
1509
+
1510
+ // Engine body-reference table read by the host after each linked drain.
1511
+ // Layout mirrors flatsqlLinkShim.js readEngineRefEntry (40 bytes LE).
1512
+ struct SdnEngineRefEntry {
1513
+ uint64_t token;
1514
+ uint64_t generation;
1515
+ uint64_t fnv1a64;
1516
+ uint32_t engine_ptr;
1517
+ uint32_t size;
1518
+ uint32_t frames;
1519
+ uint32_t used;
1520
+ };
1521
+ static_assert(sizeof(SdnEngineRefEntry) == 40, "SdnEngineRefEntry must be 40 bytes");
1522
+
1523
+ static constexpr uint32_t kSdnEngineRefSlots = 8;
1524
+ static constexpr uint64_t kSdnEngineRefTokenMagic = 0x53444E45ull << 32; // "SDNE"
1525
+
1526
+ static SdnEngineRefEntry g_engine_refs[kSdnEngineRefSlots];
1527
+ static uint64_t g_engine_ref_counter = 0;
1528
+ static int32_t g_fsl_db_handle = 0;
1529
+ static char g_fsl_error[512];
1530
+
1531
+ static void sdn_flatsql_link_reset_refs(void) {
1532
+ memset(g_engine_refs, 0, sizeof(g_engine_refs));
1533
+ }
1534
+
1535
+ // Host wiring exports: the host sets the store db handle after instantiation
1536
+ // and reads the ref table (in this module's memory) after linked drains.
1537
+ FLOW_EXPORT void sdn_flatsql_link_init(int32_t db_handle) { g_fsl_db_handle = db_handle; }
1538
+ FLOW_EXPORT uint32_t sdn_flatsql_link_ref_table(void) {
1539
+ return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(&g_engine_refs[0]));
1540
+ }
1541
+ FLOW_EXPORT uint32_t sdn_flatsql_link_ref_slots(void) { return kSdnEngineRefSlots; }
1542
+
1543
+ extern "C" uint32_t sdn_flatsql_linked_available(void) { return g_fsl_db_handle != 0 ? 1u : 0u; }
1544
+ extern "C" const char *sdn_flatsql_linked_error(void) { return g_fsl_error; }
1545
+
1546
+ static void fsl_set_error_from_engine(const char *fallback) {
1547
+ uint32_t ptr = fsl_engine_get_error();
1548
+ uint32_t i = 0;
1549
+ if (ptr != 0) {
1550
+ for (; i < sizeof(g_fsl_error) - 1; i++) {
1551
+ uint32_t b = fsl_link_peek8(ptr + i);
1552
+ if (b == 0) break;
1553
+ g_fsl_error[i] = static_cast<char>(b);
1554
+ }
1555
+ }
1556
+ if (i == 0 && fallback != nullptr) {
1557
+ strncpy(g_fsl_error, fallback, sizeof(g_fsl_error) - 1);
1558
+ i = static_cast<uint32_t>(strlen(g_fsl_error));
1559
+ }
1560
+ g_fsl_error[i] = '\0';
1561
+ }
1562
+
1563
+ static void fsl_copy_to_engine(uint32_t dst, const uint8_t *src, uint32_t len) {
1564
+ for (uint32_t i = 0; i < len; i++) fsl_link_poke8(dst + i, src[i]);
1565
+ }
1566
+
1567
+ // Copy engine memory into this module's memory (byte path: json branch,
1568
+ // error strings). 8-byte strides through the shim keep the call count low.
1569
+ extern "C" int32_t sdn_flatsql_linked_read(uint8_t *dst, uint32_t engine_ptr, uint32_t len) {
1570
+ uint32_t i = 0;
1571
+ for (; i + 8 <= len; i += 8) {
1572
+ uint64_t w = fsl_link_peek64(engine_ptr + i);
1573
+ memcpy(dst + i, &w, 8);
1574
+ }
1575
+ for (; i < len; i++) dst[i] = static_cast<uint8_t>(fsl_link_peek8(engine_ptr + i));
1576
+ return 0;
1577
+ }
1578
+
1579
+ // Local FNV-1a 64 over FLOW memory (identity keys for the etag cache below —
1580
+ // never crosses the wire, does not need the word-folded shape).
1581
+ static uint64_t fsl_local_fnv(const uint8_t *data, uint32_t len) {
1582
+ uint64_t hash = 1469598103934665603ull;
1583
+ for (uint32_t i = 0; i < len; i++) {
1584
+ hash ^= data[i];
1585
+ hash *= 1099511628211ull;
1586
+ }
1587
+ return hash;
1588
+ }
1589
+
1590
+ // Etag/frame-count cache: hashing the (possibly multi-MB) engine artifact
1591
+ // happens ONCE per (query identity, engine generation); warm requests reuse
1592
+ // the cached fnv1a64/frames with zero rehashing (the same staleness authority
1593
+ // the host mirror uses — the engine's own generation counter).
1594
+ struct FslEtagCacheEntry {
1595
+ uint64_t key_sql;
1596
+ uint64_t key_params;
1597
+ uint64_t generation;
1598
+ uint64_t fnv;
1599
+ int32_t frames;
1600
+ uint32_t valid;
1601
+ };
1602
+ static constexpr uint32_t kFslEtagCacheSlots = 8;
1603
+ static FslEtagCacheEntry g_fsl_etag_cache[kFslEtagCacheSlots];
1604
+ static uint32_t g_fsl_etag_next = 0;
1605
+
1606
+ // Submit a raw-stream query DIRECTLY to the linked engine instance.
1607
+ // Returns 0 on success; negative on failure (message via
1608
+ // sdn_flatsql_linked_error). want_ref != 0 additionally computes the
1609
+ // fnv1a64/frame metadata and registers an engine body-ref token.
1610
+ extern "C" int32_t sdn_flatsql_linked_query_raw_stream(
1611
+ const char *sql, uint32_t sql_len, const uint8_t *params_tlv, uint32_t tlv_len,
1612
+ uint32_t param_count, int32_t want_ref, SdnFlatsqlLinkedResult *out) {
1613
+ g_fsl_error[0] = '\0';
1614
+ if (out == nullptr) return -1;
1615
+ memset(out, 0, sizeof(*out));
1616
+ if (g_fsl_db_handle == 0) {
1617
+ strncpy(g_fsl_error, "flatsql linkage not initialized (sdn_flatsql_link_init not called)",
1618
+ sizeof(g_fsl_error) - 1);
1619
+ return -2;
1620
+ }
1621
+
1622
+ uint32_t sql_ptr = fsl_engine_malloc(sql_len + 1);
1623
+ if (sql_ptr == 0) {
1624
+ strncpy(g_fsl_error, "engine malloc failed for sql", sizeof(g_fsl_error) - 1);
1625
+ return -3;
1626
+ }
1627
+ fsl_copy_to_engine(sql_ptr, reinterpret_cast<const uint8_t *>(sql), sql_len);
1628
+ fsl_link_poke8(sql_ptr + sql_len, 0);
1629
+
1630
+ uint32_t tlv_ptr = 0;
1631
+ if (tlv_len > 0) {
1632
+ tlv_ptr = fsl_engine_malloc(tlv_len);
1633
+ if (tlv_ptr == 0) {
1634
+ fsl_engine_free(sql_ptr);
1635
+ strncpy(g_fsl_error, "engine malloc failed for params", sizeof(g_fsl_error) - 1);
1636
+ return -3;
1637
+ }
1638
+ fsl_copy_to_engine(tlv_ptr, params_tlv, tlv_len);
1639
+ }
1640
+
1641
+ const int32_t ok = fsl_engine_query_raw(g_fsl_db_handle, sql_ptr, tlv_ptr, tlv_len,
1642
+ static_cast<int32_t>(param_count));
1643
+ fsl_engine_free(sql_ptr);
1644
+ if (tlv_ptr != 0) fsl_engine_free(tlv_ptr);
1645
+ if (ok == 0) {
1646
+ fsl_set_error_from_engine("flatsql_query_raw_flatbuffer_stream failed");
1647
+ return -4;
1648
+ }
1649
+
1650
+ out->engine_ptr = fsl_engine_artifact_data();
1651
+ out->size = static_cast<uint32_t>(fsl_engine_artifact_size());
1652
+ out->rows = static_cast<int32_t>(fsl_engine_artifact_rows());
1653
+ out->cols = static_cast<int32_t>(fsl_engine_artifact_cols());
1654
+ out->cache_hit = fsl_engine_artifact_cache_hit();
1655
+ out->generation = static_cast<uint64_t>(fsl_engine_generation(g_fsl_db_handle));
1656
+
1657
+ if (want_ref != 0) {
1658
+ const uint64_t key_sql = fsl_local_fnv(reinterpret_cast<const uint8_t *>(sql), sql_len);
1659
+ const uint64_t key_params = tlv_len > 0 ? fsl_local_fnv(params_tlv, tlv_len) : 0;
1660
+ FslEtagCacheEntry *hit = nullptr;
1661
+ for (uint32_t i = 0; i < kFslEtagCacheSlots; i++) {
1662
+ FslEtagCacheEntry &entry = g_fsl_etag_cache[i];
1663
+ if (entry.valid && entry.key_sql == key_sql && entry.key_params == key_params &&
1664
+ entry.generation == out->generation) {
1665
+ hit = &entry;
1666
+ break;
1667
+ }
1668
+ }
1669
+ if (hit != nullptr) {
1670
+ out->fnv1a64 = hit->fnv;
1671
+ out->frames = hit->frames;
1672
+ } else {
1673
+ out->fnv1a64 = fsl_link_fnv1a64(out->engine_ptr, out->size);
1674
+ out->frames = fsl_link_count_frames(out->engine_ptr, out->size);
1675
+ FslEtagCacheEntry &slot = g_fsl_etag_cache[g_fsl_etag_next % kFslEtagCacheSlots];
1676
+ g_fsl_etag_next++;
1677
+ slot.key_sql = key_sql;
1678
+ slot.key_params = key_params;
1679
+ slot.generation = out->generation;
1680
+ slot.fnv = out->fnv1a64;
1681
+ slot.frames = out->frames;
1682
+ slot.valid = 1;
1683
+ }
1684
+
1685
+ g_engine_ref_counter++;
1686
+ const uint64_t token = kSdnEngineRefTokenMagic | (g_engine_ref_counter & 0xFFFFFFFFull);
1687
+ SdnEngineRefEntry &ref = g_engine_refs[g_engine_ref_counter % kSdnEngineRefSlots];
1688
+ ref.token = token;
1689
+ ref.generation = out->generation;
1690
+ ref.fnv1a64 = out->fnv1a64;
1691
+ ref.engine_ptr = out->engine_ptr;
1692
+ ref.size = out->size;
1693
+ ref.frames = static_cast<uint32_t>(out->frames < 0 ? 0 : out->frames);
1694
+ ref.used = 1;
1695
+ out->token = token;
1696
+ }
1697
+ return 0;
1698
+ }
1699
+ #endif // SDN_FLATSQL_LINKED