@mysten-incubation/devstack 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (838) hide show
  1. package/README.md +7 -3
  2. package/dashboard-ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  3. package/dashboard-ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  4. package/dashboard-ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  5. package/dashboard-ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  6. package/dashboard-ui/assets/geist-mono-cyrillic-ext-wght-normal-I4S5GZfc.woff2 +0 -0
  7. package/dashboard-ui/assets/geist-mono-cyrillic-wght-normal-BmXc_FBt.woff2 +0 -0
  8. package/dashboard-ui/assets/geist-mono-latin-ext-wght-normal-DrnZ1wKl.woff2 +0 -0
  9. package/dashboard-ui/assets/geist-mono-latin-wght-normal-B_7UjwxQ.woff2 +0 -0
  10. package/dashboard-ui/assets/geist-mono-symbols2-wght-normal-GZpp1pK2.woff2 +0 -0
  11. package/dashboard-ui/assets/geist-mono-vietnamese-wght-normal-D8KDMBhC.woff2 +0 -0
  12. package/dashboard-ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  13. package/dashboard-ui/assets/index-Bmi1UtAg.js +1356 -0
  14. package/dashboard-ui/assets/index-D5EShVt4.js +3 -0
  15. package/dashboard-ui/assets/index-Deml9drg.css +1 -0
  16. package/dashboard-ui/index.html +13 -0
  17. package/dist/api/define-capabilities.d.mts +14 -3
  18. package/dist/api/define-capabilities.mjs +18 -1
  19. package/dist/api/define-capabilities.mjs.map +1 -1
  20. package/dist/api/define-devstack.d.mts +1 -1
  21. package/dist/api/define-devstack.mjs +6 -26
  22. package/dist/api/define-devstack.mjs.map +1 -1
  23. package/dist/api/define-plugin.d.mts +1 -0
  24. package/dist/api/define-plugin.mjs +2 -0
  25. package/dist/api/inference-network.d.mts +132 -0
  26. package/dist/api/inference-network.mjs +105 -16
  27. package/dist/api/inference-network.mjs.map +1 -1
  28. package/dist/api/plugin-errors.d.mts +7 -0
  29. package/dist/api/run-stack.d.mts +20 -9
  30. package/dist/api/run-stack.mjs +56 -29
  31. package/dist/api/run-stack.mjs.map +1 -1
  32. package/dist/build-integrations/playwright/config.d.mts +6 -4
  33. package/dist/build-integrations/playwright/config.mjs +5 -3
  34. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  35. package/dist/build-integrations/playwright/errors.d.mts +1 -1
  36. package/dist/build-integrations/playwright/global-setup.d.mts +43 -17
  37. package/dist/build-integrations/playwright/global-setup.mjs +250 -13
  38. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  39. package/dist/build-integrations/playwright/index.d.mts +5 -3
  40. package/dist/build-integrations/playwright/index.mjs +4 -2
  41. package/dist/build-integrations/playwright/stack-context.d.mts +0 -1
  42. package/dist/build-integrations/playwright/stack-context.mjs +24 -63
  43. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  44. package/dist/build-integrations/playwright/wallet-context.d.mts +7 -10
  45. package/dist/build-integrations/playwright/wallet-context.mjs +8 -13
  46. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  47. package/dist/build-integrations/runtime/cold-start-url.d.mts +5 -0
  48. package/dist/build-integrations/runtime/cold-start-url.mjs +4 -3
  49. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/conventional-routes.d.mts +51 -0
  51. package/dist/build-integrations/runtime/conventional-routes.mjs +92 -0
  52. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -0
  53. package/dist/build-integrations/runtime/discover.d.mts +38 -30
  54. package/dist/build-integrations/runtime/discover.mjs +73 -70
  55. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/endpoint-registry.d.mts +3 -1
  57. package/dist/build-integrations/runtime/endpoint-registry.mjs +3 -1
  58. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  59. package/dist/build-integrations/runtime/index.d.mts +7 -2
  60. package/dist/build-integrations/runtime/index.mjs +9 -2
  61. package/dist/build-integrations/runtime/manifest-types.d.mts +2 -0
  62. package/dist/build-integrations/runtime/manifest-types.mjs +3 -0
  63. package/dist/build-integrations/runtime/playwright-stack-context-slot.d.mts +29 -0
  64. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs +10 -0
  65. package/dist/build-integrations/runtime/playwright-stack-context-slot.mjs.map +1 -0
  66. package/dist/build-integrations/runtime/read-stack-context.d.mts +1 -1
  67. package/dist/build-integrations/runtime/read-stack-context.mjs +10 -4
  68. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  69. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +48 -0
  70. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +42 -0
  71. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -0
  72. package/dist/build-integrations/runtime/stack-context.d.mts +1 -1
  73. package/dist/build-integrations/runtime/wallet-paths.mjs +2 -0
  74. package/dist/build-integrations/vite/index.d.mts +51 -0
  75. package/dist/build-integrations/vite/index.mjs +73 -0
  76. package/dist/build-integrations/vite/index.mjs.map +1 -0
  77. package/dist/build-integrations/vitest/env.d.mts +4 -3
  78. package/dist/build-integrations/vitest/env.mjs +6 -4
  79. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  80. package/dist/build-integrations/vitest/errors.d.mts +25 -4
  81. package/dist/build-integrations/vitest/errors.mjs +15 -2
  82. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  83. package/dist/build-integrations/vitest/index.d.mts +2 -2
  84. package/dist/build-integrations/vitest/index.mjs +3 -3
  85. package/dist/build-integrations/vitest/setup.d.mts +4 -2
  86. package/dist/build-integrations/vitest/setup.mjs +2 -2
  87. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  88. package/dist/build-integrations/vitest/stack-context.d.mts +0 -1
  89. package/dist/build-integrations/vitest/stack-context.mjs +33 -13
  90. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  91. package/dist/cli/bail.mjs +23 -0
  92. package/dist/cli/bail.mjs.map +1 -0
  93. package/dist/{surfaces/cli/commands → cli}/doctor-probes.mjs +26 -16
  94. package/dist/cli/doctor-probes.mjs.map +1 -0
  95. package/dist/cli/main.d.mts +15 -1
  96. package/dist/cli/main.mjs +166 -482
  97. package/dist/cli/main.mjs.map +1 -1
  98. package/dist/cli/prune-direct.mjs +31 -201
  99. package/dist/cli/prune-direct.mjs.map +1 -1
  100. package/dist/cli/snapshot-reader.mjs +14 -11
  101. package/dist/cli/snapshot-reader.mjs.map +1 -1
  102. package/dist/cli/up-lifecycle.mjs +4 -1
  103. package/dist/cli/up-lifecycle.mjs.map +1 -1
  104. package/dist/cli/wirings/apply.mjs +95 -0
  105. package/dist/cli/wirings/apply.mjs.map +1 -0
  106. package/dist/cli/wirings/build-verb-layers.mjs +42 -0
  107. package/dist/cli/wirings/build-verb-layers.mjs.map +1 -0
  108. package/dist/cli/wirings/config-loader.mjs +69 -0
  109. package/dist/cli/wirings/config-loader.mjs.map +1 -0
  110. package/dist/cli/wirings/engine-command.mjs +30 -0
  111. package/dist/cli/wirings/engine-command.mjs.map +1 -0
  112. package/dist/cli/wirings/identity.mjs +72 -0
  113. package/dist/cli/wirings/identity.mjs.map +1 -0
  114. package/dist/cli/wirings/provide-file-system.mjs +7 -0
  115. package/dist/cli/wirings/provide-file-system.mjs.map +1 -0
  116. package/dist/cli/wirings/snapshot.mjs +177 -0
  117. package/dist/cli/wirings/snapshot.mjs.map +1 -0
  118. package/dist/cli/wirings/up.mjs +287 -0
  119. package/dist/cli/wirings/up.mjs.map +1 -0
  120. package/dist/cli/wirings/wipe.mjs +40 -0
  121. package/dist/cli/wirings/wipe.mjs.map +1 -0
  122. package/dist/contracts/capability-decl.d.mts +0 -2
  123. package/dist/contracts/codegenable.d.mts +43 -0
  124. package/dist/contracts/container-runtime.d.mts +26 -1
  125. package/dist/contracts/faucet-strategy.d.mts +25 -0
  126. package/dist/contracts/funding-strategy.d.mts +42 -0
  127. package/dist/contracts/plugin-expander.mjs +60 -0
  128. package/dist/contracts/plugin-expander.mjs.map +1 -0
  129. package/dist/contracts/projection.d.mts +5 -3
  130. package/dist/contracts/renderer.d.mts +23 -0
  131. package/dist/contracts/routable.d.mts +14 -1
  132. package/dist/contracts/snapshotable.d.mts +25 -3
  133. package/dist/contracts/wallet-protocol.d.mts +52 -0
  134. package/dist/contracts/wallet-protocol.mjs +52 -0
  135. package/dist/contracts/wallet-protocol.mjs.map +1 -0
  136. package/dist/index.d.mts +26 -25
  137. package/dist/index.mjs +20 -13
  138. package/dist/{runtime → orchestrators}/built-in-plugin-layers.mjs +10 -5
  139. package/dist/orchestrators/built-in-plugin-layers.mjs.map +1 -0
  140. package/dist/orchestrators/codegen/bindings.d.mts +1 -2
  141. package/dist/orchestrators/codegen/bindings.mjs +52 -186
  142. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/emit.mjs +19 -1
  144. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  145. package/dist/orchestrators/codegen/errors.mjs +8 -3
  146. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/format.mjs +47 -39
  148. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/gitignore.mjs +3 -1
  150. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  151. package/dist/orchestrators/codegen/output-location.mjs +40 -0
  152. package/dist/orchestrators/codegen/output-location.mjs.map +1 -0
  153. package/dist/orchestrators/codegen/paths.d.mts +1 -1
  154. package/dist/orchestrators/codegen/paths.mjs +40 -14
  155. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  156. package/dist/orchestrators/codegen/permissions.mjs +3 -1
  157. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  158. package/dist/orchestrators/codegen/service.mjs +157 -105
  159. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  160. package/dist/orchestrators/lifecycle-prune/errors.mjs +31 -0
  161. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -0
  162. package/dist/orchestrators/lifecycle-prune/index.mjs +350 -0
  163. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -0
  164. package/dist/orchestrators/router/cleanup.mjs +26 -27
  165. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  166. package/dist/orchestrators/router/entrypoints.mjs +2 -2
  167. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  168. package/dist/orchestrators/router/file-provider.mjs +14 -5
  169. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  170. package/dist/orchestrators/router/hostname.mjs +19 -55
  171. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  172. package/dist/orchestrators/router/index.mjs +11 -0
  173. package/dist/orchestrators/router/profile.mjs +2 -2
  174. package/dist/orchestrators/router/sentinels.mjs +26 -0
  175. package/dist/orchestrators/router/sentinels.mjs.map +1 -0
  176. package/dist/orchestrators/router/service.mjs +40 -37
  177. package/dist/orchestrators/router/service.mjs.map +1 -1
  178. package/dist/orchestrators/router/traefik-container.mjs +27 -16
  179. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  180. package/dist/{substrate/runtime → orchestrators}/run.mjs +26 -21
  181. package/dist/orchestrators/run.mjs.map +1 -0
  182. package/dist/orchestrators/runtime-composition.d.mts +1 -1
  183. package/dist/orchestrators/runtime-composition.mjs +123 -39
  184. package/dist/orchestrators/runtime-composition.mjs.map +1 -1
  185. package/dist/orchestrators/snapshot/capture-command.d.mts +1 -0
  186. package/dist/orchestrators/snapshot/capture-command.mjs +25 -0
  187. package/dist/orchestrators/snapshot/capture-command.mjs.map +1 -0
  188. package/dist/orchestrators/snapshot/capture.mjs +42 -28
  189. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  190. package/dist/orchestrators/snapshot/descriptor.mjs +35 -10
  191. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  192. package/dist/orchestrators/snapshot/identity-guard.mjs +1 -1
  193. package/dist/orchestrators/snapshot/index.mjs +13 -0
  194. package/dist/orchestrators/snapshot/integrity.d.mts +1 -1
  195. package/dist/orchestrators/snapshot/integrity.mjs +73 -17
  196. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  197. package/dist/orchestrators/snapshot/pending-marker.d.mts +1 -0
  198. package/dist/orchestrators/snapshot/pending-marker.mjs +99 -0
  199. package/dist/orchestrators/snapshot/pending-marker.mjs.map +1 -0
  200. package/dist/orchestrators/snapshot/phase-error.mjs +23 -0
  201. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -0
  202. package/dist/orchestrators/snapshot/prune.mjs +20 -55
  203. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  204. package/dist/orchestrators/snapshot/recover-pending.d.mts +1 -0
  205. package/dist/orchestrators/snapshot/recover-pending.mjs +223 -0
  206. package/dist/orchestrators/snapshot/recover-pending.mjs.map +1 -0
  207. package/dist/orchestrators/snapshot/restore.mjs +118 -84
  208. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  209. package/dist/orchestrators/snapshot/service.mjs +53 -56
  210. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  211. package/dist/orchestrators/snapshot/state-document.d.mts +1 -0
  212. package/dist/orchestrators/snapshot/state-document.mjs +47 -7
  213. package/dist/orchestrators/snapshot/state-document.mjs.map +1 -1
  214. package/dist/orchestrators/snapshot/wipe.mjs +67 -10
  215. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  216. package/dist/plugins/account/codegen.mjs +9 -8
  217. package/dist/plugins/account/codegen.mjs.map +1 -1
  218. package/dist/plugins/account/errors.d.mts +33 -6
  219. package/dist/plugins/account/errors.mjs.map +1 -1
  220. package/dist/plugins/account/funding.d.mts +29 -19
  221. package/dist/plugins/account/funding.mjs +84 -66
  222. package/dist/plugins/account/funding.mjs.map +1 -1
  223. package/dist/plugins/account/index.d.mts +2 -4
  224. package/dist/plugins/account/index.mjs +64 -31
  225. package/dist/plugins/account/index.mjs.map +1 -1
  226. package/dist/plugins/account/keypair.mjs +6 -9
  227. package/dist/plugins/account/keypair.mjs.map +1 -1
  228. package/dist/plugins/account/lease.mjs +1 -0
  229. package/dist/plugins/account/lease.mjs.map +1 -1
  230. package/dist/plugins/account/registry.d.mts +14 -1
  231. package/dist/plugins/account/registry.mjs +18 -18
  232. package/dist/plugins/account/registry.mjs.map +1 -1
  233. package/dist/plugins/account/service.d.mts +23 -4
  234. package/dist/plugins/account/service.mjs +83 -45
  235. package/dist/plugins/account/service.mjs.map +1 -1
  236. package/dist/plugins/account/snapshot.mjs +2 -5
  237. package/dist/plugins/account/snapshot.mjs.map +1 -1
  238. package/dist/plugins/account/spans.mjs +16 -0
  239. package/dist/plugins/account/spans.mjs.map +1 -0
  240. package/dist/plugins/account/variants/keystore.mjs +6 -8
  241. package/dist/plugins/account/variants/keystore.mjs.map +1 -1
  242. package/dist/plugins/action/build-context.d.mts +3 -2
  243. package/dist/plugins/action/errors.d.mts +16 -12
  244. package/dist/plugins/action/errors.mjs.map +1 -1
  245. package/dist/plugins/action/execute.mjs +65 -103
  246. package/dist/plugins/action/execute.mjs.map +1 -1
  247. package/dist/plugins/action/index.d.mts +0 -2
  248. package/dist/plugins/action/index.mjs +17 -13
  249. package/dist/plugins/action/index.mjs.map +1 -1
  250. package/dist/plugins/action/service.mjs +38 -35
  251. package/dist/plugins/action/service.mjs.map +1 -1
  252. package/dist/plugins/action/spans.mjs +11 -0
  253. package/dist/plugins/action/spans.mjs.map +1 -0
  254. package/dist/plugins/coin/codegen.mjs +9 -8
  255. package/dist/plugins/coin/codegen.mjs.map +1 -1
  256. package/dist/plugins/coin/discovery.mjs +8 -4
  257. package/dist/plugins/coin/discovery.mjs.map +1 -1
  258. package/dist/plugins/coin/index.d.mts +11 -29
  259. package/dist/plugins/coin/index.mjs +56 -27
  260. package/dist/plugins/coin/index.mjs.map +1 -1
  261. package/dist/plugins/coin/metadata.mjs +8 -15
  262. package/dist/plugins/coin/metadata.mjs.map +1 -1
  263. package/dist/plugins/coin/mint.d.mts +30 -11
  264. package/dist/plugins/coin/mint.mjs +78 -74
  265. package/dist/plugins/coin/mint.mjs.map +1 -1
  266. package/dist/plugins/coin/registry.mjs +4 -7
  267. package/dist/plugins/coin/registry.mjs.map +1 -1
  268. package/dist/plugins/coin/service.d.mts +39 -1
  269. package/dist/plugins/coin/service.mjs +11 -4
  270. package/dist/plugins/coin/service.mjs.map +1 -1
  271. package/dist/plugins/coin/spans.mjs +20 -0
  272. package/dist/plugins/coin/spans.mjs.map +1 -0
  273. package/dist/plugins/dashboard/domain.mjs +402 -0
  274. package/dist/plugins/dashboard/domain.mjs.map +1 -0
  275. package/dist/plugins/dashboard/index.d.mts +29 -0
  276. package/dist/plugins/dashboard/index.mjs +91 -0
  277. package/dist/plugins/dashboard/index.mjs.map +1 -0
  278. package/dist/plugins/dashboard/origin-policy.mjs +36 -0
  279. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -0
  280. package/dist/plugins/dashboard/routable.mjs +27 -0
  281. package/dist/plugins/dashboard/routable.mjs.map +1 -0
  282. package/dist/plugins/dashboard/schema/builder.mjs +21 -0
  283. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -0
  284. package/dist/plugins/dashboard/schema/enums.mjs +72 -0
  285. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -0
  286. package/dist/plugins/dashboard/schema/root.mjs +351 -0
  287. package/dist/plugins/dashboard/schema/root.mjs.map +1 -0
  288. package/dist/plugins/dashboard/schema/types.mjs +508 -0
  289. package/dist/plugins/dashboard/schema/types.mjs.map +1 -0
  290. package/dist/plugins/dashboard/schema.mjs +11 -0
  291. package/dist/plugins/dashboard/schema.mjs.map +1 -0
  292. package/dist/plugins/dashboard/server.mjs +166 -0
  293. package/dist/plugins/dashboard/server.mjs.map +1 -0
  294. package/dist/plugins/deepbook/deploy.mjs +53 -115
  295. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  296. package/dist/plugins/deepbook/errors.mjs +1 -0
  297. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  298. package/dist/plugins/deepbook/faucet-strategy.mjs +47 -34
  299. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  300. package/dist/plugins/deepbook/hash.mjs +9 -0
  301. package/dist/plugins/deepbook/hash.mjs.map +1 -0
  302. package/dist/plugins/deepbook/index.d.mts +1 -10
  303. package/dist/plugins/deepbook/index.mjs +25 -22
  304. package/dist/plugins/deepbook/index.mjs.map +1 -1
  305. package/dist/plugins/deepbook/pyth/index.mjs +26 -55
  306. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  307. package/dist/plugins/deepbook/snapshot.mjs +17 -29
  308. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  309. package/dist/plugins/deepbook/spans.mjs +18 -0
  310. package/dist/plugins/deepbook/spans.mjs.map +1 -0
  311. package/dist/plugins/deepbook/type-strings.mjs +65 -0
  312. package/dist/plugins/deepbook/type-strings.mjs.map +1 -0
  313. package/dist/plugins/deepbook/types.d.mts +2 -3
  314. package/dist/plugins/deepbook/types.mjs.map +1 -1
  315. package/dist/plugins/faucet/dispatcher.d.mts +21 -2
  316. package/dist/plugins/faucet/dispatcher.mjs +1 -2
  317. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  318. package/dist/plugins/faucet/errors.d.mts +11 -24
  319. package/dist/plugins/faucet/errors.mjs.map +1 -1
  320. package/dist/plugins/faucet/http.mjs +33 -14
  321. package/dist/plugins/faucet/http.mjs.map +1 -1
  322. package/dist/plugins/faucet/index.d.mts +15 -5
  323. package/dist/plugins/faucet/index.mjs +3 -0
  324. package/dist/plugins/faucet/index.mjs.map +1 -1
  325. package/dist/plugins/faucet/spans.mjs +12 -0
  326. package/dist/plugins/faucet/spans.mjs.map +1 -0
  327. package/dist/plugins/host-service/errors.d.mts +1 -1
  328. package/dist/plugins/host-service/index.mjs +34 -8
  329. package/dist/plugins/host-service/index.mjs.map +1 -1
  330. package/dist/plugins/host-service/routable.mjs +2 -1
  331. package/dist/plugins/host-service/routable.mjs.map +1 -1
  332. package/dist/plugins/host-service/service.d.mts +21 -0
  333. package/dist/plugins/host-service/service.mjs +136 -81
  334. package/dist/plugins/host-service/service.mjs.map +1 -1
  335. package/dist/plugins/internal/acquire-on-chain-artifact.mjs +23 -0
  336. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -0
  337. package/dist/plugins/internal/codegen-helpers.mjs +18 -0
  338. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -0
  339. package/dist/plugins/internal/funding-failure-error.mjs +32 -0
  340. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -0
  341. package/dist/plugins/package/build.mjs +1 -1
  342. package/dist/plugins/package/build.mjs.map +1 -1
  343. package/dist/plugins/package/codegen.mjs +26 -0
  344. package/dist/plugins/package/codegen.mjs.map +1 -1
  345. package/dist/plugins/package/errors.d.mts +16 -6
  346. package/dist/plugins/package/errors.mjs.map +1 -1
  347. package/dist/plugins/package/index.d.mts +0 -4
  348. package/dist/plugins/package/index.mjs +27 -21
  349. package/dist/plugins/package/index.mjs.map +1 -1
  350. package/dist/plugins/package/mode-known.mjs +26 -12
  351. package/dist/plugins/package/mode-known.mjs.map +1 -1
  352. package/dist/plugins/package/mode-local.mjs +47 -41
  353. package/dist/plugins/package/mode-local.mjs.map +1 -1
  354. package/dist/plugins/package/publish-executor.mjs +55 -114
  355. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  356. package/dist/plugins/package/registry.mjs +2 -1
  357. package/dist/plugins/package/registry.mjs.map +1 -1
  358. package/dist/plugins/package/service.mjs.map +1 -1
  359. package/dist/plugins/package/spans.mjs +14 -0
  360. package/dist/plugins/package/spans.mjs.map +1 -0
  361. package/dist/plugins/postgres/db-ensure.mjs +19 -12
  362. package/dist/plugins/postgres/db-ensure.mjs.map +1 -1
  363. package/dist/plugins/postgres/errors.d.mts +5 -0
  364. package/dist/plugins/postgres/errors.mjs.map +1 -1
  365. package/dist/plugins/postgres/index.mjs +10 -17
  366. package/dist/plugins/postgres/index.mjs.map +1 -1
  367. package/dist/plugins/postgres/service.mjs +58 -24
  368. package/dist/plugins/postgres/service.mjs.map +1 -1
  369. package/dist/plugins/postgres/snapshot.mjs.map +1 -1
  370. package/dist/plugins/postgres/spans.mjs +11 -0
  371. package/dist/plugins/postgres/spans.mjs.map +1 -0
  372. package/dist/plugins/router-entrypoints.mjs +4 -4
  373. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  374. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +15 -13
  375. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  376. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +15 -19
  377. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  378. package/dist/plugins/seal/codegen.d.mts +2 -3
  379. package/dist/plugins/seal/codegen.mjs.map +1 -1
  380. package/dist/plugins/seal/config-render.mjs +35 -8
  381. package/dist/plugins/seal/config-render.mjs.map +1 -1
  382. package/dist/plugins/seal/deploy.mjs +31 -39
  383. package/dist/plugins/seal/deploy.mjs.map +1 -1
  384. package/dist/plugins/seal/errors.d.mts +1 -3
  385. package/dist/plugins/seal/errors.mjs +7 -1
  386. package/dist/plugins/seal/errors.mjs.map +1 -1
  387. package/dist/plugins/seal/index.d.mts +1 -6
  388. package/dist/plugins/seal/index.mjs +40 -14
  389. package/dist/plugins/seal/index.mjs.map +1 -1
  390. package/dist/plugins/seal/key-manager.d.mts +1 -9
  391. package/dist/plugins/seal/key-manager.mjs +2 -18
  392. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  393. package/dist/plugins/seal/key-server.mjs +37 -36
  394. package/dist/plugins/seal/key-server.mjs.map +1 -1
  395. package/dist/plugins/seal/keygen.mjs +16 -17
  396. package/dist/plugins/seal/keygen.mjs.map +1 -1
  397. package/dist/plugins/seal/mode/fork-known.mjs +36 -14
  398. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  399. package/dist/plugins/seal/mode/live.d.mts +6 -4
  400. package/dist/plugins/seal/mode/live.mjs +14 -21
  401. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  402. package/dist/plugins/seal/mode/local-keygen.mjs +57 -21
  403. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  404. package/dist/plugins/seal/registry-publish.mjs +1 -0
  405. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  406. package/dist/plugins/seal/routable.mjs.map +1 -1
  407. package/dist/plugins/seal/service.mjs.map +1 -1
  408. package/dist/plugins/seal/spans.mjs +18 -0
  409. package/dist/plugins/seal/spans.mjs.map +1 -0
  410. package/dist/plugins/sui/auto-tick.mjs +17 -23
  411. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  412. package/dist/plugins/sui/chain-build-container.mjs +18 -0
  413. package/dist/plugins/sui/chain-build-container.mjs.map +1 -0
  414. package/dist/plugins/sui/chain-probe.d.mts +5 -5
  415. package/dist/plugins/sui/chain-probe.mjs +28 -58
  416. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  417. package/dist/plugins/sui/codegen.mjs +26 -0
  418. package/dist/plugins/sui/codegen.mjs.map +1 -1
  419. package/dist/plugins/sui/errors.d.mts +2 -29
  420. package/dist/plugins/sui/errors.mjs +1 -3
  421. package/dist/plugins/sui/errors.mjs.map +1 -1
  422. package/dist/plugins/sui/fork-faucet-strategy.d.mts +1 -0
  423. package/dist/plugins/sui/fork-faucet-strategy.mjs +55 -0
  424. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -0
  425. package/dist/plugins/sui/fork-orchestration.d.mts +1 -0
  426. package/dist/plugins/sui/fork-orchestration.mjs +170 -2
  427. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  428. package/dist/plugins/sui/fork-transaction.d.mts +2 -0
  429. package/dist/plugins/sui/fork-transaction.mjs +92 -28
  430. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  431. package/dist/plugins/sui/index.d.mts +33 -70
  432. package/dist/plugins/sui/index.mjs +58 -35
  433. package/dist/plugins/sui/index.mjs.map +1 -1
  434. package/dist/plugins/{faucet/strategies/sui-local.mjs → sui/local-faucet-strategy.mjs} +10 -7
  435. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -0
  436. package/dist/plugins/sui/mode/external.mjs +7 -13
  437. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  438. package/dist/plugins/sui/mode/fork.mjs +136 -59
  439. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  440. package/dist/plugins/sui/mode/live.mjs +10 -16
  441. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  442. package/dist/plugins/sui/mode/local.mjs +37 -36
  443. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  444. package/dist/plugins/sui/mode/shared-boot.mjs +38 -115
  445. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  446. package/dist/plugins/sui/mode/shared.d.mts +1 -5
  447. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  448. package/dist/plugins/sui/mode/spec.d.mts +18 -0
  449. package/dist/plugins/sui/move-summary-runner.mjs +207 -0
  450. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -0
  451. package/dist/plugins/sui/network-resolver.d.mts +4 -3
  452. package/dist/plugins/sui/service.mjs.map +1 -1
  453. package/dist/plugins/sui/snapshot.mjs +3 -0
  454. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  455. package/dist/plugins/sui/spans.mjs +17 -0
  456. package/dist/plugins/sui/spans.mjs.map +1 -0
  457. package/dist/plugins/wallet/codegen.d.mts +0 -1
  458. package/dist/plugins/wallet/codegen.mjs +8 -9
  459. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  460. package/dist/plugins/wallet/index.d.mts +6 -8
  461. package/dist/plugins/wallet/index.mjs +46 -35
  462. package/dist/plugins/wallet/index.mjs.map +1 -1
  463. package/dist/plugins/wallet/origin-policy.mjs +12 -28
  464. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  465. package/dist/plugins/wallet/pairing.mjs +6 -4
  466. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  467. package/dist/plugins/wallet/protocol.d.mts +1 -0
  468. package/dist/plugins/wallet/protocol.mjs +5 -44
  469. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  470. package/dist/plugins/wallet/routable.mjs +2 -8
  471. package/dist/plugins/wallet/routable.mjs.map +1 -1
  472. package/dist/plugins/wallet/server.mjs +36 -44
  473. package/dist/plugins/wallet/server.mjs.map +1 -1
  474. package/dist/plugins/wallet/service.d.mts +1 -5
  475. package/dist/plugins/wallet/service.mjs +22 -18
  476. package/dist/plugins/wallet/service.mjs.map +1 -1
  477. package/dist/plugins/wallet/spans.mjs +22 -0
  478. package/dist/plugins/wallet/spans.mjs.map +1 -0
  479. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +33 -8
  480. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  481. package/dist/plugins/walrus/codegen.d.mts +3 -3
  482. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  483. package/dist/plugins/walrus/deploy-paths.mjs +21 -5
  484. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  485. package/dist/plugins/walrus/deploy.mjs +67 -69
  486. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  487. package/dist/plugins/walrus/errors.mjs +1 -0
  488. package/dist/plugins/walrus/errors.mjs.map +1 -1
  489. package/dist/plugins/walrus/faucet-strategy.mjs +12 -7
  490. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  491. package/dist/plugins/walrus/index.d.mts +2 -13
  492. package/dist/plugins/walrus/index.mjs +18 -23
  493. package/dist/plugins/walrus/index.mjs.map +1 -1
  494. package/dist/plugins/walrus/mode/known-deploy.mjs +7 -5
  495. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  496. package/dist/plugins/walrus/mode/local-cluster.mjs +26 -7
  497. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  498. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  499. package/dist/plugins/walrus/routable.mjs +7 -23
  500. package/dist/plugins/walrus/routable.mjs.map +1 -1
  501. package/dist/plugins/walrus/service.mjs.map +1 -1
  502. package/dist/plugins/walrus/spans.mjs +18 -0
  503. package/dist/plugins/walrus/spans.mjs.map +1 -0
  504. package/dist/plugins/walrus/storage-nodes.d.mts +6 -2
  505. package/dist/plugins/walrus/storage-nodes.mjs +15 -8
  506. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  507. package/dist/plugins/walrus/wal-swap.mjs +58 -15
  508. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  509. package/dist/primitives/artifact-publisher.d.mts +1 -1
  510. package/dist/primitives/artifact-publisher.mjs +15 -0
  511. package/dist/primitives/artifact-publisher.mjs.map +1 -0
  512. package/dist/runtime/docker/client.mjs +17 -4
  513. package/dist/runtime/docker/client.mjs.map +1 -1
  514. package/dist/runtime/docker/container.mjs +219 -132
  515. package/dist/runtime/docker/container.mjs.map +1 -1
  516. package/dist/runtime/docker/errors.mjs +0 -6
  517. package/dist/runtime/docker/errors.mjs.map +1 -1
  518. package/dist/runtime/docker/exec.mjs +21 -18
  519. package/dist/runtime/docker/exec.mjs.map +1 -1
  520. package/dist/runtime/docker/image.mjs +36 -13
  521. package/dist/runtime/docker/image.mjs.map +1 -1
  522. package/dist/runtime/docker/index.mjs +14 -0
  523. package/dist/runtime/docker/inspect-and-decode.mjs +51 -0
  524. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -0
  525. package/dist/runtime/docker/inventory.mjs +14 -11
  526. package/dist/runtime/docker/inventory.mjs.map +1 -1
  527. package/dist/runtime/docker/labels.mjs +38 -5
  528. package/dist/runtime/docker/labels.mjs.map +1 -1
  529. package/dist/runtime/docker/logs.d.mts +1 -1
  530. package/dist/runtime/docker/logs.mjs.map +1 -1
  531. package/dist/runtime/docker/network.mjs +83 -47
  532. package/dist/runtime/docker/network.mjs.map +1 -1
  533. package/dist/runtime/docker/render-run-args.mjs +82 -0
  534. package/dist/runtime/docker/render-run-args.mjs.map +1 -0
  535. package/dist/runtime/docker/service.d.mts +1 -1
  536. package/dist/runtime/docker/service.mjs +12 -7
  537. package/dist/runtime/docker/service.mjs.map +1 -1
  538. package/dist/runtime/docker/sweep.mjs +118 -43
  539. package/dist/runtime/docker/sweep.mjs.map +1 -1
  540. package/dist/runtime/docker/volume.mjs +2 -0
  541. package/dist/runtime/docker/volume.mjs.map +1 -1
  542. package/dist/runtime/docker/wrap.mjs +30 -1
  543. package/dist/runtime/docker/wrap.mjs.map +1 -1
  544. package/dist/substrate/cross-process.mjs +14 -8
  545. package/dist/substrate/cross-process.mjs.map +1 -1
  546. package/dist/substrate/event-time.mjs +33 -0
  547. package/dist/substrate/event-time.mjs.map +1 -0
  548. package/dist/substrate/events.d.mts +28 -8
  549. package/dist/substrate/identity.d.mts +2 -1
  550. package/dist/substrate/manifest.d.mts +68 -5
  551. package/dist/substrate/manifest.mjs +34 -5
  552. package/dist/substrate/manifest.mjs.map +1 -1
  553. package/dist/substrate/network.d.mts +3 -5
  554. package/dist/substrate/options.d.mts +0 -2
  555. package/dist/substrate/plugin.d.mts +13 -0
  556. package/dist/substrate/plugin.mjs +4 -2
  557. package/dist/substrate/plugin.mjs.map +1 -1
  558. package/dist/substrate/projection.d.mts +35 -3
  559. package/dist/substrate/runtime/artifact-publisher/index.mjs +19 -9
  560. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +1 -1
  561. package/dist/substrate/runtime/atomic-write.mjs +97 -32
  562. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  563. package/dist/substrate/runtime/cache/index.mjs +3 -0
  564. package/dist/substrate/runtime/cache/schema.mjs +2 -2
  565. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  566. package/dist/substrate/runtime/cache/service.mjs +9 -7
  567. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  568. package/dist/substrate/runtime/capability-sinks/index.d.mts +1 -1
  569. package/dist/substrate/runtime/capability-sinks/index.mjs +3 -0
  570. package/dist/substrate/runtime/capability-sinks/layer.mjs +1 -1
  571. package/dist/substrate/runtime/capability-sinks/service.d.mts +61 -2
  572. package/dist/substrate/runtime/capability-sinks/service.mjs +21 -26
  573. package/dist/substrate/runtime/capability-sinks/service.mjs.map +1 -1
  574. package/dist/substrate/runtime/config-validation.d.mts +1 -13
  575. package/dist/substrate/runtime/config-validation.mjs +2 -91
  576. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  577. package/dist/substrate/runtime/container-runtime.d.mts +1 -0
  578. package/dist/substrate/runtime/container-runtime.mjs +2 -0
  579. package/dist/substrate/runtime/control-plane/domain.mjs +101 -0
  580. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -0
  581. package/dist/substrate/runtime/control-plane/service.mjs +7 -0
  582. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -0
  583. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +46 -19
  584. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  585. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs +30 -12
  586. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  587. package/dist/substrate/runtime/cross-process/command-channel/index.mjs +5 -0
  588. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs +11 -2
  589. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  590. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs +1 -1
  591. package/dist/substrate/runtime/cross-process/index.mjs +11 -0
  592. package/dist/substrate/runtime/cross-process/live-clock.mjs +34 -0
  593. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -0
  594. package/dist/substrate/runtime/cross-process/liveness.mjs +52 -10
  595. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  596. package/dist/substrate/runtime/cross-process/lock.d.mts +1 -0
  597. package/dist/substrate/runtime/cross-process/lock.mjs +23 -0
  598. package/dist/substrate/runtime/cross-process/lock.mjs.map +1 -0
  599. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +64 -0
  600. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -0
  601. package/dist/substrate/runtime/cross-process/roster.mjs +97 -83
  602. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  603. package/dist/substrate/runtime/cross-process/self-pid.mjs +8 -0
  604. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -0
  605. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +24 -28
  606. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +1 -1
  607. package/dist/substrate/runtime/cross-process/stack-lock.mjs +41 -20
  608. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  609. package/dist/substrate/runtime/current-plugin.mjs +2 -2
  610. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  611. package/dist/substrate/runtime/errors.mjs +12 -6
  612. package/dist/substrate/runtime/errors.mjs.map +1 -1
  613. package/dist/substrate/runtime/format-unknown-error.mjs +30 -0
  614. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -0
  615. package/dist/substrate/runtime/host-bind-mount-owner.mjs +10 -0
  616. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -0
  617. package/dist/substrate/runtime/host-gateway.mjs +20 -0
  618. package/dist/substrate/runtime/host-gateway.mjs.map +1 -0
  619. package/dist/substrate/runtime/host-tree-tar/index.mjs +61 -50
  620. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  621. package/dist/substrate/runtime/http-probe.d.mts +1 -1
  622. package/dist/substrate/runtime/index.mjs +14 -0
  623. package/dist/substrate/runtime/lease-broker/index.d.mts +1 -1
  624. package/dist/substrate/runtime/lease-broker/index.mjs +2 -0
  625. package/dist/substrate/runtime/lease-broker/service.d.mts +1 -64
  626. package/dist/substrate/runtime/lease-broker/service.mjs +1 -1
  627. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  628. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +22 -3
  629. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  630. package/dist/substrate/runtime/lifecycle/index.mjs +9 -0
  631. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +1 -1
  632. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +20 -3
  633. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  634. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +1 -1
  635. package/dist/substrate/runtime/lifecycle/signals.mjs +5 -5
  636. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  637. package/dist/substrate/runtime/lifecycle/state-machine.mjs +1 -1
  638. package/dist/substrate/runtime/managed-container.d.mts +19 -1
  639. package/dist/substrate/runtime/managed-container.mjs +20 -1
  640. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  641. package/dist/substrate/runtime/manifest/index.mjs +3 -0
  642. package/dist/substrate/runtime/manifest/manifest.mjs +15 -11
  643. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  644. package/dist/substrate/runtime/mode-errors.mjs +20 -0
  645. package/dist/substrate/runtime/mode-errors.mjs.map +1 -0
  646. package/dist/substrate/runtime/observability/cascade-formatter.mjs +9 -13
  647. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  648. package/dist/substrate/runtime/observability/formatter-registry.mjs +19 -36
  649. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +1 -1
  650. package/dist/substrate/runtime/observability/ignore-with-log.d.mts +1 -0
  651. package/dist/substrate/runtime/observability/ignore-with-log.mjs +17 -0
  652. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -0
  653. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  654. package/dist/substrate/runtime/observability/index.mjs +13 -0
  655. package/dist/substrate/runtime/observability/log-store.d.mts +1 -0
  656. package/dist/substrate/runtime/observability/log-store.mjs +175 -0
  657. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -0
  658. package/dist/substrate/runtime/observability/logger.d.mts +2 -1
  659. package/dist/substrate/runtime/observability/logger.mjs +5 -9
  660. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  661. package/dist/substrate/runtime/observability/output-truncate.mjs +35 -0
  662. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -0
  663. package/dist/substrate/runtime/observability/pretty-error.mjs +9 -12
  664. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  665. package/dist/substrate/runtime/observability/redaction.d.mts +2 -13
  666. package/dist/substrate/runtime/observability/redaction.mjs +8 -14
  667. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  668. package/dist/substrate/runtime/observability/span-store.d.mts +1 -0
  669. package/dist/substrate/runtime/observability/span-store.mjs +110 -0
  670. package/dist/substrate/runtime/observability/span-store.mjs.map +1 -0
  671. package/dist/substrate/runtime/observability/spans.mjs +14 -14
  672. package/dist/substrate/runtime/observability/spans.mjs.map +1 -1
  673. package/dist/substrate/runtime/observability/subprocess-capture.mjs +1 -1
  674. package/dist/substrate/runtime/passthrough-or-wrap.mjs +33 -0
  675. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -0
  676. package/dist/substrate/runtime/paths.d.mts +1 -1
  677. package/dist/substrate/runtime/paths.mjs +4 -3
  678. package/dist/substrate/runtime/paths.mjs.map +1 -1
  679. package/dist/substrate/runtime/phase-preserving-produce.mjs +40 -0
  680. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -0
  681. package/dist/substrate/runtime/port-broker/index.mjs +3 -0
  682. package/dist/substrate/runtime/port-broker/service.mjs +170 -126
  683. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  684. package/dist/substrate/runtime/post-acquire-tasks.mjs +1 -1
  685. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  686. package/dist/substrate/runtime/probes.d.mts +7 -5
  687. package/dist/substrate/runtime/probes.mjs +3 -1
  688. package/dist/substrate/runtime/probes.mjs.map +1 -1
  689. package/dist/substrate/runtime/projection/index.mjs +4 -0
  690. package/dist/substrate/runtime/projection/operational-endpoints.mjs +1 -2
  691. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  692. package/dist/substrate/runtime/projection/persisted.mjs +33 -15
  693. package/dist/substrate/runtime/projection/persisted.mjs.map +1 -1
  694. package/dist/substrate/runtime/projection/state-ref.mjs +26 -2
  695. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  696. package/dist/substrate/runtime/projection/update.mjs +88 -24
  697. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  698. package/dist/substrate/runtime/random-suffix.mjs +11 -0
  699. package/dist/substrate/runtime/random-suffix.mjs.map +1 -0
  700. package/dist/substrate/runtime/retry-policy.d.mts +59 -1
  701. package/dist/substrate/runtime/retry-policy.mjs +66 -1
  702. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  703. package/dist/substrate/runtime/routed-url.d.mts +1 -0
  704. package/dist/substrate/runtime/routed-url.mjs +79 -0
  705. package/dist/substrate/runtime/routed-url.mjs.map +1 -0
  706. package/dist/substrate/runtime/runtime-decode.d.mts +1 -1
  707. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  708. package/dist/substrate/runtime/scoped-http-server.mjs +2 -3
  709. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  710. package/dist/substrate/runtime/scoped-multimap/index.mjs +2 -0
  711. package/dist/substrate/runtime/scoped-multimap/service.mjs +52 -0
  712. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +1 -0
  713. package/dist/substrate/runtime/scoped-ref-map/index.mjs +2 -0
  714. package/dist/substrate/runtime/scoped-ref-map/service.mjs +2 -2
  715. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +1 -1
  716. package/dist/substrate/runtime/stage-and-swap/index.mjs +72 -4
  717. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  718. package/dist/substrate/runtime/state-store/index.d.mts +1 -0
  719. package/dist/substrate/runtime/state-store/index.mjs +3 -0
  720. package/dist/substrate/runtime/state-store/schema.d.mts +1 -0
  721. package/dist/substrate/runtime/state-store/schema.mjs +19 -14
  722. package/dist/substrate/runtime/state-store/schema.mjs.map +1 -1
  723. package/dist/substrate/runtime/state-store/service.d.mts +1 -0
  724. package/dist/substrate/runtime/state-store/service.mjs +145 -0
  725. package/dist/substrate/runtime/state-store/service.mjs.map +1 -0
  726. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.d.mts +1 -0
  727. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs +33 -0
  728. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -0
  729. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs +1 -1
  730. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  731. package/dist/substrate/runtime/strategy-registry/index.mjs +4 -0
  732. package/dist/substrate/runtime/strategy-registry/service.mjs +19 -33
  733. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  734. package/dist/substrate/runtime/subnet-broker.mjs +60 -0
  735. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -0
  736. package/dist/substrate/runtime/sui-execute/index.d.mts +21 -1
  737. package/dist/substrate/runtime/sui-execute/index.mjs +84 -20
  738. package/dist/substrate/runtime/sui-execute/index.mjs.map +1 -1
  739. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.d.mts +1 -0
  740. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs +32 -0
  741. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +1 -0
  742. package/dist/substrate/runtime/sui-ledger/object-ref.mjs +32 -0
  743. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +1 -0
  744. package/dist/substrate/runtime/sui-move-build/index.mjs +93 -19
  745. package/dist/substrate/runtime/sui-move-build/index.mjs.map +1 -1
  746. package/dist/substrate/runtime/supervisor/acquire-node.mjs +181 -0
  747. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -0
  748. package/dist/substrate/runtime/supervisor/background-tasks.mjs +144 -0
  749. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -0
  750. package/dist/substrate/runtime/supervisor/command-loop.mjs +68 -0
  751. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -0
  752. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +151 -0
  753. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +1 -0
  754. package/dist/substrate/runtime/supervisor/errors.d.mts +1 -0
  755. package/dist/substrate/runtime/supervisor/errors.mjs +9 -0
  756. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -0
  757. package/dist/substrate/runtime/supervisor/index.d.mts +1 -0
  758. package/dist/substrate/runtime/supervisor/index.mjs +3 -0
  759. package/dist/substrate/runtime/supervisor/shutdown.mjs +51 -0
  760. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -0
  761. package/dist/substrate/runtime/supervisor/start-supervisor.d.mts +1 -0
  762. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +250 -0
  763. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -0
  764. package/dist/substrate/runtime/supervisor/state.d.mts +1 -0
  765. package/dist/substrate/runtime/supervisor/state.mjs +22 -0
  766. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -0
  767. package/dist/substrate/runtime/supervisor/teardown.mjs +57 -0
  768. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -0
  769. package/dist/substrate/runtime/supervisor/types.d.mts +1 -0
  770. package/dist/substrate/runtime/supervisor/wiring.mjs +133 -0
  771. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -0
  772. package/dist/substrate/runtime/typed-env.mjs +8 -0
  773. package/dist/substrate/runtime/typed-env.mjs.map +1 -0
  774. package/dist/substrate/state-store.d.mts +1 -0
  775. package/dist/substrate/versioned-doc-schema.mjs +17 -0
  776. package/dist/substrate/versioned-doc-schema.mjs.map +1 -0
  777. package/dist/substrate/versioned-doc-sync.mjs +94 -0
  778. package/dist/substrate/versioned-doc-sync.mjs.map +1 -0
  779. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  780. package/dist/surfaces/cli/commands/index.mjs +9 -0
  781. package/dist/surfaces/cli/commands/prune-picker-entry.mjs +13 -0
  782. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -0
  783. package/dist/surfaces/cli/commands/prune-picker.mjs +3 -2
  784. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  785. package/dist/surfaces/cli/commands/prune.mjs +34 -17
  786. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  787. package/dist/surfaces/cli/commands/status.mjs +2 -2
  788. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  789. package/dist/surfaces/cli/commands/supervisor-presence.mjs +5 -3
  790. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  791. package/dist/surfaces/cli/commands/wipe.mjs +38 -4
  792. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  793. package/dist/surfaces/cli/errors.mjs +1 -1
  794. package/dist/surfaces/cli/errors.mjs.map +1 -1
  795. package/dist/surfaces/cli/flags.mjs +0 -1
  796. package/dist/surfaces/cli/flags.mjs.map +1 -1
  797. package/dist/surfaces/cli/index.mjs +98 -18
  798. package/dist/surfaces/cli/index.mjs.map +1 -1
  799. package/dist/surfaces/cli/output.mjs +1 -0
  800. package/dist/surfaces/cli/output.mjs.map +1 -1
  801. package/dist/surfaces/cli/sysexits.mjs +2 -1
  802. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  803. package/dist/surfaces/tui/app.mjs +25 -3
  804. package/dist/surfaces/tui/app.mjs.map +1 -1
  805. package/dist/surfaces/tui/display-derivation.mjs +23 -74
  806. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  807. package/dist/surfaces/tui/event-log.mjs +35 -23
  808. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  809. package/dist/surfaces/tui/input.mjs +5 -1
  810. package/dist/surfaces/tui/input.mjs.map +1 -1
  811. package/dist/surfaces/tui/plain-renderer.mjs +70 -34
  812. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  813. package/package.json +24 -4
  814. package/dist/contracts/liveness-classifier.d.mts +0 -19
  815. package/dist/contracts/network-resolver.d.mts +0 -15
  816. package/dist/contracts/network-resolver.mjs +0 -7
  817. package/dist/contracts/network-resolver.mjs.map +0 -1
  818. package/dist/orchestrators/codegen/extras.mjs +0 -16
  819. package/dist/orchestrators/codegen/extras.mjs.map +0 -1
  820. package/dist/plugins/deepbook/routable.mjs +0 -21
  821. package/dist/plugins/deepbook/routable.mjs.map +0 -1
  822. package/dist/plugins/faucet/service.d.mts +0 -20
  823. package/dist/plugins/faucet/strategies/sui-local.d.mts +0 -23
  824. package/dist/plugins/faucet/strategies/sui-local.mjs.map +0 -1
  825. package/dist/plugins/sui/seed-objects.d.mts +0 -18
  826. package/dist/plugins/sui/seed-objects.mjs +0 -25
  827. package/dist/plugins/sui/seed-objects.mjs.map +0 -1
  828. package/dist/runtime/built-in-plugin-layers.mjs.map +0 -1
  829. package/dist/substrate/runtime/context-helpers.mjs +0 -32
  830. package/dist/substrate/runtime/context-helpers.mjs.map +0 -1
  831. package/dist/substrate/runtime/run.mjs.map +0 -1
  832. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs +0 -30
  833. package/dist/substrate/runtime/strategy-registry/faucet-capability-for.mjs.map +0 -1
  834. package/dist/substrate/runtime/supervisor.d.mts +0 -1
  835. package/dist/substrate/runtime/supervisor.mjs +0 -831
  836. package/dist/substrate/runtime/supervisor.mjs.map +0 -1
  837. package/dist/surfaces/cli/commands/doctor-probes.mjs.map +0 -1
  838. /package/dist/{substrate/runtime/strategy-registry/faucet-capability-for.d.mts → plugins/faucet/http.d.mts} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":[],"sources":["../../../src/plugins/wallet/server.ts"],"sourcesContent":["// Wallet plugin — in-process HTTP server.\n//\n// Responsibilities:\n//\n// 1. Bind a `node:http` server on a substrate-allocated port (the\n// port broker hands the port in; we don't pick it here).\n// 2. Dispatch by `(METHOD, path)` to the four handlers below\n// (health, accounts, sign-transaction, sign-personal-message,\n// execute).\n// 3. Enforce the auth gate: mandatory Origin in policy.allowed +\n// constant-time bearer compare.\n// 4. Body-cap enforcement: 64 KiB before buffering — protects the\n// supervisor from OOM via streaming payloads.\n// 5. Per-request span + log annotation under the captured\n// supervisor context — so handler errors hit the TUI logger\n// sink instead of bare stderr.\n// 6. Scope-finalizer teardown: `closeAllConnections()` →\n// `close()` awaited → port release (the substrate's scope\n// finalizer chain handles release; we just close).\n//\n// Wiring status (15-wallet.md alignment): both the per-route handler\n// bodies (auth + decode + dispatch to `AccountValue` sign closures)\n// AND the in-process `node:http.Server` listen loop are real. The\n// listener buffers each request body up to `MAX_BODY_BYTES`, forks the\n// dispatcher Effect under the captured supervisor context (so handler\n// logs flow to the TUI logger sink), while the substrate scoped HTTP\n// listener owns bind/close lifecycle.\n\nimport { Context, Effect, Schema } from 'effect';\nimport type { Scope } from 'effect';\nimport { randomUUID } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { listenScopedHttpServer } from '../../substrate/runtime/scoped-http-server.ts';\nimport { SpanAttr } from '../../substrate/runtime/observability/spans.ts';\nimport { decodeJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport type { AccountValue } from '../account/service.ts';\nimport {\n\twalletBootError,\n\twalletRequestError,\n\ttype WalletBootError,\n\ttype WalletRequestError,\n} from './errors.ts';\nimport { checkOrigin, corsHeadersFor, type OriginPolicy } from './origin-policy.ts';\nimport { parseBearerHeader, safeBearerEquals, type PairingToken } from './pairing.ts';\nimport {\n\tAccountsResponseSchema,\n\tExecuteRequestSchema,\n\tHealthResponseSchema,\n\tSignRequestSchema,\n\tSignResponseSchema,\n\tWALLET_AUTH_HEADER,\n\tWALLET_PROTOCOL_PREFIX,\n\tWalletHttpPath,\n\ttype AccountSummary,\n} from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// Server config + handle\n// ----------------------------------------------------------------------\n\n/** Inputs the substrate hands to `startHttpServer`. The plugin's\n * acquire body builds this from its resolved inputs (port broker,\n * origin policy, accounts map, token). */\nexport interface WalletServerConfig {\n\treadonly bindAddress: string;\n\treadonly port: number;\n\treadonly token: PairingToken;\n\treadonly policy: OriginPolicy;\n\treadonly accountsByAddress: ReadonlyMap<string, AccountValue>;\n\t/** Captured supervisor context — handler errors log under this so\n\t * the TUI logger sink receives them. Stub: the substrate primitive\n\t * will hand this in; today the field is opaque. */\n\treadonly supervisorCtx: unknown;\n}\n\n/** Opaque server handle. The substrate's scope finalizer chain\n * invokes `.close()`; callers don't dispatch into it directly. */\nexport interface WalletServerHandle {\n\treadonly url: string; // direct loopback URL — `http://<bindAddress>:<port>`\n\treadonly close: () => Effect.Effect<void>;\n}\n\n/** Body cap — 64 KiB. Above the largest reasonable sign-tx payload,\n * well below any OOM threshold. */\nexport const MAX_BODY_BYTES = 64 * 1024;\n\n// ----------------------------------------------------------------------\n// Top-level boot\n// ----------------------------------------------------------------------\n\n/** Per-request socket-level timeout, milliseconds. Caps the time a\n * malicious / hung peer can hold an idle connection. */\nconst REQUEST_SOCKET_TIMEOUT_MS = 30_000;\n\n/**\n * Boot the wallet HTTP server.\n *\n * Wires `node:http.createServer` against the dispatcher. Listens on\n * `bindAddress:port` (the substrate's port broker chose `port` upstream\n * of this call). The scope finalizer awaits graceful close on teardown\n * — `closeAllConnections()` followed by `close()` with await.\n *\n * Request flow (per connection):\n *\n * 1. `request` listener fires with `IncomingMessage`+`ServerResponse`.\n * 2. We buffer the request body up to `MAX_BODY_BYTES`. If the\n * inbound payload exceeds the cap, the socket is destroyed and\n * a 413 returned without ever invoking the dispatcher (closes the\n * supervisor-OOM path).\n * 3. Construct a `WalletRequest` from the buffered body + headers.\n * 4. `Effect.runForkWith(supervisorContext)(dispatch(config, req))`\n * — captured at acquire time so handler logs/spans flow to the\n * TUI logger sink.\n * 5. On fiber completion, write status + headers + body to `res`.\n * 6. On uncaught exception in the listener path: 500 with an opaque\n * message (no token leak, no internal-state leak).\n *\n * Scope finalizer:\n *\n * Calls `server.closeAllConnections()` (kills idle keepalive\n * sockets) then awaits `server.close()` (drains in-flight). The\n * finalizer is uninterruptible so a Ctrl-C double-tap doesn't\n * leave the socket dangling.\n */\nexport const startHttpServer = (\n\tconfig: WalletServerConfig,\n): Effect.Effect<WalletServerHandle, WalletBootError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// Capture the supervisor context so handler fibers run with the\n\t\t// same Logger / span sinks as the rest of the stack. The\n\t\t// dispatcher's R-channel is `never`, but capturing the context\n\t\t// keeps fiber-refs (logger, spans) flowing.\n\t\tconst supervisorContext = yield* Effect.context<never>();\n\t\tconst runDispatch = Effect.runForkWith(supervisorContext as Context.Context<never>);\n\n\t\tconst handle = yield* listenScopedHttpServer({\n\t\t\tbindAddress: config.bindAddress,\n\t\t\tport: config.port,\n\t\t\tlistener: makeRequestListener(config, runDispatch),\n\t\t\tonListenError: (cause): WalletBootError =>\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'listen',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`wallet HTTP server listen failed on ${config.bindAddress}:${config.port} — ` +\n\t\t\t\t\t\t(cause instanceof Error ? cause.message : String(cause)),\n\t\t\t\t\thint:\n\t\t\t\t\t\t'check that the port broker did not hand out a busy port; ' +\n\t\t\t\t\t\t'a sibling devstack on the same address would also explain this.',\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t});\n\n\t\tyield* Effect.logInfo('wallet HTTP server listening').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t[SpanAttr.host]: config.bindAddress,\n\t\t\t\t[SpanAttr.port]: config.port,\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: handle.url,\n\t\t\tclose: handle.close,\n\t\t} satisfies WalletServerHandle;\n\t});\n\n// ----------------------------------------------------------------------\n// Node request listener — bridges `node:http` → dispatcher Effect\n// ----------------------------------------------------------------------\n\n/**\n * Build the `(req, res)` listener Node hands to `http.createServer`.\n *\n * The listener:\n *\n * - Buffers the request body up to `MAX_BODY_BYTES`. Over-cap →\n * 413 + socket destroyed (the dispatcher's body-cap check is the\n * second line of defense; this one is the first).\n * - Drops invalid socket-level errors silently (defensive: the\n * wallet is loopback-only, but a malformed HTTP/1.1 line shouldn't\n * crash the supervisor).\n * - Forks the dispatcher Effect via the captured supervisor runtime.\n * - Writes the resolved `WalletResponse` back to `res`.\n *\n * Returns void (Node listener signature) — all error reporting flows\n * through the dispatcher (request errors) or `Effect.logError` (boot/\n * listener path errors).\n */\nconst makeRequestListener =\n\t(\n\t\tconfig: WalletServerConfig,\n\t\trunDispatch: <A, E>(effect: Effect.Effect<A, E, never>) => unknown,\n\t): ((req: IncomingMessage, res: ServerResponse) => void) =>\n\t(req, res) => {\n\t\t// Socket-level timeout — protects against hung peers parking\n\t\t// requests on the supervisor's loopback.\n\t\treq.socket.setTimeout(REQUEST_SOCKET_TIMEOUT_MS);\n\n\t\tconst chunks: Buffer[] = [];\n\t\tlet totalBytes = 0;\n\t\tlet overflowed = false;\n\n\t\treq.on('data', (chunk: Buffer) => {\n\t\t\tif (overflowed) return;\n\t\t\ttotalBytes += chunk.length;\n\t\t\tif (totalBytes > MAX_BODY_BYTES) {\n\t\t\t\toverflowed = true;\n\t\t\t\twriteOverflow(res);\n\t\t\t\ttry {\n\t\t\t\t\treq.destroy();\n\t\t\t\t} catch {\n\t\t\t\t\t/* defensive */\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tchunks.push(chunk);\n\t\t});\n\n\t\treq.on('error', () => {\n\t\t\t// Socket-level error — drop. Per-request errors are handled\n\t\t\t// inside the dispatcher.\n\t\t});\n\n\t\treq.on('end', () => {\n\t\t\tif (overflowed) return;\n\t\t\tconst body = Buffer.concat(chunks).toString('utf8');\n\t\t\tconst walletReq: WalletRequest = {\n\t\t\t\tmethod: req.method ?? 'GET',\n\t\t\t\turl: req.url ?? '/',\n\t\t\t\theaders: normalizeHeaders(req.headers),\n\t\t\t\tbody,\n\t\t\t};\n\t\t\tconst program = dispatch(config, walletReq).pipe(\n\t\t\t\tEffect.matchEffect({\n\t\t\t\t\tonFailure: (cause) =>\n\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t// `dispatch` is typed `Effect<WalletResponse>` (no\n\t\t\t\t\t\t\t// failure channel); a thrown defect lands here as a\n\t\t\t\t\t\t\t// `Cause`. Log + write a 500.\n\t\t\t\t\t\t\twriteServerError(res, cause);\n\t\t\t\t\t\t}),\n\t\t\t\t\tonSuccess: (resp) => Effect.sync(() => writeResponse(res, resp)),\n\t\t\t\t}),\n\t\t\t);\n\t\t\trunDispatch(program);\n\t\t});\n\t};\n\nconst writeResponse = (res: ServerResponse, resp: WalletResponse): void => {\n\tif (res.writableEnded) return;\n\tres.statusCode = resp.status;\n\tfor (const [k, v] of Object.entries(resp.headers)) {\n\t\tres.setHeader(k, v);\n\t}\n\tres.end(resp.body);\n};\n\nconst writeOverflow = (res: ServerResponse): void => {\n\tif (res.writableEnded) return;\n\tres.statusCode = 413;\n\tres.setHeader('content-type', 'text/plain; charset=utf-8');\n\tres.end('payload too large');\n};\n\nconst writeServerError = (res: ServerResponse, cause: unknown): void => {\n\tif (res.writableEnded) return;\n\tres.statusCode = 500;\n\tres.setHeader('content-type', 'application/json; charset=utf-8');\n\t// Opaque error body — no token, no internal state leak.\n\tres.end(\n\t\tJSON.stringify({\n\t\t\terror: 'internal error',\n\t\t\tcode: 'internal-error',\n\t\t}),\n\t);\n\tvoid cause; // surfaces via Effect.logError on the dispatcher path\n};\n\n/** Normalize Node's `IncomingHttpHeaders` (string | string[] | undefined)\n * to the `WalletRequest.headers` shape (string | undefined). Picks the\n * first value for repeated headers — matches browser send semantics\n * for the headers we care about (Origin, Authorization, Content-Type). */\nconst normalizeHeaders = (\n\theaders: IncomingMessage['headers'],\n): Readonly<Record<string, string | undefined>> => {\n\tconst out: Record<string, string | undefined> = {};\n\tfor (const [k, v] of Object.entries(headers)) {\n\t\tif (v === undefined) {\n\t\t\tout[k] = undefined;\n\t\t} else if (Array.isArray(v)) {\n\t\t\tout[k] = v[0];\n\t\t} else {\n\t\t\tout[k] = v;\n\t\t}\n\t}\n\treturn out;\n};\n\n// ----------------------------------------------------------------------\n// Dispatcher — pure(ish) function from request → effect\n// ----------------------------------------------------------------------\n\n/** Inbound request shape — substrate-runtime-agnostic. The substrate's\n * http-server primitive converts a `node:http` IncomingMessage into\n * this; tests can construct it directly. */\nexport interface WalletRequest {\n\treadonly method: string;\n\treadonly url: string; // path + query, no host\n\treadonly headers: Readonly<Record<string, string | undefined>>;\n\treadonly body: string; // already-buffered (capped at MAX_BODY_BYTES)\n}\n\n/** Outbound response. */\nexport interface WalletResponse {\n\treadonly status: number;\n\treadonly headers: Readonly<Record<string, string>>;\n\treadonly body: string;\n}\n\nconst json = (\n\tstatus: number,\n\tbody: unknown,\n\textraHeaders: Readonly<Record<string, string>> = {},\n): WalletResponse => ({\n\tstatus,\n\theaders: { 'content-type': 'application/json; charset=utf-8', ...extraHeaders },\n\tbody: JSON.stringify(body),\n});\n\nconst text = (\n\tstatus: number,\n\tbody: string,\n\textraHeaders: Readonly<Record<string, string>> = {},\n): WalletResponse => ({\n\tstatus,\n\theaders: { 'content-type': 'text/plain; charset=utf-8', ...extraHeaders },\n\tbody,\n});\n\n/**\n * Dispatch a single request to its handler. Returns an Effect that\n * always succeeds (the response is built); request-level failures\n * are projected to JSON envelopes via `errorEnvelope` below.\n *\n * The dispatch order matters:\n * 1. OPTIONS preflight (returns 204 + CORS) — no auth required.\n * 2. Path-prefix gate — anything not under `/api/v1/devstack/` is\n * a flat 404 (text/plain). PROTECTS the auth gate from being\n * visible on arbitrary URLs.\n * 3. Origin check (must be in policy.allowed; missing → 403).\n * 4. Bearer check (must constant-time-equal `config.token`).\n * 5. Method+path route to handler.\n */\nexport const dispatch = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n): Effect.Effect<WalletResponse> =>\n\tEffect.gen(function* () {\n\t\tconst requestId = randomUUID();\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'wallet.request.id': requestId,\n\t\t\t'wallet.request.method': req.method,\n\t\t\t'wallet.request.url': req.url,\n\t\t});\n\n\t\t// 1. OPTIONS preflight — no auth check, but the origin still has\n\t\t// to be in the allowlist so we don't leak CORS headers to\n\t\t// arbitrary callers.\n\t\tif (req.method === 'OPTIONS') {\n\t\t\tconst origin = req.headers['origin'];\n\t\t\tif (origin !== undefined && config.policy.allowed.has(origin)) {\n\t\t\t\treturn { status: 204, headers: corsHeadersFor(origin), body: '' };\n\t\t\t}\n\t\t\treturn text(403, 'forbidden origin');\n\t\t}\n\n\t\t// 2. Path-prefix gate.\n\t\tconst path = req.url.split('?')[0] ?? '';\n\t\tif (!path.startsWith(WALLET_PROTOCOL_PREFIX)) {\n\t\t\treturn text(404, 'not found');\n\t\t}\n\n\t\t// 3. Origin check.\n\t\tconst originResult = checkOrigin(config.policy, req.headers['origin']);\n\t\tif (originResult === 'missing') {\n\t\t\t// Log the BEARER-VALIDITY only, never the token itself.\n\t\t\tyield* Effect.logWarning('wallet origin missing').pipe(\n\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t[SpanAttr.requestId]: requestId,\n\t\t\t\t\t[SpanAttr.httpMethod]: req.method,\n\t\t\t\t\t[SpanAttr.httpPath]: path,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text(403, 'Origin header required');\n\t\t}\n\t\tif (originResult === 'forbidden') {\n\t\t\tyield* Effect.logWarning('wallet origin forbidden').pipe(\n\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t[SpanAttr.requestId]: requestId,\n\t\t\t\t\t[SpanAttr.walletOrigin]: req.headers.origin ?? '(missing)',\n\t\t\t\t\t[SpanAttr.httpMethod]: req.method,\n\t\t\t\t\t[SpanAttr.httpPath]: path,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text(403, 'forbidden origin');\n\t\t}\n\t\tconst origin = req.headers['origin']!;\n\n\t\t// 4. Bearer check. Token never appears in log lines — only the\n\t\t// boolean validity.\n\t\tconst bearer = parseBearerHeader(req.headers[WALLET_AUTH_HEADER]);\n\t\tconst bearerValid = bearer !== null && safeBearerEquals(bearer, config.token);\n\t\tyield* Effect.annotateCurrentSpan({ [SpanAttr.walletBearerValid]: bearerValid });\n\t\tif (!bearerValid) {\n\t\t\tyield* Effect.logWarning('wallet bearer check failed').pipe(\n\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t[SpanAttr.requestId]: requestId,\n\t\t\t\t\t[SpanAttr.walletBearerValid]: bearerValid,\n\t\t\t\t\t[SpanAttr.httpMethod]: req.method,\n\t\t\t\t\t[SpanAttr.httpPath]: path,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn errorEnvelope(\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'unauthorized',\n\t\t\t\t\thttpStatus: 401,\n\t\t\t\t\tmessage: 'unauthorized',\n\t\t\t\t}),\n\t\t\t\tcorsHeadersFor(origin),\n\t\t\t);\n\t\t}\n\n\t\t// 5. Route.\n\t\tconst corsHdr = corsHeadersFor(origin);\n\t\treturn yield* routeRequest(config, req, path, corsHdr).pipe(\n\t\t\tEffect.catchTag('WalletRequestError', (err) => Effect.succeed(errorEnvelope(err, corsHdr))),\n\t\t);\n\t});\n\n// ----------------------------------------------------------------------\n// Routing\n// ----------------------------------------------------------------------\n\nconst routeRequest = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n\tpath: string,\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> => {\n\tif (req.method === 'GET' && path === WalletHttpPath.HEALTH) {\n\t\treturn Effect.succeed(\n\t\t\tjson(200, { ok: true } satisfies Schema.Schema.Type<typeof HealthResponseSchema>, corsHdr),\n\t\t);\n\t}\n\tif (req.method === 'GET' && path === WalletHttpPath.ACCOUNTS) {\n\t\treturn handleAccounts(config, corsHdr);\n\t}\n\tif (req.method === 'POST' && path === WalletHttpPath.SIGN_TRANSACTION) {\n\t\treturn handleSign(config, req, 'transaction', corsHdr);\n\t}\n\tif (req.method === 'POST' && path === WalletHttpPath.SIGN_PERSONAL_MESSAGE) {\n\t\treturn handleSign(config, req, 'personal-message', corsHdr);\n\t}\n\tif (req.method === 'POST' && path === WalletHttpPath.EXECUTE) {\n\t\treturn handleExecute(config, req, corsHdr);\n\t}\n\treturn Effect.fail(\n\t\twalletRequestError({\n\t\t\tphase: 'route-not-found',\n\t\t\thttpStatus: 404,\n\t\t\tmessage: `no route for ${req.method} ${path}`,\n\t\t}),\n\t);\n};\n\n// ----------------------------------------------------------------------\n// Handlers\n// ----------------------------------------------------------------------\n\nconst handleAccounts = (\n\tconfig: WalletServerConfig,\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> =>\n\tEffect.sync(() => {\n\t\tconst accounts: ReadonlyArray<AccountSummary> = Array.from(\n\t\t\tconfig.accountsByAddress.values(),\n\t\t).map((acct) => ({\n\t\t\tname: acct.name,\n\t\t\taddress: acct.address,\n\t\t\tscheme: acct.scheme,\n\t\t\tpublicKey: Buffer.from(acct.publicKey).toString('base64'),\n\t\t\tsource: acct.source,\n\t\t}));\n\t\t// Schema-validate the response so a drift between AccountValue\n\t\t// and the wire envelope blows up at the boundary rather than at\n\t\t// the browser-side decode.\n\t\tconst validated: Schema.Schema.Type<typeof AccountsResponseSchema> = { accounts };\n\t\treturn json(200, validated, corsHdr);\n\t});\n\nconst decodeJsonBody = <A>(\n\tschema: Schema.Schema<A>,\n\tbody: string,\n): Effect.Effect<A, WalletRequestError> =>\n\tEffect.gen(function* () {\n\t\tif (body.length > MAX_BODY_BYTES) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'body-invalid',\n\t\t\t\t\thttpStatus: 400,\n\t\t\t\t\tmessage: `body exceeds ${MAX_BODY_BYTES}-byte cap`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\treturn yield* decodeJsonText(schema as Schema.Decoder<unknown>, body, {\n\t\t\tsource: 'wallet request body',\n\t\t\tmkError: (issue) =>\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'body-invalid',\n\t\t\t\t\thttpStatus: 400,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\tissue.message === 'failed to parse JSON'\n\t\t\t\t\t\t\t? 'invalid JSON body'\n\t\t\t\t\t\t\t: 'request body did not match schema',\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t}) as Effect.Effect<A, WalletRequestError>;\n\t});\n\nconst handleSign = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n\tkind: 'transaction' | 'personal-message',\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> =>\n\tEffect.gen(function* () {\n\t\tconst body = yield* decodeJsonBody(SignRequestSchema, req.body);\n\t\tconst account = config.accountsByAddress.get(body.address);\n\t\tif (account === undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'address-not-found',\n\t\t\t\t\thttpStatus: 404,\n\t\t\t\t\tmessage: `no account for address '${body.address}'`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst bytes = Buffer.from(body.bytes, 'base64');\n\t\tconst signed = yield* (\n\t\t\tkind === 'transaction' ? account.signTransaction(bytes) : account.signPersonalMessage(bytes)\n\t\t).pipe(\n\t\t\tEffect.mapError((cause) =>\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'sign-route-failed',\n\t\t\t\t\thttpStatus: 500,\n\t\t\t\t\tmessage: `${kind === 'transaction' ? 'signTransaction' : 'signPersonalMessage'} failed`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tconst resp: Schema.Schema.Type<typeof SignResponseSchema> = signed;\n\t\treturn json(200, resp, corsHdr);\n\t});\n\nconst handleExecute = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> =>\n\tEffect.gen(function* () {\n\t\tconst body = yield* decodeJsonBody(ExecuteRequestSchema, req.body);\n\t\tconst account = config.accountsByAddress.get(body.address);\n\t\tif (account === undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'address-not-found',\n\t\t\t\t\thttpStatus: 404,\n\t\t\t\t\tmessage: `no account for address '${body.address}'`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst bytes = Buffer.from(body.bytes, 'base64');\n\t\tconst result = yield* account.signAndExecute(bytes).pipe(\n\t\t\tEffect.mapError((cause) =>\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'sign-route-failed',\n\t\t\t\t\thttpStatus: 500,\n\t\t\t\t\tmessage: 'signAndExecute failed',\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\treturn json(200, result, corsHdr);\n\t});\n\n// ----------------------------------------------------------------------\n// Error envelope\n// ----------------------------------------------------------------------\n\nconst errorEnvelope = (\n\terr: WalletRequestError,\n\tcorsHdr: Readonly<Record<string, string>>,\n): WalletResponse => json(err.httpStatus, { error: err.message, code: err.phase }, corsHdr);\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.mjs","names":[],"sources":["../../../src/plugins/wallet/server.ts"],"sourcesContent":["// Wallet plugin — in-process HTTP server.\n//\n// Responsibilities:\n//\n// 1. Bind a `node:http` server on a substrate-allocated port (the\n// port broker hands the port in; we don't pick it here).\n// 2. Dispatch by `(METHOD, path)` to the four handlers below\n// (health, accounts, sign-transaction, sign-personal-message).\n// 3. Enforce the auth gate: mandatory Origin in policy.allowed +\n// constant-time bearer compare.\n// 4. Body-cap enforcement: 64 KiB before buffering — protects the\n// supervisor from OOM via streaming payloads.\n// 5. Per-request span + log annotation under the captured\n// supervisor context — so handler errors hit the TUI logger\n// sink instead of bare stderr.\n// 6. Scope-finalizer teardown: `closeAllConnections()` →\n// `close()` awaited → port release (the substrate's scope\n// finalizer chain handles release; we just close).\n//\n// Wiring status (15-wallet.md alignment): both the per-route handler\n// bodies (auth + decode + dispatch to `AccountValue` sign closures)\n// AND the in-process `node:http.Server` listen loop are real. The\n// listener buffers each request body up to `MAX_BODY_BYTES`, forks the\n// dispatcher Effect under the captured supervisor context (so handler\n// logs flow to the TUI logger sink), while the substrate scoped HTTP\n// listener owns bind/close lifecycle.\n\nimport { Cause, Context, Effect, Schema } from 'effect';\nimport type { Scope } from 'effect';\nimport { randomUUID } from 'node:crypto';\nimport type { IncomingMessage, ServerResponse } from 'node:http';\n\nimport { listenScopedHttpServer } from '../../substrate/runtime/scoped-http-server.ts';\nimport { SpanAttr } from '../../substrate/runtime/observability/spans.ts';\nimport { decodeJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.ts';\nimport { WalletSpans } from './spans.ts';\nimport type { AccountValue } from '../account/index.ts';\nimport {\n\twalletBootError,\n\twalletRequestError,\n\ttype WalletBootError,\n\ttype WalletRequestError,\n} from './errors.ts';\nimport { checkOrigin, corsHeadersFor, type OriginPolicy } from './origin-policy.ts';\nimport { parseBearerHeader, safeBearerEquals, type PairingToken } from './pairing.ts';\nimport {\n\tAccountsResponseSchema,\n\tHealthResponseSchema,\n\tSignRequestSchema,\n\tSignResponseSchema,\n\tWALLET_AUTH_HEADER,\n\tWALLET_PROTOCOL_PREFIX,\n\tWalletHttpPath,\n\ttype AccountSummary,\n} from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// Server config + handle\n// ----------------------------------------------------------------------\n\n/** Inputs the substrate hands to `startHttpServer`. The plugin's\n * acquire body builds this from its resolved inputs (port broker,\n * origin policy, accounts map, token). */\nexport interface WalletServerConfig {\n\treadonly bindAddress: string;\n\treadonly port: number;\n\treadonly token: PairingToken;\n\treadonly policy: OriginPolicy;\n\treadonly accountsByAddress: ReadonlyMap<string, AccountValue>;\n}\n\n/** Opaque server handle. The substrate's scope finalizer chain\n * invokes `.close()`; callers don't dispatch into it directly. */\nexport interface WalletServerHandle {\n\treadonly url: string; // direct loopback URL — `http://<bindAddress>:<port>`\n\treadonly close: () => Effect.Effect<void>;\n}\n\n/** Body cap — 64 KiB. Above the largest reasonable sign-tx payload,\n * well below any OOM threshold. */\nexport const MAX_BODY_BYTES = 64 * 1024;\n\n// ----------------------------------------------------------------------\n// Top-level boot\n// ----------------------------------------------------------------------\n\n/** Per-request socket-level timeout, milliseconds. Caps the time a\n * malicious / hung peer can hold an idle connection. */\nconst REQUEST_SOCKET_TIMEOUT_MS = 30_000;\n\n/**\n * Boot the wallet HTTP server.\n *\n * Wires `node:http.createServer` against the dispatcher. Listens on\n * `bindAddress:port` (the substrate's port broker chose `port` upstream\n * of this call). The scope finalizer awaits graceful close on teardown\n * — `closeAllConnections()` followed by `close()` with await.\n *\n * Request flow (per connection):\n *\n * 1. `request` listener fires with `IncomingMessage`+`ServerResponse`.\n * 2. We buffer the request body up to `MAX_BODY_BYTES`. If the\n * inbound payload exceeds the cap, the socket is destroyed and\n * a 413 returned without ever invoking the dispatcher (closes the\n * supervisor-OOM path).\n * 3. Construct a `WalletRequest` from the buffered body + headers.\n * 4. `Effect.runForkWith(supervisorContext)(dispatch(config, req))`\n * — captured at acquire time so handler logs/spans flow to the\n * TUI logger sink.\n * 5. On fiber completion, write status + headers + body to `res`.\n * 6. On uncaught exception in the listener path: 500 with an opaque\n * message (no token leak, no internal-state leak).\n *\n * Scope finalizer:\n *\n * Calls `server.closeAllConnections()` (kills idle keepalive\n * sockets) then awaits `server.close()` (drains in-flight). The\n * finalizer is uninterruptible so a Ctrl-C double-tap doesn't\n * leave the socket dangling.\n */\nexport const startHttpServer = (\n\tconfig: WalletServerConfig,\n): Effect.Effect<WalletServerHandle, WalletBootError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\t// Capture the supervisor context so handler fibers run with the\n\t\t// same Logger / span sinks as the rest of the stack. The\n\t\t// dispatcher's R-channel is `never`, but capturing the context\n\t\t// keeps fiber-refs (logger, spans) flowing.\n\t\tconst supervisorContext = yield* Effect.context<never>();\n\t\tconst runDispatch = Effect.runForkWith(supervisorContext as Context.Context<never>);\n\n\t\tconst handle = yield* listenScopedHttpServer({\n\t\t\tbindAddress: config.bindAddress,\n\t\t\tport: config.port,\n\t\t\tlistener: makeRequestListener(config, runDispatch),\n\t\t\tonListenError: (cause): WalletBootError =>\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'listen',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t`wallet HTTP server listen failed on ${config.bindAddress}:${config.port} — ` +\n\t\t\t\t\t\t(formatUnknownError(cause)),\n\t\t\t\t\thint:\n\t\t\t\t\t\t'check that the port broker did not hand out a busy port; ' +\n\t\t\t\t\t\t'a sibling devstack on the same address would also explain this.',\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t});\n\n\t\tyield* Effect.logInfo('wallet HTTP server listening').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t[SpanAttr.host]: config.bindAddress,\n\t\t\t\t[SpanAttr.port]: config.port,\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: handle.url,\n\t\t\tclose: handle.close,\n\t\t} satisfies WalletServerHandle;\n\t});\n\n// ----------------------------------------------------------------------\n// Node request listener — bridges `node:http` → dispatcher Effect\n// ----------------------------------------------------------------------\n\n/**\n * Build the `(req, res)` listener Node hands to `http.createServer`.\n *\n * The listener:\n *\n * - Buffers the request body up to `MAX_BODY_BYTES`. Over-cap →\n * 413 + socket destroyed (the dispatcher's body-cap check is the\n * second line of defense; this one is the first).\n * - Drops invalid socket-level errors silently (defensive: the\n * wallet is loopback-only, but a malformed HTTP/1.1 line shouldn't\n * crash the supervisor).\n * - Forks the dispatcher Effect via the captured supervisor runtime.\n * - Writes the resolved `WalletResponse` back to `res`.\n *\n * Returns void (Node listener signature) — all error reporting flows\n * through the dispatcher (request errors) or `Effect.logError` (boot/\n * listener path errors).\n */\nconst makeRequestListener =\n\t(\n\t\tconfig: WalletServerConfig,\n\t\trunDispatch: <A, E>(effect: Effect.Effect<A, E, never>) => unknown,\n\t): ((req: IncomingMessage, res: ServerResponse) => void) =>\n\t(req, res) => {\n\t\t// Socket-level timeout — protects against hung peers parking\n\t\t// requests on the supervisor's loopback.\n\t\treq.socket.setTimeout(REQUEST_SOCKET_TIMEOUT_MS);\n\n\t\tconst chunks: Buffer[] = [];\n\t\tlet totalBytes = 0;\n\t\tlet overflowed = false;\n\n\t\treq.on('data', (chunk: Buffer) => {\n\t\t\tif (overflowed) return;\n\t\t\ttotalBytes += chunk.length;\n\t\t\tif (totalBytes > MAX_BODY_BYTES) {\n\t\t\t\toverflowed = true;\n\t\t\t\twriteOverflow(res);\n\t\t\t\ttry {\n\t\t\t\t\treq.destroy();\n\t\t\t\t} catch {\n\t\t\t\t\t/* defensive */\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tchunks.push(chunk);\n\t\t});\n\n\t\treq.on('error', () => {\n\t\t\t// Socket-level error — drop. Per-request errors are handled\n\t\t\t// inside the dispatcher.\n\t\t});\n\n\t\treq.on('end', () => {\n\t\t\tif (overflowed) return;\n\t\t\tconst body = Buffer.concat(chunks).toString('utf8');\n\t\t\tconst walletReq: WalletRequest = {\n\t\t\t\tmethod: req.method ?? 'GET',\n\t\t\t\turl: req.url ?? '/',\n\t\t\t\theaders: normalizeHeaders(req.headers),\n\t\t\t\tbody,\n\t\t\t};\n\t\t\tconst program = dispatch(config, walletReq).pipe(\n\t\t\t\t// `dispatch` is typed `Effect<WalletResponse>` (no failure\n\t\t\t\t// channel) — its handlers map every expected failure into a\n\t\t\t\t// WalletResponse with the right HTTP status. Anything that\n\t\t\t\t// lands here is therefore a *defect* (thrown sync exception,\n\t\t\t\t// runtime invariant violation). We project the full Cause via\n\t\t\t\t// `matchCauseEffect` so we have something to log; `matchEffect`\n\t\t\t\t// would type the failure as `never` and the defect would\n\t\t\t\t// bypass it.\n\t\t\t\tEffect.matchCauseEffect({\n\t\t\t\t\tonFailure: (cause) =>\n\t\t\t\t\t\tEffect.logError('wallet dispatcher defect').pipe(\n\t\t\t\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t\t\t\t[WalletSpans.requestMethod]: walletReq.method,\n\t\t\t\t\t\t\t\t[WalletSpans.requestUrl]: walletReq.url,\n\t\t\t\t\t\t\t\tcause: Cause.pretty(cause),\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tEffect.flatMap(() =>\n\t\t\t\t\t\t\t\tEffect.sync(() => {\n\t\t\t\t\t\t\t\t\twriteServerError(res);\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t\tonSuccess: (resp) => Effect.sync(() => writeResponse(res, resp)),\n\t\t\t\t}),\n\t\t\t);\n\t\t\trunDispatch(program);\n\t\t});\n\t};\n\nconst writeResponse = (res: ServerResponse, resp: WalletResponse): void => {\n\tif (res.writableEnded) return;\n\tres.statusCode = resp.status;\n\tfor (const [k, v] of Object.entries(resp.headers)) {\n\t\tres.setHeader(k, v);\n\t}\n\tres.end(resp.body);\n};\n\nconst writeOverflow = (res: ServerResponse): void => {\n\tif (res.writableEnded) return;\n\tres.statusCode = 413;\n\tres.setHeader('content-type', 'text/plain; charset=utf-8');\n\tres.end('payload too large');\n};\n\n/** Write an opaque 500 — no token, no internal state leak. The\n * caller is responsible for logging the underlying cause (see the\n * `Effect.logError('wallet dispatcher defect')` in `dispatch`'s\n * `matchCauseEffect` onFailure branch). */\nconst writeServerError = (res: ServerResponse): void => {\n\tif (res.writableEnded) return;\n\tres.statusCode = 500;\n\tres.setHeader('content-type', 'application/json; charset=utf-8');\n\tres.end(\n\t\tJSON.stringify({\n\t\t\terror: 'internal error',\n\t\t\tcode: 'internal-error',\n\t\t}),\n\t);\n};\n\n/** Normalize Node's `IncomingHttpHeaders` (string | string[] | undefined)\n * to the `WalletRequest.headers` shape (string | undefined). Picks the\n * first value for repeated headers — matches browser send semantics\n * for the headers we care about (Origin, Authorization, Content-Type). */\nconst normalizeHeaders = (\n\theaders: IncomingMessage['headers'],\n): Readonly<Record<string, string | undefined>> => {\n\tconst out: Record<string, string | undefined> = {};\n\tfor (const [k, v] of Object.entries(headers)) {\n\t\tif (v === undefined) {\n\t\t\tout[k] = undefined;\n\t\t} else if (Array.isArray(v)) {\n\t\t\tout[k] = v[0];\n\t\t} else {\n\t\t\tout[k] = v;\n\t\t}\n\t}\n\treturn out;\n};\n\n// ----------------------------------------------------------------------\n// Dispatcher — pure(ish) function from request → effect\n// ----------------------------------------------------------------------\n\n/** Inbound request shape — substrate-runtime-agnostic. The substrate's\n * http-server primitive converts a `node:http` IncomingMessage into\n * this; tests can construct it directly. */\nexport interface WalletRequest {\n\treadonly method: string;\n\treadonly url: string; // path + query, no host\n\treadonly headers: Readonly<Record<string, string | undefined>>;\n\treadonly body: string; // already-buffered (capped at MAX_BODY_BYTES)\n}\n\n/** Outbound response. */\nexport interface WalletResponse {\n\treadonly status: number;\n\treadonly headers: Readonly<Record<string, string>>;\n\treadonly body: string;\n}\n\nconst json = (\n\tstatus: number,\n\tbody: unknown,\n\textraHeaders: Readonly<Record<string, string>> = {},\n): WalletResponse => ({\n\tstatus,\n\theaders: { 'content-type': 'application/json; charset=utf-8', ...extraHeaders },\n\tbody: JSON.stringify(body),\n});\n\nconst text = (\n\tstatus: number,\n\tbody: string,\n\textraHeaders: Readonly<Record<string, string>> = {},\n): WalletResponse => ({\n\tstatus,\n\theaders: { 'content-type': 'text/plain; charset=utf-8', ...extraHeaders },\n\tbody,\n});\n\n/**\n * Dispatch a single request to its handler. Returns an Effect that\n * always succeeds (the response is built); request-level failures\n * are projected to JSON envelopes via `errorEnvelope` below.\n *\n * The dispatch order matters:\n * 1. Path-prefix gate — anything not under `/api/v1/devstack/` is\n * a flat 404 (text/plain). PROTECTS the auth gate (and the CORS\n * preflight) from being visible on arbitrary URLs.\n * 2. OPTIONS preflight (returns 204 + CORS) — no auth required, but\n * scoped to the protocol prefix by step 1 so an allowed origin\n * cannot extract CORS headers for unrelated paths.\n * 3. Origin check (must be in policy.allowed; missing → 403).\n * 4. Bearer check (must constant-time-equal `config.token`).\n * 5. Method+path route to handler.\n */\nexport const dispatch = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n): Effect.Effect<WalletResponse> =>\n\tEffect.gen(function* () {\n\t\tconst requestId = randomUUID();\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t[WalletSpans.requestId]: requestId,\n\t\t\t[WalletSpans.requestMethod]: req.method,\n\t\t\t[WalletSpans.requestUrl]: req.url,\n\t\t});\n\n\t\t// 1. Path-prefix gate. Runs BEFORE the OPTIONS preflight so an\n\t\t// allowed origin cannot pull a `204 + CORS` response for an\n\t\t// arbitrary path — preflight success is scoped to the protocol\n\t\t// prefix, not the whole host.\n\t\tconst path = req.url.split('?')[0] ?? '';\n\t\tif (!path.startsWith(WALLET_PROTOCOL_PREFIX)) {\n\t\t\treturn text(404, 'not found');\n\t\t}\n\n\t\t// 2. OPTIONS preflight — no auth check, but the origin still has\n\t\t// to be in the allowlist so we don't leak CORS headers to\n\t\t// arbitrary callers.\n\t\tif (req.method === 'OPTIONS') {\n\t\t\tconst origin = req.headers['origin'];\n\t\t\tif (origin !== undefined && config.policy.allowed.has(origin)) {\n\t\t\t\treturn { status: 204, headers: corsHeadersFor(origin), body: '' };\n\t\t\t}\n\t\t\treturn text(403, 'forbidden origin');\n\t\t}\n\n\t\t// 3. Origin check.\n\t\tconst originResult = checkOrigin(config.policy, req.headers['origin']);\n\t\tif (originResult === 'missing') {\n\t\t\t// Log the BEARER-VALIDITY only, never the token itself.\n\t\t\tyield* Effect.logWarning('wallet origin missing').pipe(\n\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t[SpanAttr.requestId]: requestId,\n\t\t\t\t\t[SpanAttr.httpMethod]: req.method,\n\t\t\t\t\t[SpanAttr.httpPath]: path,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text(403, 'Origin header required');\n\t\t}\n\t\tif (originResult === 'forbidden') {\n\t\t\tyield* Effect.logWarning('wallet origin forbidden').pipe(\n\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t[SpanAttr.requestId]: requestId,\n\t\t\t\t\t[WalletSpans.origin]: req.headers.origin ?? '(missing)',\n\t\t\t\t\t[SpanAttr.httpMethod]: req.method,\n\t\t\t\t\t[SpanAttr.httpPath]: path,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn text(403, 'forbidden origin');\n\t\t}\n\t\tconst origin = req.headers['origin']!;\n\n\t\t// 4. Bearer check. Token never appears in log lines — only the\n\t\t// boolean validity.\n\t\tconst bearer = parseBearerHeader(req.headers[WALLET_AUTH_HEADER]);\n\t\tconst bearerValid = bearer !== null && safeBearerEquals(bearer, config.token);\n\t\tyield* Effect.annotateCurrentSpan({ [WalletSpans.bearerValid]: bearerValid });\n\t\tif (!bearerValid) {\n\t\t\tyield* Effect.logWarning('wallet bearer check failed').pipe(\n\t\t\t\tEffect.annotateLogs({\n\t\t\t\t\t[SpanAttr.requestId]: requestId,\n\t\t\t\t\t[WalletSpans.bearerValid]: bearerValid,\n\t\t\t\t\t[SpanAttr.httpMethod]: req.method,\n\t\t\t\t\t[SpanAttr.httpPath]: path,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn errorEnvelope(\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'unauthorized',\n\t\t\t\t\thttpStatus: 401,\n\t\t\t\t\tmessage: 'unauthorized',\n\t\t\t\t}),\n\t\t\t\tcorsHeadersFor(origin),\n\t\t\t);\n\t\t}\n\n\t\t// 5. Route.\n\t\tconst corsHdr = corsHeadersFor(origin);\n\t\treturn yield* routeRequest(config, req, path, corsHdr).pipe(\n\t\t\tEffect.catchTag('WalletRequestError', (err) => Effect.succeed(errorEnvelope(err, corsHdr))),\n\t\t);\n\t});\n\n// ----------------------------------------------------------------------\n// Routing\n// ----------------------------------------------------------------------\n\nconst routeRequest = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n\tpath: string,\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> => {\n\tif (req.method === 'GET' && path === WalletHttpPath.HEALTH) {\n\t\treturn Effect.succeed(\n\t\t\tjson(200, { ok: true } satisfies Schema.Schema.Type<typeof HealthResponseSchema>, corsHdr),\n\t\t);\n\t}\n\tif (req.method === 'GET' && path === WalletHttpPath.ACCOUNTS) {\n\t\treturn handleAccounts(config, corsHdr);\n\t}\n\tif (req.method === 'POST' && path === WalletHttpPath.SIGN_TRANSACTION) {\n\t\treturn handleSign(config, req, 'transaction', corsHdr);\n\t}\n\tif (req.method === 'POST' && path === WalletHttpPath.SIGN_PERSONAL_MESSAGE) {\n\t\treturn handleSign(config, req, 'personal-message', corsHdr);\n\t}\n\treturn Effect.fail(\n\t\twalletRequestError({\n\t\t\tphase: 'route-not-found',\n\t\t\thttpStatus: 404,\n\t\t\tmessage: `no route for ${req.method} ${path}`,\n\t\t}),\n\t);\n};\n\n// ----------------------------------------------------------------------\n// Handlers\n// ----------------------------------------------------------------------\n\nconst handleAccounts = (\n\tconfig: WalletServerConfig,\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> =>\n\tEffect.sync(() => {\n\t\tconst accounts: ReadonlyArray<AccountSummary> = Array.from(\n\t\t\tconfig.accountsByAddress.values(),\n\t\t).map((acct) => ({\n\t\t\tname: acct.name,\n\t\t\taddress: acct.address,\n\t\t\tscheme: acct.scheme,\n\t\t\tpublicKey: Buffer.from(acct.publicKey).toString('base64'),\n\t\t\tsource: acct.source,\n\t\t}));\n\t\t// Schema-validate the response so a drift between AccountValue\n\t\t// and the wire envelope blows up at the boundary rather than at\n\t\t// the browser-side decode.\n\t\tconst validated: Schema.Schema.Type<typeof AccountsResponseSchema> = { accounts };\n\t\treturn json(200, validated, corsHdr);\n\t});\n\nconst decodeJsonBody = <A>(\n\tschema: Schema.Schema<A>,\n\tbody: string,\n): Effect.Effect<A, WalletRequestError> =>\n\tEffect.gen(function* () {\n\t\t// Body-byte cap is enforced solely at the request listener (line\n\t\t// ~200): we accumulate `chunk.length` byte counts and write a 413\n\t\t// + destroy the socket the moment we cross `MAX_BODY_BYTES`. A\n\t\t// secondary in-dispatcher check on `body.length` would be wrong\n\t\t// anyway — `String.length` counts UTF-16 code units, not bytes,\n\t\t// so a 64 KiB body of multi-byte runes could slip past it. The\n\t\t// listener already gated correctly, so there's no second check\n\t\t// here.\n\t\t// Sanctioned cast: `decodeJsonText`'s `S extends Schema.Decoder<unknown>`\n\t\t// constraint is wider than `Schema.Schema<A>` (DecodingServices /\n\t\t// RequiresServices variance — Effect v4's `Decoder<unknown>` pins\n\t\t// these to `unknown` while `Schema.Schema<A>` pins them to `never`).\n\t\t// The runtime helper happily consumes either; only the TS variance\n\t\t// disagrees. Phase 19A attempted to widen the constraint to\n\t\t// `Schema.Top` but `decodeUnknownSync` requires `Decoder<unknown,\n\t\t// never>`, which a `Top` constraint can't satisfy without a\n\t\t// downstream cast that ends up uglier than this one.\n\t\treturn (yield* decodeJsonText(schema as Schema.Decoder<unknown>, body, {\n\t\t\tsource: 'wallet request body',\n\t\t\tmkError: (issue) =>\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'body-invalid',\n\t\t\t\t\thttpStatus: 400,\n\t\t\t\t\tmessage:\n\t\t\t\t\t\tissue.message === 'failed to parse JSON'\n\t\t\t\t\t\t\t? 'invalid JSON body'\n\t\t\t\t\t\t\t: 'request body did not match schema',\n\t\t\t\t\tcause: issue.cause,\n\t\t\t\t}),\n\t\t})) as A;\n\t});\n\nconst handleSign = (\n\tconfig: WalletServerConfig,\n\treq: WalletRequest,\n\tkind: 'transaction' | 'personal-message',\n\tcorsHdr: Readonly<Record<string, string>>,\n): Effect.Effect<WalletResponse, WalletRequestError> =>\n\tEffect.gen(function* () {\n\t\tconst body = yield* decodeJsonBody(SignRequestSchema, req.body);\n\t\tconst account = config.accountsByAddress.get(body.address);\n\t\tif (account === undefined) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'address-not-found',\n\t\t\t\t\thttpStatus: 404,\n\t\t\t\t\tmessage: `no account for address '${body.address}'`,\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst bytes = Buffer.from(body.bytes, 'base64');\n\t\tconst signed = yield* (\n\t\t\tkind === 'transaction' ? account.signTransaction(bytes) : account.signPersonalMessage(bytes)\n\t\t).pipe(\n\t\t\tEffect.mapError((cause) =>\n\t\t\t\twalletRequestError({\n\t\t\t\t\tphase: 'sign-route-failed',\n\t\t\t\t\thttpStatus: 500,\n\t\t\t\t\tmessage: `${kind === 'transaction' ? 'signTransaction' : 'signPersonalMessage'} failed`,\n\t\t\t\t\tcause,\n\t\t\t\t}),\n\t\t\t),\n\t\t);\n\t\tconst resp: Schema.Schema.Type<typeof SignResponseSchema> = signed;\n\t\treturn json(200, resp, corsHdr);\n\t});\n\n// ----------------------------------------------------------------------\n// Error envelope\n// ----------------------------------------------------------------------\n\nconst errorEnvelope = (\n\terr: WalletRequestError,\n\tcorsHdr: Readonly<Record<string, string>>,\n): WalletResponse => json(err.httpStatus, { error: err.message, code: err.phase }, corsHdr);\n"],"mappings":"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"}
