@mysten-incubation/devstack 0.1.0 → 0.2.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 (858) hide show
  1. package/README.md +7 -4
  2. package/dashboard-ui/assets/grpc-a4usE3Nk.js +3 -0
  3. package/dashboard-ui/assets/index-B82Bh84P.css +1 -0
  4. package/dashboard-ui/assets/index-CRYJ4pru.js +1277 -0
  5. package/dashboard-ui/index.html +2 -2
  6. package/dist/api/define-capabilities.d.mts +1 -20
  7. package/dist/api/define-capabilities.mjs +15 -22
  8. package/dist/api/define-capabilities.mjs.map +1 -1
  9. package/dist/api/define-devstack-with.d.mts +7 -1
  10. package/dist/api/define-devstack-with.mjs.map +1 -1
  11. package/dist/api/define-devstack.mjs.map +1 -1
  12. package/dist/api/define-plugin.d.mts +2 -1
  13. package/dist/api/inference-network.d.mts +31 -17
  14. package/dist/api/inference-network.mjs +40 -43
  15. package/dist/api/inference-network.mjs.map +1 -1
  16. package/dist/api/load-config.mjs +88 -0
  17. package/dist/api/load-config.mjs.map +1 -0
  18. package/dist/api/mode-narrowed-factory.d.mts +1 -1
  19. package/dist/api/mode-narrowed-factory.mjs.map +1 -1
  20. package/dist/api/run-stack-internal.mjs +202 -0
  21. package/dist/api/run-stack-internal.mjs.map +1 -0
  22. package/dist/api/run-stack.d.mts +82 -31
  23. package/dist/api/run-stack.mjs +22 -141
  24. package/dist/api/run-stack.mjs.map +1 -1
  25. package/dist/build-integrations/dapp-kit/index.d.mts +61 -0
  26. package/dist/build-integrations/dapp-kit/index.mjs +58 -0
  27. package/dist/build-integrations/dapp-kit/index.mjs.map +1 -0
  28. package/dist/build-integrations/playwright/config.d.mts +1 -30
  29. package/dist/build-integrations/playwright/config.mjs +1 -27
  30. package/dist/build-integrations/playwright/config.mjs.map +1 -1
  31. package/dist/build-integrations/playwright/errors.d.mts +4 -7
  32. package/dist/build-integrations/playwright/errors.mjs.map +1 -1
  33. package/dist/build-integrations/playwright/global-setup.d.mts +42 -71
  34. package/dist/build-integrations/playwright/global-setup.mjs +142 -218
  35. package/dist/build-integrations/playwright/global-setup.mjs.map +1 -1
  36. package/dist/build-integrations/playwright/index.d.mts +2 -2
  37. package/dist/build-integrations/playwright/index.mjs +2 -2
  38. package/dist/build-integrations/playwright/stack-context.d.mts +3 -1
  39. package/dist/build-integrations/playwright/stack-context.mjs +4 -2
  40. package/dist/build-integrations/playwright/stack-context.mjs.map +1 -1
  41. package/dist/build-integrations/playwright/wallet-context.mjs +44 -19
  42. package/dist/build-integrations/playwright/wallet-context.mjs.map +1 -1
  43. package/dist/build-integrations/runtime/cold-start-url.mjs.map +1 -1
  44. package/dist/build-integrations/runtime/conventional-routes.mjs.map +1 -1
  45. package/dist/build-integrations/runtime/dapp-kit-slot.mjs.map +1 -1
  46. package/dist/build-integrations/runtime/discover.d.mts +6 -4
  47. package/dist/build-integrations/runtime/discover.mjs +15 -4
  48. package/dist/build-integrations/runtime/discover.mjs.map +1 -1
  49. package/dist/build-integrations/runtime/endpoint-registry.mjs.map +1 -1
  50. package/dist/build-integrations/runtime/errors.mjs.map +1 -1
  51. package/dist/build-integrations/runtime/read-stack-context.mjs +3 -3
  52. package/dist/build-integrations/runtime/read-stack-context.mjs.map +1 -1
  53. package/dist/build-integrations/runtime/resolve-discovery-env.d.mts +23 -7
  54. package/dist/build-integrations/runtime/resolve-discovery-env.mjs +25 -12
  55. package/dist/build-integrations/runtime/resolve-discovery-env.mjs.map +1 -1
  56. package/dist/build-integrations/runtime/stack-context.d.mts +2 -2
  57. package/dist/build-integrations/vite/index.d.mts +84 -19
  58. package/dist/build-integrations/vite/index.mjs +203 -28
  59. package/dist/build-integrations/vite/index.mjs.map +1 -1
  60. package/dist/build-integrations/vitest/config.d.mts +20 -2
  61. package/dist/build-integrations/vitest/config.mjs +16 -4
  62. package/dist/build-integrations/vitest/config.mjs.map +1 -1
  63. package/dist/build-integrations/vitest/env.d.mts +18 -10
  64. package/dist/build-integrations/vitest/env.mjs +11 -10
  65. package/dist/build-integrations/vitest/env.mjs.map +1 -1
  66. package/dist/build-integrations/vitest/errors.d.mts +7 -11
  67. package/dist/build-integrations/vitest/errors.mjs +3 -4
  68. package/dist/build-integrations/vitest/errors.mjs.map +1 -1
  69. package/dist/build-integrations/vitest/global-setup.d.mts +35 -0
  70. package/dist/build-integrations/vitest/global-setup.mjs +156 -0
  71. package/dist/build-integrations/vitest/global-setup.mjs.map +1 -0
  72. package/dist/build-integrations/vitest/index.d.mts +3 -2
  73. package/dist/build-integrations/vitest/index.mjs +3 -2
  74. package/dist/build-integrations/vitest/setup.mjs +2 -2
  75. package/dist/build-integrations/vitest/setup.mjs.map +1 -1
  76. package/dist/build-integrations/vitest/stack-context.d.mts +5 -3
  77. package/dist/build-integrations/vitest/stack-context.mjs +3 -2
  78. package/dist/build-integrations/vitest/stack-context.mjs.map +1 -1
  79. package/dist/cli/bail.mjs.map +1 -1
  80. package/dist/cli/doctor-probes.mjs +1 -3
  81. package/dist/cli/doctor-probes.mjs.map +1 -1
  82. package/dist/cli/main.d.mts +23 -1
  83. package/dist/cli/main.mjs +63 -8
  84. package/dist/cli/main.mjs.map +1 -1
  85. package/dist/cli/prune-direct.mjs.map +1 -1
  86. package/dist/cli/snapshot-reader.mjs.map +1 -1
  87. package/dist/cli/up-lifecycle.mjs.map +1 -1
  88. package/dist/cli/wirings/apply.mjs +28 -21
  89. package/dist/cli/wirings/apply.mjs.map +1 -1
  90. package/dist/cli/wirings/codegen.mjs +108 -0
  91. package/dist/cli/wirings/codegen.mjs.map +1 -0
  92. package/dist/cli/wirings/config-loader.mjs +12 -62
  93. package/dist/cli/wirings/config-loader.mjs.map +1 -1
  94. package/dist/cli/wirings/dump-ids.mjs +86 -0
  95. package/dist/cli/wirings/dump-ids.mjs.map +1 -0
  96. package/dist/cli/wirings/engine-command.mjs +1 -1
  97. package/dist/cli/wirings/engine-command.mjs.map +1 -1
  98. package/dist/cli/wirings/identity.mjs +2 -2
  99. package/dist/cli/wirings/identity.mjs.map +1 -1
  100. package/dist/cli/wirings/provide-file-system.mjs.map +1 -1
  101. package/dist/cli/wirings/read-devstack-version.mjs +17 -0
  102. package/dist/cli/wirings/read-devstack-version.mjs.map +1 -0
  103. package/dist/cli/wirings/snapshot.mjs +27 -34
  104. package/dist/cli/wirings/snapshot.mjs.map +1 -1
  105. package/dist/cli/wirings/up-ipc.mjs +147 -0
  106. package/dist/cli/wirings/up-ipc.mjs.map +1 -0
  107. package/dist/cli/wirings/up.mjs +218 -198
  108. package/dist/cli/wirings/up.mjs.map +1 -1
  109. package/dist/cli/wirings/wipe.mjs +1 -1
  110. package/dist/cli/wirings/wipe.mjs.map +1 -1
  111. package/dist/contracts/chain-probe.d.mts +1 -1
  112. package/dist/contracts/chain-probe.mjs +1 -1
  113. package/dist/contracts/chain-probe.mjs.map +1 -1
  114. package/dist/contracts/codegenable.d.mts +60 -6
  115. package/dist/contracts/codegenable.mjs +28 -0
  116. package/dist/contracts/codegenable.mjs.map +1 -0
  117. package/dist/contracts/config-bindings.mjs +162 -0
  118. package/dist/contracts/config-bindings.mjs.map +1 -0
  119. package/dist/contracts/container-runtime.d.mts +23 -17
  120. package/dist/contracts/plugin-expander.mjs.map +1 -1
  121. package/dist/contracts/snapshotable.d.mts +5 -7
  122. package/dist/contracts/wallet-protocol.d.mts +2 -5
  123. package/dist/contracts/wallet-protocol.mjs +2 -5
  124. package/dist/contracts/wallet-protocol.mjs.map +1 -1
  125. package/dist/index.d.mts +21 -26
  126. package/dist/index.mjs +8 -9
  127. package/dist/orchestrators/boot.d.mts +28 -0
  128. package/dist/orchestrators/boot.mjs +440 -0
  129. package/dist/orchestrators/boot.mjs.map +1 -0
  130. package/dist/orchestrators/codegen/bindings.d.mts +13 -1
  131. package/dist/orchestrators/codegen/bindings.mjs +23 -9
  132. package/dist/orchestrators/codegen/bindings.mjs.map +1 -1
  133. package/dist/orchestrators/codegen/config-runtime.mjs +208 -0
  134. package/dist/orchestrators/codegen/config-runtime.mjs.map +1 -0
  135. package/dist/orchestrators/codegen/emit.mjs +1 -1
  136. package/dist/orchestrators/codegen/emit.mjs.map +1 -1
  137. package/dist/orchestrators/codegen/errors.mjs +21 -1
  138. package/dist/orchestrators/codegen/errors.mjs.map +1 -1
  139. package/dist/orchestrators/codegen/format.mjs +2 -0
  140. package/dist/orchestrators/codegen/format.mjs.map +1 -1
  141. package/dist/orchestrators/codegen/gitignore.mjs +30 -11
  142. package/dist/orchestrators/codegen/gitignore.mjs.map +1 -1
  143. package/dist/orchestrators/codegen/id-config.mjs +107 -0
  144. package/dist/orchestrators/codegen/id-config.mjs.map +1 -0
  145. package/dist/orchestrators/codegen/output-location.mjs +23 -31
  146. package/dist/orchestrators/codegen/output-location.mjs.map +1 -1
  147. package/dist/orchestrators/codegen/paths.mjs +8 -1
  148. package/dist/orchestrators/codegen/paths.mjs.map +1 -1
  149. package/dist/orchestrators/codegen/permissions.mjs.map +1 -1
  150. package/dist/orchestrators/codegen/service.mjs +286 -58
  151. package/dist/orchestrators/codegen/service.mjs.map +1 -1
  152. package/dist/orchestrators/layers.mjs +31 -0
  153. package/dist/orchestrators/layers.mjs.map +1 -0
  154. package/dist/orchestrators/lifecycle-prune/errors.mjs.map +1 -1
  155. package/dist/orchestrators/lifecycle-prune/index.mjs +8 -10
  156. package/dist/orchestrators/lifecycle-prune/index.mjs.map +1 -1
  157. package/dist/orchestrators/network-options.d.mts +52 -0
  158. package/dist/orchestrators/network-options.mjs +43 -0
  159. package/dist/orchestrators/network-options.mjs.map +1 -0
  160. package/dist/orchestrators/router/cleanup.mjs.map +1 -1
  161. package/dist/orchestrators/router/cors.mjs.map +1 -1
  162. package/dist/orchestrators/router/entrypoints.mjs.map +1 -1
  163. package/dist/orchestrators/router/errors.mjs.map +1 -1
  164. package/dist/orchestrators/router/file-provider.mjs.map +1 -1
  165. package/dist/orchestrators/router/hostname.mjs.map +1 -1
  166. package/dist/orchestrators/router/profile.mjs +1 -1
  167. package/dist/orchestrators/router/profile.mjs.map +1 -1
  168. package/dist/orchestrators/router/service.d.mts +2 -2
  169. package/dist/orchestrators/router/service.mjs +12 -27
  170. package/dist/orchestrators/router/service.mjs.map +1 -1
  171. package/dist/orchestrators/router/traefik-container.mjs +1 -1
  172. package/dist/orchestrators/router/traefik-container.mjs.map +1 -1
  173. package/dist/orchestrators/snapshot/capture.mjs +171 -195
  174. package/dist/orchestrators/snapshot/capture.mjs.map +1 -1
  175. package/dist/orchestrators/snapshot/descriptor.d.mts +1 -1
  176. package/dist/orchestrators/snapshot/descriptor.mjs +53 -11
  177. package/dist/orchestrators/snapshot/descriptor.mjs.map +1 -1
  178. package/dist/orchestrators/snapshot/identity-guard.mjs +3 -5
  179. package/dist/orchestrators/snapshot/identity-guard.mjs.map +1 -1
  180. package/dist/orchestrators/snapshot/image-bundle-tags.mjs +32 -101
  181. package/dist/orchestrators/snapshot/image-bundle-tags.mjs.map +1 -1
  182. package/dist/orchestrators/snapshot/index.mjs +1 -4
  183. package/dist/orchestrators/snapshot/integrity.mjs.map +1 -1
  184. package/dist/orchestrators/snapshot/interrupted-restore.mjs +116 -0
  185. package/dist/orchestrators/snapshot/interrupted-restore.mjs.map +1 -0
  186. package/dist/orchestrators/snapshot/phase-error.mjs.map +1 -1
  187. package/dist/orchestrators/snapshot/prune.mjs +40 -31
  188. package/dist/orchestrators/snapshot/prune.mjs.map +1 -1
  189. package/dist/orchestrators/snapshot/restore.mjs +221 -196
  190. package/dist/orchestrators/snapshot/restore.mjs.map +1 -1
  191. package/dist/orchestrators/snapshot/service.mjs +72 -62
  192. package/dist/orchestrators/snapshot/service.mjs.map +1 -1
  193. package/dist/orchestrators/snapshot/wipe.mjs +67 -56
  194. package/dist/orchestrators/snapshot/wipe.mjs.map +1 -1
  195. package/dist/plugins/account/codegen.mjs +3 -0
  196. package/dist/plugins/account/codegen.mjs.map +1 -1
  197. package/dist/plugins/account/errors.d.mts +2 -2
  198. package/dist/plugins/account/errors.mjs +1 -4
  199. package/dist/plugins/account/errors.mjs.map +1 -1
  200. package/dist/plugins/account/funding.mjs +3 -27
  201. package/dist/plugins/account/funding.mjs.map +1 -1
  202. package/dist/plugins/account/index.d.mts +3 -8
  203. package/dist/plugins/account/index.mjs +71 -35
  204. package/dist/plugins/account/index.mjs.map +1 -1
  205. package/dist/plugins/account/keypair.d.mts +8 -7
  206. package/dist/plugins/account/keypair.mjs +5 -18
  207. package/dist/plugins/account/keypair.mjs.map +1 -1
  208. package/dist/plugins/account/lease.mjs.map +1 -1
  209. package/dist/plugins/account/registry.d.mts +1 -46
  210. package/dist/plugins/account/registry.mjs.map +1 -1
  211. package/dist/plugins/account/service.d.mts +43 -34
  212. package/dist/plugins/account/service.mjs +9 -54
  213. package/dist/plugins/account/service.mjs.map +1 -1
  214. package/dist/plugins/account/snapshot.mjs.map +1 -1
  215. package/dist/plugins/account/variants/ephemeral.mjs.map +1 -1
  216. package/dist/plugins/account/variants/impersonate.mjs.map +1 -1
  217. package/dist/plugins/account/variants/signer.mjs.map +1 -1
  218. package/dist/plugins/action/discriminator.mjs.map +1 -1
  219. package/dist/plugins/action/errors.mjs +1 -4
  220. package/dist/plugins/action/errors.mjs.map +1 -1
  221. package/dist/plugins/action/execute.mjs +3 -4
  222. package/dist/plugins/action/execute.mjs.map +1 -1
  223. package/dist/plugins/action/index.d.mts +2 -2
  224. package/dist/plugins/action/index.mjs +7 -11
  225. package/dist/plugins/action/index.mjs.map +1 -1
  226. package/dist/plugins/action/service.d.mts +5 -7
  227. package/dist/plugins/action/service.mjs +2 -16
  228. package/dist/plugins/action/service.mjs.map +1 -1
  229. package/dist/plugins/coin/address-resolution.mjs.map +1 -1
  230. package/dist/plugins/coin/codegen.mjs +97 -16
  231. package/dist/plugins/coin/codegen.mjs.map +1 -1
  232. package/dist/plugins/coin/discovery.mjs.map +1 -1
  233. package/dist/plugins/coin/errors.mjs +1 -4
  234. package/dist/plugins/coin/errors.mjs.map +1 -1
  235. package/dist/plugins/coin/index.d.mts +4 -34
  236. package/dist/plugins/coin/index.mjs +47 -28
  237. package/dist/plugins/coin/index.mjs.map +1 -1
  238. package/dist/plugins/coin/metadata.d.mts +1 -0
  239. package/dist/plugins/coin/metadata.mjs +8 -9
  240. package/dist/plugins/coin/metadata.mjs.map +1 -1
  241. package/dist/plugins/coin/mint.mjs +7 -21
  242. package/dist/plugins/coin/mint.mjs.map +1 -1
  243. package/dist/plugins/coin/registry.mjs +33 -20
  244. package/dist/plugins/coin/registry.mjs.map +1 -1
  245. package/dist/plugins/coin/service.d.mts +1 -1
  246. package/dist/plugins/coin/service.mjs +1 -8
  247. package/dist/plugins/coin/service.mjs.map +1 -1
  248. package/dist/plugins/coin/snapshot.mjs.map +1 -1
  249. package/dist/plugins/coin/type-strings.mjs.map +1 -1
  250. package/dist/plugins/dashboard/domain.mjs +106 -167
  251. package/dist/plugins/dashboard/domain.mjs.map +1 -1
  252. package/dist/plugins/dashboard/index.mjs +13 -13
  253. package/dist/plugins/dashboard/index.mjs.map +1 -1
  254. package/dist/plugins/dashboard/origin-policy.mjs +3 -3
  255. package/dist/plugins/dashboard/origin-policy.mjs.map +1 -1
  256. package/dist/plugins/dashboard/routable.mjs.map +1 -1
  257. package/dist/plugins/dashboard/schema/builder.mjs.map +1 -1
  258. package/dist/plugins/dashboard/schema/enums.mjs.map +1 -1
  259. package/dist/plugins/dashboard/schema/root.mjs +23 -44
  260. package/dist/plugins/dashboard/schema/root.mjs.map +1 -1
  261. package/dist/plugins/dashboard/schema/types.mjs +37 -91
  262. package/dist/plugins/dashboard/schema/types.mjs.map +1 -1
  263. package/dist/plugins/dashboard/schema.mjs.map +1 -1
  264. package/dist/plugins/dashboard/server.mjs.map +1 -1
  265. package/dist/plugins/deepbook/codegen.d.mts +12 -11
  266. package/dist/plugins/deepbook/codegen.mjs +108 -11
  267. package/dist/plugins/deepbook/codegen.mjs.map +1 -1
  268. package/dist/plugins/deepbook/deploy.mjs +7 -14
  269. package/dist/plugins/deepbook/deploy.mjs.map +1 -1
  270. package/dist/plugins/deepbook/errors.mjs +4 -2
  271. package/dist/plugins/deepbook/errors.mjs.map +1 -1
  272. package/dist/plugins/deepbook/faucet-strategy.mjs +4 -10
  273. package/dist/plugins/deepbook/faucet-strategy.mjs.map +1 -1
  274. package/dist/plugins/deepbook/hash.mjs.map +1 -1
  275. package/dist/plugins/deepbook/index.d.mts +38 -34
  276. package/dist/plugins/deepbook/index.mjs +133 -64
  277. package/dist/plugins/deepbook/index.mjs.map +1 -1
  278. package/dist/plugins/deepbook/plugin-key.mjs.map +1 -1
  279. package/dist/plugins/deepbook/pyth/index.mjs +4 -8
  280. package/dist/plugins/deepbook/pyth/index.mjs.map +1 -1
  281. package/dist/plugins/deepbook/snapshot.mjs +0 -1
  282. package/dist/plugins/deepbook/snapshot.mjs.map +1 -1
  283. package/dist/plugins/deepbook/type-strings.mjs.map +1 -1
  284. package/dist/plugins/deepbook/types.d.mts +2 -1
  285. package/dist/plugins/deepbook/types.mjs.map +1 -1
  286. package/dist/plugins/faucet/dispatcher.d.mts +3 -3
  287. package/dist/plugins/faucet/dispatcher.mjs.map +1 -1
  288. package/dist/plugins/faucet/errors.mjs.map +1 -1
  289. package/dist/plugins/faucet/http.mjs +2 -10
  290. package/dist/plugins/faucet/http.mjs.map +1 -1
  291. package/dist/plugins/faucet/index.mjs +0 -1
  292. package/dist/plugins/faucet/index.mjs.map +1 -1
  293. package/dist/plugins/host-service/errors.d.mts +1 -3
  294. package/dist/plugins/host-service/errors.mjs +1 -2
  295. package/dist/plugins/host-service/errors.mjs.map +1 -1
  296. package/dist/plugins/host-service/index.d.mts +1 -2
  297. package/dist/plugins/host-service/index.mjs +11 -11
  298. package/dist/plugins/host-service/index.mjs.map +1 -1
  299. package/dist/plugins/host-service/routable.mjs.map +1 -1
  300. package/dist/plugins/host-service/service.mjs +13 -13
  301. package/dist/plugins/host-service/service.mjs.map +1 -1
  302. package/dist/plugins/internal/acquire-on-chain-artifact.mjs.map +1 -1
  303. package/dist/plugins/internal/codegen-helpers.mjs +2 -0
  304. package/dist/plugins/internal/codegen-helpers.mjs.map +1 -1
  305. package/dist/plugins/internal/funding-failure-error.mjs.map +1 -1
  306. package/dist/plugins/{postgres → internal/postgres-sidecar}/connection.mjs +1 -1
  307. package/dist/plugins/internal/postgres-sidecar/connection.mjs.map +1 -0
  308. package/dist/plugins/{postgres → internal/postgres-sidecar}/db-ensure.mjs +30 -16
  309. package/dist/plugins/internal/postgres-sidecar/db-ensure.mjs.map +1 -0
  310. package/dist/plugins/internal/postgres-sidecar/errors.mjs +21 -0
  311. package/dist/plugins/internal/postgres-sidecar/errors.mjs.map +1 -0
  312. package/dist/{substrate/runtime/scoped-multimap → plugins/internal/postgres-sidecar}/index.mjs +1 -0
  313. package/dist/plugins/internal/postgres-sidecar/service.mjs +121 -0
  314. package/dist/plugins/internal/postgres-sidecar/service.mjs.map +1 -0
  315. package/dist/plugins/package/build.mjs +2 -2
  316. package/dist/plugins/package/build.mjs.map +1 -1
  317. package/dist/plugins/package/codegen.d.mts +7 -14
  318. package/dist/plugins/package/codegen.mjs +187 -59
  319. package/dist/plugins/package/codegen.mjs.map +1 -1
  320. package/dist/plugins/package/dep-resolution.mjs +51 -3
  321. package/dist/plugins/package/dep-resolution.mjs.map +1 -1
  322. package/dist/plugins/package/errors.mjs +1 -4
  323. package/dist/plugins/package/errors.mjs.map +1 -1
  324. package/dist/plugins/package/git-source.d.mts +18 -0
  325. package/dist/plugins/package/git-source.mjs +119 -0
  326. package/dist/plugins/package/git-source.mjs.map +1 -0
  327. package/dist/plugins/package/index.d.mts +28 -19
  328. package/dist/plugins/package/index.mjs +107 -32
  329. package/dist/plugins/package/index.mjs.map +1 -1
  330. package/dist/plugins/package/mode-known.mjs +2 -2
  331. package/dist/plugins/package/mode-known.mjs.map +1 -1
  332. package/dist/plugins/package/mode-local.mjs +18 -35
  333. package/dist/plugins/package/mode-local.mjs.map +1 -1
  334. package/dist/plugins/package/publish-executor.mjs +13 -8
  335. package/dist/plugins/package/publish-executor.mjs.map +1 -1
  336. package/dist/plugins/package/publish-output.d.mts +1 -11
  337. package/dist/plugins/package/publish-output.mjs +1 -9
  338. package/dist/plugins/package/publish-output.mjs.map +1 -1
  339. package/dist/plugins/package/registry.d.mts +1 -1
  340. package/dist/plugins/package/registry.mjs +26 -14
  341. package/dist/plugins/package/registry.mjs.map +1 -1
  342. package/dist/plugins/package/service.mjs.map +1 -1
  343. package/dist/plugins/package/snapshot.mjs.map +1 -1
  344. package/dist/plugins/router-entrypoints.mjs +1 -3
  345. package/dist/plugins/router-entrypoints.mjs.map +1 -1
  346. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs +1 -2
  347. package/dist/plugins/seal/bootstrap-assets/cargo-image.mjs.map +1 -1
  348. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs +1 -6
  349. package/dist/plugins/seal/bootstrap-assets/source-fetch.mjs.map +1 -1
  350. package/dist/plugins/seal/codegen.d.mts +1 -5
  351. package/dist/plugins/seal/codegen.mjs +83 -12
  352. package/dist/plugins/seal/codegen.mjs.map +1 -1
  353. package/dist/plugins/seal/config-render.mjs +2 -6
  354. package/dist/plugins/seal/config-render.mjs.map +1 -1
  355. package/dist/plugins/seal/deploy.mjs +18 -29
  356. package/dist/plugins/seal/deploy.mjs.map +1 -1
  357. package/dist/plugins/seal/errors.d.mts +4 -5
  358. package/dist/plugins/seal/errors.mjs +1 -8
  359. package/dist/plugins/seal/errors.mjs.map +1 -1
  360. package/dist/plugins/seal/index.d.mts +7 -28
  361. package/dist/plugins/seal/index.mjs +69 -47
  362. package/dist/plugins/seal/index.mjs.map +1 -1
  363. package/dist/plugins/seal/key-manager.mjs.map +1 -1
  364. package/dist/plugins/seal/key-server.mjs +1 -6
  365. package/dist/plugins/seal/key-server.mjs.map +1 -1
  366. package/dist/plugins/seal/keygen.mjs +2 -3
  367. package/dist/plugins/seal/keygen.mjs.map +1 -1
  368. package/dist/plugins/seal/mode/fork-known.mjs.map +1 -1
  369. package/dist/plugins/seal/mode/live.mjs +1 -1
  370. package/dist/plugins/seal/mode/live.mjs.map +1 -1
  371. package/dist/plugins/seal/mode/local-keygen.mjs +4 -10
  372. package/dist/plugins/seal/mode/local-keygen.mjs.map +1 -1
  373. package/dist/plugins/seal/plugin-key.mjs.map +1 -1
  374. package/dist/plugins/seal/registry-publish.mjs.map +1 -1
  375. package/dist/plugins/seal/routable.mjs.map +1 -1
  376. package/dist/plugins/seal/service.mjs.map +1 -1
  377. package/dist/plugins/seal/snapshot.mjs +1 -2
  378. package/dist/plugins/seal/snapshot.mjs.map +1 -1
  379. package/dist/plugins/sui/auto-tick.mjs +5 -5
  380. package/dist/plugins/sui/auto-tick.mjs.map +1 -1
  381. package/dist/plugins/sui/chain-build-container.mjs +2 -2
  382. package/dist/plugins/sui/chain-build-container.mjs.map +1 -1
  383. package/dist/plugins/sui/chain-probe.mjs +4 -4
  384. package/dist/plugins/sui/chain-probe.mjs.map +1 -1
  385. package/dist/plugins/sui/codegen.d.mts +9 -7
  386. package/dist/plugins/sui/codegen.mjs +49 -46
  387. package/dist/plugins/sui/codegen.mjs.map +1 -1
  388. package/dist/plugins/sui/errors.d.mts +1 -1
  389. package/dist/plugins/sui/errors.mjs +1 -9
  390. package/dist/plugins/sui/errors.mjs.map +1 -1
  391. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.d.mts +1 -1
  392. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/index.mjs +3 -7
  393. package/dist/plugins/sui/exec/index.mjs.map +1 -0
  394. package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.mjs +1 -1
  395. package/dist/plugins/sui/exec/sign-and-dispatch.mjs.map +1 -0
  396. package/dist/plugins/sui/fork-faucet-strategy.mjs +2 -6
  397. package/dist/plugins/sui/fork-faucet-strategy.mjs.map +1 -1
  398. package/dist/plugins/sui/fork-orchestration.mjs +13 -23
  399. package/dist/plugins/sui/fork-orchestration.mjs.map +1 -1
  400. package/dist/plugins/sui/fork-transaction.mjs.map +1 -1
  401. package/dist/plugins/sui/index.d.mts +34 -83
  402. package/dist/plugins/sui/index.mjs +200 -68
  403. package/dist/plugins/sui/index.mjs.map +1 -1
  404. package/dist/{substrate/runtime/sui-ledger → plugins/sui/ledger}/object-ref.mjs +1 -1
  405. package/dist/plugins/sui/ledger/object-ref.mjs.map +1 -0
  406. package/dist/plugins/sui/local-faucet-strategy.mjs +1 -5
  407. package/dist/plugins/sui/local-faucet-strategy.mjs.map +1 -1
  408. package/dist/plugins/sui/log-attrs.mjs +11 -0
  409. package/dist/plugins/sui/log-attrs.mjs.map +1 -0
  410. package/dist/plugins/sui/mode/external.mjs +4 -12
  411. package/dist/plugins/sui/mode/external.mjs.map +1 -1
  412. package/dist/plugins/sui/mode/fork.mjs +19 -16
  413. package/dist/plugins/sui/mode/fork.mjs.map +1 -1
  414. package/dist/plugins/sui/mode/live.mjs +4 -17
  415. package/dist/plugins/sui/mode/live.mjs.map +1 -1
  416. package/dist/plugins/sui/mode/local.d.mts +1 -0
  417. package/dist/plugins/sui/mode/local.mjs +203 -61
  418. package/dist/plugins/sui/mode/local.mjs.map +1 -1
  419. package/dist/plugins/sui/mode/shared-boot.mjs +18 -23
  420. package/dist/plugins/sui/mode/shared-boot.mjs.map +1 -1
  421. package/dist/plugins/sui/mode/shared.d.mts +6 -7
  422. package/dist/plugins/sui/mode/shared.mjs.map +1 -1
  423. package/dist/plugins/sui/mode/spec.d.mts +23 -6
  424. package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.mjs +28 -30
  425. package/dist/plugins/sui/move/index.mjs.map +1 -0
  426. package/dist/plugins/sui/move-summary-runner.mjs +8 -4
  427. package/dist/plugins/sui/move-summary-runner.mjs.map +1 -1
  428. package/dist/{substrate/network.d.mts → plugins/sui/network-config.d.mts} +11 -10
  429. package/dist/plugins/sui/network-resolver.d.mts +6 -6
  430. package/dist/plugins/sui/routable.mjs.map +1 -1
  431. package/dist/plugins/sui/service.mjs +10 -6
  432. package/dist/plugins/sui/service.mjs.map +1 -1
  433. package/dist/plugins/sui/snapshot.mjs +10 -5
  434. package/dist/plugins/sui/snapshot.mjs.map +1 -1
  435. package/dist/plugins/wallet/codegen.d.mts +8 -6
  436. package/dist/plugins/wallet/codegen.mjs +4 -10
  437. package/dist/plugins/wallet/codegen.mjs.map +1 -1
  438. package/dist/plugins/wallet/errors.mjs +1 -4
  439. package/dist/plugins/wallet/errors.mjs.map +1 -1
  440. package/dist/plugins/wallet/index.d.mts +3 -6
  441. package/dist/plugins/wallet/index.mjs +16 -21
  442. package/dist/plugins/wallet/index.mjs.map +1 -1
  443. package/dist/plugins/wallet/origin-policy.mjs +3 -3
  444. package/dist/plugins/wallet/origin-policy.mjs.map +1 -1
  445. package/dist/plugins/wallet/pairing.mjs +6 -24
  446. package/dist/plugins/wallet/pairing.mjs.map +1 -1
  447. package/dist/plugins/wallet/protocol.mjs.map +1 -1
  448. package/dist/plugins/wallet/routable.mjs.map +1 -1
  449. package/dist/plugins/wallet/server.mjs +17 -24
  450. package/dist/plugins/wallet/server.mjs.map +1 -1
  451. package/dist/plugins/wallet/service.d.mts +2 -2
  452. package/dist/plugins/wallet/service.mjs +4 -15
  453. package/dist/plugins/wallet/service.mjs.map +1 -1
  454. package/dist/plugins/wallet/snapshot.mjs.map +1 -1
  455. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs +1 -5
  456. package/dist/plugins/walrus/bootstrap-assets/cargo-image.mjs.map +1 -1
  457. package/dist/plugins/walrus/codegen.d.mts +2 -13
  458. package/dist/plugins/walrus/codegen.mjs +93 -28
  459. package/dist/plugins/walrus/codegen.mjs.map +1 -1
  460. package/dist/plugins/walrus/deploy-paths.mjs +2 -3
  461. package/dist/plugins/walrus/deploy-paths.mjs.map +1 -1
  462. package/dist/plugins/walrus/deploy.mjs +26 -19
  463. package/dist/plugins/walrus/deploy.mjs.map +1 -1
  464. package/dist/plugins/walrus/errors.mjs +1 -8
  465. package/dist/plugins/walrus/errors.mjs.map +1 -1
  466. package/dist/plugins/walrus/faucet-strategy.mjs +15 -1
  467. package/dist/plugins/walrus/faucet-strategy.mjs.map +1 -1
  468. package/dist/plugins/walrus/index.d.mts +13 -44
  469. package/dist/plugins/walrus/index.mjs +119 -122
  470. package/dist/plugins/walrus/index.mjs.map +1 -1
  471. package/dist/plugins/walrus/mode/known-deploy.mjs +6 -6
  472. package/dist/plugins/walrus/mode/known-deploy.mjs.map +1 -1
  473. package/dist/plugins/walrus/mode/local-cluster.d.mts +2 -2
  474. package/dist/plugins/walrus/mode/local-cluster.mjs +2 -10
  475. package/dist/plugins/walrus/mode/local-cluster.mjs.map +1 -1
  476. package/dist/plugins/walrus/plugin-key.mjs.map +1 -1
  477. package/dist/plugins/walrus/registry-publish.mjs.map +1 -1
  478. package/dist/plugins/walrus/routable.mjs.map +1 -1
  479. package/dist/plugins/walrus/service.mjs.map +1 -1
  480. package/dist/plugins/walrus/snapshot.mjs +3 -3
  481. package/dist/plugins/walrus/snapshot.mjs.map +1 -1
  482. package/dist/plugins/walrus/storage-nodes.mjs +53 -14
  483. package/dist/plugins/walrus/storage-nodes.mjs.map +1 -1
  484. package/dist/plugins/walrus/wal-swap.mjs +4 -8
  485. package/dist/plugins/walrus/wal-swap.mjs.map +1 -1
  486. package/dist/primitives/artifact-publisher.mjs.map +1 -1
  487. package/dist/primitives/cache.d.mts +1 -1
  488. package/dist/runtime/docker/client.mjs +47 -3
  489. package/dist/runtime/docker/client.mjs.map +1 -1
  490. package/dist/runtime/docker/container.mjs +37 -57
  491. package/dist/runtime/docker/container.mjs.map +1 -1
  492. package/dist/runtime/docker/errors.mjs.map +1 -1
  493. package/dist/runtime/docker/exec.mjs +2 -2
  494. package/dist/runtime/docker/exec.mjs.map +1 -1
  495. package/dist/runtime/docker/image.mjs +10 -11
  496. package/dist/runtime/docker/image.mjs.map +1 -1
  497. package/dist/runtime/docker/index.mjs +0 -1
  498. package/dist/runtime/docker/inspect-and-decode.mjs.map +1 -1
  499. package/dist/runtime/docker/inventory.mjs +9 -9
  500. package/dist/runtime/docker/inventory.mjs.map +1 -1
  501. package/dist/runtime/docker/labels.mjs +12 -1
  502. package/dist/runtime/docker/labels.mjs.map +1 -1
  503. package/dist/runtime/docker/network.mjs +6 -6
  504. package/dist/runtime/docker/network.mjs.map +1 -1
  505. package/dist/runtime/docker/render-run-args.mjs.map +1 -1
  506. package/dist/runtime/docker/service.mjs +31 -45
  507. package/dist/runtime/docker/service.mjs.map +1 -1
  508. package/dist/runtime/docker/sweep.mjs +14 -64
  509. package/dist/runtime/docker/sweep.mjs.map +1 -1
  510. package/dist/runtime/docker/volume.mjs +1 -1
  511. package/dist/runtime/docker/volume.mjs.map +1 -1
  512. package/dist/runtime/docker/wrap.mjs +14 -2
  513. package/dist/runtime/docker/wrap.mjs.map +1 -1
  514. package/dist/substrate/brand.d.mts +2 -5
  515. package/dist/substrate/brand.mjs +3 -2
  516. package/dist/substrate/brand.mjs.map +1 -1
  517. package/dist/substrate/cross-process.mjs +2 -16
  518. package/dist/substrate/cross-process.mjs.map +1 -1
  519. package/dist/substrate/event-time.mjs +0 -2
  520. package/dist/substrate/event-time.mjs.map +1 -1
  521. package/dist/substrate/events.d.mts +46 -16
  522. package/dist/substrate/identity.d.mts +11 -5
  523. package/dist/substrate/manifest.d.mts +33 -26
  524. package/dist/substrate/manifest.mjs +8 -3
  525. package/dist/substrate/manifest.mjs.map +1 -1
  526. package/dist/substrate/options.d.mts +33 -4
  527. package/dist/substrate/plugin-ctx.d.mts +70 -0
  528. package/dist/substrate/plugin-ctx.mjs +66 -0
  529. package/dist/substrate/plugin-ctx.mjs.map +1 -0
  530. package/dist/substrate/plugin.d.mts +46 -38
  531. package/dist/substrate/plugin.mjs +13 -9
  532. package/dist/substrate/plugin.mjs.map +1 -1
  533. package/dist/substrate/projection.d.mts +1 -1
  534. package/dist/substrate/runtime/atomic-write.mjs +3 -4
  535. package/dist/substrate/runtime/atomic-write.mjs.map +1 -1
  536. package/dist/substrate/runtime/cache/schema.mjs.map +1 -1
  537. package/dist/substrate/runtime/cache/service.mjs +50 -20
  538. package/dist/substrate/runtime/cache/service.mjs.map +1 -1
  539. package/dist/substrate/runtime/config-validation.mjs.map +1 -1
  540. package/dist/substrate/runtime/control-plane/domain.mjs +34 -12
  541. package/dist/substrate/runtime/control-plane/domain.mjs.map +1 -1
  542. package/dist/substrate/runtime/control-plane/service.mjs.map +1 -1
  543. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs +14 -17
  544. package/dist/substrate/runtime/cross-process/command-channel/channel.mjs.map +1 -1
  545. package/dist/substrate/runtime/cross-process/command-channel/file-channel.mjs.map +1 -1
  546. package/dist/substrate/runtime/cross-process/command-channel/protocol.mjs.map +1 -1
  547. package/dist/substrate/runtime/cross-process/command-channel/runtime-control-lock.mjs.map +1 -1
  548. package/dist/substrate/runtime/cross-process/index.mjs +0 -2
  549. package/dist/substrate/runtime/cross-process/live-clock.mjs +1 -1
  550. package/dist/substrate/runtime/cross-process/live-clock.mjs.map +1 -1
  551. package/dist/substrate/runtime/cross-process/liveness.mjs +11 -18
  552. package/dist/substrate/runtime/cross-process/liveness.mjs.map +1 -1
  553. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs +2 -2
  554. package/dist/substrate/runtime/cross-process/reclaim-stale-file.mjs.map +1 -1
  555. package/dist/substrate/runtime/cross-process/roster.mjs +17 -127
  556. package/dist/substrate/runtime/cross-process/roster.mjs.map +1 -1
  557. package/dist/substrate/runtime/cross-process/self-pid.mjs.map +1 -1
  558. package/dist/substrate/runtime/cross-process/stack-lock.mjs +29 -28
  559. package/dist/substrate/runtime/cross-process/stack-lock.mjs.map +1 -1
  560. package/dist/substrate/runtime/current-plugin.mjs.map +1 -1
  561. package/dist/substrate/runtime/errors.mjs +1 -13
  562. package/dist/substrate/runtime/errors.mjs.map +1 -1
  563. package/dist/substrate/runtime/format-unknown-error.mjs.map +1 -1
  564. package/dist/substrate/runtime/host-bind-mount-owner.mjs.map +1 -1
  565. package/dist/substrate/runtime/host-gateway.mjs.map +1 -1
  566. package/dist/substrate/runtime/host-tree-tar/index.mjs +15 -163
  567. package/dist/substrate/runtime/host-tree-tar/index.mjs.map +1 -1
  568. package/dist/substrate/runtime/http-probe.d.mts +1 -3
  569. package/dist/substrate/runtime/http-probe.mjs.map +1 -1
  570. package/dist/substrate/runtime/index.mjs +1 -6
  571. package/dist/substrate/runtime/lease-broker/service.mjs +5 -23
  572. package/dist/substrate/runtime/lease-broker/service.mjs.map +1 -1
  573. package/dist/substrate/runtime/lifecycle/dep-graph.mjs +3 -3
  574. package/dist/substrate/runtime/lifecycle/dep-graph.mjs.map +1 -1
  575. package/dist/substrate/runtime/lifecycle/graph-input-id.d.mts +1 -0
  576. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs +133 -0
  577. package/dist/substrate/runtime/lifecycle/graph-input-id.mjs.map +1 -0
  578. package/dist/substrate/runtime/lifecycle/index.mjs +2 -2
  579. package/dist/substrate/runtime/lifecycle/plugin-registry.d.mts +1 -1
  580. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs +22 -1
  581. package/dist/substrate/runtime/lifecycle/plugin-registry.mjs.map +1 -1
  582. package/dist/substrate/runtime/lifecycle/ready-gate.mjs +2 -15
  583. package/dist/substrate/runtime/lifecycle/ready-gate.mjs.map +1 -1
  584. package/dist/substrate/runtime/lifecycle/selective-restart.mjs +29 -25
  585. package/dist/substrate/runtime/lifecycle/selective-restart.mjs.map +1 -1
  586. package/dist/substrate/runtime/lifecycle/signals.mjs +1 -1
  587. package/dist/substrate/runtime/lifecycle/signals.mjs.map +1 -1
  588. package/dist/substrate/runtime/lifecycle/state-machine.mjs.map +1 -1
  589. package/dist/substrate/runtime/lifecycle/watch-attribution.d.mts +1 -1
  590. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs +0 -1
  591. package/dist/substrate/runtime/lifecycle/watch-attribution.mjs.map +1 -1
  592. package/dist/substrate/runtime/managed-container.d.mts +5 -1
  593. package/dist/substrate/runtime/managed-container.mjs +8 -10
  594. package/dist/substrate/runtime/managed-container.mjs.map +1 -1
  595. package/dist/substrate/runtime/manifest/manifest.mjs +8 -30
  596. package/dist/substrate/runtime/manifest/manifest.mjs.map +1 -1
  597. package/dist/substrate/runtime/mode-errors.d.mts +1 -2
  598. package/dist/substrate/runtime/mode-errors.mjs.map +1 -1
  599. package/dist/substrate/runtime/observability/cascade-formatter.mjs +1 -11
  600. package/dist/substrate/runtime/observability/cascade-formatter.mjs.map +1 -1
  601. package/dist/substrate/runtime/observability/ignore-with-log.mjs.map +1 -1
  602. package/dist/substrate/runtime/observability/index.d.mts +1 -1
  603. package/dist/substrate/runtime/observability/index.mjs +4 -6
  604. package/dist/substrate/runtime/observability/log-attrs.mjs +46 -0
  605. package/dist/substrate/runtime/observability/log-attrs.mjs.map +1 -0
  606. package/dist/substrate/runtime/observability/log-store.mjs.map +1 -1
  607. package/dist/substrate/runtime/observability/logger.mjs +3 -3
  608. package/dist/substrate/runtime/observability/logger.mjs.map +1 -1
  609. package/dist/substrate/runtime/observability/output-truncate.mjs.map +1 -1
  610. package/dist/substrate/runtime/observability/pretty-error.mjs.map +1 -1
  611. package/dist/substrate/runtime/observability/process-lines.mjs.map +1 -1
  612. package/dist/substrate/runtime/observability/redaction.mjs.map +1 -1
  613. package/dist/substrate/runtime/observability/subprocess-capture.mjs +2 -2
  614. package/dist/substrate/runtime/observability/subprocess-capture.mjs.map +1 -1
  615. package/dist/substrate/runtime/passthrough-or-wrap.mjs.map +1 -1
  616. package/dist/substrate/runtime/paths.mjs +27 -9
  617. package/dist/substrate/runtime/paths.mjs.map +1 -1
  618. package/dist/substrate/runtime/phase-preserving-produce.mjs.map +1 -1
  619. package/dist/substrate/runtime/port-broker/service.mjs +10 -27
  620. package/dist/substrate/runtime/port-broker/service.mjs.map +1 -1
  621. package/dist/substrate/runtime/post-acquire-tasks.mjs.map +1 -1
  622. package/dist/substrate/runtime/probes.d.mts +2 -3
  623. package/dist/substrate/runtime/probes.mjs.map +1 -1
  624. package/dist/substrate/runtime/process-supervisor.mjs.map +1 -1
  625. package/dist/substrate/runtime/projection/operational-endpoints.mjs.map +1 -1
  626. package/dist/substrate/runtime/projection/state-ref.mjs.map +1 -1
  627. package/dist/substrate/runtime/projection/update.mjs +87 -11
  628. package/dist/substrate/runtime/projection/update.mjs.map +1 -1
  629. package/dist/substrate/runtime/random-suffix.mjs.map +1 -1
  630. package/dist/substrate/runtime/reconcile/fs-plan.mjs +146 -0
  631. package/dist/substrate/runtime/reconcile/fs-plan.mjs.map +1 -0
  632. package/dist/substrate/runtime/reconcile/graph.mjs +19 -0
  633. package/dist/substrate/runtime/reconcile/graph.mjs.map +1 -0
  634. package/dist/substrate/runtime/reconcile/index.mjs +5 -0
  635. package/dist/substrate/runtime/reconcile/label.mjs +41 -0
  636. package/dist/substrate/runtime/reconcile/label.mjs.map +1 -0
  637. package/dist/substrate/runtime/reconcile/spec.mjs +15 -0
  638. package/dist/substrate/runtime/reconcile/spec.mjs.map +1 -0
  639. package/dist/substrate/runtime/retry-policy.d.mts +2 -3
  640. package/dist/substrate/runtime/retry-policy.mjs.map +1 -1
  641. package/dist/substrate/runtime/routed-url.mjs.map +1 -1
  642. package/dist/substrate/runtime/runtime-decode.mjs.map +1 -1
  643. package/dist/substrate/runtime/scoped-http-server.mjs.map +1 -1
  644. package/dist/substrate/runtime/stage-and-swap/index.mjs +49 -12
  645. package/dist/substrate/runtime/stage-and-swap/index.mjs.map +1 -1
  646. package/dist/substrate/runtime/strategy-registry/chain-keyed-strategy-for.mjs.map +1 -1
  647. package/dist/substrate/runtime/strategy-registry/chain-probe-for.mjs.map +1 -1
  648. package/dist/substrate/runtime/strategy-registry/service.mjs +29 -17
  649. package/dist/substrate/runtime/strategy-registry/service.mjs.map +1 -1
  650. package/dist/substrate/runtime/subnet-broker.mjs.map +1 -1
  651. package/dist/substrate/runtime/supervisor/acquire-node.mjs +188 -56
  652. package/dist/substrate/runtime/supervisor/acquire-node.mjs.map +1 -1
  653. package/dist/substrate/runtime/supervisor/background-tasks.mjs +79 -65
  654. package/dist/substrate/runtime/supervisor/background-tasks.mjs.map +1 -1
  655. package/dist/substrate/runtime/supervisor/command-loop.mjs +82 -23
  656. package/dist/substrate/runtime/supervisor/command-loop.mjs.map +1 -1
  657. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs +16 -0
  658. package/dist/substrate/runtime/supervisor/contribution-dispatcher.mjs.map +1 -0
  659. package/dist/substrate/runtime/supervisor/errors.mjs +11 -2
  660. package/dist/substrate/runtime/supervisor/errors.mjs.map +1 -1
  661. package/dist/substrate/runtime/supervisor/index.mjs +2 -0
  662. package/dist/substrate/runtime/supervisor/shutdown.mjs +6 -6
  663. package/dist/substrate/runtime/supervisor/shutdown.mjs.map +1 -1
  664. package/dist/substrate/runtime/supervisor/start-supervisor.mjs +60 -70
  665. package/dist/substrate/runtime/supervisor/start-supervisor.mjs.map +1 -1
  666. package/dist/substrate/runtime/supervisor/state.d.mts +1 -1
  667. package/dist/substrate/runtime/supervisor/state.mjs +1 -1
  668. package/dist/substrate/runtime/supervisor/state.mjs.map +1 -1
  669. package/dist/substrate/runtime/supervisor/teardown.mjs +10 -9
  670. package/dist/substrate/runtime/supervisor/teardown.mjs.map +1 -1
  671. package/dist/substrate/runtime/supervisor/wiring.mjs +6 -20
  672. package/dist/substrate/runtime/supervisor/wiring.mjs.map +1 -1
  673. package/dist/substrate/runtime/tar/reader.mjs +216 -0
  674. package/dist/substrate/runtime/tar/reader.mjs.map +1 -0
  675. package/dist/substrate/runtime/typed-env.mjs.map +1 -1
  676. package/dist/substrate/versioned-doc-schema.mjs.map +1 -1
  677. package/dist/substrate/versioned-doc-sync.mjs +5 -5
  678. package/dist/substrate/versioned-doc-sync.mjs.map +1 -1
  679. package/dist/surfaces/cli/command-tree.mjs +49 -7
  680. package/dist/surfaces/cli/command-tree.mjs.map +1 -1
  681. package/dist/surfaces/cli/commands/config.mjs +1 -1
  682. package/dist/surfaces/cli/commands/config.mjs.map +1 -1
  683. package/dist/surfaces/cli/commands/confirm-node.mjs.map +1 -1
  684. package/dist/surfaces/cli/commands/confirm.mjs.map +1 -1
  685. package/dist/surfaces/cli/commands/doctor.mjs +1 -1
  686. package/dist/surfaces/cli/commands/doctor.mjs.map +1 -1
  687. package/dist/surfaces/cli/commands/prune-picker-entry.mjs.map +1 -1
  688. package/dist/surfaces/cli/commands/prune-picker.mjs.map +1 -1
  689. package/dist/surfaces/cli/commands/prune.mjs +1 -1
  690. package/dist/surfaces/cli/commands/prune.mjs.map +1 -1
  691. package/dist/surfaces/cli/commands/snapshot.mjs +1 -1
  692. package/dist/surfaces/cli/commands/snapshot.mjs.map +1 -1
  693. package/dist/surfaces/cli/commands/status.mjs +1 -1
  694. package/dist/surfaces/cli/commands/status.mjs.map +1 -1
  695. package/dist/surfaces/cli/commands/supervisor-presence.mjs.map +1 -1
  696. package/dist/surfaces/cli/commands/wipe.mjs +3 -4
  697. package/dist/surfaces/cli/commands/wipe.mjs.map +1 -1
  698. package/dist/surfaces/cli/envelope.mjs.map +1 -1
  699. package/dist/surfaces/cli/errors.mjs.map +1 -1
  700. package/dist/surfaces/cli/flags.mjs.map +1 -1
  701. package/dist/surfaces/cli/index.mjs +45 -14
  702. package/dist/surfaces/cli/index.mjs.map +1 -1
  703. package/dist/surfaces/cli/output.mjs.map +1 -1
  704. package/dist/surfaces/cli/sysexits.mjs.map +1 -1
  705. package/dist/surfaces/tui/app.mjs +0 -21
  706. package/dist/surfaces/tui/app.mjs.map +1 -1
  707. package/dist/surfaces/tui/dashboard.mjs +0 -48
  708. package/dist/surfaces/tui/dashboard.mjs.map +1 -1
  709. package/dist/surfaces/tui/display-derivation.mjs +80 -16
  710. package/dist/surfaces/tui/display-derivation.mjs.map +1 -1
  711. package/dist/surfaces/tui/errors.mjs.map +1 -1
  712. package/dist/surfaces/tui/event-log.mjs +0 -9
  713. package/dist/surfaces/tui/event-log.mjs.map +1 -1
  714. package/dist/surfaces/tui/heartbeat.mjs.map +1 -1
  715. package/dist/surfaces/tui/index.mjs +2 -2
  716. package/dist/surfaces/tui/index.mjs.map +1 -1
  717. package/dist/surfaces/tui/input.mjs.map +1 -1
  718. package/dist/surfaces/tui/mode-detect.mjs.map +1 -1
  719. package/dist/surfaces/tui/mount-ink.mjs.map +1 -1
  720. package/dist/surfaces/tui/plain-renderer.mjs +26 -19
  721. package/dist/surfaces/tui/plain-renderer.mjs.map +1 -1
  722. package/dist/surfaces/tui/resource-table.mjs +8 -10
  723. package/dist/surfaces/tui/resource-table.mjs.map +1 -1
  724. package/images/postgres/Dockerfile +0 -1
  725. package/images/sui/Dockerfile +27 -54
  726. package/images/sui/entrypoint.sh +17 -179
  727. package/package.json +23 -15
  728. package/dashboard-ui/assets/index-Bmi1UtAg.js +0 -1356
  729. package/dashboard-ui/assets/index-D5EShVt4.js +0 -3
  730. package/dashboard-ui/assets/index-Deml9drg.css +0 -1
  731. package/dist/api/plugin-errors.d.mts +0 -7
  732. package/dist/api/plugin-errors.mjs +0 -10
  733. package/dist/api/plugin-errors.mjs.map +0 -1
  734. package/dist/cli/wirings/build-verb-layers.mjs +0 -42
  735. package/dist/cli/wirings/build-verb-layers.mjs.map +0 -1
  736. package/dist/contracts/capability-decl.d.mts +0 -40
  737. package/dist/orchestrators/built-in-plugin-layers.mjs +0 -54
  738. package/dist/orchestrators/built-in-plugin-layers.mjs.map +0 -1
  739. package/dist/orchestrators/run.mjs +0 -91
  740. package/dist/orchestrators/run.mjs.map +0 -1
  741. package/dist/orchestrators/runtime-composition.d.mts +0 -10
  742. package/dist/orchestrators/runtime-composition.mjs +0 -252
  743. package/dist/orchestrators/runtime-composition.mjs.map +0 -1
  744. package/dist/orchestrators/snapshot/capture-command.d.mts +0 -1
  745. package/dist/orchestrators/snapshot/capture-command.mjs +0 -25
  746. package/dist/orchestrators/snapshot/capture-command.mjs.map +0 -1
  747. package/dist/orchestrators/snapshot/pending-marker.mjs +0 -99
  748. package/dist/orchestrators/snapshot/pending-marker.mjs.map +0 -1
  749. package/dist/orchestrators/snapshot/recover-pending.mjs +0 -223
  750. package/dist/orchestrators/snapshot/recover-pending.mjs.map +0 -1
  751. package/dist/orchestrators/snapshot/state-document.d.mts +0 -1
  752. package/dist/orchestrators/snapshot/state-document.mjs +0 -58
  753. package/dist/orchestrators/snapshot/state-document.mjs.map +0 -1
  754. package/dist/plugins/account/spans.mjs +0 -16
  755. package/dist/plugins/account/spans.mjs.map +0 -1
  756. package/dist/plugins/account/variants/env.mjs +0 -24
  757. package/dist/plugins/account/variants/env.mjs.map +0 -1
  758. package/dist/plugins/account/variants/inline.mjs +0 -13
  759. package/dist/plugins/account/variants/inline.mjs.map +0 -1
  760. package/dist/plugins/account/variants/keystore.mjs +0 -91
  761. package/dist/plugins/account/variants/keystore.mjs.map +0 -1
  762. package/dist/plugins/action/spans.mjs +0 -11
  763. package/dist/plugins/action/spans.mjs.map +0 -1
  764. package/dist/plugins/coin/spans.mjs +0 -20
  765. package/dist/plugins/coin/spans.mjs.map +0 -1
  766. package/dist/plugins/deepbook/spans.mjs +0 -18
  767. package/dist/plugins/deepbook/spans.mjs.map +0 -1
  768. package/dist/plugins/faucet/spans.mjs +0 -12
  769. package/dist/plugins/faucet/spans.mjs.map +0 -1
  770. package/dist/plugins/package/spans.mjs +0 -14
  771. package/dist/plugins/package/spans.mjs.map +0 -1
  772. package/dist/plugins/postgres/codegen.mjs +0 -42
  773. package/dist/plugins/postgres/codegen.mjs.map +0 -1
  774. package/dist/plugins/postgres/connection.d.mts +0 -33
  775. package/dist/plugins/postgres/connection.mjs.map +0 -1
  776. package/dist/plugins/postgres/db-ensure.mjs.map +0 -1
  777. package/dist/plugins/postgres/errors.d.mts +0 -60
  778. package/dist/plugins/postgres/errors.mjs +0 -29
  779. package/dist/plugins/postgres/errors.mjs.map +0 -1
  780. package/dist/plugins/postgres/index.d.mts +0 -37
  781. package/dist/plugins/postgres/index.mjs +0 -68
  782. package/dist/plugins/postgres/index.mjs.map +0 -1
  783. package/dist/plugins/postgres/routable.mjs +0 -33
  784. package/dist/plugins/postgres/routable.mjs.map +0 -1
  785. package/dist/plugins/postgres/service.d.mts +0 -50
  786. package/dist/plugins/postgres/service.mjs +0 -179
  787. package/dist/plugins/postgres/service.mjs.map +0 -1
  788. package/dist/plugins/postgres/snapshot.mjs +0 -32
  789. package/dist/plugins/postgres/snapshot.mjs.map +0 -1
  790. package/dist/plugins/postgres/spans.mjs +0 -11
  791. package/dist/plugins/postgres/spans.mjs.map +0 -1
  792. package/dist/plugins/seal/spans.mjs +0 -18
  793. package/dist/plugins/seal/spans.mjs.map +0 -1
  794. package/dist/plugins/sui/spans.mjs +0 -17
  795. package/dist/plugins/sui/spans.mjs.map +0 -1
  796. package/dist/plugins/wallet/spans.mjs +0 -22
  797. package/dist/plugins/wallet/spans.mjs.map +0 -1
  798. package/dist/plugins/walrus/registry-publish.d.mts +0 -24
  799. package/dist/plugins/walrus/spans.mjs +0 -18
  800. package/dist/plugins/walrus/spans.mjs.map +0 -1
  801. package/dist/runtime/docker/logs.d.mts +0 -1
  802. package/dist/runtime/docker/logs.mjs +0 -34
  803. package/dist/runtime/docker/logs.mjs.map +0 -1
  804. package/dist/substrate/runtime/artifact-publisher/index.mjs +0 -86
  805. package/dist/substrate/runtime/artifact-publisher/index.mjs.map +0 -1
  806. package/dist/substrate/runtime/capability-sinks/index.d.mts +0 -1
  807. package/dist/substrate/runtime/capability-sinks/index.mjs +0 -3
  808. package/dist/substrate/runtime/capability-sinks/layer.d.mts +0 -1
  809. package/dist/substrate/runtime/capability-sinks/layer.mjs +0 -31
  810. package/dist/substrate/runtime/capability-sinks/layer.mjs.map +0 -1
  811. package/dist/substrate/runtime/capability-sinks/service.d.mts +0 -95
  812. package/dist/substrate/runtime/capability-sinks/service.mjs +0 -69
  813. package/dist/substrate/runtime/capability-sinks/service.mjs.map +0 -1
  814. package/dist/substrate/runtime/cross-process/lock.d.mts +0 -1
  815. package/dist/substrate/runtime/cross-process/lock.mjs +0 -23
  816. package/dist/substrate/runtime/cross-process/lock.mjs.map +0 -1
  817. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs +0 -113
  818. package/dist/substrate/runtime/cross-process/snapshot-reservation.mjs.map +0 -1
  819. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs +0 -31
  820. package/dist/substrate/runtime/lifecycle/lifecycle-fact.mjs.map +0 -1
  821. package/dist/substrate/runtime/observability/formatter-registry.d.mts +0 -1
  822. package/dist/substrate/runtime/observability/formatter-registry.mjs +0 -48
  823. package/dist/substrate/runtime/observability/formatter-registry.mjs.map +0 -1
  824. package/dist/substrate/runtime/observability/span-store.d.mts +0 -1
  825. package/dist/substrate/runtime/observability/span-store.mjs +0 -110
  826. package/dist/substrate/runtime/observability/span-store.mjs.map +0 -1
  827. package/dist/substrate/runtime/observability/spans.d.mts +0 -1
  828. package/dist/substrate/runtime/observability/spans.mjs +0 -87
  829. package/dist/substrate/runtime/observability/spans.mjs.map +0 -1
  830. package/dist/substrate/runtime/projection/index.mjs +0 -4
  831. package/dist/substrate/runtime/projection/persisted.mjs +0 -213
  832. package/dist/substrate/runtime/projection/persisted.mjs.map +0 -1
  833. package/dist/substrate/runtime/scoped-multimap/service.mjs +0 -52
  834. package/dist/substrate/runtime/scoped-multimap/service.mjs.map +0 -1
  835. package/dist/substrate/runtime/scoped-ref-map/index.mjs +0 -2
  836. package/dist/substrate/runtime/scoped-ref-map/service.mjs +0 -83
  837. package/dist/substrate/runtime/scoped-ref-map/service.mjs.map +0 -1
  838. package/dist/substrate/runtime/state-store/index.mjs +0 -3
  839. package/dist/substrate/runtime/state-store/schema.d.mts +0 -1
  840. package/dist/substrate/runtime/state-store/schema.mjs +0 -41
  841. package/dist/substrate/runtime/state-store/schema.mjs.map +0 -1
  842. package/dist/substrate/runtime/state-store/service.d.mts +0 -1
  843. package/dist/substrate/runtime/state-store/service.mjs +0 -145
  844. package/dist/substrate/runtime/state-store/service.mjs.map +0 -1
  845. package/dist/substrate/runtime/sui-execute/index.mjs.map +0 -1
  846. package/dist/substrate/runtime/sui-execute/sign-and-dispatch.mjs.map +0 -1
  847. package/dist/substrate/runtime/sui-ledger/object-ref.mjs.map +0 -1
  848. package/dist/substrate/runtime/sui-move-build/index.mjs.map +0 -1
  849. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs +0 -151
  850. package/dist/substrate/runtime/supervisor/dispatch-contributions.mjs.map +0 -1
  851. package/dist/substrate/state-store.d.mts +0 -1
  852. /package/dist/orchestrators/{snapshot/pending-marker.d.mts → codegen/id-config.d.mts} +0 -0
  853. /package/dist/orchestrators/snapshot/{recover-pending.d.mts → interrupted-restore.d.mts} +0 -0
  854. /package/dist/{substrate/runtime/sui-execute → plugins/sui/exec}/sign-and-dispatch.d.mts +0 -0
  855. /package/dist/plugins/{postgres/snapshot.d.mts → sui/ledger/object-ref.d.mts} +0 -0
  856. /package/dist/{substrate/runtime/sui-move-build → plugins/sui/move}/index.d.mts +0 -0
  857. /package/dist/substrate/runtime/{state-store/index.d.mts → reconcile/graph.d.mts} +0 -0
  858. /package/dist/substrate/runtime/{cross-process/snapshot-reservation.d.mts → supervisor/contribution-dispatcher.d.mts} +0 -0