@@ -36,7 +36,7 @@ interface WalletOptions<Accounts extends ReadonlyArray<WalletAccountMember> = Re
36
36
  * composer invokes once the account-providing members are
37
37
  * known. */
38
38
  readonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;
39
- /** Extra origins merged on top of the stack-scoped auto-derived
39
+ /** Extra origins merged on top of the router-fronted dev-server
40
40
  * origin. Useful for headless test runners and custom dev hosts. */
41
41
  readonly allowedOrigins?: ReadonlyArray<string>;
42
42
  /** Preferred host port. Substrate's port broker forward-scans if
@@ -47,10 +47,6 @@ interface WalletOptions<Accounts extends ReadonlyArray<WalletAccountMember> = Re
47
47
  * host-gateway address on native Linux. The public wallet URL remains
48
48
  * router-fronted and stack-scoped. */
49
49
  readonly bindAddress?: string;
50
- /** Opt-in: allowlist the bare `http://localhost:<vite-port>` form.
51
- * Off by default to close the cross-stack pairing risk (see
52
- * `origin-policy.ts` for the long-form rationale). */
53
- readonly allowLocalhostVite?: boolean;
54
50
  }
55
51
  interface WalletValue {
56
52
  readonly url: string;
@@ -1,6 +1,7 @@
1
- import { SpanAttr } from "../../substrate/runtime/observability/spans.mjs";
1
+ import { WalletHttpPath } from "../../contracts/wallet-protocol.mjs";
2
2
  import { walletBootError } from "./errors.mjs";
3
- import { WalletHttpPath } from "./protocol.mjs";
3
+ import { WalletSpans } from "./spans.mjs";
4
+ import "./protocol.mjs";
4
5
  import { acquirePairingToken, composePairUrl, tokenPath } from "./pairing.mjs";
5
6
  import { resolveOriginPolicy } from "./origin-policy.mjs";
6
7
  import { startHttpServer } from "./server.mjs";
@@ -29,19 +30,26 @@ const directUrlHostForBindAddress = (bindAddress) => bindAddress === WALLET_DEFA
29
30
  */
30
31
  const acquireWallet = (opts, ctx) => Effect.gen(function* () {
31
32
  yield* Effect.annotateCurrentSpan({
32
- "wallet.app": ctx.app,
33
- "wallet.stack": ctx.stack,
34
- "wallet.chain": ctx.chain
33
+ [WalletSpans.app]: ctx.app,
34
+ [WalletSpans.stack]: ctx.stack,
35
+ [WalletSpans.chain]: ctx.chain
35
36
  });
36
37
  const accounts = yield* ctx.resolveAccounts();
37
- yield* Effect.annotateCurrentSpan({ "wallet.accountCount": accounts.length });
38
+ yield* Effect.annotateCurrentSpan({ [WalletSpans.accountCount]: accounts.length });
38
39
  if (accounts.length === 0) return yield* Effect.fail(walletBootError({
39
40
  phase: "no-accounts",
40
41
  message: "wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.",
41
42
  hint: "`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack."
42
43
  }));
43
44
  const accountsByAddress = /* @__PURE__ */ new Map();
44
- for (const acct of accounts) accountsByAddress.set(acct.address, acct);
45
+ for (const acct of accounts) {
46
+ if (accountsByAddress.has(acct.address)) return yield* Effect.fail(walletBootError({
47
+ phase: "bind-account",
48
+ message: `wallet resolved two accounts at the same address ${acct.address}; each account must own a distinct address.`,
49
+ hint: "Remove the duplicate account member, or give the colliding accounts distinct keypairs."
50
+ }));
51
+ accountsByAddress.set(acct.address, acct);
52
+ }
45
53
  const bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;
46
54
  const port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));
47
55
  const token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));
@@ -52,13 +60,10 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
52
60
  policy: yield* resolveOriginPolicy({
53
61
  app: ctx.app,
54
62
  stack: ctx.stack,
55
- vitePortForThisStack: ctx.vitePortForThisStack,
56
63
  routedAppOrigin: ctx.routedAppOrigin,
57
- extraOrigins: opts.allowedOrigins ?? [],
58
- allowLocalhostVite: opts.allowLocalhostVite ?? false
64
+ extraOrigins: opts.allowedOrigins ?? []
59
65
  }),
60
- accountsByAddress,
61
- supervisorCtx: ctx.supervisorCtx
66
+ accountsByAddress
62
67
  });
63
68
  const walletUrl = ctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;
64
69
  const pairUrl = composePairUrl(walletUrl, token);
@@ -70,17 +75,16 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
70
75
  health: WalletHttpPath.HEALTH,
71
76
  accounts: WalletHttpPath.ACCOUNTS,
72
77
  signTransaction: WalletHttpPath.SIGN_TRANSACTION,
73
- signPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,
74
- execute: WalletHttpPath.EXECUTE
78
+ signPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE
75
79
  }
76
80
  };
77
81
  yield* Effect.annotateCurrentSpan({
78
- "wallet.url": walletUrl,
79
- "wallet.localPort": port
82
+ [WalletSpans.url]: walletUrl,
83
+ [WalletSpans.localPort]: port
80
84
  });