@@ -1,12 +1,11 @@
1
- import { SpanAttr } from "../../substrate/runtime/observability/spans.mjs";
2
1
  import { decodeJsonText } from "../../substrate/runtime/runtime-decode.mjs";
2
+ import { LogAttr } from "../../substrate/runtime/observability/log-attrs.mjs";
3
3
  import { WALLET_AUTH_HEADER, WalletHttpPath } from "../../contracts/wallet-protocol.mjs";
4
4
  import { formatUnknownError } from "../../substrate/runtime/format-unknown-error.mjs";
5
5
  import { walletBootError, walletRequestError } from "./errors.mjs";
6
- import { WalletSpans } from "./spans.mjs";
6
+ import { checkOrigin, corsHeadersFor } from "./origin-policy.mjs";
7
7
  import { SignRequestSchema } from "./protocol.mjs";
8
8
  import { parseBearerHeader, safeBearerEquals } from "./pairing.mjs";
9
- import { checkOrigin, corsHeadersFor } from "./origin-policy.mjs";
10
9
  import { listenScopedHttpServer } from "../../substrate/runtime/scoped-http-server.mjs";
11
10
  import { Cause, Effect } from "effect";
12
11
  import { randomUUID } from "node:crypto";
@@ -62,8 +61,8 @@ const startHttpServer = (config) => Effect.gen(function* () {
62
61
  })
63
62
  });
64
63
  yield* Effect.logInfo("wallet HTTP server listening").pipe(Effect.annotateLogs({
65
- [SpanAttr.host]: config.bindAddress,
66
- [SpanAttr.port]: config.port
64
+ [LogAttr.host]: config.bindAddress,
65
+ [LogAttr.port]: config.port
67
66
  }));
68
67
  return {
69
68
  url: handle.url,
@@ -118,8 +117,8 @@ const makeRequestListener = (config, runDispatch) => (req, res) => {
118
117
  };
119
118
  runDispatch(dispatch(config, walletReq).pipe(Effect.matchCauseEffect({
120
119
  onFailure: (cause) => Effect.logError("wallet dispatcher defect").pipe(Effect.annotateLogs({
121
- [WalletSpans.requestMethod]: walletReq.method,
122
- [WalletSpans.requestUrl]: walletReq.url,
120
+ "wallet.request.method": walletReq.method,
121
+ "wallet.request.url": walletReq.url,
123
122
  cause: Cause.pretty(cause)
124
123
  }), Effect.flatMap(() => Effect.sync(() => {
125
124
  writeServerError(res);
@@ -198,11 +197,6 @@ const text = (status, body, extraHeaders = {}) => ({
198
197
  */
199
198
  const dispatch = (config, req) => Effect.gen(function* () {
200
199
  const requestId = randomUUID();
201
- yield* Effect.annotateCurrentSpan({
202
- [WalletSpans.requestId]: requestId,
203
- [WalletSpans.requestMethod]: req.method,
204
- [WalletSpans.requestUrl]: req.url
205
- });
206
200
  const path = req.url.split("?")[0] ?? "";
207
201
  if (!path.startsWith("/api/v1/devstack/")) return text(404, "not found");
208
202
  if (req.method === "OPTIONS") {
@@ -217,31 +211,30 @@ const dispatch = (config, req) => Effect.gen(function* () {
217
211
  const originResult = checkOrigin(config.policy, req.headers["origin"]);
218
212
  if (originResult === "missing") {
219
213
  yield* Effect.logWarning("wallet origin missing").pipe(Effect.annotateLogs({
220
- [SpanAttr.requestId]: requestId,
221
- [SpanAttr.httpMethod]: req.method,
222
- [SpanAttr.httpPath]: path
214
+ [LogAttr.requestId]: requestId,
215
+ [LogAttr.httpMethod]: req.method,
216
+ [LogAttr.httpPath]: path
223
217
  }));
224
218
  return text(403, "Origin header required");
225
219
  }
226
220
  if (originResult === "forbidden") {
227
221
  yield* Effect.logWarning("wallet origin forbidden").pipe(Effect.annotateLogs({
228
- [SpanAttr.requestId]: requestId,
229
- [WalletSpans.origin]: req.headers.origin ?? "(missing)",
230
- [SpanAttr.httpMethod]: req.method,
231
- [SpanAttr.httpPath]: path
222
+ [LogAttr.requestId]: requestId,
223
+ "wallet.origin": req.headers.origin ?? "(missing)",
224
+ [LogAttr.httpMethod]: req.method,
225
+ [LogAttr.httpPath]: path
232
226
  }));
233
227
  return text(403, "forbidden origin");
234
228
  }
235
229
  const origin = req.headers["origin"];
236
230
  const bearer = parseBearerHeader(req.headers[WALLET_AUTH_HEADER]);
237
231
  const bearerValid = bearer !== null && safeBearerEquals(bearer, config.token);
238
- yield* Effect.annotateCurrentSpan({ [WalletSpans.bearerValid]: bearerValid });
239
232
  if (!bearerValid) {
240
233
  yield* Effect.logWarning("wallet bearer check failed").pipe(Effect.annotateLogs({
241
- [SpanAttr.requestId]: requestId,
242
- [WalletSpans.bearerValid]: bearerValid,
243
- [SpanAttr.httpMethod]: req.method,
244
- [SpanAttr.httpPath]: path
234
+ [LogAttr.requestId]: requestId,
235
+ "wallet.auth.bearerValid": bearerValid,
236
+ [LogAttr.httpMethod]: req.method,
237
+ [LogAttr.httpPath]: path
245
238
  }));
246
239
  return errorEnvelope(walletRequestError({
247
240
  phase: "unauthorized",
@@ -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// 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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"}
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 { LogAttr } from '../../substrate/runtime/observability/log-attrs.ts';\nimport { decodeJsonText } from '../../substrate/runtime/runtime-decode.ts';\nimport { formatUnknownError } from '../../substrate/runtime/format-unknown-error.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\tformatUnknownError(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[LogAttr.host]: config.bindAddress,\n\t\t\t\t[LogAttr.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'wallet.request.method': walletReq.method,\n\t\t\t\t\t\t\t\t'wallet.request.url': 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\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[LogAttr.requestId]: requestId,\n\t\t\t\t\t[LogAttr.httpMethod]: req.method,\n\t\t\t\t\t[LogAttr.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[LogAttr.requestId]: requestId,\n\t\t\t\t\t'wallet.origin': req.headers.origin ?? '(missing)',\n\t\t\t\t\t[LogAttr.httpMethod]: req.method,\n\t\t\t\t\t[LogAttr.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\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[LogAttr.requestId]: requestId,\n\t\t\t\t\t'wallet.auth.bearerValid': bearerValid,\n\t\t\t\t\t[LogAttr.httpMethod]: req.method,\n\t\t\t\t\t[LogAttr.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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"}
@@ -1,7 +1,7 @@
1
1
  import { ResourceRef } from "../../substrate/plugin.mjs";
2
2
  import { AccountValue } from "../account/service.mjs";
3
3
  import { AccountResourceId } from "../account/index.mjs";
4
- import { DappKitConfigBindings } from "./codegen.mjs";
4
+ import { DevWalletConfig } from "./codegen.mjs";
5
5
  import { PairingToken } from "./pairing.mjs";
6
6
  import { WalletServerHandle } from "./server.mjs";
7
7
  import { Effect, FileSystem, Scope } from "effect";
@@ -51,7 +51,7 @@ interface WalletOptions<Accounts extends ReadonlyArray<WalletAccountMember> = Re
51
51
  interface WalletValue {
52
52
  readonly url: string;
53
53
  readonly pairUrl: string;
54
- readonly bindings: DappKitConfigBindings;
54
+ readonly bindings: DevWalletConfig;
55
55
  readonly localPort: number;
56
56
  readonly token: PairingToken;
57
57
  /** Server handle — substrate's scope finalizer chain invokes
@@ -1,9 +1,8 @@
1
1
  import { WalletHttpPath } from "../../contracts/wallet-protocol.mjs";
2
2
  import { walletBootError } from "./errors.mjs";
3
- import { WalletSpans } from "./spans.mjs";
3
+ import { resolveOriginPolicy } from "./origin-policy.mjs";
4
4
  import "./protocol.mjs";
5
5
  import { acquirePairingToken, composePairUrl, tokenPath } from "./pairing.mjs";
6
- import { resolveOriginPolicy } from "./origin-policy.mjs";
7
6
  import { startHttpServer } from "./server.mjs";
8
7
  import { Effect } from "effect";
9
8
  //#region src/plugins/wallet/service.ts
@@ -29,13 +28,7 @@ const directUrlHostForBindAddress = (bindAddress) => bindAddress === WALLET_DEFA
29
28
  * `origin-policy.ts:resolveOriginPolicy`.
30
29
  */
31
30
  const acquireWallet = (opts, ctx) => Effect.gen(function* () {
32
- yield* Effect.annotateCurrentSpan({
33
- [WalletSpans.app]: ctx.app,
34
- [WalletSpans.stack]: ctx.stack,
35
- [WalletSpans.chain]: ctx.chain
36
- });
37
31
  const accounts = yield* ctx.resolveAccounts();
38
- yield* Effect.annotateCurrentSpan({ [WalletSpans.accountCount]: accounts.length });
39
32
  if (accounts.length === 0) return yield* Effect.fail(walletBootError({
40
33
  phase: "no-accounts",
41
34
  message: "wallet resolved zero accounts; add account('name') to the stack or pass accounts explicitly.",
@@ -70,7 +63,7 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
70
63
  const bindings = {
71
64
  walletUrl,
72
65
  pairUrl,
73
- chain: ctx.chain,
66
+ network: ctx.network,
74
67
  protocolPaths: {
75
68
  health: WalletHttpPath.HEALTH,
76
69
  accounts: WalletHttpPath.ACCOUNTS,
@@ -78,13 +71,9 @@ const acquireWallet = (opts, ctx) => Effect.gen(function* () {
78
71
  signPersonalMessage: WalletHttpPath.SIGN_PERSONAL_MESSAGE
79
72
  }
80
73
  };
81
- yield* Effect.annotateCurrentSpan({
82
- [WalletSpans.url]: walletUrl,
83
- [WalletSpans.localPort]: port
84
- });
85
74
  yield* Effect.logInfo("wallet ready").pipe(Effect.annotateLogs({
86
- [WalletSpans.url]: walletUrl,
87
- [WalletSpans.token]: "redacted-fragment"
75
+ "wallet.url": walletUrl,
76
+ "wallet.token": "redacted-fragment"
88
77
  }));
89
78
  return {
90
79
  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 { 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"}
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 { DevWalletConfig } 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';\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: DevWalletConfig;\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 network: 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\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\tconst accounts = yield* ctx.resolveAccounts();\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: DevWalletConfig = {\n\t\t\twalletUrl,\n\t\t\tpairUrl,\n\t\t\tnetwork: ctx.network,\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\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'wallet.url': walletUrl,\n\t\t\t\t'wallet.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":";;;;;;;;AA6GA,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;CAIvB,MAAM,WAAW,OAAO,IAAI,gBAAgB;CAC5C,IAAI,SAAS,WAAW,GACvB,OAAO,OAAO,OAAO,KACpB,gBAAgB;EACf,OAAO;EACP,SACC;EACD,MAAM;CACP,CAAC,CACF;CAED,MAAM,oCAAoB,IAAI,IAA0B;CACxD,KAAK,MAAM,QAAQ,UAAU;EAK5B,IAAI,kBAAkB,IAAI,KAAK,OAAO,GACrC,OAAO,OAAO,OAAO,KACpB,gBAAgB;GACf,OAAO;GACP,SAAS,oDAAoD,KAAK,QAAQ;GAC1E,MAAM;EACP,CAAC,CACF;EAED,kBAAkB,IAAI,KAAK,SAAS,IAAI;CACzC;CAKA,MAAM,cAAc,KAAK,eAAe;CACxC,MAAM,OAAO,OAAO,IAAI,aAAa,KAAK,MAAM,4BAA4B,WAAW,CAAC;CAKxF,MAAM,QAAQ,OAAO,oBAAoB,UAAU,IAAI,SAAS,CAAC;CAqBjE,MAAM,SAAS,OAAO,gBAAgB;EANrC;EACA;EACA;EACA,QAAA,OAdqB,oBAAoB;GACzC,KAAK,IAAI;GACT,OAAO,IAAI;GACX,iBAAiB,IAAI;GACrB,cAAc,KAAK,kBAAkB,CAAC;EACvC,CAAC;EAUA;CAEgD,CAAC;CAKlD,MAAM,YACL,IAAI,oBAAoB,UAAU,4BAA4B,WAAW,EAAE,GAAG;CAC/E,MAAM,UAAU,eAAe,WAAW,KAAK;CAE/C,MAAM,WAA4B;EACjC;EACA;EACA,SAAS,IAAI;EACb,eAAe;GACd,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;EACrC;CACD;CAGA,OAAO,OAAO,QAAQ,cAAc,CAAC,CAAC,KACrC,OAAO,aAAa;EACnB,cAAc;EACd,gBAAgB;CACjB,CAAC,CACF;CAEA,OAAO;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX;EACA;CACD;AACD,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../src/plugins/wallet/snapshot.ts"],"sourcesContent":["// Wallet plugin — Snapshotable contribution.\n//\n// The dev-wallet pairing token is plugin-owned state. Snapshot L3 must\n// not special-case its runtime path; the wallet declares the subtree\n// like any other stateful plugin.\n\nimport { Effect } from 'effect';\n\nimport type { SnapshotableDecl } from '../../contracts/snapshotable.ts';\n\nexport const makeWalletSnapshotable = (): SnapshotableDecl => ({\n\tkind: 'snapshotable',\n\tsubtrees: ['wallet/token'],\n\tmissingTolerance: 'fatal',\n\tsecretMaterial: true,\n\tpreRestore: Effect.succeed({ kind: 'wallet-pairing-token' as const }),\n\tpostRestore: Effect.void,\n});\n"],"mappings":";;AAUA,MAAa,gCAAkD;CAC9D,MAAM;CACN,UAAU,CAAC,eAAe;CAC1B,kBAAkB;CAClB,gBAAgB;CAChB,YAAY,OAAO,QAAQ,EAAE,MAAM,wBAAiC,CAAC;CACrE,aAAa,OAAO;CACpB"}
1
+ {"version":3,"file":"snapshot.mjs","names":[],"sources":["../../../src/plugins/wallet/snapshot.ts"],"sourcesContent":["// Wallet plugin — Snapshotable contribution.\n//\n// The dev-wallet pairing token is plugin-owned state. Snapshot L3 must\n// not special-case its runtime path; the wallet declares the subtree\n// like any other stateful plugin.\n\nimport { Effect } from 'effect';\n\nimport type { SnapshotableDecl } from '../../contracts/snapshotable.ts';\n\nexport const makeWalletSnapshotable = (): SnapshotableDecl => ({\n\tkind: 'snapshotable',\n\tsubtrees: ['wallet/token'],\n\tmissingTolerance: 'fatal',\n\tsecretMaterial: true,\n\tpreRestore: Effect.succeed({ kind: 'wallet-pairing-token' as const }),\n\tpostRestore: Effect.void,\n});\n"],"mappings":";;AAUA,MAAa,gCAAkD;CAC9D,MAAM;CACN,UAAU,CAAC,cAAc;CACzB,kBAAkB;CAClB,gBAAgB;CAChB,YAAY,OAAO,QAAQ,EAAE,MAAM,uBAAgC,CAAC;CACpE,aAAa,OAAO;AACrB"}
@@ -1,5 +1,4 @@
1
1
  import { walrusPluginError } from "../errors.mjs";
2
- import { WalrusSpans } from "../spans.mjs";
3
2
  import { readEnv } from "../../../substrate/runtime/typed-env.mjs";
4
3
  import { Effect } from "effect";
5
4
  import { fileURLToPath } from "node:url";
@@ -57,10 +56,7 @@ const resolveCargoImage = (runtime, inputs, expectedTag) => Effect.gen(function*
57
56
  digest: built.digest,
58
57
  tag: built.tag ?? built.digest
59
58
  };
60
- }).pipe(Effect.withSpan("devstack.plugin.walrus.cargoImage.resolve", { attributes: {
61
- [WalrusSpans.ref]: inputs.walrusRef,
62
- [WalrusSpans.suiVersion]: inputs.suiVersion
63
- } }));
59
+ });
64
60
  //#endregion
65
61
  export { DEFAULT_SUI_VERSION, DEFAULT_WALRUS_REF, resolveCargoImage };
66
62
 
@@ -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';\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"}
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';\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});\n"],"mappings":";;;;;AAiCA,MAAM,kCAAkC;;;AAIxC,MAAa,qBAAqB;;;;AAKlC,MAAa,sBAAsB;;;;;;AA6BnC,MAAM,kCACL,cAAc,IAAI,IAAI,8BAA8B,OAAO,KAAK,GAAG,CAAC;;;;;;;;;;;;AAarE,MAAa,qBACZ,SACA,QACA,gBAEA,OAAO,IAAI,aAAa;CACvB,MAAM,WAAW,QAAQ,+BAA+B;CACxD,IAAI,YAAY,SAAS,SAAS,GASjC,OAAO;EAAE,QAAQ;EAAU,KAAK;CAAS;CAI1C,MAAM,WAAW;EAChB,aAAa,0BAA0B;EACvC,YAAY;EACZ,WAAW;GACV,gBAAgB,OAAO;GACvB,aAAa,OAAO;EACrB;EACA,OAAO;GACN,KAAK,OAAO,MAAM;GAClB,OAAO,OAAO,MAAM;GACpB,QAAQ;GACR,MAAM;EACP;CACD;CAMA,MAAM,QAAQ,OAAO,QACnB,YACA,UACA,gBAAgB,KAAA,IAAY;EAAE,QAAQ;EAAa,KAAK;CAAY,IAAI,KAAA,CACzE,CAAC,CACA,KACA,OAAO,UAAU,UAChB,kBACC,eACA,wCAAwC,OAAO,UAAU,eAAe,OAAO,WAAW,KAAK,MAAM,OAAO,IAAI,MAAM,UACtH,EAAE,MAAM,CACT,CACD,CACD;CACD,OAAO;EAAE,QAAQ,MAAM;EAAQ,KAAK,MAAM,OAAO,MAAM;CAAO;AAC/D,CAAC"}
@@ -1,7 +1,5 @@
1
1
  //#region src/plugins/walrus/codegen.d.ts
2
- /** Per-node descriptor — matches the storage-nodes module's shape
3
- * for the local-cluster path, and is empty (or whatever the user
4
- * provides) for known. */
2
+ /** Per-node descriptor. */
5
3
  interface WalrusNodeBinding {
6
4
  readonly nodeIndex: number;
7
5
  readonly publicHostname: string;
@@ -10,27 +8,18 @@ interface WalrusNodeBinding {
10
8
  /** The typed shape the emitted file exports. */
11
9
  interface WalrusBindings {
12
10
  readonly mode: 'local' | 'known';
13
- readonly chain: string;
11
+ readonly network: string;
14
12
  readonly walrusPackageId: string | null;
15
13
  readonly walPackageId: string | null;
16
14
  readonly walCoinType: string | null;
17
- /** SDK-ready `packageConfig` — structurally compatible with
18
- * `@mysten/walrus`'s `WalrusPackageConfig`. */
19
15
  readonly packageConfig: {
20
16
  readonly systemObjectId: string;
21
17
  readonly stakingPoolId: string;
22
18
  readonly exchangeIds?: ReadonlyArray<string>;
23
19
  };
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
20
  readonly proxyUrl: string | null;
28
21
  readonly aggregatorUrl: string | null;
29
22
  readonly publisherUrl: string | null;
30
- /** Storage-node committee — local publishes N descriptors; known
31
- * publishes user-supplied nodes (testnet/mainnet's 100+ nodes
32
- * are fetched dynamically by the SDK, not pinned here —
33
- * distilled-doc invariant 16). */
34
23
  readonly nodes: ReadonlyArray<WalrusNodeBinding>;
35
24
  }
36
25
  //#endregion
@@ -1,34 +1,99 @@
1
- import { Effect } from "effect";
1
+ import { configCodegenable } from "../../contracts/config-bindings.mjs";
2
2
  //#region src/plugins/walrus/codegen.ts
3
- /** Construct the Codegenable contribution. Emit is byte-deterministic
4
- * on unchanged input (architecture: no mtime churn on no-op
5
- * cycles). */
6
- const makeCodegenable = (inputs) => ({
7
- kind: "codegenable",
8
- emitterName: "walrus-network",
9
- outputPath: "walrus/network.ts",
10
- emit: (ctx) => Effect.sync(() => {
11
- const bindings = {
12
- mode: inputs.mode,
13
- chain: inputs.chain,
14
- walrusPackageId: inputs.walrusPackageId,
15
- walPackageId: inputs.walPackageId,
16
- walCoinType: inputs.walCoinType,
17
- packageConfig: {
18
- systemObjectId: inputs.systemObjectId,
19
- stakingPoolId: inputs.stakingPoolId,
20
- exchangeIds: inputs.exchangeIds.length > 0 ? inputs.exchangeIds : void 0
3
+ const NAMESPACE = "walrus";
4
+ /** TS source-type strings for the resolved walrus fields — keeps the committed
5
+ * `walrus.ts` typed as `WalrusBindings` declares (the generic `resolveValue`
6
+ * channel would otherwise return `unknown`). Composite blobs inline their
7
+ * structural literal types so no emitted type-import is needed. */
8
+ const PACKAGE_CONFIG_TS_TYPE = "{ readonly systemObjectId: string; readonly stakingPoolId: string; readonly exchangeIds?: ReadonlyArray<string> }";
9
+ const NODES_TS_TYPE = "ReadonlyArray<{ readonly nodeIndex: number; readonly publicHostname: string; readonly rpcUrl: string }>";
10
+ /** The walrus config bindings, declared ONCE. `mode` / `network` are
11
+ * structural literals; every id / coin type / URL / committee value is a
12
+ * RESOLVED binding on the generic `resolveValue('walrus', '<key>')` channel.
13
+ * Both the live boot decl and the static committed-tree decl derive from it. */
14
+ const walrusConfigBindings = (structural) => {
15
+ const known = structural.known;
16
+ const field = (key, live) => known !== void 0 ? {
17
+ variant: "literal",
18
+ configPath: [key],
19
+ value: known[key]
20
+ } : {
21
+ variant: "resolved",
22
+ configPath: [key],
23
+ namespace: NAMESPACE,
24
+ key,
25
+ tsType: "string | null",
26
+ live
27
+ };
28
+ const packageConfigBinding = known !== void 0 ? {
29
+ variant: "literal",
30
+ configPath: ["packageConfig"],
31
+ value: known.packageConfig
32
+ } : {
33
+ variant: "resolved",
34
+ configPath: ["packageConfig"],
35
+ namespace: NAMESPACE,
36
+ key: "packageConfig",
37
+ tsType: PACKAGE_CONFIG_TS_TYPE,
38
+ live: (i) => ({
39
+ systemObjectId: i.systemObjectId,
40
+ stakingPoolId: i.stakingPoolId,
41
+ ...i.exchangeIds.length > 0 ? { exchangeIds: [...i.exchangeIds] } : {}
42
+ })
43
+ };
44
+ const nodesBinding = known !== void 0 ? {
45
+ variant: "literal",
46
+ configPath: ["nodes"],
47
+ value: known.nodes
48
+ } : {
49
+ variant: "resolved",
50
+ configPath: ["nodes"],
51
+ namespace: NAMESPACE,
52
+ key: "nodes",
53
+ tsType: NODES_TS_TYPE,
54
+ live: (i) => i.nodes
55
+ };
56
+ return {
57
+ bucket: "walrus.ts",
58
+ kind: "walrus",
59
+ emitterName: "walrus-network",
60
+ bindings: [
61
+ {
62
+ variant: "literal",
63
+ configPath: ["mode"],
64
+ value: structural.mode
21
65
  },
22
- proxyUrl: inputs.proxyUrl,
23
- aggregatorUrl: inputs.aggregatorUrl,
24
- publisherUrl: inputs.publisherUrl,
25
- nodes: inputs.nodes
26
- };
27
- ctx.exportConst("walrus", bindings);
28
- return ctx.done();
29
- })
66
+ {
67
+ variant: "literal",
68
+ configPath: ["network"],
69
+ value: structural.network
70
+ },
71
+ field("walrusPackageId", (i) => i.walrusPackageId),
72
+ field("walPackageId", (i) => i.walPackageId),
73
+ field("walCoinType", (i) => i.walCoinType),
74
+ packageConfigBinding,
75
+ field("proxyUrl", (i) => i.proxyUrl),
76
+ field("aggregatorUrl", (i) => i.aggregatorUrl),
77
+ field("publisherUrl", (i) => i.publisherUrl),
78
+ nodesBinding
79
+ ]
80
+ };
81
+ };
82
+ /** Construct the LIVE Codegenable contribution. Bakes the resolved ids /
83
+ * URLs into the ephemeral tree + feeds the generic id-config `values`
84
+ * channel. */
85
+ const makeCodegenable = (inputs) => configCodegenable(walrusConfigBindings({
86
+ mode: inputs.mode,
87
+ network: inputs.network
88
+ }), {
89
+ mode: "live",
90
+ state: inputs
30
91
  });
92
+ /** Construct the STATIC (stack-free) Codegenable contribution. Emits
93
+ * `resolveValue('walrus', '<key>')` for the runtime fields; the committed
94
+ * `walrus.ts` carries no baked object id / endpoint URL. */
95
+ const makeWalrusStaticCodegen = (config) => configCodegenable(walrusConfigBindings(config), "static");
31
96
  //#endregion
32
- export { makeCodegenable };
97
+ export { makeCodegenable, makeWalrusStaticCodegen };
33
98
 
34
99
  //# sourceMappingURL=codegen.mjs.map