81
85
  yield* Effect.logInfo("wallet ready").pipe(Effect.annotateLogs({
82
- [SpanAttr.walletUrl]: walletUrl,
83
- [SpanAttr.walletToken]: "redacted-fragment"
86
+ [WalletSpans.url]: walletUrl,
87
+ [WalletSpans.token]: "redacted-fragment"
84
88
  }));
85
89
  return {
86
90
  url: walletUrl,
@@ -1 +1 @@
1
- {"version":3,"file":"service.mjs","names":[],"sources":["../../../src/plugins/wallet/service.ts"],"sourcesContent":["// Wallet plugin — main acquire Effect.\n//\n// What this file does (15-wallet.md §Lifecycle, Startup ordered):\n//\n// 1. Resolve each consumed account tag and key the AccountValues by\n// address (load-bearing: sign endpoints look up by address).\n// 2. Allocate a port via the substrate's `PortBrokerService`.\n// 3. Mint or rehydrate the pairing token (read-existing-or-mint at\n// `<stateRoot>/wallet/token`, mode 0o600) via the substrate's\n// `atomicWriteFile`.\n// 4. Resolve the stack-scoped origin allowlist via\n// `origin-policy.ts`.\n// 5. Start the in-process HTTP server.\n// 6. Compose the wallet URL + pair URL + return the resolved\n// `WalletValue`.\n//\n// The substrate handles:\n//\n// - Scope finalizers (port release, server close) — declared by the\n// substrate primitives we call into.\n// - Endpoint registry publication (we surface the URL/pairUrl via\n// our resolved tag value; the substrate's endpoint-registry\n// primitive consumes the `RoutableDecl` from `routable.ts`).\n// - Manifest emit — the substrate projects from registry. We never\n// write `.devstack/manifest.json` directly.\n\nimport { Effect } from 'effect';\nimport type { FileSystem, Scope } from 'effect';\n\nimport type { AccountValue } from '../account/service.ts';\nimport type { DappKitConfigBindings } from './codegen.ts';\nimport { walletBootError, type WalletBootError } from './errors.ts';\nimport { resolveOriginPolicy } from './origin-policy.ts';\nimport { acquirePairingToken, composePairUrl, tokenPath, type PairingToken } from './pairing.ts';\nimport { startHttpServer, type WalletServerConfig, type WalletServerHandle } from './server.ts';\nimport { WalletHttpPath } from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// User-facing options\n// ----------------------------------------------------------------------\n\nimport type { ResourceRef } from '../../api/define-plugin.ts';\nimport { SpanAttr } from '../../substrate/runtime/observability/spans.ts';\nimport type { AccountResourceId } from '../account/index.ts';\n\n/** Literal sentinel for `WalletOptions.accounts: 'all'` — every account\n * member in the stack. Expanded by the composer at `defineDevstack`\n * call time (api-surface-design §4 D6). Kept as an exported constant\n * so the wallet factory + composer share one source of truth. */\nexport const WALLET_ACCOUNTS_ALL = 'all' as const;\nexport type WalletAccountsAll = typeof WALLET_ACCOUNTS_ALL;\n\n/** A user-supplied account ref. The user passes the result of\n * `account('alice')` — NOT a bare string. Generic over the literal\n * account name so the wallet's dependency tuple preserves each\n * per-account resource id (`account/alice`, `account/bob`, ...). */\nexport type WalletAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\nexport interface WalletOptions<\n\tAccounts extends ReadonlyArray<WalletAccountMember> = ReadonlyArray<WalletAccountMember>,\n> {\n\t/** Accounts the wallet binds. Each is yielded for ordering AND its\n\t * resolved value is keyed by address into the sign-handler map.\n\t *\n\t * Two shapes:\n\t *\n\t * - Explicit tuple — each entry is the plugin/resource ref returned\n\t * by `account('name')`. Pins the bound set at the wallet's call\n\t * site; preserves each literal `account/${Name}` so stack\n\t * composition can validate and recursively expand the refs.\n\t *\n\t * - The literal `'all'` — shorthand for \"every account member in\n\t * the stack\". The composer expands this against the final\n\t * member tuple at `defineDevstack(...)` time (api-surface-design\n\t * §4 D6). The wallet member returned by the factory carries an\n\t * expander hook keyed off `WALLET_ACCOUNTS_ALL` that the\n\t * composer invokes once the account-providing members are\n\t * known. */\n\treadonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;\n\t/** Extra origins merged on top of the stack-scoped auto-derived\n\t * origin. Useful for headless test runners and custom dev hosts. */\n\treadonly allowedOrigins?: ReadonlyArray<string>;\n\t/** Preferred host port. Substrate's port broker forward-scans if\n\t * this is taken. Default: substrate-picked (no preference). */\n\treadonly port?: number;\n\t/** NIC the HTTP server binds. Defaults to `'0.0.0.0'` because the\n\t * router runs in Docker and must reach the host process through the\n\t * host-gateway address on native Linux. The public wallet URL remains\n\t * router-fronted and stack-scoped. */\n\treadonly bindAddress?: string;\n\t/** Opt-in: allowlist the bare `http://localhost:<vite-port>` form.\n\t * Off by default to close the cross-stack pairing risk (see\n\t * `origin-policy.ts` for the long-form rationale). */\n\treadonly allowLocalhostVite?: boolean;\n}\n\n// ----------------------------------------------------------------------\n// Resolved value (what the wallet plugin publishes)\n// ----------------------------------------------------------------------\n\nexport interface WalletValue {\n\treadonly url: string; // router-fronted URL when available, loopback otherwise\n\treadonly pairUrl: string;\n\treadonly bindings: DappKitConfigBindings;\n\treadonly localPort: number;\n\treadonly token: PairingToken;\n\t/** Server handle — substrate's scope finalizer chain invokes\n\t * `.close()`; callers don't reach in. Exposed here so tests can\n\t * drive teardown explicitly. */\n\treadonly server: WalletServerHandle;\n}\n\nconst WALLET_DEFAULT_BIND_ADDRESS = '0.0.0.0' as const;\nconst WALLET_DIRECT_URL_HOST = '127.0.0.1' as const;\ntype WalletPortProbeHost = '127.0.0.1' | '0.0.0.0';\n\nconst portProbeHostForBindAddress = (bindAddress: string): WalletPortProbeHost =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? '0.0.0.0' : '127.0.0.1';\n\nconst directUrlHostForBindAddress = (bindAddress: string): string =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? WALLET_DIRECT_URL_HOST : bindAddress;\n\n// ----------------------------------------------------------------------\n// Per-acquire context — supplied by the barrel\n// ----------------------------------------------------------------------\n\n/** Inputs from the substrate. The barrel fills these from the\n * BuildContext; tests can construct directly. */\nexport interface WalletAcquireContext {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly chain: string;\n\t/** State root where `wallet/token` lives. Convention:\n\t * `<appDir>/.devstack/stacks/<stack>/runtime`. */\n\treadonly stateRoot: string;\n\t/** Vite port for THIS stack (per-stack scoping — see\n\t * `origin-policy.ts`). `null` if no vite is mounted. */\n\treadonly vitePortForThisStack: number | null;\n\t/** Port broker seam — returns the allocated port + a scope-\n\t * finalizer-installed release. The barrel adapts the substrate's\n\t * `PortBrokerService.allocate` to this signature so tests can pin\n\t * the port without yielding from a substrate Layer. */\n\treadonly allocatePort: (\n\t\tpreferred?: number,\n\t\tprobeHost?: WalletPortProbeHost,\n\t) => Effect.Effect<number, WalletBootError, Scope.Scope>;\n\t/** Account value resolver — the barrel hands this in keyed off the\n\t * BuildContext so the service body stays substrate-agnostic. */\n\treadonly resolveAccounts: () => Effect.Effect<ReadonlyArray<AccountValue>, WalletBootError>;\n\t/** Stable router-fronted base URL for this wallet on the stack-scoped\n\t * hostname (e.g. `http://wallet.<app>.localhost:<router-port>`). Null\n\t * only in tests that bypass the router derivation. */\n\treadonly routerFrontedUrl: string | null;\n\t/** Stable router-fronted dev-server origin for this stack. Added to\n\t * the wallet allowlist so app+wallet stacks work without repeating\n\t * router origins in user configs. */\n\treadonly routedAppOrigin: string | null;\n\t/** Supervisor context captured for log/span propagation on handler\n\t * fibers (the in-process HTTP server forks per-request from this). */\n\treadonly supervisorCtx: unknown;\n}\n\n// ----------------------------------------------------------------------\n// Acquire\n// ----------------------------------------------------------------------\n\n/**\n * Acquire the wallet service.\n *\n * Distilled-doc invariants honored:\n *\n * - C12 (mandatory Origin + bearer): both gates are wired in\n * `server.ts:dispatch`.\n * - Token comparison constant-time: `pairing.ts:safeBearerEquals`.\n * - Token file 0o600: `pairing.ts:acquirePairingToken`.\n * - Token in URL fragment only: `pairing.ts:composePairUrl`.\n * - Token NEVER in log lines: handlers log only `bearerValid:\n * boolean`.\n * - Default bindAddress `'0.0.0.0'`: required for the Docker router to\n * reach host-loopback services on native Linux.\n * - Stack-scoped origin allowlist (no cross-stack pairing risk):\n * `origin-policy.ts:resolveOriginPolicy`.\n */\nexport const acquireWallet = (\n\topts: WalletOptions,\n\tctx: WalletAcquireContext,\n): Effect.Effect<WalletValue, WalletBootError, Scope.Scope | FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'wallet.app': ctx.app,\n\t\t\t'wallet.stack': ctx.stack,\n\t\t\t'wallet.chain': ctx.chain,\n\t\t});\n\n\t\t// 1. Resolve the dependency account values. The barrel sets up\n\t\t// `resolveAccounts` to walk the BuildContext via resolved dependencies in\n\t\t// `opts.accounts`; we just project to the address-keyed map.\n\t\t// Count is annotated AFTER resolution — `opts.accounts` may\n\t\t// be the `'all'` sentinel before composer expansion, so the\n\t\t// resolved-array length is the load-bearing value.\n\t\tconst accounts = yield* ctx.resolveAccounts();\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'wallet.accountCount': accounts.length,\n\t\t});\n\t\tif (accounts.length === 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'no-accounts',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.\",\n\t\t\t\t\thint: \"`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst accountsByAddress = new Map<string, AccountValue>();\n\t\tfor (const acct of accounts) {\n\t\t\taccountsByAddress.set(acct.address, acct);\n\t\t}\n\n\t\t// 2. Port allocation. Probe the same host family the real\n\t\t// listener uses, otherwise a process bound on a non-loopback\n\t\t// interface could race the wallet's all-interface listen.\n\t\tconst bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;\n\t\tconst port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));\n\n\t\t// 3. Token mint or rehydrate. Lives at the stack-scoped state root\n\t\t// so warm-start + snapshot-restore preserve the existing\n\t\t// pairing.\n\t\tconst token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));\n\n\t\t// 4. Origin policy. Stack-scoped — only allows THIS stack's vite\n\t\t// port through.\n\t\tconst policy = yield* resolveOriginPolicy({\n\t\t\tapp: ctx.app,\n\t\t\tstack: ctx.stack,\n\t\t\tvitePortForThisStack: ctx.vitePortForThisStack,\n\t\t\troutedAppOrigin: ctx.routedAppOrigin,\n\t\t\textraOrigins: opts.allowedOrigins ?? [],\n\t\t\tallowLocalhostVite: opts.allowLocalhostVite ?? false,\n\t\t});\n\n\t\t// 5. Start the HTTP server. The dispatcher in `server.ts` owns\n\t\t// route matching + the constant-time bearer compare + the\n\t\t// JSON envelope contract.\n\t\tconst serverConfig: WalletServerConfig = {\n\t\t\tbindAddress,\n\t\t\tport,\n\t\t\ttoken,\n\t\t\tpolicy,\n\t\t\taccountsByAddress,\n\t\t\tsupervisorCtx: ctx.supervisorCtx,\n\t\t};\n\t\tconst server = yield* startHttpServer(serverConfig);\n\n\t\t// 6. Compose URLs. Router-fronted form when available, loopback\n\t\t// fallback otherwise. The token rides ONLY the fragment — never\n\t\t// a query param — per C13.\n\t\tconst walletUrl =\n\t\t\tctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;\n\t\tconst pairUrl = composePairUrl(walletUrl, token);\n\n\t\tconst bindings: DappKitConfigBindings = {\n\t\t\twalletUrl,\n\t\t\tpairUrl,\n\t\t\tchain: ctx.chain,\n\t\t\tprotocolPaths: {\n\t\t\t\thealth: WalletHttpPath.HEALTH,\n\t\t\t\taccounts: WalletHttpPath.ACCOUNTS,\n\t\t\t\tsignTransaction: WalletHttpPath.SIGN_TRANSACTION,\n\t\t\t\tsignPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,\n\t\t\t\texecute: WalletHttpPath.EXECUTE,\n\t\t\t},\n\t\t};\n\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t'wallet.url': walletUrl,\n\t\t\t'wallet.localPort': port,\n\t\t});\n\n\t\t// Defensive: NEVER log `pairUrl` directly. It carries the token.\n\t\tyield* Effect.logInfo('wallet ready').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t[SpanAttr.walletUrl]: walletUrl,\n\t\t\t\t[SpanAttr.walletToken]: 'redacted-fragment',\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: walletUrl,\n\t\t\tpairUrl,\n\t\t\tbindings,\n\t\t\tlocalPort: port,\n\t\t\ttoken,\n\t\t\tserver,\n\t\t} satisfies WalletValue;\n\t});\n"],"mappings":";;;;;;;;AAmHA,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAG/B,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,YAAY;AAE3D,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,yBAAyB;;;;;;;;;;;;;;;;;;AA+DxE,MAAa,iBACZ,MACA,QAEA,OAAO,IAAI,aAAa;AACvB,QAAO,OAAO,oBAAoB;EACjC,cAAc,IAAI;EAClB,gBAAgB,IAAI;EACpB,gBAAgB,IAAI;EACpB,CAAC;CAQF,MAAM,WAAW,OAAO,IAAI,iBAAiB;AAC7C,QAAO,OAAO,oBAAoB,EACjC,uBAAuB,SAAS,QAChC,CAAC;AACF,KAAI,SAAS,WAAW,EACvB,QAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;EACN,CAAC,CACF;CAEF,MAAM,oCAAoB,IAAI,KAA2B;AACzD,MAAK,MAAM,QAAQ,SAClB,mBAAkB,IAAI,KAAK,SAAS,KAAK;CAM1C,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,YAAY,CAAC;CAKzF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,UAAU,CAAC;CAwBlE,MAAM,SAAS,OAAO,gBAAgB;EAPrC;EACA;EACA;EACA,QAAA,OAhBqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,sBAAsB,IAAI;GAC1B,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,EAAE;GACvC,oBAAoB,KAAK,sBAAsB;GAC/C,CAAC;EAUD;EACA,eAAe,IAAI;EAE8B,CAAC;CAKnD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,YAAY,CAAC,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,MAAM;CAEhD,MAAM,WAAkC;EACvC;EACA;EACA,OAAO,IAAI;EACX,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;GACpC,SAAS,eAAe;GACxB;EACD;AAED,QAAO,OAAO,oBAAoB;EACjC,cAAc;EACd,oBAAoB;EACpB,CAAC;AAGF,QAAO,OAAO,QAAQ,eAAe,CAAC,KACrC,OAAO,aAAa;GAClB,SAAS,YAAY;GACrB,SAAS,cAAc;EACxB,CAAC,CACF;AAED,QAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;EACA;EACA"}
1
+ {"version":3,"file":"service.mjs","names":[],"sources":["../../../src/plugins/wallet/service.ts"],"sourcesContent":["// Wallet plugin — main acquire Effect.\n//\n// What this file does (15-wallet.md §Lifecycle, Startup ordered):\n//\n// 1. Resolve each consumed account tag and key the AccountValues by\n// address (load-bearing: sign endpoints look up by address).\n// 2. Allocate a port via the substrate's `PortBrokerService`.\n// 3. Mint or rehydrate the pairing token (read-existing-or-mint at\n// `<stateRoot>/wallet/token`, mode 0o600) via the substrate's\n// `atomicWriteFile`.\n// 4. Resolve the stack-scoped origin allowlist via\n// `origin-policy.ts`.\n// 5. Start the in-process HTTP server.\n// 6. Compose the wallet URL + pair URL + return the resolved\n// `WalletValue`.\n//\n// The substrate handles:\n//\n// - Scope finalizers (port release, server close) — declared by the\n// substrate primitives we call into.\n// - Endpoint registry publication (we surface the URL/pairUrl via\n// our resolved tag value; the substrate's endpoint-registry\n// primitive consumes the `RoutableDecl` from `routable.ts`).\n// - Manifest emit — the substrate projects from registry. We never\n// write `.devstack/manifest.json` directly.\n\nimport { Effect } from 'effect';\nimport type { FileSystem, Scope } from 'effect';\n\nimport type { AccountResourceId, AccountValue } from '../account/index.ts';\nimport type { DappKitConfigBindings } from './codegen.ts';\nimport { walletBootError, type WalletBootError } from './errors.ts';\nimport { resolveOriginPolicy } from './origin-policy.ts';\nimport { acquirePairingToken, composePairUrl, tokenPath, type PairingToken } from './pairing.ts';\nimport { startHttpServer, type WalletServerConfig, type WalletServerHandle } from './server.ts';\nimport { WalletHttpPath } from './protocol.ts';\n\n// ----------------------------------------------------------------------\n// User-facing options\n// ----------------------------------------------------------------------\n\nimport type { ResourceRef } from '../../api/define-plugin.ts';\nimport { WalletSpans } from './spans.ts';\n\n/** Literal sentinel for `WalletOptions.accounts: 'all'` — every account\n * member in the stack. Expanded by the composer at `defineDevstack`\n * call time (api-surface-design §4 D6). Kept as an exported constant\n * so the wallet factory + composer share one source of truth. */\nexport const WALLET_ACCOUNTS_ALL = 'all' as const;\nexport type WalletAccountsAll = typeof WALLET_ACCOUNTS_ALL;\n\n/** A user-supplied account ref. The user passes the result of\n * `account('alice')` — NOT a bare string. Generic over the literal\n * account name so the wallet's dependency tuple preserves each\n * per-account resource id (`account/alice`, `account/bob`, ...). */\nexport type WalletAccountMember<Name extends string = string> = ResourceRef<\n\tAccountResourceId<Name>,\n\tAccountValue\n>;\n\nexport interface WalletOptions<\n\tAccounts extends ReadonlyArray<WalletAccountMember> = ReadonlyArray<WalletAccountMember>,\n> {\n\t/** Accounts the wallet binds. Each is yielded for ordering AND its\n\t * resolved value is keyed by address into the sign-handler map.\n\t *\n\t * Two shapes:\n\t *\n\t * - Explicit tuple — each entry is the plugin/resource ref returned\n\t * by `account('name')`. Pins the bound set at the wallet's call\n\t * site; preserves each literal `account/${Name}` so stack\n\t * composition can validate and recursively expand the refs.\n\t *\n\t * - The literal `'all'` — shorthand for \"every account member in\n\t * the stack\". The composer expands this against the final\n\t * member tuple at `defineDevstack(...)` time (api-surface-design\n\t * §4 D6). The wallet member returned by the factory carries an\n\t * expander hook keyed off `WALLET_ACCOUNTS_ALL` that the\n\t * composer invokes once the account-providing members are\n\t * known. */\n\treadonly accounts: Accounts | typeof WALLET_ACCOUNTS_ALL;\n\t/** Extra origins merged on top of the router-fronted dev-server\n\t * origin. Useful for headless test runners and custom dev hosts. */\n\treadonly allowedOrigins?: ReadonlyArray<string>;\n\t/** Preferred host port. Substrate's port broker forward-scans if\n\t * this is taken. Default: substrate-picked (no preference). */\n\treadonly port?: number;\n\t/** NIC the HTTP server binds. Defaults to `'0.0.0.0'` because the\n\t * router runs in Docker and must reach the host process through the\n\t * host-gateway address on native Linux. The public wallet URL remains\n\t * router-fronted and stack-scoped. */\n\treadonly bindAddress?: string;\n}\n\n// ----------------------------------------------------------------------\n// Resolved value (what the wallet plugin publishes)\n// ----------------------------------------------------------------------\n\nexport interface WalletValue {\n\treadonly url: string; // router-fronted URL when available, loopback otherwise\n\treadonly pairUrl: string;\n\treadonly bindings: DappKitConfigBindings;\n\treadonly localPort: number;\n\treadonly token: PairingToken;\n\t/** Server handle — substrate's scope finalizer chain invokes\n\t * `.close()`; callers don't reach in. Exposed here so tests can\n\t * drive teardown explicitly. */\n\treadonly server: WalletServerHandle;\n}\n\nconst WALLET_DEFAULT_BIND_ADDRESS = '0.0.0.0' as const;\nconst WALLET_DIRECT_URL_HOST = '127.0.0.1' as const;\ntype WalletPortProbeHost = '127.0.0.1' | '0.0.0.0';\n\nconst portProbeHostForBindAddress = (bindAddress: string): WalletPortProbeHost =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? '0.0.0.0' : '127.0.0.1';\n\nconst directUrlHostForBindAddress = (bindAddress: string): string =>\n\tbindAddress === WALLET_DEFAULT_BIND_ADDRESS ? WALLET_DIRECT_URL_HOST : bindAddress;\n\n// ----------------------------------------------------------------------\n// Per-acquire context — supplied by the barrel\n// ----------------------------------------------------------------------\n\n/** Inputs from the substrate. The barrel fills these from the\n * BuildContext; tests can construct directly. */\nexport interface WalletAcquireContext {\n\treadonly app: string;\n\treadonly stack: string;\n\treadonly chain: string;\n\t/** State root where `wallet/token` lives. Convention:\n\t * `<appDir>/.devstack/stacks/<stack>/runtime`. */\n\treadonly stateRoot: string;\n\t/** Port broker seam — returns the allocated port + a scope-\n\t * finalizer-installed release. The barrel adapts the substrate's\n\t * `PortBrokerService.allocate` to this signature so tests can pin\n\t * the port without yielding from a substrate Layer. */\n\treadonly allocatePort: (\n\t\tpreferred?: number,\n\t\tprobeHost?: WalletPortProbeHost,\n\t) => Effect.Effect<number, WalletBootError, Scope.Scope>;\n\t/** Account value resolver — the barrel hands this in keyed off the\n\t * BuildContext so the service body stays substrate-agnostic. */\n\treadonly resolveAccounts: () => Effect.Effect<ReadonlyArray<AccountValue>, WalletBootError>;\n\t/** Stable router-fronted base URL for this wallet on the stack-scoped\n\t * hostname (e.g. `http://wallet.<app>.localhost:<router-port>`). Null\n\t * only in tests that bypass the router derivation. */\n\treadonly routerFrontedUrl: string | null;\n\t/** Stable router-fronted dev-server origin for this stack. Added to\n\t * the wallet allowlist so app+wallet stacks work without repeating\n\t * router origins in user configs. */\n\treadonly routedAppOrigin: string | null;\n}\n\n// ----------------------------------------------------------------------\n// Acquire\n// ----------------------------------------------------------------------\n\n/**\n * Acquire the wallet service.\n *\n * Distilled-doc invariants honored:\n *\n * - C12 (mandatory Origin + bearer): both gates are wired in\n * `server.ts:dispatch`.\n * - Token comparison constant-time: `pairing.ts:safeBearerEquals`.\n * - Token file 0o600: `pairing.ts:acquirePairingToken`.\n * - Token in URL fragment only: `pairing.ts:composePairUrl`.\n * - Token NEVER in log lines: handlers log only `bearerValid:\n * boolean`.\n * - Default bindAddress `'0.0.0.0'`: required for the Docker router to\n * reach host-loopback services on native Linux.\n * - Stack-scoped origin allowlist (no cross-stack pairing risk):\n * `origin-policy.ts:resolveOriginPolicy`.\n */\nexport const acquireWallet = (\n\topts: WalletOptions,\n\tctx: WalletAcquireContext,\n): Effect.Effect<WalletValue, WalletBootError, Scope.Scope | FileSystem.FileSystem> =>\n\tEffect.gen(function* () {\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t[WalletSpans.app]: ctx.app,\n\t\t\t[WalletSpans.stack]: ctx.stack,\n\t\t\t[WalletSpans.chain]: ctx.chain,\n\t\t});\n\n\t\t// 1. Resolve the dependency account values. The barrel sets up\n\t\t// `resolveAccounts` to walk the BuildContext via resolved dependencies in\n\t\t// `opts.accounts`; we just project to the address-keyed map.\n\t\t// Count is annotated AFTER resolution — `opts.accounts` may\n\t\t// be the `'all'` sentinel before composer expansion, so the\n\t\t// resolved-array length is the load-bearing value.\n\t\tconst accounts = yield* ctx.resolveAccounts();\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t[WalletSpans.accountCount]: accounts.length,\n\t\t});\n\t\tif (accounts.length === 0) {\n\t\t\treturn yield* Effect.fail(\n\t\t\t\twalletBootError({\n\t\t\t\t\tphase: 'no-accounts',\n\t\t\t\t\tmessage:\n\t\t\t\t\t\t\"wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.\",\n\t\t\t\t\thint: \"`wallet()` and `wallet({ accounts: 'all' })` require at least one account member in the final stack.\",\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tconst accountsByAddress = new Map<string, AccountValue>();\n\t\tfor (const acct of accounts) {\n\t\t\t// Two accounts resolving to the same address would silently\n\t\t\t// last-write-wins the sign-route map, so a sign request for\n\t\t\t// that address binds to a non-deterministic account. Fail at\n\t\t\t// boot with the colliding address named.\n\t\t\tif (accountsByAddress.has(acct.address)) {\n\t\t\t\treturn yield* Effect.fail(\n\t\t\t\t\twalletBootError({\n\t\t\t\t\t\tphase: 'bind-account',\n\t\t\t\t\t\tmessage: `wallet resolved two accounts at the same address ${acct.address}; each account must own a distinct address.`,\n\t\t\t\t\t\thint: 'Remove the duplicate account member, or give the colliding accounts distinct keypairs.',\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\taccountsByAddress.set(acct.address, acct);\n\t\t}\n\n\t\t// 2. Port allocation. Probe the same host family the real\n\t\t// listener uses, otherwise a process bound on a non-loopback\n\t\t// interface could race the wallet's all-interface listen.\n\t\tconst bindAddress = opts.bindAddress ?? WALLET_DEFAULT_BIND_ADDRESS;\n\t\tconst port = yield* ctx.allocatePort(opts.port, portProbeHostForBindAddress(bindAddress));\n\n\t\t// 3. Token mint or rehydrate. Lives at the stack-scoped state root\n\t\t// so warm-start + snapshot-restore preserve the existing\n\t\t// pairing.\n\t\tconst token = yield* acquirePairingToken(tokenPath(ctx.stateRoot));\n\n\t\t// 4. Origin policy. Allowlist is the router-fronted dev-server\n\t\t// origin for this stack plus any explicit `allowedOrigins`.\n\t\tconst policy = yield* resolveOriginPolicy({\n\t\t\tapp: ctx.app,\n\t\t\tstack: ctx.stack,\n\t\t\troutedAppOrigin: ctx.routedAppOrigin,\n\t\t\textraOrigins: opts.allowedOrigins ?? [],\n\t\t});\n\n\t\t// 5. Start the HTTP server. The dispatcher in `server.ts` owns\n\t\t// route matching + the constant-time bearer compare + the\n\t\t// JSON envelope contract.\n\t\tconst serverConfig: WalletServerConfig = {\n\t\t\tbindAddress,\n\t\t\tport,\n\t\t\ttoken,\n\t\t\tpolicy,\n\t\t\taccountsByAddress,\n\t\t};\n\t\tconst server = yield* startHttpServer(serverConfig);\n\n\t\t// 6. Compose URLs. Router-fronted form when available, loopback\n\t\t// fallback otherwise. The token rides ONLY the fragment — never\n\t\t// a query param — per C13.\n\t\tconst walletUrl =\n\t\t\tctx.routerFrontedUrl ?? `http://${directUrlHostForBindAddress(bindAddress)}:${port}`;\n\t\tconst pairUrl = composePairUrl(walletUrl, token);\n\n\t\tconst bindings: DappKitConfigBindings = {\n\t\t\twalletUrl,\n\t\t\tpairUrl,\n\t\t\tchain: ctx.chain,\n\t\t\tprotocolPaths: {\n\t\t\t\thealth: WalletHttpPath.HEALTH,\n\t\t\t\taccounts: WalletHttpPath.ACCOUNTS,\n\t\t\t\tsignTransaction: WalletHttpPath.SIGN_TRANSACTION,\n\t\t\t\tsignPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE,\n\t\t\t},\n\t\t};\n\n\t\tyield* Effect.annotateCurrentSpan({\n\t\t\t[WalletSpans.url]: walletUrl,\n\t\t\t[WalletSpans.localPort]: port,\n\t\t});\n\n\t\t// Defensive: NEVER log `pairUrl` directly. It carries the token.\n\t\tyield* Effect.logInfo('wallet ready').pipe(\n\t\t\tEffect.annotateLogs({\n\t\t\t\t[WalletSpans.url]: walletUrl,\n\t\t\t\t[WalletSpans.token]: 'redacted-fragment',\n\t\t\t}),\n\t\t);\n\n\t\treturn {\n\t\t\turl: walletUrl,\n\t\t\tpairUrl,\n\t\t\tbindings,\n\t\t\tlocalPort: port,\n\t\t\ttoken,\n\t\t\tserver,\n\t\t} satisfies WalletValue;\n\t});\n"],"mappings":";;;;;;;;;AA8GA,MAAM,8BAA8B;AACpC,MAAM,yBAAyB;AAG/B,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,YAAY;AAE3D,MAAM,+BAA+B,gBACpC,gBAAgB,8BAA8B,yBAAyB;;;;;;;;;;;;;;;;;;AAyDxE,MAAa,iBACZ,MACA,QAEA,OAAO,IAAI,aAAa;AACvB,QAAO,OAAO,oBAAoB;GAChC,YAAY,MAAM,IAAI;GACtB,YAAY,QAAQ,IAAI;GACxB,YAAY,QAAQ,IAAI;EACzB,CAAC;CAQF,MAAM,WAAW,OAAO,IAAI,iBAAiB;AAC7C,QAAO,OAAO,oBAAoB,GAChC,YAAY,eAAe,SAAS,QACrC,CAAC;AACF,KAAI,SAAS,WAAW,EACvB,QAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;EACN,CAAC,CACF;CAEF,MAAM,oCAAoB,IAAI,KAA2B;AACzD,MAAK,MAAM,QAAQ,UAAU;AAK5B,MAAI,kBAAkB,IAAI,KAAK,QAAQ,CACtC,QAAO,OAAO,OAAO,KACpB,gBAAgB;GACf,OAAO;GACP,SAAS,oDAAoD,KAAK,QAAQ;GAC1E,MAAM;GACN,CAAC,CACF;AAEF,oBAAkB,IAAI,KAAK,SAAS,KAAK;;CAM1C,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,YAAY,CAAC;CAKzF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,UAAU,CAAC;CAqBlE,MAAM,SAAS,OAAO,gBAAgB;EANrC;EACA;EACA;EACA,QAAA,OAdqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,EAAE;GACvC,CAAC;EAUD;EAEiD,CAAC;CAKnD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,YAAY,CAAC,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,MAAM;CAEhD,MAAM,WAAkC;EACvC;EACA;EACA,OAAO,IAAI;EACX,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;GACpC;EACD;AAED,QAAO,OAAO,oBAAoB;GAChC,YAAY,MAAM;GAClB,YAAY,YAAY;EACzB,CAAC;AAGF,QAAO,OAAO,QAAQ,eAAe,CAAC,KACrC,OAAO,aAAa;GAClB,YAAY,MAAM;GAClB,YAAY,QAAQ;EACrB,CAAC,CACF;AAED,QAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;EACA;EACA"}
@@ -0,0 +1,22 @@
1
+ //#region src/plugins/wallet/spans.ts
2
+ const WalletSpans = {
3
+ accountCount: "wallet.accountCount",
4
+ app: "wallet.app",
5
+ bearerValid: "wallet.auth.bearerValid",
6
+ chain: "wallet.chain",
7
+ codegenPairUrl: "wallet.codegen.pairUrl",
8
+ codegenWalletUrl: "wallet.codegen.walletUrl",
9
+ localPort: "wallet.localPort",
10
+ origin: "wallet.origin",
11
+ requestId: "wallet.request.id",
12
+ requestMethod: "wallet.request.method",
13
+ requestUrl: "wallet.request.url",
14
+ stack: "wallet.stack",
15
+ token: "wallet.token",
16
+ tokenFile: "wallet.tokenFile",
17
+ url: "wallet.url"
18
+ };
19
+ //#endregion
20
+ export { WalletSpans };
21
+
22
+ //# sourceMappingURL=spans.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spans.mjs","names":[],"sources":["../../../src/plugins/wallet/spans.ts"],"sourcesContent":["// Wallet plugin span-attribute vocabulary. Plugin-local — substrate's\n// `SpanAttr` carries only engine-dimensional + http/process generic\n// keys; plugin-domain keys live next to the plugin that owns them.\n//\n// Callsite pattern: `Effect.annotateCurrentSpan({ [WalletSpans.token]:\n// value })`. Free-form string literals are a STYLE_GUIDE §16 violation.\n\nexport const WalletSpans = {\n\taccountCount: 'wallet.accountCount',\n\tapp: 'wallet.app',\n\tbearerValid: 'wallet.auth.bearerValid',\n\tchain: 'wallet.chain',\n\tcodegenPairUrl: 'wallet.codegen.pairUrl',\n\tcodegenWalletUrl: 'wallet.codegen.walletUrl',\n\tlocalPort: 'wallet.localPort',\n\torigin: 'wallet.origin',\n\trequestId: 'wallet.request.id',\n\trequestMethod: 'wallet.request.method',\n\trequestUrl: 'wallet.request.url',\n\tstack: 'wallet.stack',\n\ttoken: 'wallet.token',\n\ttokenFile: 'wallet.tokenFile',\n\turl: 'wallet.url',\n} as const;\n"],"mappings":";AAOA,MAAa,cAAc;CAC1B,cAAc;CACd,KAAK;CACL,aAAa;CACb,OAAO;CACP,gBAAgB;CAChB,kBAAkB;CAClB,WAAW;CACX,QAAQ;CACR,WAAW;CACX,eAAe;CACf,YAAY;CACZ,OAAO;CACP,OAAO;CACP,WAAW;CACX,KAAK;CACL"}
@@ -1,5 +1,8 @@
1
1
  import { walrusPluginError } from "../errors.mjs";
2
+ import { WalrusSpans } from "../spans.mjs";
3
+ import { readEnv } from "../../../substrate/runtime/typed-env.mjs";
2
4
  import { Effect } from "effect";
5
+ import { fileURLToPath } from "node:url";
3
6
  //#region src/plugins/walrus/bootstrap-assets/cargo-image.ts
4
7
  const WALRUS_CARGO_IMAGE_OVERRIDE_ENV = "WALRUS_CARGO_IMAGE_OVERRIDE";
5
8
  /** Pinned Walrus release whose native binaries are packaged into the
@@ -9,12 +12,25 @@ const DEFAULT_WALRUS_REF = "testnet-v1.49.1";
9
12
  * match the localnet image's sui release. Pinned here in lockstep
10
13
  * with the walrus ref bump. */
11
14
  const DEFAULT_SUI_VERSION = "devnet-v1.71.0";
12
- /** Resolve to the on-disk path of the vendored Dockerfile context. */
13
- const vendoredDockerfileContext = () => new URL("../../../../images/walrus/", import.meta.url).pathname;
15
+ /** Resolve to the on-disk path of the vendored Dockerfile context.
16
+ * `fileURLToPath` normalises the URL host-path conversion across
17
+ * platforms (Windows `file:///C:/...` → `C:\...`; POSIX strips the
18
+ * scheme and percent-decodes). Reading `.pathname` directly would
19
+ * leave the leading `/` on Windows. */
20
+ const vendoredDockerfileContext = () => fileURLToPath(new URL("../../../../images/walrus/", import.meta.url));
14
21
  /** Resolve the walrus image. Path (a) trusts an env-override tag; path
15
- * (b) builds the vendored Dockerfile via `runtime.ensureImage`. */
16
- const resolveCargoImage = (runtime, inputs) => Effect.gen(function* () {
17
- const override = globalThis.process?.env?.[WALRUS_CARGO_IMAGE_OVERRIDE_ENV];
22
+ * (b) builds the vendored Dockerfile via `runtime.ensureImage`.
23
+ *
24
+ * `expectedTag`, when given, is the on-host tag the resolved image is
25
+ * published under (the build itself is content-cached, so multiple distinct
26
+ * tags of the SAME build are cheap re-tags). The storage-node committee uses
27
+ * this to give each node its OWN image tag — otherwise all N nodes share one
28
+ * tag and snapshot-restore's per-container image promote collapses N committed
29
+ * writable layers onto that single tag (last-write-wins), losing N-1 nodes'
30
+ * RocksDB. `ensureImage` reuses an existing tag without rebuilding, so a tag
31
+ * the restore already promoted to a committed layer is preserved on reboot. */
32
+ const resolveCargoImage = (runtime, inputs, expectedTag) => Effect.gen(function* () {
33
+ const override = readEnv(WALRUS_CARGO_IMAGE_OVERRIDE_ENV);
18
34
  if (override && override.length > 0) return {
19
35
  digest: override,
20
36
  tag: override
@@ -25,16 +41,25 @@ const resolveCargoImage = (runtime, inputs) => Effect.gen(function* () {
25
41
  buildArgs: {
26
42
  WALRUS_VERSION: inputs.walrusRef,
27
43
  SUI_VERSION: inputs.suiVersion
44
+ },
45
+ owner: {
46
+ app: inputs.owner.app,
47
+ stack: inputs.owner.stack,
48
+ plugin: "walrus",
49
+ role: "cluster"
28
50
  }
29
51
  };
30
- const built = yield* runtime.ensureImage(buildCtx).pipe(Effect.mapError((cause) => walrusPluginError("image-build", `walrus image build failed (walrusRef=${inputs.walrusRef}, suiVersion=${inputs.suiVersion}): ${cause.reason}: ${cause.detail}`, { cause })));
52
+ const built = yield* runtime.ensureImage(buildCtx, expectedTag !== void 0 ? {
53
+ digest: expectedTag,
54
+ tag: expectedTag
55
+ } : void 0).pipe(Effect.mapError((cause) => walrusPluginError("image-build", `walrus image build failed (walrusRef=${inputs.walrusRef}, suiVersion=${inputs.suiVersion}): ${cause.reason}: ${cause.detail}`, { cause })));
31
56
  return {
32
57
  digest: built.digest,
33
58
  tag: built.tag ?? built.digest
34
59
  };
35
60
  }).pipe(Effect.withSpan("devstack.plugin.walrus.cargoImage.resolve", { attributes: {
36
- "walrus.ref": inputs.walrusRef,
37
- "walrus.suiVersion": inputs.suiVersion
61
+ [WalrusSpans.ref]: inputs.walrusRef,
62
+ [WalrusSpans.suiVersion]: inputs.suiVersion
38
63
  } }));
39
64
  //#endregion
40
65
  export { DEFAULT_SUI_VERSION, DEFAULT_WALRUS_REF, resolveCargoImage };
@@ -1 +1 @@
1
- {"version":3,"file":"cargo-image.mjs","names":[],"sources":["../../../../src/plugins/walrus/bootstrap-assets/cargo-image.ts"],"sourcesContent":["// Walrus vendored Dockerfile-built image.\n//\n// CONTRACT:\n//\n// The vendored Dockerfile packages the upstream Walrus release tarball\n// selected by Docker's TARGETARCH. It never builds Walrus from source:\n// upstream release binaries are the contract, and the Dockerfile verifies\n// the selected asset before the image is accepted.\n//\n// Two staged paths:\n//\n// (a) `WALRUS_CARGO_IMAGE_OVERRIDE` env var — points at a pre-baked\n// registry tag (or a locally `docker build`-tagged image). Skips\n// the runtime build entirely and returns a bare `ImageRef`. The\n// e2e stub test uses this path (`walrus-test-stub:latest`).\n//\n// (b) Vendored Dockerfile build via `runtime.ensureImage()`. The\n// substrate's content-addressed build cache short-circuits when\n// the (context + dockerfile + buildArgs) hash matches a prior result;\n// cold-cache wall-time is the upstream release download.\n//\n// Distilled-doc invariants honored:\n// - 24: `DEFAULT_SUI_VERSION` (the wrapper-baked sui binary) MUST\n// align with the localnet image's sui release.\n// - 25: ubuntu:24.04 base — handled in the Dockerfile.\n\nimport { Effect, type Scope } from 'effect';\n\nimport type { ContainerRuntime, ImageRef } from '../../../contracts/container-runtime.ts';\nimport { walrusPluginError, type WalrusPluginError } from '../errors.ts';\n\nconst WALRUS_CARGO_IMAGE_OVERRIDE_ENV = 'WALRUS_CARGO_IMAGE_OVERRIDE' as const;\n\n/** Pinned Walrus release whose native binaries are packaged into the\n * local-cluster wrapper image. */\nexport const DEFAULT_WALRUS_REF = 'testnet-v1.49.1' as const;\n\n/** Distilled-doc invariant 24: the wrapper-baked sui binary must\n * match the localnet image's sui release. Pinned here in lockstep\n * with the walrus ref bump. */\nexport const DEFAULT_SUI_VERSION = 'devnet-v1.71.0' as const;\n\n/** Inputs to the walrus image resolver. */\nexport interface WalrusCargoImageInputs<Ref extends string = string, SuiV extends string = string> {\n\treadonly walrusRef: Ref;\n\treadonly suiVersion: SuiV;\n}\n\n/** Resolved value — the content-addressed image ref. */\nexport interface WalrusCargoImageResolved {\n\treadonly digest: string;\n\treadonly tag: string;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime resolver\n// ---------------------------------------------------------------------------\n\n/** Resolve to the on-disk path of the vendored Dockerfile context. */\nconst vendoredDockerfileContext = (): string =>\n\tnew URL('../../../../images/walrus/', import.meta.url).pathname;\n\n/** Resolve the walrus image. Path (a) trusts an env-override tag; path\n * (b) builds the vendored Dockerfile via `runtime.ensureImage`. */\nexport const resolveCargoImage = (\n\truntime: ContainerRuntime,\n\tinputs: WalrusCargoImageInputs,\n): Effect.Effect<WalrusCargoImageResolved, WalrusPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst override = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n\t\t\t.process?.env?.[WALRUS_CARGO_IMAGE_OVERRIDE_ENV];\n\t\tif (override && override.length > 0) {\n\t\t\t// Trust-the-tag path. The digest is opaque (substrate's\n\t\t\t// content-addressed cache will re-resolve via `docker inspect`\n\t\t\t// when it materializes the image).\n\t\t\treturn { digest: override, tag: override };\n\t\t}\n\n\t\t// Real build via the vendored Dockerfile.\n\t\tconst buildCtx = {\n\t\t\tcontextPath: vendoredDockerfileContext(),\n\t\t\tdockerfile: 'Dockerfile',\n\t\t\tbuildArgs: {\n\t\t\t\tWALRUS_VERSION: inputs.walrusRef,\n\t\t\t\tSUI_VERSION: inputs.suiVersion,\n\t\t\t},\n\t\t};\n\n\t\tconst built = yield* runtime\n\t\t\t.ensureImage(buildCtx)\n\t\t\t.pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t'image-build',\n\t\t\t\t\t\t`walrus image build failed (walrusRef=${inputs.walrusRef}, suiVersion=${inputs.suiVersion}): ${cause.reason}: ${cause.detail}`,\n\t\t\t\t\t\t{ cause },\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\treturn { digest: built.digest, tag: built.tag ?? built.digest };\n\t}).pipe(\n\t\tEffect.withSpan('devstack.plugin.walrus.cargoImage.resolve', {\n\t\t\tattributes: {\n\t\t\t\t'walrus.ref': inputs.walrusRef,\n\t\t\t\t'walrus.suiVersion': inputs.suiVersion,\n\t\t\t},\n\t\t}),\n\t);\n\n/** Convenience: resolve via the default inputs. */\nexport const resolveDefaultCargoImage = (\n\truntime: ContainerRuntime,\n): Effect.Effect<ImageRef, WalrusPluginError, Scope.Scope> =>\n\tresolveCargoImage(runtime, {\n\t\twalrusRef: DEFAULT_WALRUS_REF,\n\t\tsuiVersion: DEFAULT_SUI_VERSION,\n\t});\n"],"mappings":";;;AA+BA,MAAM,kCAAkC;;;AAIxC,MAAa,qBAAqB;;;;AAKlC,MAAa,sBAAsB;;AAmBnC,MAAM,kCACL,IAAI,IAAI,8BAA8B,OAAO,KAAK,IAAI,CAAC;;;AAIxD,MAAa,qBACZ,SACA,WAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAY,WAChB,SAAS,MAAM;AACjB,KAAI,YAAY,SAAS,SAAS,EAIjC,QAAO;EAAE,QAAQ;EAAU,KAAK;EAAU;CAI3C,MAAM,WAAW;EAChB,aAAa,2BAA2B;EACxC,YAAY;EACZ,WAAW;GACV,gBAAgB,OAAO;GACvB,aAAa,OAAO;GACpB;EACD;CAED,MAAM,QAAQ,OAAO,QACnB,YAAY,SAAS,CACrB,KACA,OAAO,UAAU,UAChB,kBACC,eACA,wCAAwC,OAAO,UAAU,eAAe,OAAO,WAAW,KAAK,MAAM,OAAO,IAAI,MAAM,UACtH,EAAE,OAAO,CACT,CACD,CACD;AACF,QAAO;EAAE,QAAQ,MAAM;EAAQ,KAAK,MAAM,OAAO,MAAM;EAAQ;EAC9D,CAAC,KACF,OAAO,SAAS,6CAA6C,EAC5D,YAAY;CACX,cAAc,OAAO;CACrB,qBAAqB,OAAO;CAC5B,EACD,CAAC,CACF"}
1
+ {"version":3,"file":"cargo-image.mjs","names":[],"sources":["../../../../src/plugins/walrus/bootstrap-assets/cargo-image.ts"],"sourcesContent":["// Walrus vendored Dockerfile-built image.\n//\n// CONTRACT:\n//\n// The vendored Dockerfile packages the upstream Walrus release tarball\n// selected by Docker's TARGETARCH. It never builds Walrus from source:\n// upstream release binaries are the contract, and the Dockerfile verifies\n// the selected asset before the image is accepted.\n//\n// Two staged paths:\n//\n// (a) `WALRUS_CARGO_IMAGE_OVERRIDE` env var — points at a pre-baked\n// registry tag (or a locally `docker build`-tagged image). Skips\n// the runtime build entirely and returns a bare `ImageRef`. The\n// e2e stub test uses this path (`walrus-test-stub:latest`).\n//\n// (b) Vendored Dockerfile build via `runtime.ensureImage()`. The\n// substrate's content-addressed build cache short-circuits when\n// the (context + dockerfile + buildArgs) hash matches a prior result;\n// cold-cache wall-time is the upstream release download.\n//\n// Distilled-doc invariants honored:\n// - 24: `DEFAULT_SUI_VERSION` (the wrapper-baked sui binary) MUST\n// align with the localnet image's sui release.\n// - 25: ubuntu:24.04 base — handled in the Dockerfile.\n\nimport { Effect, type Scope } from 'effect';\nimport { fileURLToPath } from 'node:url';\n\nimport type { ContainerRuntime } from '../../../contracts/container-runtime.ts';\nimport { readEnv } from '../../../substrate/runtime/typed-env.ts';\nimport { walrusPluginError, type WalrusPluginError } from '../errors.ts';\nimport { WalrusSpans } from '../spans.ts';\n\nconst WALRUS_CARGO_IMAGE_OVERRIDE_ENV = 'WALRUS_CARGO_IMAGE_OVERRIDE' as const;\n\n/** Pinned Walrus release whose native binaries are packaged into the\n * local-cluster wrapper image. */\nexport const DEFAULT_WALRUS_REF = 'testnet-v1.49.1' as const;\n\n/** Distilled-doc invariant 24: the wrapper-baked sui binary must\n * match the localnet image's sui release. Pinned here in lockstep\n * with the walrus ref bump. */\nexport const DEFAULT_SUI_VERSION = 'devnet-v1.71.0' as const;\n\n/** Inputs to the walrus image resolver. */\nexport interface WalrusCargoImageInputs<Ref extends string = string, SuiV extends string = string> {\n\treadonly walrusRef: Ref;\n\treadonly suiVersion: SuiV;\n\t/** Owner identity stamped on the built image so label-driven prune\n\t * finds it. */\n\treadonly owner: {\n\t\treadonly app: string;\n\t\treadonly stack: string;\n\t};\n}\n\n/** Resolved value — the content-addressed image ref. */\nexport interface WalrusCargoImageResolved {\n\treadonly digest: string;\n\treadonly tag: string;\n}\n\n// ---------------------------------------------------------------------------\n// Runtime resolver\n// ---------------------------------------------------------------------------\n\n/** Resolve to the on-disk path of the vendored Dockerfile context.\n * `fileURLToPath` normalises the URL → host-path conversion across\n * platforms (Windows `file:///C:/...` → `C:\\...`; POSIX strips the\n * scheme and percent-decodes). Reading `.pathname` directly would\n * leave the leading `/` on Windows. */\nconst vendoredDockerfileContext = (): string =>\n\tfileURLToPath(new URL('../../../../images/walrus/', import.meta.url));\n\n/** Resolve the walrus image. Path (a) trusts an env-override tag; path\n * (b) builds the vendored Dockerfile via `runtime.ensureImage`.\n *\n * `expectedTag`, when given, is the on-host tag the resolved image is\n * published under (the build itself is content-cached, so multiple distinct\n * tags of the SAME build are cheap re-tags). The storage-node committee uses\n * this to give each node its OWN image tag — otherwise all N nodes share one\n * tag and snapshot-restore's per-container image promote collapses N committed\n * writable layers onto that single tag (last-write-wins), losing N-1 nodes'\n * RocksDB. `ensureImage` reuses an existing tag without rebuilding, so a tag\n * the restore already promoted to a committed layer is preserved on reboot. */\nexport const resolveCargoImage = (\n\truntime: ContainerRuntime,\n\tinputs: WalrusCargoImageInputs,\n\texpectedTag?: string,\n): Effect.Effect<WalrusCargoImageResolved, WalrusPluginError, Scope.Scope> =>\n\tEffect.gen(function* () {\n\t\tconst override = readEnv(WALRUS_CARGO_IMAGE_OVERRIDE_ENV);\n\t\tif (override && override.length > 0) {\n\t\t\t// Trust-the-tag path. The digest is opaque (substrate's\n\t\t\t// content-addressed cache will re-resolve via `docker inspect`\n\t\t\t// when it materializes the image). The per-node `expectedTag`\n\t\t\t// aliasing is build-path only (it relies on `ensureImage`'s\n\t\t\t// reuse-if-tag-exists to stay restore-safe across reboots);\n\t\t\t// override images keep the shared tag, so override + multi-node +\n\t\t\t// snapshot/restore is not committee-distinct (a documented niche —\n\t\t\t// the e2e matrix forces the build path).\n\t\t\treturn { digest: override, tag: override };\n\t\t}\n\n\t\t// Real build via the vendored Dockerfile.\n\t\tconst buildCtx = {\n\t\t\tcontextPath: vendoredDockerfileContext(),\n\t\t\tdockerfile: 'Dockerfile',\n\t\t\tbuildArgs: {\n\t\t\t\tWALRUS_VERSION: inputs.walrusRef,\n\t\t\t\tSUI_VERSION: inputs.suiVersion,\n\t\t\t},\n\t\t\towner: {\n\t\t\t\tapp: inputs.owner.app,\n\t\t\t\tstack: inputs.owner.stack,\n\t\t\t\tplugin: 'walrus',\n\t\t\t\trole: 'cluster',\n\t\t\t},\n\t\t};\n\n\t\t// `expected.tag`, when present, is the on-host tag ensureImage publishes\n\t\t// (and REUSES without rebuilding if it already exists — so a per-node tag\n\t\t// the restore promoted to a committed layer survives reboot). The build\n\t\t// itself is content-cached, so N per-node tags of one build are cheap.\n\t\tconst built = yield* runtime\n\t\t\t.ensureImage(\n\t\t\t\tbuildCtx,\n\t\t\t\texpectedTag !== undefined ? { digest: expectedTag, tag: expectedTag } : undefined,\n\t\t\t)\n\t\t\t.pipe(\n\t\t\t\tEffect.mapError((cause) =>\n\t\t\t\t\twalrusPluginError(\n\t\t\t\t\t\t'image-build',\n\t\t\t\t\t\t`walrus image build failed (walrusRef=${inputs.walrusRef}, suiVersion=${inputs.suiVersion}): ${cause.reason}: ${cause.detail}`,\n\t\t\t\t\t\t{ cause },\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\treturn { digest: built.digest, tag: built.tag ?? built.digest };\n\t}).pipe(\n\t\tEffect.withSpan('devstack.plugin.walrus.cargoImage.resolve', {\n\t\t\tattributes: {\n\t\t\t\t[WalrusSpans.ref]: inputs.walrusRef,\n\t\t\t\t[WalrusSpans.suiVersion]: inputs.suiVersion,\n\t\t\t},\n\t\t}),\n\t);\n"],"mappings":";;;;;;AAkCA,MAAM,kCAAkC;;;AAIxC,MAAa,qBAAqB;;;;AAKlC,MAAa,sBAAsB;;;;;;AA6BnC,MAAM,kCACL,cAAc,IAAI,IAAI,8BAA8B,OAAO,KAAK,IAAI,CAAC;;;;;;;;;;;;AAatE,MAAa,qBACZ,SACA,QACA,gBAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,QAAQ,gCAAgC;AACzD,KAAI,YAAY,SAAS,SAAS,EASjC,QAAO;EAAE,QAAQ;EAAU,KAAK;EAAU;CAI3C,MAAM,WAAW;EAChB,aAAa,2BAA2B;EACxC,YAAY;EACZ,WAAW;GACV,gBAAgB,OAAO;GACvB,aAAa,OAAO;GACpB;EACD,OAAO;GACN,KAAK,OAAO,MAAM;GAClB,OAAO,OAAO,MAAM;GACpB,QAAQ;GACR,MAAM;GACN;EACD;CAMD,MAAM,QAAQ,OAAO,QACnB,YACA,UACA,gBAAgB,KAAA,IAAY;EAAE,QAAQ;EAAa,KAAK;EAAa,GAAG,KAAA,EACxE,CACA,KACA,OAAO,UAAU,UAChB,kBACC,eACA,wCAAwC,OAAO,UAAU,eAAe,OAAO,WAAW,KAAK,MAAM,OAAO,IAAI,MAAM,UACtH,EAAE,OAAO,CACT,CACD,CACD;AACF,QAAO;EAAE,QAAQ,MAAM;EAAQ,KAAK,MAAM,OAAO,MAAM;EAAQ;EAC9D,CAAC,KACF,OAAO,SAAS,6CAA6C,EAC5D,YAAY;EACV,YAAY,MAAM,OAAO;EACzB,YAAY,aAAa,OAAO;CACjC,EACD,CAAC,CACF"}
@@ -21,9 +21,9 @@ interface WalrusBindings {
21
21
  readonly stakingPoolId: string;
22
22
  readonly exchangeIds?: ReadonlyArray<string>;
23
23
  };
24
- /** HTTP URLs — present iff the publishing mode populated them
25
- * (local always; known only when all three are non-empty
26
- * distilled-doc invariant 15). */
24
+ /** HTTP URLs — each is `null` only when that specific URL is
25
+ * unresolved (local resolves all three; known surfaces each
26
+ * independently — distilled-doc invariant 15). */
27
27
  readonly proxyUrl: string | null;
28
28
  readonly aggregatorUrl: string | null;
29
29
  readonly publisherUrl: string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/walrus/codegen.ts"],"sourcesContent":["// Walrus plugin — Codegenable contribution.\n//\n// Architecture §6: plugins emit typed `CodegenableDecl`s; the codegen\n// orchestrator stages files into the user's source tree WITHOUT\n// naming the plugin. Walrus's contribution is the SDK-ready\n// `packageConfig` shape that the `@mysten/walrus` SDK consumes —\n// `{systemObjectId, stakingPoolId, exchangeIds}` — plus the proxy /\n// aggregator / publisher URLs for HTTP consumers.\n//\n// Distilled-doc opportunity #8: the v3 `localnetWalrusOptions` couples\n// to a free-form cast. We surface the bindings here as a typed\n// `WalrusBindings` shape so downstream code can `import { walrus }`\n// from the codegen-staged file and get a fully-typed handle.\n//\n// Mode-asymmetric emission:\n// - Local: emits `{packageConfig, proxyUrl, aggregatorUrl,\n// publisherUrl, nodes}` — full shape.\n// - Known: emits `{packageConfig, proxyUrl?, aggregatorUrl?,\n// publisherUrl?}` the proxy URLs are optional because the\n// contract only publishes them when ALL three are present\n// (distilled-doc invariant 15).\n\nimport { Effect } from 'effect';\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\n/** Per-node descriptor — matches the storage-nodes module's shape\n * for the local-cluster path, and is empty (or whatever the user\n * provides) for known. */\nexport interface WalrusNodeBinding {\n\treadonly nodeIndex: number;\n\treadonly publicHostname: string;\n\treadonly rpcUrl: string;\n}\n\n/** The typed shape the emitted file exports. */\nexport interface WalrusBindings {\n\treadonly mode: 'local' | 'known';\n\treadonly chain: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\t/** SDK-ready `packageConfig` — structurally compatible with\n\t * `@mysten/walrus`'s `WalrusPackageConfig`. */\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\t/** HTTP URLs — present iff the publishing mode populated them\n\t * (local always; known only when all three are non-empty —\n\t * distilled-doc invariant 15). */\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\t/** Storage-node committee — local publishes N descriptors; known\n\t * publishes user-supplied nodes (testnet/mainnet's 100+ nodes\n\t * are fetched dynamically by the SDK, not pinned here —\n\t * distilled-doc invariant 16). */\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** Inputs to the codegen contribution — supplied at acquire-time.\n * The codegen orchestrator's resolve-once memo picks up the real\n * values via the dynamic capability factory (same shape used by\n * Sui + Account: the factory stamps placeholders at compose-time\n * and the post-acquire factory restamps with resolved fields). */\nexport interface MakeCodegenableInputs {\n\treadonly mode: 'local' | 'known';\n\treadonly chain: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\treadonly systemObjectId: string;\n\treadonly stakingPoolId: string;\n\treadonly exchangeIds: ReadonlyArray<string>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** Construct the Codegenable contribution. Emit is byte-deterministic\n * on unchanged input (architecture: no mtime churn on no-op\n * cycles). */\nexport const makeCodegenable = (\n\tinputs: MakeCodegenableInputs,\n): CodegenableDecl<'walrus-network'> => ({\n\tkind: 'codegenable',\n\temitterName: 'walrus-network',\n\toutputPath: 'walrus/network.ts',\n\temit: (ctx) =>\n\t\tEffect.sync(() => {\n\t\t\tconst bindings: WalrusBindings = {\n\t\t\t\tmode: inputs.mode,\n\t\t\t\tchain: inputs.chain,\n\t\t\t\twalrusPackageId: inputs.walrusPackageId,\n\t\t\t\twalPackageId: inputs.walPackageId,\n\t\t\t\twalCoinType: inputs.walCoinType,\n\t\t\t\tpackageConfig: {\n\t\t\t\t\tsystemObjectId: inputs.systemObjectId,\n\t\t\t\t\tstakingPoolId: inputs.stakingPoolId,\n\t\t\t\t\texchangeIds: inputs.exchangeIds.length > 0 ? inputs.exchangeIds : undefined,\n\t\t\t\t},\n\t\t\t\tproxyUrl: inputs.proxyUrl,\n\t\t\t\taggregatorUrl: inputs.aggregatorUrl,\n\t\t\t\tpublisherUrl: inputs.publisherUrl,\n\t\t\t\tnodes: inputs.nodes,\n\t\t\t};\n\t\t\tctx.exportConst('walrus', bindings);\n\t\t\treturn ctx.done();\n\t\t}),\n});\n"],"mappings":";;;;;AAqFA,MAAa,mBACZ,YACwC;CACxC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,OAAO,QACN,OAAO,WAAW;EACjB,MAAM,WAA2B;GAChC,MAAM,OAAO;GACb,OAAO,OAAO;GACd,iBAAiB,OAAO;GACxB,cAAc,OAAO;GACrB,aAAa,OAAO;GACpB,eAAe;IACd,gBAAgB,OAAO;IACvB,eAAe,OAAO;IACtB,aAAa,OAAO,YAAY,SAAS,IAAI,OAAO,cAAc,KAAA;IAClE;GACD,UAAU,OAAO;GACjB,eAAe,OAAO;GACtB,cAAc,OAAO;GACrB,OAAO,OAAO;GACd;AACD,MAAI,YAAY,UAAU,SAAS;AACnC,SAAO,IAAI,MAAM;GAChB;CACH"}
1
+ {"version":3,"file":"codegen.mjs","names":[],"sources":["../../../src/plugins/walrus/codegen.ts"],"sourcesContent":["// Walrus plugin — Codegenable contribution.\n//\n// Architecture §6: plugins emit typed `CodegenableDecl`s; the codegen\n// orchestrator stages files into the user's source tree WITHOUT\n// naming the plugin. Walrus's contribution is the SDK-ready\n// `packageConfig` shape that the `@mysten/walrus` SDK consumes —\n// `{systemObjectId, stakingPoolId, exchangeIds}` — plus the proxy /\n// aggregator / publisher URLs for HTTP consumers.\n//\n// Distilled-doc opportunity #8: the v3 `localnetWalrusOptions` couples\n// to a free-form cast. We surface the bindings here as a typed\n// `WalrusBindings` shape so downstream code can `import { walrus }`\n// from the codegen-staged file and get a fully-typed handle.\n//\n// Mode-asymmetric emission:\n// - Local: emits `{packageConfig, proxyUrl, aggregatorUrl,\n// publisherUrl, nodes}` — full shape (all three URLs resolved).\n// - Known: emits the same shape; each of `proxyUrl /\n// aggregatorUrl / publisherUrl` is `string | null` and surfaces\n// INDEPENDENTLY a field is null only when THAT specific URL is\n// unresolved (distilled-doc invariant 15). A missing publisher\n// URL no longer suppresses an available proxy/aggregator URL.\n\nimport { Effect } from 'effect';\n\nimport type { CodegenableDecl } from '../../contracts/codegenable.ts';\n\n/** Per-node descriptor — matches the storage-nodes module's shape\n * for the local-cluster path, and is empty (or whatever the user\n * provides) for known. */\nexport interface WalrusNodeBinding {\n\treadonly nodeIndex: number;\n\treadonly publicHostname: string;\n\treadonly rpcUrl: string;\n}\n\n/** The typed shape the emitted file exports. */\nexport interface WalrusBindings {\n\treadonly mode: 'local' | 'known';\n\treadonly chain: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\t/** SDK-ready `packageConfig` — structurally compatible with\n\t * `@mysten/walrus`'s `WalrusPackageConfig`. */\n\treadonly packageConfig: {\n\t\treadonly systemObjectId: string;\n\t\treadonly stakingPoolId: string;\n\t\treadonly exchangeIds?: ReadonlyArray<string>;\n\t};\n\t/** HTTP URLs — each is `null` only when that specific URL is\n\t * unresolved (local resolves all three; known surfaces each\n\t * independently — distilled-doc invariant 15). */\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\t/** Storage-node committee — local publishes N descriptors; known\n\t * publishes user-supplied nodes (testnet/mainnet's 100+ nodes\n\t * are fetched dynamically by the SDK, not pinned here —\n\t * distilled-doc invariant 16). */\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** Inputs to the codegen contribution — supplied at acquire-time.\n * The codegen orchestrator's resolve-once memo picks up the real\n * values via the dynamic capability factory (same shape used by\n * Sui + Account: the factory stamps placeholders at compose-time\n * and the post-acquire factory restamps with resolved fields). */\nexport interface MakeCodegenableInputs {\n\treadonly mode: 'local' | 'known';\n\treadonly chain: string;\n\treadonly walrusPackageId: string | null;\n\treadonly walPackageId: string | null;\n\treadonly walCoinType: string | null;\n\treadonly systemObjectId: string;\n\treadonly stakingPoolId: string;\n\treadonly exchangeIds: ReadonlyArray<string>;\n\treadonly proxyUrl: string | null;\n\treadonly aggregatorUrl: string | null;\n\treadonly publisherUrl: string | null;\n\treadonly nodes: ReadonlyArray<WalrusNodeBinding>;\n}\n\n/** Construct the Codegenable contribution. Emit is byte-deterministic\n * on unchanged input (architecture: no mtime churn on no-op\n * cycles). */\nexport const makeCodegenable = (\n\tinputs: MakeCodegenableInputs,\n): CodegenableDecl<'walrus-network'> => ({\n\tkind: 'codegenable',\n\temitterName: 'walrus-network',\n\toutputPath: 'walrus/network.ts',\n\temit: (ctx) =>\n\t\tEffect.sync(() => {\n\t\t\tconst bindings: WalrusBindings = {\n\t\t\t\tmode: inputs.mode,\n\t\t\t\tchain: inputs.chain,\n\t\t\t\twalrusPackageId: inputs.walrusPackageId,\n\t\t\t\twalPackageId: inputs.walPackageId,\n\t\t\t\twalCoinType: inputs.walCoinType,\n\t\t\t\tpackageConfig: {\n\t\t\t\t\tsystemObjectId: inputs.systemObjectId,\n\t\t\t\t\tstakingPoolId: inputs.stakingPoolId,\n\t\t\t\t\texchangeIds: inputs.exchangeIds.length > 0 ? inputs.exchangeIds : undefined,\n\t\t\t\t},\n\t\t\t\tproxyUrl: inputs.proxyUrl,\n\t\t\t\taggregatorUrl: inputs.aggregatorUrl,\n\t\t\t\tpublisherUrl: inputs.publisherUrl,\n\t\t\t\tnodes: inputs.nodes,\n\t\t\t};\n\t\t\tctx.exportConst('walrus', bindings);\n\t\t\treturn ctx.done();\n\t\t}),\n});\n"],"mappings":";;;;;AAsFA,MAAa,mBACZ,YACwC;CACxC,MAAM;CACN,aAAa;CACb,YAAY;CACZ,OAAO,QACN,OAAO,WAAW;EACjB,MAAM,WAA2B;GAChC,MAAM,OAAO;GACb,OAAO,OAAO;GACd,iBAAiB,OAAO;GACxB,cAAc,OAAO;GACrB,aAAa,OAAO;GACpB,eAAe;IACd,gBAAgB,OAAO;IACvB,eAAe,OAAO;IACtB,aAAa,OAAO,YAAY,SAAS,IAAI,OAAO,cAAc,KAAA;IAClE;GACD,UAAU,OAAO;GACjB,eAAe,OAAO;GACtB,cAAc,OAAO;GACrB,OAAO,OAAO;GACd;AACD,MAAI,YAAY,UAAU,SAAS;AACnC,SAAO,IAAI,MAAM;GAChB;CACH"}
@@ -1,11 +1,27 @@
1
- import { dirname, join, relative } from "node:path";
1
+ import { join, relative } from "node:path";
2
2
  //#region src/plugins/walrus/deploy-paths.ts
3
- const walrusDeployMountPaths = (deployOutputDirHostPath, mountTarget) => {
4
- const sourceHostPath = dirname(dirname(dirname(deployOutputDirHostPath)));
3
+ /** Compute the bind-mount triple shared by the walrus deploy
4
+ * one-shot + storage nodes.
5
+ *
6
+ * Previous implementation walked up exactly three levels with
7
+ * `dirname(dirname(dirname(deployOutputDirHostPath)))`. A shorter
8
+ * input (e.g. a single-segment path under `/`) would silently
9
+ * return `/`, and the bind-mount would mount the host root into
10
+ * the container — a load-bearing footgun. We now require the
11
+ * caller to pass `stackRoot` explicitly (matching the existing
12
+ * `paths.stackRoot` injection pattern across the postgres / sui /
13
+ * seal / wallet plugins) and assert the deploy dir lives under it.
14
+ *
15
+ * The mount source is `stackRoot`; the in-container path is
16
+ * re-derived through `relative(stackRoot, deployOutputDirHostPath)`. */
17
+ const walrusDeployMountPaths = (input) => {
18
+ const { stackRoot, deployOutputDirHostPath, mountTarget } = input;
19
+ const rel = relative(stackRoot, deployOutputDirHostPath);
20
+ if (rel.length === 0 || rel.startsWith("..")) throw new Error(`walrusDeployMountPaths: deployOutputDirHostPath (${deployOutputDirHostPath}) must be a descendant of stackRoot (${stackRoot}). Cross-check the StackPathsService thread + the deploy directory layout.`);
5
21
  return {
6
- sourceHostPath,
22
+ sourceHostPath: stackRoot,
7
23
  mountTarget,
8
- outputDirInContainer: join(mountTarget, relative(sourceHostPath, deployOutputDirHostPath))
24
+ outputDirInContainer: join(mountTarget, rel)
9
25
  };
10
26
  };
11
27
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"deploy-paths.mjs","names":[],"sources":["../../../src/plugins/walrus/deploy-paths.ts"],"sourcesContent":["import { dirname, join, relative } from 'node:path';\n\nexport interface WalrusDeployMountPaths {\n\treadonly sourceHostPath: string;\n\treadonly mountTarget: string;\n\treadonly outputDirInContainer: string;\n}\n\nexport const walrusDeployMountPaths = (\n\tdeployOutputDirHostPath: string,\n\tmountTarget: string,\n): WalrusDeployMountPaths => {\n\tconst sourceHostPath = dirname(dirname(dirname(deployOutputDirHostPath)));\n\tconst outputDirInContainer = join(mountTarget, relative(sourceHostPath, deployOutputDirHostPath));\n\treturn { sourceHostPath, mountTarget, outputDirInContainer };\n};\n"],"mappings":";;AAQA,MAAa,0BACZ,yBACA,gBAC4B;CAC5B,MAAM,iBAAiB,QAAQ,QAAQ,QAAQ,wBAAwB,CAAC,CAAC;AAEzE,QAAO;EAAE;EAAgB;EAAa,sBADT,KAAK,aAAa,SAAS,gBAAgB,wBAAwB,CACtC;EAAE"}
1
+ {"version":3,"file":"deploy-paths.mjs","names":[],"sources":["../../../src/plugins/walrus/deploy-paths.ts"],"sourcesContent":["import { join, relative } from 'node:path';\n\nexport interface WalrusDeployMountPaths {\n\treadonly sourceHostPath: string;\n\treadonly mountTarget: string;\n\treadonly outputDirInContainer: string;\n}\n\n/** Compute the bind-mount triple shared by the walrus deploy\n * one-shot + storage nodes.\n *\n * Previous implementation walked up exactly three levels with\n * `dirname(dirname(dirname(deployOutputDirHostPath)))`. A shorter\n * input (e.g. a single-segment path under `/`) would silently\n * return `/`, and the bind-mount would mount the host root into\n * the container — a load-bearing footgun. We now require the\n * caller to pass `stackRoot` explicitly (matching the existing\n * `paths.stackRoot` injection pattern across the postgres / sui /\n * seal / wallet plugins) and assert the deploy dir lives under it.\n *\n * The mount source is `stackRoot`; the in-container path is\n * re-derived through `relative(stackRoot, deployOutputDirHostPath)`. */\nexport const walrusDeployMountPaths = (input: {\n\treadonly stackRoot: string;\n\treadonly deployOutputDirHostPath: string;\n\treadonly mountTarget: string;\n}): WalrusDeployMountPaths => {\n\tconst { stackRoot, deployOutputDirHostPath, mountTarget } = input;\n\tconst rel = relative(stackRoot, deployOutputDirHostPath);\n\tif (rel.length === 0 || rel.startsWith('..')) {\n\t\tthrow new Error(\n\t\t\t`walrusDeployMountPaths: deployOutputDirHostPath (${deployOutputDirHostPath}) ` +\n\t\t\t\t`must be a descendant of stackRoot (${stackRoot}). ` +\n\t\t\t\t`Cross-check the StackPathsService thread + the deploy directory layout.`,\n\t\t);\n\t}\n\tconst outputDirInContainer = join(mountTarget, rel);\n\treturn { sourceHostPath: stackRoot, mountTarget, outputDirInContainer };\n};\n"],"mappings":";;;;;;;;;;;;;;;;AAsBA,MAAa,0BAA0B,UAIT;CAC7B,MAAM,EAAE,WAAW,yBAAyB,gBAAgB;CAC5D,MAAM,MAAM,SAAS,WAAW,wBAAwB;AACxD,KAAI,IAAI,WAAW,KAAK,IAAI,WAAW,KAAK,CAC3C,OAAM,IAAI,MACT,oDAAoD,wBAAwB,uCACrC,UAAU,4EAEjD;AAGF,QAAO;EAAE,gBAAgB;EAAW;EAAa,sBADpB,KAAK,aAAa,IACsB;EAAE"}